/* * Copyright (c) 2024, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #define OTBR_LOG_TAG "NcpSpinel" #include "ncp_spinel.hpp" #include #include #include #include #include #include "common/code_utils.hpp" #include "common/logging.hpp" #include "lib/spinel/spinel.h" #include "lib/spinel/spinel_decoder.hpp" #include "lib/spinel/spinel_driver.hpp" #include "lib/spinel/spinel_encoder.hpp" #include "lib/spinel/spinel_helper.hpp" #include "lib/spinel/spinel_prop_codec.hpp" namespace otbr { namespace Host { static constexpr char kSpinelDataUnpackFormat[] = "CiiD"; NcpSpinel::NcpSpinel(void) : mSpinelDriver(nullptr) , mCmdTidsInUse(0) , mCmdNextTid(1) , mNcpBuffer(mTxBuffer, kTxBufferSize) , mEncoder(mNcpBuffer) , mIid(SPINEL_HEADER_INVALID_IID) , mPropsObserver(nullptr) #if OTBR_ENABLE_SRP_ADVERTISING_PROXY , mPublisher(nullptr) #endif { std::fill_n(mWaitingKeyTable, SPINEL_PROP_LAST_STATUS, sizeof(mWaitingKeyTable)); memset(mCmdTable, 0, sizeof(mCmdTable)); } void NcpSpinel::Init(ot::Spinel::SpinelDriver &aSpinelDriver, PropsObserver &aObserver) { mSpinelDriver = &aSpinelDriver; mPropsObserver = &aObserver; mIid = mSpinelDriver->GetIid(); mSpinelDriver->SetFrameHandler(&HandleReceivedFrame, &HandleSavedFrame, this); } void NcpSpinel::Deinit(void) { mSpinelDriver = nullptr; mIp6AddressTableCallback = nullptr; mNetifStateChangedCallback = nullptr; #if OTBR_ENABLE_SRP_ADVERTISING_PROXY mPublisher = nullptr; #endif } otbrError NcpSpinel::SpinelDataUnpack(const uint8_t *aDataIn, spinel_size_t aDataLen, const char *aPackFormat, ...) { otbrError error = OTBR_ERROR_NONE; spinel_ssize_t unpacked; va_list args; va_start(args, aPackFormat); unpacked = spinel_datatype_vunpack(aDataIn, aDataLen, aPackFormat, args); va_end(args); VerifyOrExit(unpacked > 0, error = OTBR_ERROR_PARSE); exit: return error; } void NcpSpinel::DatasetSetActiveTlvs(const otOperationalDatasetTlvs &aActiveOpDatasetTlvs, AsyncTaskPtr aAsyncTask) { otError error = OT_ERROR_NONE; EncodingFunc encodingFunc = [&aActiveOpDatasetTlvs](ot::Spinel::Encoder &aEncoder) { return aEncoder.WriteData(aActiveOpDatasetTlvs.mTlvs, aActiveOpDatasetTlvs.mLength); }; VerifyOrExit(mDatasetSetActiveTask == nullptr, error = OT_ERROR_BUSY); SuccessOrExit(error = SetProperty(SPINEL_PROP_THREAD_ACTIVE_DATASET_TLVS, encodingFunc)); mDatasetSetActiveTask = aAsyncTask; exit: if (error != OT_ERROR_NONE) { mTaskRunner.Post([aAsyncTask, error](void) { aAsyncTask->SetResult(error, "Failed to set active dataset!"); }); } } void NcpSpinel::DatasetMgmtSetPending(std::shared_ptr aPendingOpDatasetTlvsPtr, AsyncTaskPtr aAsyncTask) { otError error = OT_ERROR_NONE; EncodingFunc encodingFunc = [aPendingOpDatasetTlvsPtr](ot::Spinel::Encoder &aEncoder) { return aEncoder.WriteData(aPendingOpDatasetTlvsPtr->mTlvs, aPendingOpDatasetTlvsPtr->mLength); }; VerifyOrExit(mDatasetMgmtSetPendingTask == nullptr, error = OT_ERROR_BUSY); SuccessOrExit(error = SetProperty(SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS, encodingFunc)); mDatasetMgmtSetPendingTask = aAsyncTask; exit: if (error != OT_ERROR_NONE) { mTaskRunner.Post([aAsyncTask, error] { aAsyncTask->SetResult(error, "Failed to set pending dataset!"); }); } } void NcpSpinel::Ip6SetEnabled(bool aEnable, AsyncTaskPtr aAsyncTask) { otError error = OT_ERROR_NONE; EncodingFunc encodingFunc = [aEnable](ot::Spinel::Encoder &aEncoder) { return aEncoder.WriteBool(aEnable); }; VerifyOrExit(mIp6SetEnabledTask == nullptr, error = OT_ERROR_BUSY); SuccessOrExit(error = SetProperty(SPINEL_PROP_NET_IF_UP, encodingFunc)); mIp6SetEnabledTask = aAsyncTask; exit: if (error != OT_ERROR_NONE) { mTaskRunner.Post( [aAsyncTask, error](void) { aAsyncTask->SetResult(error, "Failed to enable the network interface!"); }); } return; } otbrError NcpSpinel::Ip6Send(const uint8_t *aData, uint16_t aLength) { otbrError error = OTBR_ERROR_NONE; EncodingFunc encodingFunc = [aData, aLength](ot::Spinel::Encoder &aEncoder) { return aEncoder.WriteDataWithLen(aData, aLength); }; SuccessOrExit(SetProperty(SPINEL_PROP_STREAM_NET, encodingFunc), error = OTBR_ERROR_OPENTHREAD); exit: return error; } void NcpSpinel::ThreadSetEnabled(bool aEnable, AsyncTaskPtr aAsyncTask) { otError error = OT_ERROR_NONE; EncodingFunc encodingFunc = [aEnable](ot::Spinel::Encoder &aEncoder) { return aEncoder.WriteBool(aEnable); }; VerifyOrExit(mThreadSetEnabledTask == nullptr, error = OT_ERROR_BUSY); SuccessOrExit(error = SetProperty(SPINEL_PROP_NET_STACK_UP, encodingFunc)); mThreadSetEnabledTask = aAsyncTask; exit: if (error != OT_ERROR_NONE) { mTaskRunner.Post( [aAsyncTask, error](void) { aAsyncTask->SetResult(error, "Failed to enable the Thread network!"); }); } return; } void NcpSpinel::ThreadDetachGracefully(AsyncTaskPtr aAsyncTask) { otError error = OT_ERROR_NONE; EncodingFunc encodingFunc = [](ot::Spinel::Encoder &) { return OT_ERROR_NONE; }; VerifyOrExit(mThreadDetachGracefullyTask == nullptr, error = OT_ERROR_BUSY); SuccessOrExit(error = SetProperty(SPINEL_PROP_NET_LEAVE_GRACEFULLY, encodingFunc)); mThreadDetachGracefullyTask = aAsyncTask; exit: if (error != OT_ERROR_NONE) { mTaskRunner.Post([aAsyncTask, error](void) { aAsyncTask->SetResult(error, "Failed to detach gracefully!"); }); } return; } void NcpSpinel::ThreadErasePersistentInfo(AsyncTaskPtr aAsyncTask) { otError error = OT_ERROR_NONE; spinel_tid_t tid = GetNextTid(); VerifyOrExit(mThreadErasePersistentInfoTask == nullptr, error = OT_ERROR_BUSY); SuccessOrExit(error = mSpinelDriver->SendCommand(SPINEL_CMD_NET_CLEAR, SPINEL_PROP_LAST_STATUS, tid)); mWaitingKeyTable[tid] = SPINEL_PROP_LAST_STATUS; mCmdTable[tid] = SPINEL_CMD_NET_CLEAR; mThreadErasePersistentInfoTask = aAsyncTask; exit: if (error != OT_ERROR_NONE) { FreeTidTableItem(tid); mTaskRunner.Post( [aAsyncTask, error](void) { aAsyncTask->SetResult(error, "Failed to erase persistent info!"); }); } } #if OTBR_ENABLE_SRP_ADVERTISING_PROXY void NcpSpinel::SrpServerSetAutoEnableMode(bool aEnabled) { otError error; EncodingFunc encodingFunc = [aEnabled](ot::Spinel::Encoder &aEncoder) { return aEncoder.WriteBool(aEnabled); }; error = SetProperty(SPINEL_PROP_SRP_SERVER_AUTO_ENABLE_MODE, encodingFunc); if (error != OT_ERROR_NONE) { otbrLogWarning("Failed to call SrpServerSetAutoEnableMode, %s", otThreadErrorToString(error)); } } void NcpSpinel::SrpServerSetEnabled(bool aEnabled) { otError error; EncodingFunc encodingFunc = [aEnabled](ot::Spinel::Encoder &aEncoder) { return aEncoder.WriteBool(aEnabled); }; error = SetProperty(SPINEL_PROP_SRP_SERVER_ENABLED, encodingFunc); if (error != OT_ERROR_NONE) { otbrLogWarning("Failed to call SrpServerSetEnabled, %s", otThreadErrorToString(error)); } } void NcpSpinel::DnssdSetState(Mdns::Publisher::State aState) { otError error; otPlatDnssdState state = (aState == Mdns::Publisher::State::kReady) ? OT_PLAT_DNSSD_READY : OT_PLAT_DNSSD_STOPPED; EncodingFunc encodingFunc = [state](ot::Spinel::Encoder &aEncoder) { return aEncoder.WriteUint8(state); }; error = SetProperty(SPINEL_PROP_DNSSD_STATE, encodingFunc); if (error != OT_ERROR_NONE) { otbrLogWarning("Failed to call DnssdSetState, %s", otThreadErrorToString(error)); } } #endif // OTBR_ENABLE_SRP_ADVERTISING_PROXY void NcpSpinel::HandleReceivedFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aSave, void *aContext) { static_cast(aContext)->HandleReceivedFrame(aFrame, aLength, aHeader, aSave); } void NcpSpinel::HandleReceivedFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aShouldSaveFrame) { spinel_tid_t tid = SPINEL_HEADER_GET_TID(aHeader); if (tid == 0) { HandleNotification(aFrame, aLength); } else if (tid < kMaxTids) { HandleResponse(tid, aFrame, aLength); } else { otbrLogCrit("Received unexpected tid: %u", tid); } aShouldSaveFrame = false; } void NcpSpinel::HandleSavedFrame(const uint8_t *aFrame, uint16_t aLength, void *aContext) { /* Intentionally Empty */ OT_UNUSED_VARIABLE(aFrame); OT_UNUSED_VARIABLE(aLength); OT_UNUSED_VARIABLE(aContext); } void NcpSpinel::HandleNotification(const uint8_t *aFrame, uint16_t aLength) { spinel_prop_key_t key; spinel_size_t len = 0; uint8_t *data = nullptr; uint32_t cmd; uint8_t header; otbrError error = OTBR_ERROR_NONE; SuccessOrExit(error = SpinelDataUnpack(aFrame, aLength, kSpinelDataUnpackFormat, &header, &cmd, &key, &data, &len)); VerifyOrExit(SPINEL_HEADER_GET_TID(header) == 0, error = OTBR_ERROR_PARSE); switch (cmd) { case SPINEL_CMD_PROP_VALUE_IS: HandleValueIs(key, data, static_cast(len)); break; case SPINEL_CMD_PROP_VALUE_INSERTED: HandleValueInserted(key, data, static_cast(len)); break; case SPINEL_CMD_PROP_VALUE_REMOVED: HandleValueRemoved(key, data, static_cast(len)); break; } exit: otbrLogResult(error, "%s", __FUNCTION__); } void NcpSpinel::HandleResponse(spinel_tid_t aTid, const uint8_t *aFrame, uint16_t aLength) { spinel_prop_key_t key; spinel_size_t len = 0; uint8_t *data = nullptr; uint32_t cmd; uint8_t header; otbrError error = OTBR_ERROR_NONE; FailureHandler failureHandler = nullptr; SuccessOrExit(error = SpinelDataUnpack(aFrame, aLength, kSpinelDataUnpackFormat, &header, &cmd, &key, &data, &len)); switch (mCmdTable[aTid]) { case SPINEL_CMD_PROP_VALUE_SET: { error = HandleResponseForPropSet(aTid, key, data, len); break; } case SPINEL_CMD_PROP_VALUE_INSERT: { error = HandleResponseForPropInsert(aTid, cmd, key, data, len); break; } case SPINEL_CMD_PROP_VALUE_REMOVE: { error = HandleResponseForPropRemove(aTid, cmd, key, data, len); break; } case SPINEL_CMD_NET_CLEAR: { spinel_status_t status = SPINEL_STATUS_OK; SuccessOrExit(error = SpinelDataUnpack(data, len, SPINEL_DATATYPE_UINT_PACKED_S, &status)); CallAndClear(mThreadErasePersistentInfoTask, ot::Spinel::SpinelStatusToOtError(status)); break; } default: break; } exit: if (error == OTBR_ERROR_INVALID_STATE) { otbrLogCrit("Received unexpected response with (cmd:%u, key:%u), waiting (cmd:%u, key:%u) for tid:%u", cmd, key, mCmdTable[aTid], mWaitingKeyTable[aTid], aTid); } else if (error == OTBR_ERROR_PARSE) { otbrLogCrit("Error parsing response with tid:%u", aTid); } FreeTidTableItem(aTid); } void NcpSpinel::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength) { otbrError error = OTBR_ERROR_NONE; switch (aKey) { case SPINEL_PROP_LAST_STATUS: { spinel_status_t status = SPINEL_STATUS_OK; SuccessOrExit(error = SpinelDataUnpack(aBuffer, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status)); otbrLogInfo("NCP last status: %s", spinel_status_to_cstr(status)); break; } case SPINEL_PROP_NET_ROLE: { spinel_net_role_t role = SPINEL_NET_ROLE_DISABLED; otDeviceRole deviceRole; SuccessOrExit(error = SpinelDataUnpack(aBuffer, aLength, SPINEL_DATATYPE_UINT8_S, &role)); deviceRole = SpinelRoleToDeviceRole(role); mPropsObserver->SetDeviceRole(deviceRole); otbrLogInfo("Device role changed to %s", otThreadDeviceRoleToString(deviceRole)); break; } case SPINEL_PROP_NET_LEAVE_GRACEFULLY: { CallAndClear(mThreadDetachGracefullyTask, OT_ERROR_NONE); break; } case SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS: { spinel_status_t status = SPINEL_STATUS_OK; SuccessOrExit(error = SpinelDataUnpack(aBuffer, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status)); CallAndClear(mDatasetMgmtSetPendingTask, ot::Spinel::SpinelStatusToOtError(status)); break; } case SPINEL_PROP_IPV6_ADDRESS_TABLE: { std::vector addressInfoTable; VerifyOrExit(ParseIp6AddressTable(aBuffer, aLength, addressInfoTable) == OT_ERROR_NONE, error = OTBR_ERROR_PARSE); SafeInvoke(mIp6AddressTableCallback, addressInfoTable); break; } case SPINEL_PROP_IPV6_MULTICAST_ADDRESS_TABLE: { std::vector addressTable; VerifyOrExit(ParseIp6MulticastAddresses(aBuffer, aLength, addressTable) == OT_ERROR_NONE, error = OTBR_ERROR_PARSE); SafeInvoke(mIp6MulticastAddressTableCallback, addressTable); break; } case SPINEL_PROP_NET_IF_UP: { bool isUp; SuccessOrExit(error = SpinelDataUnpack(aBuffer, aLength, SPINEL_DATATYPE_BOOL_S, &isUp)); SafeInvoke(mNetifStateChangedCallback, isUp); break; } case SPINEL_PROP_THREAD_CHILD_TABLE: case SPINEL_PROP_THREAD_ON_MESH_NETS: case SPINEL_PROP_THREAD_OFF_MESH_ROUTES: case SPINEL_PROP_THREAD_LEADER_NETWORK_DATA: case SPINEL_PROP_IPV6_LL_ADDR: break; case SPINEL_PROP_STREAM_NET: { const uint8_t *data; uint16_t dataLen; SuccessOrExit(ParseIp6StreamNet(aBuffer, aLength, data, dataLen), error = OTBR_ERROR_PARSE); SafeInvoke(mIp6ReceiveCallback, data, dataLen); break; } case SPINEL_PROP_INFRA_IF_SEND_ICMP6: { uint32_t infraIfIndex; const otIp6Address *destAddress; const uint8_t *data; uint16_t dataLen; SuccessOrExit(ParseInfraIfIcmp6Nd(aBuffer, aLength, infraIfIndex, destAddress, data, dataLen), error = OTBR_ERROR_PARSE); SafeInvoke(mInfraIfIcmp6NdCallback, infraIfIndex, *destAddress, data, dataLen); break; } default: otbrLogWarning("Received uncognized key: %u", aKey); break; } exit: otbrLogResult(error, "NcpSpinel: %s", __FUNCTION__); return; } #if OTBR_ENABLE_SRP_ADVERTISING_PROXY static std::string KeyNameFor(const otPlatDnssdKey &aKey) { std::string name(aKey.mName); if (aKey.mServiceType != nullptr) { // TODO: current code would not work with service instance labels that include a '.' name += "."; name += aKey.mServiceType; } return name; } #endif void NcpSpinel::HandleValueInserted(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength) { otbrError error = OTBR_ERROR_NONE; ot::Spinel::Decoder decoder; VerifyOrExit(aBuffer != nullptr, error = OTBR_ERROR_INVALID_ARGS); decoder.Init(aBuffer, aLength); switch (aKey) { #if OTBR_ENABLE_SRP_ADVERTISING_PROXY case SPINEL_PROP_DNSSD_HOST: { Mdns::Publisher::AddressList addressList; otPlatDnssdHost host; otPlatDnssdRequestId requestId; const uint8_t *callbackData; uint16_t callbackDataSize; std::vector callbackDataCopy; SuccessOrExit(ot::Spinel::DecodeDnssdHost(decoder, host, requestId, callbackData, callbackDataSize)); for (uint16_t i = 0; i < host.mAddressesLength; i++) { addressList.push_back(Ip6Address(host.mAddresses[i].mFields.m8)); } callbackDataCopy.assign(callbackData, callbackData + callbackDataSize); mPublisher->PublishHost(host.mHostName, addressList, [this, requestId, callbackDataCopy](otbrError aError) { OT_UNUSED_VARIABLE(SendDnssdResult(requestId, callbackDataCopy, OtbrErrorToOtError(aError))); }); break; } case SPINEL_PROP_DNSSD_SERVICE: { otPlatDnssdService service; Mdns::Publisher::SubTypeList subTypeList; const char *subTypeArray[kMaxSubTypes]; uint16_t subTypeCount; Mdns::Publisher::TxtData txtData; otPlatDnssdRequestId requestId; const uint8_t *callbackData; uint16_t callbackDataSize; std::vector callbackDataCopy; SuccessOrExit(ot::Spinel::DecodeDnssdService(decoder, service, subTypeArray, subTypeCount, requestId, callbackData, callbackDataSize)); for (uint16_t i = 0; i < subTypeCount; i++) { subTypeList.push_back(subTypeArray[i]); } txtData.assign(service.mTxtData, service.mTxtData + service.mTxtDataLength); callbackDataCopy.assign(callbackData, callbackData + callbackDataSize); mPublisher->PublishService(service.mHostName, service.mServiceInstance, service.mServiceType, subTypeList, service.mPort, txtData, [this, requestId, callbackDataCopy](otbrError aError) { OT_UNUSED_VARIABLE( SendDnssdResult(requestId, callbackDataCopy, OtbrErrorToOtError(aError))); }); break; } case SPINEL_PROP_DNSSD_KEY_RECORD: { otPlatDnssdKey key; Mdns::Publisher::KeyData keyData; otPlatDnssdRequestId requestId; const uint8_t *callbackData; uint16_t callbackDataSize; std::vector callbackDataCopy; SuccessOrExit(ot::Spinel::DecodeDnssdKey(decoder, key, requestId, callbackData, callbackDataSize)); keyData.assign(key.mKeyData, key.mKeyData + key.mKeyDataLength); callbackDataCopy.assign(callbackData, callbackData + callbackDataSize); mPublisher->PublishKey(KeyNameFor(key), keyData, [this, requestId, callbackDataCopy](otbrError aError) { OT_UNUSED_VARIABLE(SendDnssdResult(requestId, callbackDataCopy, OtbrErrorToOtError(aError))); }); break; } #endif // OTBR_ENABLE_SRP_ADVERTISING_PROXY default: error = OTBR_ERROR_DROPPED; break; } exit: otbrLogResult(error, "HandleValueInserted, key:%u", aKey); return; } void NcpSpinel::HandleValueRemoved(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength) { otbrError error = OTBR_ERROR_NONE; ot::Spinel::Decoder decoder; VerifyOrExit(aBuffer != nullptr, error = OTBR_ERROR_INVALID_ARGS); decoder.Init(aBuffer, aLength); switch (aKey) { #if OTBR_ENABLE_SRP_ADVERTISING_PROXY case SPINEL_PROP_DNSSD_HOST: { otPlatDnssdHost host; otPlatDnssdRequestId requestId; const uint8_t *callbackData; uint16_t callbackDataSize; std::vector callbackDataCopy; SuccessOrExit(ot::Spinel::DecodeDnssdHost(decoder, host, requestId, callbackData, callbackDataSize)); callbackDataCopy.assign(callbackData, callbackData + callbackDataSize); mPublisher->UnpublishHost(host.mHostName, [this, requestId, callbackDataCopy](otbrError aError) { OT_UNUSED_VARIABLE(SendDnssdResult(requestId, callbackDataCopy, OtbrErrorToOtError(aError))); }); break; } case SPINEL_PROP_DNSSD_SERVICE: { otPlatDnssdService service; const char *subTypeArray[kMaxSubTypes]; uint16_t subTypeCount; otPlatDnssdRequestId requestId; const uint8_t *callbackData; uint16_t callbackDataSize; std::vector callbackDataCopy; SuccessOrExit(ot::Spinel::DecodeDnssdService(decoder, service, subTypeArray, subTypeCount, requestId, callbackData, callbackDataSize)); callbackDataCopy.assign(callbackData, callbackData + callbackDataSize); mPublisher->UnpublishService( service.mHostName, service.mServiceType, [this, requestId, callbackDataCopy](otbrError aError) { OT_UNUSED_VARIABLE(SendDnssdResult(requestId, callbackDataCopy, OtbrErrorToOtError(aError))); }); break; } case SPINEL_PROP_DNSSD_KEY_RECORD: { otPlatDnssdKey key; otPlatDnssdRequestId requestId; const uint8_t *callbackData; uint16_t callbackDataSize; std::vector callbackDataCopy; SuccessOrExit(ot::Spinel::DecodeDnssdKey(decoder, key, requestId, callbackData, callbackDataSize)); callbackDataCopy.assign(callbackData, callbackData + callbackDataSize); mPublisher->UnpublishKey(KeyNameFor(key), [this, requestId, callbackDataCopy](otbrError aError) { OT_UNUSED_VARIABLE(SendDnssdResult(requestId, callbackDataCopy, OtbrErrorToOtError(aError))); }); break; } #endif // OTBR_ENABLE_SRP_ADVERTISING_PROXY default: error = OTBR_ERROR_DROPPED; break; } exit: otbrLogResult(error, "HandleValueRemoved, key:%u", aKey); return; } otbrError NcpSpinel::HandleResponseForPropSet(spinel_tid_t aTid, spinel_prop_key_t aKey, const uint8_t *aData, uint16_t aLength) { OTBR_UNUSED_VARIABLE(aData); OTBR_UNUSED_VARIABLE(aLength); otbrError error = OTBR_ERROR_NONE; spinel_status_t status = SPINEL_STATUS_OK; switch (mWaitingKeyTable[aTid]) { case SPINEL_PROP_THREAD_ACTIVE_DATASET_TLVS: VerifyOrExit(aKey == SPINEL_PROP_THREAD_ACTIVE_DATASET_TLVS, error = OTBR_ERROR_INVALID_STATE); CallAndClear(mDatasetSetActiveTask, OT_ERROR_NONE); { otOperationalDatasetTlvs datasetTlvs; VerifyOrExit(ParseOperationalDatasetTlvs(aData, aLength, datasetTlvs) == OT_ERROR_NONE, error = OTBR_ERROR_PARSE); mPropsObserver->SetDatasetActiveTlvs(datasetTlvs); } break; case SPINEL_PROP_NET_IF_UP: VerifyOrExit(aKey == SPINEL_PROP_NET_IF_UP, error = OTBR_ERROR_INVALID_STATE); CallAndClear(mIp6SetEnabledTask, OT_ERROR_NONE); { bool isUp; SuccessOrExit(error = SpinelDataUnpack(aData, aLength, SPINEL_DATATYPE_BOOL_S, &isUp)); SafeInvoke(mNetifStateChangedCallback, isUp); } break; case SPINEL_PROP_NET_STACK_UP: VerifyOrExit(aKey == SPINEL_PROP_NET_STACK_UP, error = OTBR_ERROR_INVALID_STATE); CallAndClear(mThreadSetEnabledTask, OT_ERROR_NONE); break; case SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS: if (aKey == SPINEL_PROP_LAST_STATUS) { // Failed case SuccessOrExit(error = SpinelDataUnpack(aData, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status)); CallAndClear(mDatasetMgmtSetPendingTask, ot::Spinel::SpinelStatusToOtError(status)); } else if (aKey != SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS) { ExitNow(error = OTBR_ERROR_INVALID_STATE); } break; case SPINEL_PROP_STREAM_NET: break; case SPINEL_PROP_INFRA_IF_STATE: VerifyOrExit(aKey == SPINEL_PROP_LAST_STATUS, error = OTBR_ERROR_INVALID_STATE); SuccessOrExit(error = SpinelDataUnpack(aData, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status)); otbrLogInfo("Infra If state update result: %s", spinel_status_to_cstr(status)); break; case SPINEL_PROP_INFRA_IF_RECV_ICMP6: VerifyOrExit(aKey == SPINEL_PROP_LAST_STATUS, error = OTBR_ERROR_INVALID_STATE); SuccessOrExit(error = SpinelDataUnpack(aData, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status)); otbrLogInfo("Infra If handle ICMP6 ND result: %s", spinel_status_to_cstr(status)); break; case SPINEL_PROP_DNSSD_STATE: VerifyOrExit(aKey == SPINEL_PROP_LAST_STATUS, error = OTBR_ERROR_INVALID_STATE); SuccessOrExit(error = SpinelDataUnpack(aData, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status)); otbrLogInfo("Update dnssd state result: %s", spinel_status_to_cstr(status)); break; default: VerifyOrExit(aKey == mWaitingKeyTable[aTid], error = OTBR_ERROR_INVALID_STATE); break; } exit: return error; } otbrError NcpSpinel::HandleResponseForPropInsert(spinel_tid_t aTid, spinel_command_t aCmd, spinel_prop_key_t aKey, const uint8_t *aData, uint16_t aLength) { otbrError error = OTBR_ERROR_NONE; switch (mWaitingKeyTable[aTid]) { case SPINEL_PROP_IPV6_MULTICAST_ADDRESS_TABLE: if (aCmd == SPINEL_CMD_PROP_VALUE_IS) { spinel_status_t status = SPINEL_STATUS_OK; VerifyOrExit(aKey == SPINEL_PROP_LAST_STATUS, error = OTBR_ERROR_INVALID_STATE); SuccessOrExit(error = SpinelDataUnpack(aData, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status)); otbrLogInfo("Failed to subscribe to multicast address on NCP, error:%s", spinel_status_to_cstr(status)); } else { error = aCmd == SPINEL_CMD_PROP_VALUE_INSERTED ? OTBR_ERROR_NONE : OTBR_ERROR_INVALID_STATE; } break; default: break; } exit: otbrLogResult(error, "HandleResponseForPropInsert, key:%u", mWaitingKeyTable[aTid]); return error; } otbrError NcpSpinel::HandleResponseForPropRemove(spinel_tid_t aTid, spinel_command_t aCmd, spinel_prop_key_t aKey, const uint8_t *aData, uint16_t aLength) { otbrError error = OTBR_ERROR_NONE; switch (mWaitingKeyTable[aTid]) { case SPINEL_PROP_IPV6_MULTICAST_ADDRESS_TABLE: if (aCmd == SPINEL_CMD_PROP_VALUE_IS) { spinel_status_t status = SPINEL_STATUS_OK; VerifyOrExit(aKey == SPINEL_PROP_LAST_STATUS, error = OTBR_ERROR_INVALID_STATE); SuccessOrExit(error = SpinelDataUnpack(aData, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status)); otbrLogInfo("Failed to unsubscribe to multicast address on NCP, error:%s", spinel_status_to_cstr(status)); } else { error = aCmd == SPINEL_CMD_PROP_VALUE_REMOVED ? OTBR_ERROR_NONE : OTBR_ERROR_INVALID_STATE; } break; default: break; } exit: otbrLogResult(error, "HandleResponseForPropRemove, key:%u", mWaitingKeyTable[aTid]); return error; } otbrError NcpSpinel::Ip6MulAddrUpdateSubscription(const otIp6Address &aAddress, bool aIsAdded) { otbrError error = OTBR_ERROR_NONE; EncodingFunc encodingFunc = [&aAddress](ot::Spinel::Encoder &aEncoder) { return aEncoder.WriteIp6Address(aAddress); }; if (aIsAdded) { SuccessOrExit(InsertProperty(SPINEL_PROP_IPV6_MULTICAST_ADDRESS_TABLE, encodingFunc), error = OTBR_ERROR_OPENTHREAD); } else { SuccessOrExit(RemoveProperty(SPINEL_PROP_IPV6_MULTICAST_ADDRESS_TABLE, encodingFunc), error = OTBR_ERROR_OPENTHREAD); } exit: return error; } spinel_tid_t NcpSpinel::GetNextTid(void) { spinel_tid_t tid = mCmdNextTid; while (((1 << tid) & mCmdTidsInUse) != 0) { tid = SPINEL_GET_NEXT_TID(tid); if (tid == mCmdNextTid) { // We looped back to `mCmdNextTid` indicating that all // TIDs are in-use. ExitNow(tid = 0); } } mCmdTidsInUse |= (1 << tid); mCmdNextTid = SPINEL_GET_NEXT_TID(tid); exit: return tid; } void NcpSpinel::FreeTidTableItem(spinel_tid_t aTid) { mCmdTidsInUse &= ~(1 << aTid); mCmdTable[aTid] = SPINEL_CMD_NOOP; mWaitingKeyTable[aTid] = SPINEL_PROP_LAST_STATUS; } otError NcpSpinel::SendCommand(spinel_command_t aCmd, spinel_prop_key_t aKey, const EncodingFunc &aEncodingFunc) { otError error = OT_ERROR_NONE; spinel_tid_t tid = GetNextTid(); uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | tid; VerifyOrExit(tid != 0, error = OT_ERROR_BUSY); SuccessOrExit(error = mEncoder.BeginFrame(header, aCmd, aKey)); SuccessOrExit(error = aEncodingFunc(mEncoder)); SuccessOrExit(error = mEncoder.EndFrame()); SuccessOrExit(error = SendEncodedFrame()); mCmdTable[tid] = aCmd; mWaitingKeyTable[tid] = aKey; exit: if (error != OT_ERROR_NONE) { FreeTidTableItem(tid); } return error; } otError NcpSpinel::SetProperty(spinel_prop_key_t aKey, const EncodingFunc &aEncodingFunc) { return SendCommand(SPINEL_CMD_PROP_VALUE_SET, aKey, aEncodingFunc); } otError NcpSpinel::InsertProperty(spinel_prop_key_t aKey, const EncodingFunc &aEncodingFunc) { return SendCommand(SPINEL_CMD_PROP_VALUE_INSERT, aKey, aEncodingFunc); } otError NcpSpinel::RemoveProperty(spinel_prop_key_t aKey, const EncodingFunc &aEncodingFunc) { return SendCommand(SPINEL_CMD_PROP_VALUE_REMOVE, aKey, aEncodingFunc); } otError NcpSpinel::SendEncodedFrame(void) { otError error = OT_ERROR_NONE; uint8_t frame[kTxBufferSize]; uint16_t frameLength; SuccessOrExit(error = mNcpBuffer.OutFrameBegin()); frameLength = mNcpBuffer.OutFrameGetLength(); VerifyOrExit(mNcpBuffer.OutFrameRead(frameLength, frame) == frameLength, error = OT_ERROR_FAILED); SuccessOrExit(error = mSpinelDriver->GetSpinelInterface()->SendFrame(frame, frameLength)); exit: error = mNcpBuffer.OutFrameRemove(); return error; } otError NcpSpinel::ParseIp6AddressTable(const uint8_t *aBuf, uint16_t aLength, std::vector &aAddressTable) { otError error = OT_ERROR_NONE; ot::Spinel::Decoder decoder; VerifyOrExit(aBuf != nullptr, error = OT_ERROR_INVALID_ARGS); decoder.Init(aBuf, aLength); while (!decoder.IsAllReadInStruct()) { Ip6AddressInfo cur; const otIp6Address *addr; uint8_t prefixLength; uint32_t preferredLifetime; uint32_t validLifetime; SuccessOrExit(error = decoder.OpenStruct()); SuccessOrExit(error = decoder.ReadIp6Address(addr)); memcpy(&cur.mAddress, addr, sizeof(otIp6Address)); SuccessOrExit(error = decoder.ReadUint8(prefixLength)); cur.mPrefixLength = prefixLength; SuccessOrExit(error = decoder.ReadUint32(preferredLifetime)); cur.mPreferred = preferredLifetime ? true : false; SuccessOrExit(error = decoder.ReadUint32(validLifetime)); OTBR_UNUSED_VARIABLE(validLifetime); SuccessOrExit((error = decoder.CloseStruct())); aAddressTable.push_back(cur); } exit: return error; } otError NcpSpinel::ParseIp6MulticastAddresses(const uint8_t *aBuf, uint16_t aLen, std::vector &aAddressList) { otError error = OT_ERROR_NONE; ot::Spinel::Decoder decoder; VerifyOrExit(aBuf != nullptr, error = OT_ERROR_INVALID_ARGS); decoder.Init(aBuf, aLen); while (!decoder.IsAllReadInStruct()) { const otIp6Address *addr; SuccessOrExit(error = decoder.OpenStruct()); SuccessOrExit(error = decoder.ReadIp6Address(addr)); aAddressList.emplace_back(Ip6Address(*addr)); SuccessOrExit((error = decoder.CloseStruct())); } exit: return error; } otError NcpSpinel::ParseIp6StreamNet(const uint8_t *aBuf, uint16_t aLen, const uint8_t *&aData, uint16_t &aDataLen) { otError error = OT_ERROR_NONE; ot::Spinel::Decoder decoder; VerifyOrExit(aBuf != nullptr, error = OT_ERROR_INVALID_ARGS); decoder.Init(aBuf, aLen); error = decoder.ReadDataWithLen(aData, aDataLen); exit: return error; } otError NcpSpinel::ParseOperationalDatasetTlvs(const uint8_t *aBuf, uint16_t aLen, otOperationalDatasetTlvs &aDatasetTlvs) { otError error = OT_ERROR_NONE; ot::Spinel::Decoder decoder; const uint8_t *datasetTlvsData; uint16_t datasetTlvsLen; decoder.Init(aBuf, aLen); SuccessOrExit(error = decoder.ReadData(datasetTlvsData, datasetTlvsLen)); VerifyOrExit(datasetTlvsLen <= sizeof(aDatasetTlvs.mTlvs), error = OT_ERROR_PARSE); memcpy(aDatasetTlvs.mTlvs, datasetTlvsData, datasetTlvsLen); aDatasetTlvs.mLength = datasetTlvsLen; exit: return error; } otError NcpSpinel::ParseInfraIfIcmp6Nd(const uint8_t *aBuf, uint8_t aLen, uint32_t &aInfraIfIndex, const otIp6Address *&aAddr, const uint8_t *&aData, uint16_t &aDataLen) { otError error = OT_ERROR_NONE; ot::Spinel::Decoder decoder; VerifyOrExit(aBuf != nullptr, error = OT_ERROR_INVALID_ARGS); decoder.Init(aBuf, aLen); SuccessOrExit(error = decoder.ReadUint32(aInfraIfIndex)); SuccessOrExit(error = decoder.ReadIp6Address(aAddr)); SuccessOrExit(error = decoder.ReadDataWithLen(aData, aDataLen)); exit: return error; } otError NcpSpinel::SendDnssdResult(otPlatDnssdRequestId aRequestId, const std::vector &aCallbackData, otError aError) { otError error; EncodingFunc encodingFunc = [aRequestId, &aCallbackData, aError](ot::Spinel::Encoder &aEncoder) { otError error = OT_ERROR_NONE; SuccessOrExit(aEncoder.WriteUint8(aError)); SuccessOrExit(aEncoder.WriteUint32(aRequestId)); SuccessOrExit(aEncoder.WriteData(aCallbackData.data(), aCallbackData.size())); exit: return error; }; error = SetProperty(SPINEL_PROP_DNSSD_REQUEST_RESULT, encodingFunc); if (error != OT_ERROR_NONE) { otbrLogWarning("Failed to SendDnssdResult, %s", otThreadErrorToString(error)); } return error; } otbrError NcpSpinel::SetInfraIf(uint32_t aInfraIfIndex, bool aIsRunning, const std::vector &aIp6Addresses) { otbrError error = OTBR_ERROR_NONE; EncodingFunc encodingFunc = [aInfraIfIndex, aIsRunning, &aIp6Addresses](ot::Spinel::Encoder &aEncoder) { otError error = OT_ERROR_NONE; SuccessOrExit(error = aEncoder.WriteUint32(aInfraIfIndex)); SuccessOrExit(error = aEncoder.WriteBool(aIsRunning)); for (const Ip6Address &addr : aIp6Addresses) { SuccessOrExit(error = aEncoder.WriteIp6Address(reinterpret_cast(addr))); } exit: return error; }; SuccessOrExit(SetProperty(SPINEL_PROP_INFRA_IF_STATE, encodingFunc), error = OTBR_ERROR_OPENTHREAD); exit: return error; } otbrError NcpSpinel::HandleIcmp6Nd(uint32_t aInfraIfIndex, const Ip6Address &aIp6Address, const uint8_t *aData, uint16_t aDataLen) { otbrError error = OTBR_ERROR_NONE; EncodingFunc encodingFunc = [aInfraIfIndex, &aIp6Address, aData, aDataLen](ot::Spinel::Encoder &aEncoder) { otError error = OT_ERROR_NONE; SuccessOrExit(error = aEncoder.WriteUint32(aInfraIfIndex)); SuccessOrExit(error = aEncoder.WriteIp6Address(reinterpret_cast(aIp6Address))); SuccessOrExit(error = aEncoder.WriteData(aData, aDataLen)); exit: return error; }; SuccessOrExit(SetProperty(SPINEL_PROP_INFRA_IF_RECV_ICMP6, encodingFunc), error = OTBR_ERROR_OPENTHREAD); exit: if (error != OTBR_ERROR_NONE) { otbrLogWarning("Failed to passthrough ICMP6 ND to NCP, %s", otbrErrorString(error)); } return error; } otDeviceRole NcpSpinel::SpinelRoleToDeviceRole(spinel_net_role_t aRole) { otDeviceRole role = OT_DEVICE_ROLE_DISABLED; switch (aRole) { case SPINEL_NET_ROLE_DISABLED: role = OT_DEVICE_ROLE_DISABLED; break; case SPINEL_NET_ROLE_DETACHED: role = OT_DEVICE_ROLE_DETACHED; break; case SPINEL_NET_ROLE_CHILD: role = OT_DEVICE_ROLE_CHILD; break; case SPINEL_NET_ROLE_ROUTER: role = OT_DEVICE_ROLE_ROUTER; break; case SPINEL_NET_ROLE_LEADER: role = OT_DEVICE_ROLE_LEADER; break; default: otbrLogWarning("Unsupported spinel net role: %u", aRole); break; } return role; } } // namespace Host } // namespace otbr