blob: ee038fb4a5d931a616a93ae8e46c811dbc7d75c3 [file] [log] [blame]
/* cti-services.c
*
* Copyright (c) 2020-2023 Apple Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This code adds border router support to 3rd party HomeKit Routers as part of Apple’s commitment to the CHIP project.
*
* Concise Thread Interface for Thread Border router control.
*/
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <inttypes.h>
#include "srp.h"
#include "dns-msg.h"
#include "ioloop.h"
#include "cti-services.h"
static void cti_message_parse(cti_connection_t connection);
//*************************************************************************************************************
// Globals
#include "cti-common.h"
#include "cti-proto.h"
// For configuration comments, we return success/failure.
static void
cti_internal_reply_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
{
cti_reply_t callback;
INFO("conn_ref = %p", conn_ref);
callback = conn_ref->callback.reply;
if (callback != NULL) {
callback(conn_ref->context, status);
// We only ever call this callback once.
conn_ref->callback.reply = NULL;
}
cti_connection_close(conn_ref);
}
static void
cti_fd_finalize(void *context)
{
cti_connection_t connection = context;
connection->io_context = NULL;
if (connection->callback.reply != NULL && connection->internal_callback != NULL) {
connection->internal_callback(connection, NULL, kCTIStatus_Disconnected);
}
RELEASE_HERE(connection, cti_connection);
}
void
cti_connection_close(cti_connection_t connection)
{
// The reason we test for NULL here is to save some typing: when a connection is closed remotely, we have call the
// internal event handler for the event; this event handler closes the connection when we've just received a
// successful reply. However, when the remote end closes the connection without that reply having been processed, we
// get to the internal callback with connection->io_context set to NULL. Rather than checking in every event handler,
// it's easier to check here.
if (connection->io_context != NULL) {
ioloop_close(connection->io_context);
ioloop_file_descriptor_release(connection->io_context);
connection->io_context = NULL;
}
}
static void
cti_response_parse(cti_connection_t connection)
{
uint16_t responding_to;
int32_t status;
// And statement will fail as soon as anything fails to parse.
if (cti_connection_u16_parse(connection, &responding_to) &&
cti_connection_i32_parse(connection, &status) &&
cti_connection_parse_done(connection))
{
INFO("%d %d", responding_to, status);
connection->internal_callback(connection, NULL, status);
}
}
static void
cti_tunnel_response_parse(cti_connection_t connection)
{
char *tunnel_name = NULL;
// And statement will fail as soon as anything fails to parse.
if (cti_connection_string_parse(connection, &tunnel_name) &&
cti_connection_parse_done(connection))
{
INFO("%s", tunnel_name);
connection->internal_callback(connection, tunnel_name, kCTIStatus_NoError);
}
if (tunnel_name != NULL) {
free(tunnel_name);
}
}
static void
cti_connection_read_callback(io_t *UNUSED io, void *context)
{
cti_connection_t connection = context;
cti_read(connection, cti_message_parse);
}
void
cti_connection_release_(cti_connection_t connection, const char *file, int line)
{
RELEASE(connection, cti_connection);
}
static int
cti_connection_create(void *context, cti_callback_t callback,
cti_internal_callback_t internal_callback, cti_connection_t *retcon)
{
cti_connection_t connection = cti_connection_allocate(500);
if (connection == NULL) {
ERROR("cti_connection_create: no memory for connection.");
return kCTIStatus_NoMemory;
}
RETAIN_HERE(connection, cti_connection);
connection->fd = cti_make_unix_socket(CTI_SERVER_SOCKET_NAME, sizeof(CTI_SERVER_SOCKET_NAME), false);
if (connection->fd < 0) {
int ret = errno == ECONNREFUSED ? kCTIStatus_DaemonNotRunning : EPERM ? kCTIStatus_NotPermitted : kCTIStatus_UnknownError;
ERROR("cti_connection_create: socket: %s", strerror(errno));
cti_connection_release(connection);
return ret;
}
connection->io_context = ioloop_file_descriptor_create(connection->fd, connection, cti_fd_finalize);
if (connection->io_context == NULL) {
ERROR("cti_connection_create: can't create file descriptor object.");
close(connection->fd);
cti_connection_release(connection);
return kCTIStatus_NoMemory;
}
ioloop_add_reader(connection->io_context, cti_connection_read_callback);
connection->context = context;
connection->callback = callback;
connection->internal_callback = internal_callback;
*retcon = connection;
return kCTIStatus_NoError;
}
cti_status_t
cti_add_service_(srp_server_t *UNUSED server, void *context, cti_reply_t callback, run_context_t client_queue,
uint32_t enterprise_number, const uint8_t *NONNULL service_data, size_t service_data_length,
const uint8_t *server_data, size_t server_data_length, const char *file, int line)
{
cti_callback_t app_callback;
app_callback.reply = callback;
int ret;
cti_connection_t conn_ref;
ret = cti_connection_create(context, app_callback, cti_internal_reply_callback, &conn_ref);
if (ret == kCTIStatus_NoError) {
conn_ref->callback.reply = callback;
if (cti_connection_message_create(conn_ref, kCTIMessageType_AddService,
// sizeof(u32) + sizeof(length) + sizeof(length) = 8
8 + service_data_length + server_data_length) &&
cti_connection_u32_put(conn_ref, enterprise_number) &&
cti_connection_data_put(conn_ref, service_data, service_data_length) &&
cti_connection_data_put(conn_ref, server_data, server_data_length))
{
if (!cti_connection_message_send(conn_ref)) {
ret = kCTIStatus_Disconnected;
}
} else {
ret = kCTIStatus_NoMemory;
}
if (ret != kCTIStatus_NoError) {
cti_connection_close(conn_ref);
}
}
return ret;
}
cti_status_t
cti_remove_service_(srp_server_t *UNUSED server, void *context, cti_reply_t callback, run_context_t client_queue,
uint32_t enterprise_number, const uint8_t *NONNULL service_data, size_t service_data_length,
const char *file, int line)
{
cti_callback_t app_callback;
app_callback.reply = callback;
int ret;
cti_connection_t conn_ref;
ret = cti_connection_create(context, app_callback, cti_internal_reply_callback, &conn_ref);
if (ret == kCTIStatus_NoError) {
if (cti_connection_message_create(conn_ref, kCTIMessageType_RemoveService,
// sizeof(u32) + sizeof(length) = 6
6 + service_data_length) &&
cti_connection_u32_put(conn_ref, enterprise_number) &&
cti_connection_data_put(conn_ref, service_data, service_data_length))
{
if (!cti_connection_message_send(conn_ref)) {
ret = kCTIStatus_Disconnected;
}
} else {
ret = kCTIStatus_NoMemory;
}
if (ret != kCTIStatus_NoError) {
cti_connection_close(conn_ref);
}
}
return ret;
}
cti_status_t
cti_add_prefix_(srp_server_t *UNUSED server, void *context, cti_reply_t callback, run_context_t client_queue,
struct in6_addr *prefix, int prefix_length, bool on_mesh, bool preferred, bool slaac, bool stable,
int priority, const char *file, int line)
{
cti_callback_t app_callback;
int ret;
cti_connection_t conn_ref;
app_callback.reply = callback;
ret = cti_connection_create(context, app_callback, cti_internal_reply_callback, &conn_ref);
if (ret == kCTIStatus_NoError) {
if (cti_connection_message_create(conn_ref, kCTIMessageType_AddPrefix,
// sizeof(u32) * 2 + sizeof(prefix) (8) + sizeof(u8) + 3 * sizeof(bool) = 20
20) &&
cti_connection_u32_put(conn_ref, ND6_INFINITE_LIFETIME) &&
cti_connection_u32_put(conn_ref, ND6_INFINITE_LIFETIME) &&
cti_connection_data_put(conn_ref, prefix, 8) &&
cti_connection_u8_put(conn_ref, prefix_length) &&
cti_connection_bool_put(conn_ref, slaac) &&
cti_connection_bool_put(conn_ref, on_mesh) &&
cti_connection_bool_put(conn_ref, stable))
{
if (!cti_connection_message_send(conn_ref)) {
ret = kCTIStatus_Disconnected;
}
} else {
ret = kCTIStatus_NoMemory;
}
if (ret != kCTIStatus_NoError) {
cti_connection_close(conn_ref);
}
}
return ret;
}
cti_status_t
cti_remove_prefix_(srp_server_t *UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback,
run_context_t NULLABLE client_queue, struct in6_addr *NONNULL prefix, int prefix_length,
const char *file, int line)
{
cti_callback_t app_callback;
int ret;
cti_connection_t conn_ref;
app_callback.reply = callback;
ret = cti_connection_create(context, app_callback, cti_internal_reply_callback, &conn_ref);
if (ret == kCTIStatus_NoError) {
if (cti_connection_message_create(conn_ref, kCTIMessageType_RemovePrefix,
// sizeof(prefix) (8) + sizeof(u8) = 9
9) &&
cti_connection_data_put(conn_ref, prefix, 8) &&
cti_connection_u8_put(conn_ref, prefix_length))
{
if (!cti_connection_message_send(conn_ref)) {
ret = kCTIStatus_Disconnected;
}
} else {
ret = kCTIStatus_NoMemory;
}
if (ret != kCTIStatus_NoError) {
cti_connection_close(conn_ref);
}
}
return ret;
}
// For configuration comments, we return success/failure.
static void
cti_internal_string_property_reply(cti_connection_t conn_ref, void *tunnel_name, cti_status_t status)
{
cti_tunnel_reply_t callback;
INFO("conn_ref = %p name = %s", conn_ref,
tunnel_name == NULL ? "<NULL>" : (char *)tunnel_name);
callback = conn_ref->callback.tunnel_reply;
if (callback != NULL) {
callback(conn_ref->context, tunnel_name, status);
conn_ref->callback.reply = NULL;
}
cti_connection_close(conn_ref);
}
cti_status_t
cti_get_tunnel_name_(srp_server_t *UNUSED server, void *NULLABLE context, cti_string_property_reply_t NONNULL callback,
run_context_t NULLABLE client_queue, const char *file, int line)
{
cti_callback_t app_callback;
app_callback.string_property_reply = callback;
int ret;
cti_connection_t conn_ref;
ret = cti_connection_create(context, app_callback, cti_internal_string_property_reply, &conn_ref);
if (ret == kCTIStatus_NoError) {
if (cti_connection_message_create(conn_ref, kCTIMessageType_GetTunnelName, 0))
{
if (!cti_connection_message_send(conn_ref)) {
ret = kCTIStatus_Disconnected;
}
} else {
ret = kCTIStatus_NoMemory;
}
if (ret != kCTIStatus_NoError) {
cti_connection_close(conn_ref);
}
}
return ret;
}
// For event comamnds, we return failure and close; on success, we just wait for events to flow and return those.
static void
cti_internal_state_event_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
{
cti_state_reply_t callback;
INFO("conn_ref = %p", conn_ref);
if (status != kCTIStatus_NoError) {
callback = conn_ref->callback.state_reply;
if (callback != NULL) {
callback(conn_ref->context, 0, status);
// Only one error callback ever.
conn_ref->callback.reply = NULL;
}
cti_connection_close(conn_ref);
}
}
cti_status_t
cti_get_state_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context,
cti_state_reply_t NONNULL callback, run_context_t NULLABLE client_queue, const char *file, int line)
{
cti_callback_t app_callback;
app_callback.state_reply = callback;
int ret;
cti_connection_t conn_ref;
ret = cti_connection_create(context, app_callback, cti_internal_state_event_callback, &conn_ref);
if (ret == kCTIStatus_NoError) {
if (cti_connection_message_create(conn_ref, kCTIMessageType_RequestStateEvents, 4)) {
if (!cti_connection_message_send(conn_ref)) {
ret = kCTIStatus_Disconnected;
}
} else {
ret = kCTIStatus_NoMemory;
}
if (ret != kCTIStatus_NoError) {
cti_connection_close(conn_ref);
}
}
return ret;
}
typedef uint32_t cti_property_name_t;
// For event commands, we return failure and close; on success, we just wait for events to flow and return those.
static void
cti_internal_uint64_property_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
{
cti_uint64_property_reply_t callback = conn_ref->callback.uint64_property_reply;
INFO("conn_ref = %p", conn_ref);
if (status != kCTIStatus_NoError) {
if (callback != NULL) {
callback(conn_ref->context, 0, status);
// Only one error callback ever.
conn_ref->callback.reply = NULL;
}
cti_connection_close(conn_ref);
}
}
static cti_status_t
cti_get_uint64_property(cti_connection_t *ref, void *NULLABLE context, cti_uint64_property_reply_t NONNULL callback,
run_context_t NULLABLE client_queue, cti_property_name_t property_name, const char *file, int line)
{
cti_callback_t app_callback;
app_callback.uint64_property_reply = callback;
int ret;
cti_connection_t conn_ref;
ret = cti_connection_create(context, app_callback, cti_internal_uint64_property_callback, &conn_ref);
if (ret == kCTIStatus_NoError) {
if (cti_connection_message_create(conn_ref, kCTIMessageType_RequestUInt64PropEvents, 4)) {
if (!cti_connection_u32_put(conn_ref, property_name) || !cti_connection_message_send(conn_ref)) {
ret = kCTIStatus_Disconnected;
}
} else {
ret = kCTIStatus_NoMemory;
}
if (ret != kCTIStatus_NoError) {
cti_connection_close(conn_ref);
}
}
return ret;
}
cti_status_t
cti_get_partition_id_(srp_server_t *UNUSED server, cti_connection_t NULLABLE *NULLABLE ref, void *NULLABLE context,
cti_uint64_property_reply_t NONNULL callback, run_context_t NULLABLE client_queue,
const char *NONNULL file, int line)
{
return cti_get_uint64_property(ref, context, callback, client_queue, kCTIPropertyPartitionID, file, line);
}
cti_status_t
cti_get_extended_pan_id_(srp_server_t *UNUSED server, cti_connection_t NULLABLE *NULLABLE ref, void *NULLABLE context,
cti_uint64_property_reply_t NONNULL callback, run_context_t NULLABLE client_queue,
const char *NONNULL file, int line)
{
return cti_get_uint64_property(ref, context, callback, client_queue, kCTIPropertyExtendedPANID, file, line);
}
// For event commands, we return failure and close; on success, we just wait for events to flow and return those.
static void
cti_internal_node_type_event_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
{
cti_network_node_type_reply_t callback;
INFO("conn_ref = %p", conn_ref);
if (status != kCTIStatus_NoError) {
callback = conn_ref->callback.network_node_type_reply;
if (callback != NULL) {
callback(conn_ref->context, 0, status);
// Only one error callback ever.
conn_ref->callback.reply = NULL;
}
cti_connection_close(conn_ref);
}
}
cti_status_t
cti_get_network_node_type_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context,
cti_network_node_type_reply_t NONNULL callback,
run_context_t NULLABLE client_queue, const char *file, int line)
{
cti_callback_t app_callback;
app_callback.network_node_type_reply = callback;
int ret;
cti_connection_t conn_ref;
ret = cti_connection_create(context, app_callback, cti_internal_node_type_event_callback, &conn_ref);
if (ret == kCTIStatus_NoError) {
if (cti_connection_message_create(conn_ref, kCTIMessageType_RequestRoleEvents, 4)) {
if (!cti_connection_message_send(conn_ref)) {
ret = kCTIStatus_Disconnected;
}
} else {
ret = kCTIStatus_NoMemory;
}
if (ret != kCTIStatus_NoError) {
cti_connection_close(conn_ref);
}
}
return ret;
}
static void
cti_service_finalize(cti_service_t *service)
{
if (service->server != NULL) {
free(service->server);
}
if (service->service != NULL) {
free(service->service);
}
free(service);
}
static void
cti_service_vec_finalize(cti_service_vec_t *services)
{
size_t i;
if (services->services != NULL) {
for (i = 0; i < services->num; i++) {
if (services->services[i] != NULL) {
RELEASE_HERE(services->services[i], cti_service);
}
}
free(services->services);
}
free(services);
}
cti_service_vec_t *
cti_service_vec_create_(size_t num_services, const char *file, int line)
{
cti_service_vec_t *services = calloc(1, sizeof(*services));
if (services != NULL) {
if (num_services != 0) {
services->services = calloc(num_services, sizeof(cti_service_t *));
if (services->services == NULL) {
free(services);
return NULL;
}
}
services->num = num_services;
RETAIN(services, cti_service_vec);
}
return services;
}
void
cti_service_vec_release_(cti_service_vec_t *services, const char *file, int line)
{
RELEASE(services, cti_service_vec);
}
cti_service_t *
cti_service_create_(uint64_t enterprise_number, uint16_t rloc16, uint16_t service_type,
uint16_t service_version, uint8_t *service_data, size_t service_length,
uint8_t *server, size_t server_length, uint16_t service_id, int flags, const char *file, int line)
{
cti_service_t *service = calloc(1, sizeof(*service));
if (service != NULL) {
service->enterprise_number = enterprise_number;
service->service_type = service_type;
service->service_version = service_version;
service->rloc16 = rloc16;
service->service = service_data;
service->service_length = service_length;
service->server = server;
service->server_length = server_length;
service->service_id = service_id;
service->flags = flags;
RETAIN(service, cti_service);
}
return service;
}
void
cti_service_release_(cti_service_t *service, const char *file, int line)
{
RELEASE(service, cti_service);
}
// For event comamnds, we return failure and close; on success, we just wait for events to flow and return those.
static void
cti_internal_service_event_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
{
cti_service_reply_t callback;
INFO("conn_ref = %p", conn_ref);
if (status != kCTIStatus_NoError) {
callback = conn_ref->callback.service_reply;
if (callback != NULL) {
callback(conn_ref->context, 0, status);
// Only one error callback ever.
conn_ref->callback.reply = NULL;
}
cti_connection_close(conn_ref);
}
}
cti_status_t
cti_get_service_list_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context,
cti_service_reply_t NONNULL callback, run_context_t NULLABLE client_queue,
const char *file, int line)
{
cti_callback_t app_callback;
app_callback.service_reply = callback;
int ret;
cti_connection_t conn_ref;
ret = cti_connection_create(context, app_callback, cti_internal_service_event_callback, &conn_ref);
if (ret == kCTIStatus_NoError) {
if (cti_connection_message_create(conn_ref, kCTIMessageType_RequestServiceEvents, 4)) {
if (!cti_connection_message_send(conn_ref)) {
ret = kCTIStatus_Disconnected;
}
} else {
ret = kCTIStatus_NoMemory;
}
if (ret != kCTIStatus_NoError) {
cti_connection_close(conn_ref);
}
}
return ret;
}
static void
cti_prefix_finalize(cti_prefix_t *prefix)
{
free(prefix);
}
static void
cti_prefix_vec_finalize(cti_prefix_vec_t *prefixes)
{
size_t i;
if (prefixes->prefixes != NULL) {
for (i = 0; i < prefixes->num; i++) {
if (prefixes->prefixes[i] != NULL) {
RELEASE_HERE(prefixes->prefixes[i], cti_prefix);
}
}
free(prefixes->prefixes);
}
free(prefixes);
}
cti_prefix_vec_t *
cti_prefix_vec_create_(size_t num_prefixes, const char *file, int line)
{
cti_prefix_vec_t *prefixes = calloc(1, sizeof(*prefixes));
if (prefixes != NULL) {
if (num_prefixes != 0) {
prefixes->prefixes = calloc(num_prefixes, sizeof(cti_prefix_t *));
if (prefixes->prefixes == NULL) {
free(prefixes);
return NULL;
}
}
prefixes->num = num_prefixes;
RETAIN(prefixes, cti_prefix_vec);
}
return prefixes;
}
void
cti_prefix_vec_release_(cti_prefix_vec_t *prefixes, const char *file, int line)
{
RELEASE(prefixes, cti_prefix_vec);
}
cti_prefix_t *
cti_prefix_create_(struct in6_addr *prefix, int prefix_length, int metric, int flags, int rloc, bool stable, bool ncp,
const char *file, int line)
{
cti_prefix_t *prefix_ret = calloc(1, sizeof(*prefix_ret));
if (prefix != NULL) {
prefix_ret->prefix = *prefix;
prefix_ret->prefix_length = prefix_length;
prefix_ret->metric = metric;
prefix_ret->flags = flags;
prefix_ret->rloc = rloc;
prefix_ret->stable = stable;
prefix_ret->ncp = ncp;
RETAIN(prefix_ret, cti_prefix);
}
return prefix_ret;
}
void
cti_prefix_release_(cti_prefix_t *prefix, const char *file, int line)
{
RELEASE(prefix, cti_prefix);
}
// For event comamnds, we return failure and close; on success, we just wait for events to flow and return those.
static void
cti_internal_prefix_event_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
{
cti_prefix_reply_t callback;
INFO("conn_ref = %p", conn_ref);
if (status != kCTIStatus_NoError) {
callback = conn_ref->callback.prefix_reply;
if (callback != NULL && conn_ref->context != NULL) {
callback(conn_ref->context, 0, status);
// Only one error callback ever.
conn_ref->callback.reply = NULL;
}
cti_connection_close(conn_ref);
}
}
cti_status_t
cti_get_prefix_list_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context,
cti_prefix_reply_t NONNULL callback, run_context_t NULLABLE client_queue,
const char *file, int line)
{
cti_callback_t app_callback;
app_callback.prefix_reply = callback;
int ret;
cti_connection_t conn_ref;
ret = cti_connection_create(context, app_callback, cti_internal_prefix_event_callback, &conn_ref);
if (ret == kCTIStatus_NoError) {
if (cti_connection_message_create(conn_ref, kCTIMessageType_RequestPrefixEvents, 4)) {
if (!cti_connection_message_send(conn_ref)) {
ret = kCTIStatus_Disconnected;
}
} else {
ret = kCTIStatus_NoMemory;
}
if (ret != kCTIStatus_NoError) {
cti_connection_close(conn_ref);
}
}
return ret;
}
cti_status_t
cti_events_discontinue(cti_connection_t connection)
{
if (connection->io_context != NULL) {
cti_connection_close(connection);
}
cti_connection_release(connection);
return kCTIStatus_NoError;
}
static void
cti_role_event_parse(cti_connection_t connection)
{
uint8_t role;
// And statement will fail as soon as anything fails to parse.
if (cti_connection_u8_parse(connection, &role) &&
cti_connection_parse_done(connection))
{
INFO("%d", role);
connection->callback.network_node_type_reply(connection, role, kCTIStatus_NoError);
}
}
static void
cti_state_event_parse(cti_connection_t connection)
{
uint8_t state;
// And statement will fail as soon as anything fails to parse.
if (cti_connection_u8_parse(connection, &state) &&
cti_connection_parse_done(connection))
{
INFO("%d", state);
connection->callback.state_reply(connection, state, kCTIStatus_NoError);
}
}
static void
cti_uint64_property_event_parse(cti_connection_t connection)
{
uint64_t property_value;
// And statement will fail as soon as anything fails to parse.
if (cti_connection_u64_parse(connection, &property_value) &&
cti_connection_parse_done(connection))
{
INFO("%" PRIx64, property_value);
connection->callback.uint64_property_reply(connection, property_value, kCTIStatus_NoError);
}
}
static void
cti_service_event_parse(cti_connection_t connection)
{
uint8_t service_count, i;
// And statement will fail as soon as anything fails to parse.
if (cti_connection_u8_parse(connection, &service_count)) {
cti_service_vec_t *vec = cti_service_vec_create(service_count);
if (vec == NULL) {
ERROR("cti_service_event_parse: no memory for service vector.");
return;
}
vec->num = 0;
for (i = 0; i < service_count; i++) {
uint32_t enterprise_number;
void *service_data = NULL;
uint16_t service_data_length;
void *server_data = NULL;
uint16_t server_data_length;
if (!cti_connection_u32_parse(connection, &enterprise_number) ||
!cti_connection_data_parse(connection, &service_data, &service_data_length) ||
!cti_connection_data_parse(connection, &server_data, &server_data_length))
{
if (service_data != NULL) {
free(service_data);
}
cti_service_vec_release(vec);
return;
}
char service_data_buf[13], server_data_buf[55];
cti_service_t *service = NULL;
dump_to_hex(service_data, service_data_length, service_data_buf, sizeof(service_data_buf));
dump_to_hex(server_data, server_data_length, server_data_buf, sizeof(server_data_buf));
INFO("%" PRIu32 " %" PRIu16 "[ %s ] %" PRIu16 "[ %s ]",
enterprise_number, service_data_length, service_data_buf, server_data_length, server_data_buf);
if (enterprise_number == THREAD_ENTERPRISE_NUMBER) {
if (service_data_length == 1) {
service = cti_service_create(enterprise_number,
((uint8_t *)service_data)[0], 1, server_data, server_data_length, 0);
}
}
if (service == NULL) {
free(service_data);
free(server_data);
} else {
vec->services[vec->num++] = service;
}
}
if (!cti_connection_parse_done(connection)) {
cti_service_vec_release(vec);
return;
}
INFO("%zd", vec->num);
connection->callback.service_reply(connection, vec, kCTIStatus_NoError);
}
}
static void
cti_prefix_event_parse(cti_connection_t connection)
{
uint16_t prefix_count, i;
// And statement will fail as soon as anything fails to parse.
if (cti_connection_u16_parse(connection, &prefix_count)) {
if (prefix_count > 200) {
ERROR("cti_prefix_event_parse: bogus number of prefixes returned: %d", prefix_count);
cti_connection_close(connection);
return;
}
cti_prefix_vec_t *vec = cti_prefix_vec_create(prefix_count);
if (vec == NULL) {
ERROR("cti_prefix_event_parse: no memory for prefix vector.");
return;
}
vec->num = 0;
for (i = 0; i < prefix_count; i++) {
uint16_t flags;
uint8_t prefix_length;
void *prefix_data = NULL;
uint16_t prefix_data_length;
struct in6_addr *prefix_addr;
if (!cti_connection_u16_parse(connection, &flags) ||
!cti_connection_u8_parse(connection, &prefix_length) ||
!cti_connection_data_parse(connection, &prefix_data, &prefix_data_length))
{
cti_prefix_vec_release(vec);
return;
}
if (prefix_data_length != 8) {
ERROR("cti_prefix_event_parse: wrong prefix length: %d", prefix_data_length);
cti_prefix_vec_release(vec);
return;
}
prefix_addr = calloc(1, sizeof(*prefix_addr));
if (prefix_addr == NULL) {
ERROR("cti_prefix_event_parse: no memory for prefix data.");
cti_prefix_vec_release(vec);
return;
}
in6prefix_copy_from_data(prefix_addr, prefix_data, prefix_data_length);
free(prefix_data);
cti_prefix_t *prefix = cti_prefix_create(prefix_addr, prefix_length, 0, flags);
if (prefix == NULL) {
ERROR("cti_prefix_event_parse: no memory for prefix object.");
cti_prefix_vec_release(vec);
return;
}
vec->prefixes[vec->num++] = prefix;
}
if (!cti_connection_parse_done(connection)) {
cti_prefix_vec_release(vec);
return;
}
INFO("%zd", vec->num);
connection->callback.prefix_reply(connection, vec, kCTIStatus_NoError);
}
}
static void
cti_message_parse(cti_connection_t connection)
{
cti_connection_parse_start(connection);
if (!cti_connection_u16_parse(connection, &connection->message_type)) {
return;
}
switch(connection->message_type) {
case kCTIMessageType_Response:
cti_response_parse(connection);
break;
case kCTIMessageType_TunnelNameResponse:
cti_tunnel_response_parse(connection);
break;
case kCTIMessageType_StateEvent:
cti_state_event_parse(connection);
break;
case kCTIMessageType_UInt64PropEvent:
cti_uint64_property_event_parse(connection);
break;
case kCTIMessageType_RoleEvent:
cti_role_event_parse(connection);
break;
case kCTIMessageType_ServiceEvent:
cti_service_event_parse(connection);
break;
case kCTIMessageType_PrefixEvent:
cti_prefix_event_parse(connection);
break;
}
}
// Local Variables:
// mode: C
// tab-width: 4
// c-file-style: "bsd"
// c-basic-offset: 4
// fill-column: 120
// indent-tabs-mode: nil
// End: