diff --git a/.travis.yml b/.travis.yml index a82903156..0ba5cb8f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,6 +65,10 @@ jobs: os: linux compiler: gcc script: .travis/script.sh + - env: BUILD_TARGET="posix-app-migrate" VERBOSE=1 COVERAGE=1 + os: linux + compiler: clang + script: .travis/script.sh - env: BUILD_TARGET="android-build" VERBOSE=1 os: linux dist: trusty diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 03792b9c4..510964e4f 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -104,6 +104,10 @@ cd /tmp || die ) || die } + [ $BUILD_TARGET != posix-app-migrate ] || { + sudo apt-get install expect || die + } + [ $BUILD_TARGET != arm-gcc-4 ] || { sudo apt-get install lib32z1 || die wget https://launchpad.net/gcc-arm-embedded/4.9/4.9-2015-q3-update/+download/gcc-arm-none-eabi-4_9-2015q3-20150921-linux.tar.bz2 || die diff --git a/.travis/check-ncp-rcp-migrate b/.travis/check-ncp-rcp-migrate new file mode 100755 index 000000000..968a35acb --- /dev/null +++ b/.travis/check-ncp-rcp-migrate @@ -0,0 +1,142 @@ +#!/bin/bash +# +# Copyright (c) 2020, 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. +# + +set -e +set -x + +at_exit() { + EXIT_CODE=$? + + killall expect || true + killall ot-cli-ftd || true + killall ot-cli || true + killall ot-rcp || true + + exit $EXIT_CODE +} + +build() { + make -f examples/Makefile-posix + make -f src/posix/Makefile-posix +} + +check() { + trap at_exit INT TERM EXIT + + rm -rf tmp/ + + PANID="0xc001" + EXT_PANID="0123456789abcdef" + NETWORK_NAME="OT_NCP_TO_RCP" + CHANNEL="20" + MASTER_KEY="0123456789abcdef0123456789abcdef" + + echo "Step 1. Start NCP platform and form a PAN..." + RADIO_NCP_CMD="$(pwd)/$(ls output/*linux*/bin/ot-cli-ftd)" + + expect < otError NcpBase::HandlePropertyGet(void) // Zero out the last 8 bytes. memset(addr.mFields.m8 + 8, 0, 8); - SuccessOrExit(error = mEncoder.WriteIp6Address(addr)); // Mesh local prefix - SuccessOrExit(error = mEncoder.WriteUint8(64)); // Prefix length (in bits) + SuccessOrExit(error = mEncoder.WriteIp6Address(addr)); // Mesh local prefix + SuccessOrExit(error = mEncoder.WriteUint8(OT_IP6_PREFIX_BITSIZE)); // Prefix length (in bits) exit: return error; @@ -1572,7 +1572,7 @@ template <> otError NcpBase::HandlePropertySet(void) SuccessOrExit(error = mDecoder.ReadIp6Address(meshLocalPrefix)); SuccessOrExit(error = mDecoder.ReadUint8(prefixLength)); - VerifyOrExit(prefixLength == 64, error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(prefixLength == OT_IP6_PREFIX_BITSIZE, error = OT_ERROR_INVALID_ARGS); error = otThreadSetMeshLocalPrefix(mInstance, reinterpret_cast(meshLocalPrefix)); diff --git a/src/posix/main.c b/src/posix/main.c index 3689beb22..9ccdeeb14 100644 --- a/src/posix/main.c +++ b/src/posix/main.c @@ -98,6 +98,7 @@ static const struct option kOptions[] = {{"debug-level", required_argument, NULL {"radio-version", no_argument, NULL, 0}, {"time-speed", required_argument, NULL, 's'}, {"verbose", no_argument, NULL, 'v'}, + {"ncp-dataset", no_argument, NULL, 0}, {0, 0, 0, 0}}; static void PrintUsage(const char *aProgramName, FILE *aStream, int aExitCode) @@ -111,6 +112,7 @@ static void PrintUsage(const char *aProgramName, FILE *aStream, int aExitCode) " -n --dry-run Just verify if arguments is valid and radio spinel is compatible.\n" " --no-reset Do not reset RCP on initialization\n" " --radio-version Print radio firmware version\n" + " --ncp-dataset Retrieve and save NCP dataset to file\n" " -s --time-speed factor Time speed up factor.\n" " -v --verbose Also log to stderr.\n" " -h --help Display this usage information.\n", @@ -178,6 +180,10 @@ static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig) { aConfig->mPlatformConfig.mResetRadio = false; } + else if (!strcmp(kOptions[index].name, "ncp-dataset")) + { + aConfig->mPlatformConfig.mRestoreDatasetFromNcp = true; + } break; case '?': PrintUsage(aArgVector[0], stderr, OT_EXIT_INVALID_ARGUMENTS); diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index 8ba5e9d39..5e32fb068 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -68,4 +68,5 @@ target_include_directories(openthread-posix PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/core ${PROJECT_SOURCE_DIR}/src/posix/platform + ${PROJECT_SOURCE_DIR}/third_party/mbedtls/repo/include ) diff --git a/src/posix/platform/openthread-system.h b/src/posix/platform/openthread-system.h index 7ae5a9514..aa95c4dd0 100644 --- a/src/posix/platform/openthread-system.h +++ b/src/posix/platform/openthread-system.h @@ -90,12 +90,13 @@ enum */ typedef struct otPlatformConfig { - uint64_t mNodeId; ///< Unique node ID. - uint32_t mSpeedUpFactor; ///< Speed up factor. - const char *mInterfaceName; ///< Thread network interface name. - const char *mRadioFile; ///< Radio file path. - const char *mRadioConfig; ///< Radio configurations. - bool mResetRadio; ///< Whether to reset RCP when initializing. + uint64_t mNodeId; ///< Unique node ID. + uint32_t mSpeedUpFactor; ///< Speed up factor. + const char *mInterfaceName; ///< Thread network interface name. + const char *mRadioFile; ///< Radio file path. + const char *mRadioConfig; ///< Radio configurations. + bool mResetRadio; ///< Whether to reset RCP when initializing. + bool mRestoreDatasetFromNcp; ///< Whether to retrieve dataset from NCP and save to file. } otPlatformConfig; /** diff --git a/src/posix/platform/radio_spinel.cpp b/src/posix/platform/radio_spinel.cpp index e1cc334bc..040018ffa 100644 --- a/src/posix/platform/radio_spinel.cpp +++ b/src/posix/platform/radio_spinel.cpp @@ -33,6 +33,7 @@ #include "openthread-core-config.h" #include "platform-posix.h" +#include "ncp/spinel_decoder.hpp" #include "radio_spinel.hpp" @@ -52,9 +53,14 @@ #include #include #include +#include +#include +#include +#include #include #include #include +#include #ifndef TX_WAIT_US #define TX_WAIT_US (5 * US_PER_S) @@ -184,6 +190,7 @@ RadioSpinel::RadioSpinel(void) void RadioSpinel::Init(const otPlatformConfig &aPlatformConfig) { otError error = OT_ERROR_NONE; + bool isRcp; SuccessOrExit(error = mHdlcInterface.Init(aPlatformConfig)); @@ -196,14 +203,19 @@ void RadioSpinel::Init(const otPlatformConfig &aPlatformConfig) VerifyOrExit(mIsReady, error = OT_ERROR_FAILED); SuccessOrExit(error = CheckSpinelVersion()); - SuccessOrExit(error = CheckCapabilities()); - SuccessOrExit(error = CheckRadioCapabilities()); - + SuccessOrExit(error = CheckCapabilities(isRcp)); SuccessOrExit(error = Get(SPINEL_PROP_NCP_VERSION, SPINEL_DATATYPE_UTF8_S, mVersion, sizeof(mVersion))); SuccessOrExit(error = Get(SPINEL_PROP_HWADDR, SPINEL_DATATYPE_UINT64_S, &gNodeId)); gNodeId = ot::Encoding::BigEndian::HostSwap64(gNodeId); + if (aPlatformConfig.mRestoreDatasetFromNcp && !isRcp) + { + DieNow((RestoreDatasetFromNcp() == OT_ERROR_NONE) ? OT_EXIT_SUCCESS : OT_EXIT_FAILURE); + } + + SuccessOrExit(error = CheckRadioCapabilities()); + mRxRadioFrame.mPsdu = mRxPsdu; mTxRadioFrame.mPsdu = mTxPsdu; mAckRadioFrame.mPsdu = mAckPsdu; @@ -234,7 +246,7 @@ exit: return error; } -otError RadioSpinel::CheckCapabilities(void) +otError RadioSpinel::CheckCapabilities(bool &aIsRcp) { otError error = OT_ERROR_NONE; uint8_t capsBuffer[kCapsBufferSize]; @@ -244,6 +256,8 @@ otError RadioSpinel::CheckCapabilities(void) SuccessOrExit(error = Get(SPINEL_PROP_CAPS, SPINEL_DATATYPE_DATA_S, capsBuffer, &capsLength)); + aIsRcp = false; + while (capsLength > 0) { unsigned int capability; @@ -262,11 +276,16 @@ otError RadioSpinel::CheckCapabilities(void) supportsRawRadio = true; } + if (capability == SPINEL_CAP_CONFIG_RADIO) + { + aIsRcp = true; + } + capsData += unpacked; capsLength -= static_cast(unpacked); } - if (!supportsRawRadio) + if (!supportsRawRadio && aIsRcp) { otLogCritPlat("RCP capability list does not include support for radio/raw mode"); DieNow(OT_EXIT_RADIO_SPINEL_INCOMPATIBLE); @@ -301,6 +320,23 @@ exit: return error; } +otError RadioSpinel::RestoreDatasetFromNcp(void) +{ + otError error = OT_ERROR_NONE; + + otPlatSettingsInit(mInstance); + + otLogInfoPlat("Trying to get saved dataset from NCP"); + SuccessOrExit( + error = Get(SPINEL_PROP_THREAD_ACTIVE_DATASET, SPINEL_DATATYPE_VOID_S, &RadioSpinel::ThreadDatasetHandler)); + SuccessOrExit( + error = Get(SPINEL_PROP_THREAD_PENDING_DATASET, SPINEL_DATATYPE_VOID_S, &RadioSpinel::ThreadDatasetHandler)); + +exit: + otPlatSettingsDeinit(mInstance); + return error; +} + void RadioSpinel::Deinit(void) { mHdlcInterface.Deinit(); @@ -452,6 +488,159 @@ exit: LogIfFail("Error processing response", error); } +otError RadioSpinel::ThreadDatasetHandler(const uint8_t *aBuffer, uint16_t aLength) +{ + otError error = OT_ERROR_NONE; + otOperationalDataset opDataset; + bool isActive = ((mWaitingKey == SPINEL_PROP_THREAD_ACTIVE_DATASET) ? true : false); + Ncp::SpinelDecoder decoder; + MeshCoP::Dataset dataset(isActive ? MeshCoP::Tlv::kActiveTimestamp : MeshCoP::Tlv::kPendingTimestamp); + + memset(&opDataset, 0, sizeof(otOperationalDataset)); + decoder.Init(aBuffer, aLength); + + while (!decoder.IsAllReadInStruct()) + { + unsigned int propKey; + + SuccessOrExit(error = decoder.OpenStruct()); + SuccessOrExit(error = decoder.ReadUintPacked(propKey)); + + switch (static_cast(propKey)) + { + case SPINEL_PROP_NET_MASTER_KEY: + { + const uint8_t *key; + uint16_t len; + + SuccessOrExit(error = decoder.ReadData(key, len)); + VerifyOrExit(len == OT_MASTER_KEY_SIZE, error = OT_ERROR_INVALID_ARGS); + memcpy(opDataset.mMasterKey.m8, key, len); + opDataset.mComponents.mIsMasterKeyPresent = true; + break; + } + + case SPINEL_PROP_NET_NETWORK_NAME: + { + const char *name; + size_t len; + + SuccessOrExit(error = decoder.ReadUtf8(name)); + len = strnlen(name, OT_NETWORK_NAME_MAX_SIZE); + memcpy(opDataset.mNetworkName.m8, name, len); + opDataset.mNetworkName.m8[len] = '\0'; + opDataset.mComponents.mIsNetworkNamePresent = true; + break; + } + + case SPINEL_PROP_NET_XPANID: + { + const uint8_t *xpanid; + uint16_t len; + + SuccessOrExit(error = decoder.ReadData(xpanid, len)); + VerifyOrExit(len == OT_EXT_PAN_ID_SIZE, error = OT_ERROR_INVALID_ARGS); + memcpy(opDataset.mExtendedPanId.m8, xpanid, len); + opDataset.mComponents.mIsExtendedPanIdPresent = true; + break; + } + + case SPINEL_PROP_IPV6_ML_PREFIX: + { + const otIp6Address *addr; + uint8_t prefixLen; + + SuccessOrExit(error = decoder.ReadIp6Address(addr)); + SuccessOrExit(error = decoder.ReadUint8(prefixLen)); + VerifyOrExit(prefixLen == OT_IP6_PREFIX_BITSIZE, error = OT_ERROR_INVALID_ARGS); + memcpy(opDataset.mMeshLocalPrefix.m8, addr, OT_MESH_LOCAL_PREFIX_SIZE); + opDataset.mComponents.mIsMeshLocalPrefixPresent = true; + break; + } + + case SPINEL_PROP_DATASET_DELAY_TIMER: + { + SuccessOrExit(error = decoder.ReadUint32(opDataset.mDelay)); + opDataset.mComponents.mIsDelayPresent = true; + break; + } + + case SPINEL_PROP_MAC_15_4_PANID: + { + SuccessOrExit(error = decoder.ReadUint16(opDataset.mPanId)); + opDataset.mComponents.mIsPanIdPresent = true; + break; + } + + case SPINEL_PROP_PHY_CHAN: + { + uint8_t channel; + + SuccessOrExit(error = decoder.ReadUint8(channel)); + opDataset.mChannel = channel; + opDataset.mComponents.mIsChannelPresent = true; + break; + } + + case SPINEL_PROP_NET_PSKC: + { + const uint8_t *psk; + uint16_t len; + + SuccessOrExit(error = decoder.ReadData(psk, len)); + VerifyOrExit(len == OT_PSKC_MAX_SIZE, error = OT_ERROR_INVALID_ARGS); + memcpy(opDataset.mPskc.m8, psk, OT_PSKC_MAX_SIZE); + opDataset.mComponents.mIsPskcPresent = true; + break; + } + + case SPINEL_PROP_DATASET_SECURITY_POLICY: + { + SuccessOrExit(error = decoder.ReadUint16(opDataset.mSecurityPolicy.mRotationTime)); + SuccessOrExit(error = decoder.ReadUint8(opDataset.mSecurityPolicy.mFlags)); + opDataset.mComponents.mIsSecurityPolicyPresent = true; + break; + } + + case SPINEL_PROP_PHY_CHAN_SUPPORTED: + { + uint8_t channel; + + opDataset.mChannelMask = 0; + + while (!decoder.IsAllReadInStruct()) + { + SuccessOrExit(error = decoder.ReadUint8(channel)); + VerifyOrExit(channel <= 31, error = OT_ERROR_INVALID_ARGS); + opDataset.mChannelMask |= (1UL << channel); + } + opDataset.mComponents.mIsChannelMaskPresent = true; + break; + } + + default: + break; + } + + SuccessOrExit(error = decoder.CloseStruct()); + } + + /* + * Initially set Active Timestamp to 0. This is to allow the node to join the network + * yet retrieve the full Active Dataset from a neighboring device if one exists. + */ + opDataset.mActiveTimestamp = 0; + opDataset.mComponents.mIsActiveTimestampPresent = true; + + SuccessOrExit(error = dataset.Set(opDataset)); + SuccessOrExit(error = otPlatSettingsSet( + mInstance, isActive ? SettingsBase::kKeyActiveDataset : SettingsBase::kKeyPendingDataset, + dataset.GetBytes(), dataset.GetSize())); + +exit: + return error; +} + void RadioSpinel::HandleWaitingResponse(uint32_t aCommand, spinel_prop_key_t aKey, const uint8_t * aBuffer, @@ -480,11 +669,22 @@ void RadioSpinel::HandleWaitingResponse(uint32_t aCommand, { if (mPropertyFormat) { - spinel_ssize_t unpacked = - spinel_datatype_vunpack_in_place(aBuffer, aLength, mPropertyFormat, mPropertyArgs); + if (static_cast(mPropertyFormat[0]) == SPINEL_DATATYPE_VOID_C) + { + // reserved SPINEL_DATATYPE_VOID_C indicate caller want to parse the spinel response itself + ResponseHandler handler = va_arg(mPropertyArgs, ResponseHandler); - VerifyOrExit(unpacked > 0, mError = OT_ERROR_PARSE); - mError = OT_ERROR_NONE; + assert(handler != NULL); + mError = (this->*handler)(aBuffer, aLength); + } + else + { + spinel_ssize_t unpacked = + spinel_datatype_vunpack_in_place(aBuffer, aLength, mPropertyFormat, mPropertyArgs); + + VerifyOrExit(unpacked > 0, mError = OT_ERROR_PARSE); + mError = OT_ERROR_NONE; + } } else { diff --git a/src/posix/platform/radio_spinel.hpp b/src/posix/platform/radio_spinel.hpp index cd09e40cb..b1263249d 100644 --- a/src/posix/platform/radio_spinel.hpp +++ b/src/posix/platform/radio_spinel.hpp @@ -538,8 +538,10 @@ private: kStateTransmitDone, ///< Radio indicated frame transmission is done. }; + typedef otError (RadioSpinel::*ResponseHandler)(const uint8_t *aBuffer, uint16_t aLength); + otError CheckSpinelVersion(void); - otError CheckCapabilities(void); + otError CheckCapabilities(bool &aIsRcp); otError CheckRadioCapabilities(void); void ProcessFrameQueue(void); @@ -612,6 +614,7 @@ private: const char * pack_format, va_list args); otError ParseRadioFrame(otRadioFrame &aFrame, const uint8_t *aBuffer, uint16_t aLength); + otError ThreadDatasetHandler(const uint8_t *aBuffer, uint16_t aLength); /** * This method returns if the property changed event is safe to be handled now. @@ -641,6 +644,17 @@ private: void TransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError); + /** + * This method gets dataset from NCP radio and saves it. + * + * @retval OT_ERROR_NONE Successfully restore dataset. + * @retval OT_ERROR_BUSY Failed due to another operation is on going. + * @retval OT_ERROR_RESPONSE_TIMEOUT Failed due to no response received from the transceiver. + * @retval OT_ERROR_NOT_FOUND Failed due to spinel property not supported in radio. + * @retval OT_ERROR_FAILED Failed due to other reasons. + */ + otError RestoreDatasetFromNcp(void); + otInstance *mInstance; HdlcInterface mHdlcInterface;