| /* cti-openthread.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 implementation |
| */ |
| |
| #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 "cti-proto.h" |
| #include "cti-server.h" |
| #include "cti-openthread.h" |
| |
| #include <syslog.h> |
| #include <openthread/config.h> |
| #include <openthread/server.h> |
| #include <openthread/border_router.h> |
| #include <openthread/instance.h> |
| #include <openthread/thread.h> |
| #include <openthread/platform/misc.h> |
| #include <openthread/openthread-system.h> |
| |
| static otInstance* cti_instance = NULL; |
| void handleThreadStateChanged(uint32_t flags, void UNUSED *context); |
| |
| #define SuccessOrFailure(eval) \ |
| do \ |
| { \ |
| if (!(eval)) \ |
| { \ |
| return kCTIStatus_UnknownError; \ |
| } \ |
| } while (false) |
| |
| // OT accessorys and mutators |
| // Implementions of the getters and setters called by cti-server |
| int ctiAddService( uint32_t enterprise_number, |
| const uint8_t* service_data, |
| size_t service_data_length, |
| const uint8_t* server_data, |
| size_t server_data_length) { |
| |
| otError error = OT_ERROR_NONE; |
| otServiceConfig serviceCfg; |
| |
| if ( !cti_instance) { |
| return kCTIStatus_UnknownError; |
| } |
| |
| if (service_data_length > sizeof(serviceCfg.mServiceData) || |
| server_data_length > sizeof(serviceCfg.mServerConfig.mServerData)) { |
| return kCTIStatus_BadParam; |
| } |
| |
| serviceCfg.mEnterpriseNumber = enterprise_number; |
| memcpy(serviceCfg.mServiceData, service_data, service_data_length); |
| serviceCfg.mServiceDataLength = service_data_length; |
| memcpy(serviceCfg.mServerConfig.mServerData, server_data, server_data_length); |
| serviceCfg.mServerConfig.mServerDataLength = server_data_length; |
| serviceCfg.mServerConfig.mStable = true; |
| |
| error = otServerAddService(cti_instance, &serviceCfg); |
| |
| if (error != OT_ERROR_NONE) { |
| syslog(LOG_INFO, "Failed to add service: %d", error); |
| return kCTIStatus_UnknownError; |
| } |
| error = otBorderRouterRegister(cti_instance); |
| if (error != OT_ERROR_NONE) { |
| syslog(LOG_INFO, "Failed to push service: %d", error); |
| return kCTIStatus_UnknownError; |
| } |
| |
| return kCTIStatus_NoError; |
| } |
| |
| int ctiRemoveService( uint32_t enterprise_number, |
| const uint8_t *service_data, |
| size_t service_data_length) { |
| |
| if ( !cti_instance) { |
| return kCTIStatus_UnknownError; |
| } |
| |
| otError error = otServerRemoveService(cti_instance, |
| enterprise_number, |
| service_data, |
| service_data_length); |
| if (error != OT_ERROR_NONE) { |
| syslog(LOG_INFO, "Failed to remove service %d", enterprise_number); |
| return kCTIStatus_UnknownError; |
| } |
| error = otBorderRouterRegister(cti_instance); |
| if (error != OT_ERROR_NONE) { |
| syslog(LOG_INFO, "Failed to push service: %d", error); |
| return kCTIStatus_UnknownError; |
| } |
| return kCTIStatus_NoError; |
| } |
| |
| int ctiRetrieveServiceList(cti_connection_t connection, int UNUSED event) |
| { |
| otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT; |
| otServiceConfig config; |
| if ( !cti_instance) { |
| return kCTIStatus_UnknownError; |
| } |
| |
| uint16_t numServices = 0; |
| uint16_t totalSize = 0; |
| // Walk through the list and calculate the number and size of services |
| while (otNetDataGetNextService(cti_instance, &iterator, &config) == OT_ERROR_NONE) { |
| numServices++; |
| |
| // We will send the data back in this order: |
| totalSize += sizeof(config.mEnterpriseNumber); // Enterprise Num |
| totalSize += sizeof(config.mServiceDataLength); // Size of the service data |
| totalSize += config.mServiceDataLength; // The actual service data bytes |
| totalSize += sizeof(config.mServerConfig.mServerDataLength); // Size of the server data |
| totalSize += config.mServerConfig.mServerDataLength; // The actual server data bytes |
| } |
| |
| totalSize += sizeof(numServices); |
| |
| // Message: |
| // Num Services in List |
| // For each Service: |
| // Enterprise number |
| // Service Data Length |
| // Service Data |
| // Server Data Length |
| // Server Data |
| SuccessOrFailure( cti_connection_message_create(connection, kCTIMessageType_ServiceEvent, totalSize) ); |
| |
| // Indicate the number of services in the vector |
| SuccessOrFailure(cti_connection_u8_put(connection, numServices)); |
| |
| iterator = OT_NETWORK_DATA_ITERATOR_INIT; |
| |
| int i = 0; |
| while ((otNetDataGetNextService(cti_instance, &iterator, &config) == OT_ERROR_NONE) && i < numServices) { |
| SuccessOrFailure(cti_connection_u32_put(connection, config.mEnterpriseNumber)); |
| SuccessOrFailure(cti_connection_data_put(connection, config.mServiceData, config.mServiceDataLength)); |
| SuccessOrFailure(cti_connection_data_put(connection, |
| config.mServerConfig.mServerData, |
| config.mServerConfig.mServerDataLength)); |
| } |
| |
| SuccessOrFailure( cti_connection_message_send(connection) ); |
| |
| return kCTIStatus_NoError; |
| } |
| |
| int ctiAddMeshPrefix(struct in6_addr *prefix, size_t prefix_length, bool on_mesh, bool preferred, bool slaac, bool stable) |
| { |
| otError error = OT_ERROR_NONE; |
| if ( !cti_instance) { |
| return kCTIStatus_UnknownError; |
| } |
| otBorderRouterConfig borderRouterConfig; |
| memset(&borderRouterConfig, 0, sizeof(borderRouterConfig)); |
| |
| if ( prefix_length > 64) { |
| return kCTIStatus_BadParam; |
| } |
| memcpy(borderRouterConfig.mPrefix.mPrefix.mFields.m8, prefix, sizeof(*prefix)); |
| |
| borderRouterConfig.mPrefix.mLength = prefix_length; |
| borderRouterConfig.mStable = stable; |
| borderRouterConfig.mPreference = OT_ROUTE_PREFERENCE_MED; // Can also be high or low. |
| borderRouterConfig.mPreferred = preferred; |
| borderRouterConfig.mSlaac = slaac; |
| borderRouterConfig.mOnMesh = on_mesh; |
| borderRouterConfig.mStable = stable; |
| borderRouterConfig.mConfigure = false; |
| borderRouterConfig.mDefaultRoute = true; |
| borderRouterConfig.mDhcp = false; |
| |
| error = otBorderRouterAddOnMeshPrefix(cti_instance, &borderRouterConfig); |
| if (error != OT_ERROR_NONE) { |
| return kCTIStatus_UnknownError; |
| } |
| error = otBorderRouterRegister(cti_instance); |
| if (error != OT_ERROR_NONE) { |
| syslog(LOG_INFO, "Failed to push service: %d", error); |
| return kCTIStatus_UnknownError; |
| } |
| |
| return kCTIStatus_NoError; |
| } |
| |
| int ctiRemoveMeshPrefix(struct in6_addr *prefix, size_t prefix_length) { |
| |
| if (!cti_instance) { |
| return kCTIStatus_UnknownError; |
| } |
| |
| otIp6Prefix ip6Prefix; |
| memset(&ip6Prefix, 0, sizeof(ip6Prefix)); |
| |
| if (prefix_length > 64) { |
| return kCTIStatus_BadParam; |
| } |
| memcpy(ip6Prefix.mPrefix.mFields.m8, prefix, sizeof(*prefix)); |
| ip6Prefix.mLength = prefix_length; |
| otError error = otBorderRouterRemoveOnMeshPrefix(cti_instance, &ip6Prefix); |
| if (error != OT_ERROR_NONE && error != OT_ERROR_NOT_FOUND) { |
| return kCTIStatus_UnknownError; |
| } |
| error = otBorderRouterRegister(cti_instance); |
| if (error != OT_ERROR_NONE) { |
| syslog(LOG_INFO, "Failed to push service: %d", error); |
| return kCTIStatus_UnknownError; |
| } |
| return kCTIStatus_NoError; |
| } |
| |
| int ctiRetrievePrefixList(cti_connection_t connection, int UNUSED event) |
| { |
| if (!cti_instance) { |
| return kCTIStatus_UnknownError; |
| } |
| otBorderRouterConfig config; |
| otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT; |
| |
| uint16_t numPrefixes = 0; |
| int totalSize = 0; |
| do { |
| int error = otNetDataGetNextOnMeshPrefix(cti_instance, &iterator, &config); |
| if (error != OT_ERROR_NONE) { |
| if (error != OT_ERROR_NOT_FOUND) { |
| syslog(LOG_ERR, "ctiRetrievePrefixList: otBorderRouterGetNextRoute: %d", error); |
| return kCTIStatus_UnknownError; |
| } |
| break; |
| } |
| numPrefixes++; |
| totalSize += 1; // 1 byte stability / source flag. |
| totalSize += sizeof (config.mPrefix.mLength); |
| totalSize += config.mPrefix.mLength; // size of the prefix |
| } while (1); |
| |
| // Message: |
| // Num prefixes in List |
| // For each prefix: |
| // flags |
| // prefix length |
| // prefix |
| SuccessOrFailure( cti_connection_message_create(connection, kCTIMessageType_PrefixEvent, totalSize) ); |
| |
| // Indicate the number of services in the vector |
| SuccessOrFailure(cti_connection_u16_put(connection, numPrefixes)); |
| |
| iterator = OT_NETWORK_DATA_ITERATOR_INIT; |
| |
| int i = 0; |
| while (i < numPrefixes) |
| { |
| int error = otNetDataGetNextOnMeshPrefix(cti_instance, &iterator, &config); |
| if (error != OT_ERROR_NONE) { |
| if (error != OT_ERROR_NOT_FOUND) { |
| syslog(LOG_ERR, "ctiRetrievePrefixList: otBorderRouterGetNextRoute: %d", error); |
| } |
| break; |
| } |
| // DJC Note: Shouldn't be possible for the items in the Thread Network data to have changed |
| // between above count and now, but I'm paranoid and assumptions rot over time. |
| uint16_t flags = 0; |
| flags |= config.mDefaultRoute ? 0 : kCTIFlag_NCP; |
| flags |= config.mStable ? kCTIFlag_Stable : 0; |
| |
| SuccessOrFailure(cti_connection_u16_put(connection, flags)); |
| SuccessOrFailure(cti_connection_u8_put(connection, config.mPrefix.mLength)); |
| SuccessOrFailure(cti_connection_data_put(connection, config.mPrefix.mPrefix.mFields.m8, 8)); |
| } |
| |
| SuccessOrFailure( cti_connection_message_send(connection) ); |
| return kCTIStatus_NoError; |
| } |
| |
| int ctiRetrievePartitionId(cti_connection_t connection, int UNUSED event) |
| { |
| uint32_t partitionId = otThreadGetPartitionId(cti_instance); |
| SuccessOrFailure(cti_connection_message_create(connection, kCTIMessageType_UInt64PropEvent, |
| sizeof(partitionId) + sizeof(uint32_t))); |
| SuccessOrFailure(cti_connection_u32_put(connection, kCTIPropertyPartitionID)); |
| SuccessOrFailure(cti_connection_u64_put(connection, partitionId)); |
| SuccessOrFailure(cti_connection_message_send(connection)); |
| return kCTIStatus_NoError; |
| } |
| |
| int ctiRetrieveXPANID(cti_connection_t connection, int UNUSED event) |
| { |
| const otExtendedPanId *panid_buf = otThreadGetExtendedPanId(cti_instance); |
| uint64_t xpanid = 0; |
| for (int i = 7; i >= 0; i--) { |
| xpanid = (xpanid << 8) | panid_buf->m8[i]; |
| } |
| SuccessOrFailure(cti_connection_message_create(connection, kCTIMessageType_UInt64PropEvent, |
| sizeof(xpanid) + sizeof(uint32_t))); |
| SuccessOrFailure(cti_connection_u32_put(connection, kCTIPropertyExtendedPANID)); |
| SuccessOrFailure(cti_connection_u64_put(connection, xpanid)); |
| SuccessOrFailure(cti_connection_message_send(connection)); |
| return kCTIStatus_NoError; |
| } |
| |
| int ctiRetrieveTunnel(cti_connection_t connection) { |
| const char *interfaceName = "wpan0"; |
| uint32_t interfaceIndex = 0; |
| #if defined(OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE) && OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE |
| otError error = otPlatGetNetif(cti_instance, &interfaceName, &interfaceIndex); |
| if (error != OT_ERROR_NONE) { |
| return kCTIStatus_UnknownError; |
| } |
| #endif |
| (void)interfaceIndex; |
| SuccessOrFailure(cti_connection_message_create(connection, |
| kCTIMessageType_TunnelNameResponse, |
| 2 + strlen(interfaceName))); |
| SuccessOrFailure(cti_connection_string_put(connection, interfaceName)); |
| SuccessOrFailure(cti_connection_message_send(connection)); |
| return kCTIStatus_NoError; |
| } |
| |
| /* This will return one of the following: |
| * OT_DEVICE_ROLE_DISABLED = 0, ///< The Thread stack is disabled. |
| * OT_DEVICE_ROLE_DETACHED = 1, ///< Not currently participating in a Thread network/partition. |
| * OT_DEVICE_ROLE_CHILD = 2, ///< The Thread Child role. |
| * OT_DEVICE_ROLE_ROUTER = 3, ///< The Thread Router role. |
| * OT_DEVICE_ROLE_LEADER = 4, ///< The Thread Leader role. |
| * Note: Disabled = "OFF", Detached = "Associating", Others = "Associated" |
| */ |
| int |
| ctiRetrieveNodeType(cti_connection_t connection, int event) |
| { |
| otDeviceRole role = otThreadGetDeviceRole(cti_instance); |
| uint8_t datum; |
| int event_type; |
| |
| if (event == CTI_EVENT_ROLE) { |
| event_type = kCTIMessageType_RoleEvent; |
| switch(role) { |
| default: |
| case OT_DEVICE_ROLE_DISABLED: |
| case OT_DEVICE_ROLE_DETACHED: |
| datum = kCTI_NetworkNodeType_Unknown; |
| break; |
| case OT_DEVICE_ROLE_CHILD: |
| datum = kCTI_NetworkNodeType_EndDevice; |
| break; |
| case OT_DEVICE_ROLE_ROUTER: |
| datum = kCTI_NetworkNodeType_Router; |
| break; |
| case OT_DEVICE_ROLE_LEADER: |
| datum = kCTI_NetworkNodeType_Leader; |
| break; |
| } |
| } else { |
| event_type = kCTIMessageType_StateEvent; |
| switch(role) { |
| default: |
| case OT_DEVICE_ROLE_DISABLED: |
| datum = kCTI_NCPState_Offline; |
| break; |
| case OT_DEVICE_ROLE_DETACHED: |
| datum = kCTI_NCPState_Associating; |
| break; |
| case OT_DEVICE_ROLE_CHILD: |
| case OT_DEVICE_ROLE_ROUTER: |
| case OT_DEVICE_ROLE_LEADER: |
| datum = kCTI_NCPState_Associated; |
| break; |
| } |
| } |
| if (cti_connection_message_create(connection, event_type, 1) && |
| cti_connection_u8_put(connection, datum) && |
| cti_connection_message_send(connection)) { |
| } |
| return 0; |
| } |
| |
| void |
| handleThreadStateChanged(uint32_t flags, void UNUSED *context) |
| { |
| syslog(LOG_INFO, "handleThreadStateChanged: flags = %" PRIx32, flags); |
| |
| if ( !cti_instance) { |
| return; |
| } |
| |
| syslog(LOG_INFO, "Thread state changed, flag: %d", flags ); |
| if ( flags & OT_CHANGED_THREAD_ROLE) |
| { |
| syslog(LOG_INFO, " Thread Role changed. Notify registered clients" ); |
| cti_notify_event(CTI_EVENT_ROLE, ctiRetrieveNodeType); |
| cti_notify_event(CTI_EVENT_STATE, ctiRetrieveNodeType); |
| } |
| if ( flags & OT_CHANGED_THREAD_NETDATA) |
| { |
| syslog(LOG_INFO, " Thread Netdata changed. Notify registered clients" ); |
| cti_notify_event(CTI_EVENT_SERVICE, ctiRetrieveServiceList); |
| cti_notify_event(CTI_EVENT_PREFIX, ctiRetrievePrefixList); |
| |
| } |
| if ( flags & OT_CHANGED_THREAD_PARTITION_ID) |
| { |
| syslog(LOG_INFO, " Thread Partition ID changed. Notify registered clients" ); |
| cti_notify_event(CTI_EVENT_PARTITION_ID, ctiRetrievePartitionId); |
| } |
| if ( flags & OT_CHANGED_THREAD_EXT_PANID) |
| { |
| syslog(LOG_INFO, " Thread Partition ID changed. Notify registered clients" ); |
| cti_notify_event(CTI_EVENT_XPANID, ctiRetrievePartitionId); |
| } |
| |
| } |
| |
| // Functions to be called by ot-daemon's main: |
| // Called from main after InitInstance() |
| void otCtiServerInit(otInstance* aInstance) { |
| cti_instance = aInstance; |
| otError error = otSetStateChangedCallback(cti_instance, handleThreadStateChanged, NULL); |
| if(error != OT_ERROR_NONE && error != OT_ERROR_ALREADY) { |
| syslog(LOG_INFO, "otCtiServerInit: otSetStateChangedCallback: Unable to register: %d", error); |
| } |
| |
| cti_init(); |
| } |
| |
| // Called in main's idle loop after FD Prep. |
| void otCtiServerUpdate(otInstance* aInstance, otSysMainloopContext *aMainloop) { |
| int nfds = 0; |
| cti_instance = aInstance; |
| // Note: This will not compile as it requires a change to cti_fd_init's arg list since |
| // ot-daemon's make does not allow unused arguments. |
| cti_fd_init(&nfds, &aMainloop->mReadFdSet); |
| if (aMainloop->mMaxFd < nfds) { |
| aMainloop->mMaxFd = nfds; |
| } |
| } |
| |
| // Called in main loop after a successful otSysMainloopPoll |
| void otCtiServerProcess(otInstance* aInstance, otSysMainloopContext *aMainloop) { |
| cti_instance = aInstance; |
| // Note: this will not compile as it is as it removes unused args in cti_fd_process |
| cti_fd_process(&aMainloop->mReadFdSet); |
| } |
| |
| // Local Variables: |
| // mode: C |
| // tab-width: 4 |
| // c-file-style: "bsd" |
| // c-basic-offset: 4 |
| // fill-column: 108 |
| // indent-tabs-mode: nil |
| // End: |