| /* cti-server.c |
| * |
| * Copyright (c) 2020 Apple Computer, 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 |
| * |
| * http://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 Server |
| */ |
| |
| #define _GNU_SOURCE 1 |
| #include <stdio.h> |
| #include <arpa/inet.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <syslog.h> |
| #include <pwd.h> |
| #include <grp.h> |
| #include <sys/un.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/signal.h> |
| #include "cti-server.h" |
| #include "cti-proto.h" |
| |
| #include <syslog.h> |
| |
| int cti_listener_fd; |
| cti_connection_t connections; |
| |
| void |
| cti_connection_close(cti_connection_t connection) |
| { |
| if (connection->fd != -1) { |
| close(connection->fd); |
| connection->fd = -1; |
| } |
| } |
| |
| static void |
| cti_service_add_parse(cti_connection_t connection) |
| { |
| uint32_t enterprise_id; |
| uint16_t service_data_length; |
| uint16_t server_data_length; |
| int status = kCTIStatus_NoError; |
| void *service_data; |
| void *server_data; |
| char service_data_buf[13], server_data_buf[55]; |
| |
| // And statement will fail as soon as anything fails to parse. |
| if (cti_connection_u32_parse(connection, &enterprise_id) && |
| cti_connection_data_parse(connection, &service_data, &service_data_length) && |
| cti_connection_data_parse(connection, &server_data, &server_data_length) && |
| cti_connection_parse_done(connection)) |
| { |
| 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)); |
| syslog(LOG_INFO, "cti_service_add_parse: %" PRIu32 " %" PRIu16 "[ %s ] %" PRIu16 "[ %s ]", |
| enterprise_id, service_data_length, service_data_buf, server_data_length, server_data_buf); |
| |
| #ifndef POSIX_BUILD |
| status = ctiAddService(enterprise_id, |
| service_data, |
| service_data_length, |
| server_data, |
| server_data_length); |
| |
| #else |
| status = kCTIStatus_NoError; |
| #endif |
| cti_send_response(connection, status); |
| } |
| } |
| |
| static void |
| cti_service_remove_parse(cti_connection_t connection) |
| { |
| uint32_t enterprise_id; |
| uint16_t service_data_length; |
| int status = kCTIStatus_NoError; |
| void *service_data; |
| char service_data_buf[13]; |
| |
| // And statement will fail as soon as anything fails to parse. |
| if (cti_connection_u32_parse(connection, &enterprise_id) && |
| cti_connection_data_parse(connection, &service_data, &service_data_length) && |
| cti_connection_parse_done(connection)) |
| { |
| dump_to_hex(service_data, service_data_length, service_data_buf, sizeof(service_data_buf)); |
| syslog(LOG_INFO, "cti_service_add_parse: %" PRIu32 " %" PRIu16 "[ %s ]", enterprise_id, service_data_length, service_data_buf); |
| |
| #ifndef POSIX_BUILD |
| status = ctiRemoveService(enterprise_id, |
| service_data, |
| service_data_length); |
| #else |
| status = kCTIStatus_NoError; |
| #endif |
| cti_send_response(connection, status); |
| } |
| } |
| |
| static void |
| cti_prefix_add_parse(cti_connection_t connection) |
| { |
| uint32_t preferred, valid; |
| uint8_t prefix_length; |
| struct in6_addr prefix; |
| void *prefix_data = NULL; |
| uint16_t prefix_data_length; |
| bool slaac, on_mesh, stable; |
| if (cti_connection_u32_parse(connection, &preferred) && |
| cti_connection_u32_parse(connection, &valid) && |
| cti_connection_data_parse(connection, &prefix_data, &prefix_data_length) && |
| cti_connection_u8_parse(connection, &prefix_length) && |
| cti_connection_bool_parse(connection, &slaac) && |
| cti_connection_bool_parse(connection, &on_mesh) && |
| cti_connection_bool_parse(connection, &stable) && |
| cti_connection_parse_done(connection)) |
| { |
| int status = kCTIStatus_NoError; |
| if (prefix_data_length != 8) { |
| status = kCTIStatus_Invalid; |
| } else { |
| memset(((char *)&prefix) + 8, 0, sizeof(prefix) - 8); |
| memcpy(&prefix, prefix_data, prefix_data_length); |
| #ifndef POSIX_BUILD |
| status = ctiAddMeshPrefix(&prefix, prefix_length, on_mesh, true, slaac, stable); |
| #endif |
| } |
| cti_send_response(connection, status); |
| } |
| if (prefix_data != NULL) { |
| free(prefix_data); |
| } |
| } |
| |
| static void |
| cti_prefix_remove_parse(cti_connection_t connection) |
| { |
| uint8_t prefix_length; |
| struct in6_addr prefix; |
| void *prefix_data = NULL; |
| uint16_t prefix_data_length; |
| if (cti_connection_data_parse(connection, &prefix_data, &prefix_data_length) && |
| cti_connection_u8_parse(connection, &prefix_length) && |
| cti_connection_parse_done(connection)) |
| { |
| int status = kCTIStatus_NoError; |
| if (prefix_data_length != 8) { |
| status = kCTIStatus_Invalid; |
| } else { |
| memset(((char *)&prefix) + 8, 0, sizeof(prefix) - 8); |
| memcpy(&prefix, prefix_data, prefix_data_length); |
| #ifndef POSIX_BUILD |
| status = ctiRemoveMeshPrefix(&prefix, prefix_length); |
| #endif |
| cti_send_response(connection, status); |
| } |
| } |
| if (prefix_data != NULL) { |
| free(prefix_data); |
| } |
| } |
| |
| static void |
| cti_get_tunnel_name_parse(cti_connection_t connection) |
| { |
| if (cti_connection_parse_done(connection)) { |
| #ifndef POSIX_BUILD |
| ctiRetrieveTunnel(connection); |
| #endif |
| } |
| } |
| |
| static void |
| cti_message_parse(cti_connection_t connection) |
| { |
| uint32_t propertyName; |
| |
| cti_connection_parse_start(connection); |
| if (!cti_connection_u16_parse(connection, &connection->message_type)) { |
| return; |
| } |
| switch(connection->message_type) { |
| case kCTIMessageType_AddService: |
| cti_service_add_parse(connection); |
| break; |
| case kCTIMessageType_RemoveService: |
| cti_service_remove_parse(connection); |
| break; |
| case kCTIMessageType_AddPrefix: |
| cti_prefix_add_parse(connection); |
| break; |
| case kCTIMessageType_RemovePrefix: |
| cti_prefix_remove_parse(connection); |
| break; |
| case kCTIMessageType_GetTunnelName: |
| cti_get_tunnel_name_parse(connection); |
| break; |
| case kCTIMessageType_RequestStateEvents: |
| if (cti_connection_parse_done(connection)) { |
| connection->registered_event_flags |= CTI_EVENT_STATE; |
| cti_send_response(connection, kCTIStatus_NoError); |
| #ifndef POSIX_BUILD |
| ctiRetrieveNodeType(connection, CTI_EVENT_STATE); |
| |
| #endif |
| } |
| break; |
| case kCTIMessageType_RequestUInt64PropEvents: |
| if (cti_connection_u32_parse(connection, &propertyName) && cti_connection_parse_done(connection)) { |
| cti_send_response(connection, kCTIStatus_NoError); |
| #ifndef POSIX_BUILD |
| switch(propertyName) { |
| case kCTIPropertyPartitionID: |
| connection->registered_event_flags |= CTI_EVENT_PARTITION_ID; |
| ctiRetrievePartitionId(connection, CTI_EVENT_PARTITION_ID); |
| break; |
| case kCTIPropertyExtendedPANID: |
| connection->registered_event_flags |= CTI_EVENT_XPANID; |
| ctiRetrieveXPANID(connection, CTI_EVENT_XPANID); |
| break; |
| default: |
| cti_connection_close(connection); |
| break; |
| } |
| #endif |
| } |
| break; |
| case kCTIMessageType_RequestRoleEvents: |
| if (cti_connection_parse_done(connection)) { |
| connection->registered_event_flags |= CTI_EVENT_ROLE; |
| cti_send_response(connection, kCTIStatus_NoError); |
| #ifndef POSIX_BUILD |
| ctiRetrieveNodeType(connection, CTI_EVENT_ROLE); |
| #endif |
| } |
| break; |
| case kCTIMessageType_RequestServiceEvents: |
| if (cti_connection_parse_done(connection)) { |
| connection->registered_event_flags |= CTI_EVENT_SERVICE; |
| cti_send_response(connection, kCTIStatus_NoError); |
| #ifndef POSIX_BUILD |
| ctiRetrieveServiceList(connection, CTI_EVENT_SERVICE); |
| #endif |
| } |
| break; |
| case kCTIMessageType_RequestPrefixEvents: |
| if (cti_connection_parse_done(connection)) { |
| connection->registered_event_flags |= CTI_EVENT_PREFIX; |
| cti_send_response(connection, kCTIStatus_NoError); |
| #ifndef POSIX_BUILD |
| ctiRetrievePrefixList(connection, CTI_EVENT_PREFIX); |
| #endif |
| } |
| break; |
| default: |
| cti_send_response(connection, kCTIStatus_Invalid); |
| |
| } |
| } |
| |
| static void |
| cti_listen_callback(void) |
| |
| { |
| cti_connection_t connection; |
| int fd; |
| uid_t uid; |
| pid_t pid; |
| |
| fd = cti_accept(cti_listener_fd, &uid, NULL, &pid); |
| |
| // User is authenticated. |
| connection = cti_connection_allocate(100); |
| if (connection == NULL) { |
| close(fd); |
| return; |
| } |
| connection->fd = fd; |
| connection->next = connections; |
| connections = connection; |
| syslog(LOG_INFO, "cti_accept: connection from user %d, pid %d accepted", uid, pid); |
| } |
| |
| int |
| cti_init(void) |
| { |
| cti_listener_fd = cti_make_unix_socket(CTI_SERVER_SOCKET_NAME, sizeof(CTI_SERVER_SOCKET_NAME), true); |
| if (cti_listener_fd == -1) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| void |
| cti_fd_init(int *p_nfds, fd_set *r) |
| { |
| int nfds = *p_nfds; |
| cti_connection_t connection, *p_connection; |
| |
| if (cti_listener_fd >= nfds) { |
| nfds = cti_listener_fd + 1; |
| } |
| FD_SET(cti_listener_fd, r); |
| |
| // GC any closed connections. |
| for (p_connection = &connections; *p_connection; ) { |
| connection = *p_connection; |
| if (connection->fd == -1) { |
| *p_connection = connection->next; |
| cti_connection_finalize(connection); |
| } else { |
| p_connection = &connection->next; |
| } |
| } |
| |
| // Now process input on any connections that are still around. |
| for (connection = connections; connection; connection = connection->next) { |
| if (connection->fd >= nfds) { |
| nfds = connection->fd + 1; |
| } |
| FD_SET(connection->fd, r); |
| } |
| *p_nfds = nfds; |
| } |
| |
| void |
| cti_fd_process(fd_set *r) |
| { |
| cti_connection_t connection; |
| |
| if (FD_ISSET(cti_listener_fd, r)) { |
| cti_listen_callback(); |
| } |
| |
| for (connection = connections; connection; connection = connection->next) { |
| if (connection->fd != -1 && FD_ISSET(connection->fd, r)) { |
| cti_read(connection, cti_message_parse); |
| } |
| } |
| } |
| |
| void |
| cti_notify_event(unsigned int evt, send_event_t evt_handler) |
| { |
| // Walk through connections and see if have registered for this particular event. |
| cti_connection_t connection = connections; |
| while (connection) { |
| if (evt & connection->registered_event_flags) { |
| evt_handler(connection, evt); |
| } |
| connection = connection->next; |
| } |
| } |
| |
| #ifdef POSIX_BUILD |
| int |
| main(int argc, char **argv) |
| { |
| fd_set fd_r, fd_w, fd_x; |
| int nfds = 0; |
| |
| openlog("cti-server", LOG_PERROR, LOG_DAEMON); |
| signal(SIGPIPE, SIG_IGN); // because why ever? |
| cti_init(); |
| |
| do { |
| FD_ZERO(&fd_r); |
| FD_ZERO(&fd_w); |
| FD_ZERO(&fd_x); |
| |
| cti_fd_init(&nfds, &fd_r); |
| syslog(LOG_INFO, "selecting: %d descriptors.", nfds); |
| if (select(nfds, &fd_r, &fd_w, &fd_x, NULL) < 0) { |
| syslog(LOG_ERR, "select: %s", strerror(errno)); |
| exit(1); |
| } |
| |
| cti_fd_process(&fd_r); |
| } while (1); |
| } |
| #endif |
| |
| // Local Variables: |
| // mode: C |
| // tab-width: 4 |
| // c-file-style: "bsd" |
| // c-basic-offset: 4 |
| // fill-column: 108 |
| // indent-tabs-mode: nil |
| // End: |