diff --git a/.travis.yml b/.travis.yml index 965753d7b..79070ee53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,6 +49,10 @@ stages: jobs: include: + - env: BUILD_TARGET="v1.2" VERBOSE=1 VIRTUAL_TIME=1 THREAD_VERSION=1.2 + os: linux + compiler: gcc + script: .travis/script.sh - env: BUILD_TARGET="posix-app-cli" VERBOSE=1 VIRTUAL_TIME=1 os: linux compiler: gcc diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 510964e4f..66d698a05 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -47,14 +47,18 @@ cd /tmp || die pip3 install --upgrade pip pip3 install cmake - [ $BUILD_TARGET != posix-distcheck -a $BUILD_TARGET != posix-32-bit -a $BUILD_TARGET != posix-app-cli -a $BUILD_TARGET != posix-mtd -a $BUILD_TARGET != posix-ncp -a $BUILD_TARGET != posix-app-ncp ] || { + case "${BUILD_TARGET}" in + posix-distcheck|posix-32-bit|posix-app-cli|posix-mtd|posix-ncp|posix-app-ncp|v1.2) pip install --upgrade pip || die pip install -r $TRAVIS_BUILD_DIR/tests/scripts/thread-cert/requirements.txt || die [ $BUILD_TARGET != posix-ncp -a $BUILD_TARGET != posix-app-ncp ] || { # Packages used by ncp tools. pip install git+https://github.com/openthread/pyspinel || die } - } + ;; + *) + ;; + esac [ $BUILD_TARGET != android-build ] || { sudo apt-get install -y bison gcc-multilib g++-multilib diff --git a/.travis/script.sh b/.travis/script.sh index 42667f96d..826522e74 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -581,6 +581,12 @@ build_samr21() { REFERENCE_DEVICE=1 COVERAGE=1 PYTHONUNBUFFERED=1 OT_CLI_PATH="$(pwd)/$(ls output/posix/*/bin/ot-cli) -v" RADIO_DEVICE="$(pwd)/$(ls output/*/bin/ot-rcp)" make -f src/posix/Makefile-posix check || die } +[ $BUILD_TARGET != v1.2 ] || { + ./bootstrap || die + REFERENCE_DEVICE=1 make -f examples/Makefile-posix || die + ./script/test v1.2 || die +} + [ $BUILD_TARGET != posix-app-pty ] || { # check daemon mode git checkout -- . || die diff --git a/CMakeLists.txt b/CMakeLists.txt index 61090712f..89e5cce60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,8 +61,18 @@ list(APPEND OT_PRIVATE_DEFINES "PACKAGE_VERSION=\"${OT_VERSION}\"" ) -set(OT_PLATFORM "none" CACHE STRING "Target platform chosen by the user at configure time") +set(OT_THREAD_VERSION "1.1" CACHE STRING "Thread version chosen by the user at configure time") +set_property(CACHE OT_THREAD_VERSION PROPERTY STRINGS "1.1" "1.2") +if(${OT_THREAD_VERSION} EQUAL "1.1") + list(APPEND OT_PRIVATE_DEFINES "OPENTHREAD_CONFIG_THREAD_VERSION=OT_THREAD_VERSION_1_1") +elseif(${OT_THREAD_VERSION} EQUAL "1.2") + list(APPEND OT_PRIVATE_DEFINES "OPENTHREAD_CONFIG_THREAD_VERSION=OT_THREAD_VERSION_1_2") +else() + message(FATAL_ERROR "Thread version unknown: ${OT_THREAD_VERSION}") +endif() + +set(OT_PLATFORM "none" CACHE STRING "Target platform chosen by the user at configure time") ot_get_platforms(OT_EXAMPLE_PLATFORMS) set_property(CACHE OT_PLATFORM PROPERTY STRINGS ${OT_EXAMPLE_PLATFORMS}) if(NOT OT_PLATFORM IN_LIST OT_EXAMPLE_PLATFORMS) diff --git a/examples/common-switches.mk b/examples/common-switches.mk index 1c290921a..c5506469e 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -65,6 +65,7 @@ SETTINGS_RAM ?= 0 # SLAAC is enabled by default SLAAC ?= 1 SNTP_CLIENT ?= 0 +THREAD_VERSION ?= 1.1 TIME_SYNC ?= 0 UDP_FORWARD ?= 0 @@ -202,6 +203,12 @@ ifeq ($(SNTP_CLIENT),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE=1 endif +ifeq ($(THREAD_VERSION),1.1) +COMMONCFLAGS += -DOPENTHREAD_CONFIG_THREAD_VERSION=2 +else ifeq ($(THREAD_VERSION),1.2) +COMMONCFLAGS += -DOPENTHREAD_CONFIG_THREAD_VERSION=3 +endif + ifeq ($(TIME_SYNC),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_TIME_SYNC_ENABLE=1 -DOPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT=1 endif diff --git a/script/test b/script/test index 5fd6c440f..ff3263404 100755 --- a/script/test +++ b/script/test @@ -57,6 +57,13 @@ do_cert() { PYTHONUNBUFFERED=1 "$1" } +do_v1_2() { + ls tests/scripts/thread-cert/v1_2_*.py | while read test_case; do + [[ ! -d tmp ]] || rm -rvf tmp + PYTHONUNBUFFERED=1 "${test_case}" + done +} + print_usage() { echo "USAGE: [ENVIRONMENTS] $0 COMMANDS @@ -124,6 +131,10 @@ main() shift do_cert "$1" ;; + v1.2) + do_v1_2 + break + ;; help) print_usage ;; diff --git a/src/core/api/thread_api.cpp b/src/core/api/thread_api.cpp index 4d53e4c5d..442b6d9ee 100644 --- a/src/core/api/thread_api.cpp +++ b/src/core/api/thread_api.cpp @@ -412,7 +412,7 @@ otError otThreadSetEnabled(otInstance *aInstance, bool aEnabled) uint16_t otThreadGetVersion(void) { - return OPENTHREAD_THREAD_VERSION; + return OPENTHREAD_CONFIG_THREAD_VERSION; } bool otThreadIsSingleton(otInstance *aInstance) diff --git a/src/core/common/settings.cpp b/src/core/common/settings.cpp index aeb342e8a..d3c8cf1e1 100644 --- a/src/core/common/settings.cpp +++ b/src/core/common/settings.cpp @@ -50,10 +50,11 @@ namespace ot { void SettingsBase::LogNetworkInfo(const char *aAction, const NetworkInfo &aNetworkInfo) const { - otLogInfoCore("Non-volatile: %s NetworkInfo {rloc:0x%04x, extaddr:%s, role:%s, mode:0x%02x, keyseq:0x%x, ...", - aAction, aNetworkInfo.GetRloc16(), aNetworkInfo.GetExtAddress().ToString().AsCString(), - Mle::Mle::RoleToString(static_cast(aNetworkInfo.GetRole())), - aNetworkInfo.GetDeviceMode(), aNetworkInfo.GetKeySequence()); + otLogInfoCore( + "Non-volatile: %s NetworkInfo {rloc:0x%04x, extaddr:%s, role:%s, mode:0x%02x, version:%hu, keyseq:0x%x, ...", + aAction, aNetworkInfo.GetRloc16(), aNetworkInfo.GetExtAddress().ToString().AsCString(), + Mle::Mle::RoleToString(static_cast(aNetworkInfo.GetRole())), aNetworkInfo.GetDeviceMode(), + aNetworkInfo.GetVersion(), aNetworkInfo.GetKeySequence()); otLogInfoCore( "Non-volatile: ... pid:0x%x, mlecntr:0x%x, maccntr:0x%x, mliid:%02x%02x%02x%02x%02x%02x%02x%02x}", @@ -65,15 +66,15 @@ void SettingsBase::LogNetworkInfo(const char *aAction, const NetworkInfo &aNetwo void SettingsBase::LogParentInfo(const char *aAction, const ParentInfo &aParentInfo) const { - otLogInfoCore("Non-volatile: %s ParentInfo {extaddr:%s}", aAction, - aParentInfo.GetExtAddress().ToString().AsCString()); + otLogInfoCore("Non-volatile: %s ParentInfo {extaddr:%s, version:%hu}", aAction, + aParentInfo.GetExtAddress().ToString().AsCString(), aParentInfo.GetVersion()); } void SettingsBase::LogChildInfo(const char *aAction, const ChildInfo &aChildInfo) const { - otLogInfoCore("Non-volatile: %s ChildInfo {rloc:0x%04x, extaddr:%s, timeout:%u, mode:0x%02x}", aAction, + otLogInfoCore("Non-volatile: %s ChildInfo {rloc:0x%04x, extaddr:%s, timeout:%u, mode:0x%02x, version:%hu}", aAction, aChildInfo.GetRloc16(), aChildInfo.GetExtAddress().ToString().AsCString(), aChildInfo.GetTimeout(), - aChildInfo.GetMode()); + aChildInfo.GetMode(), aChildInfo.GetVersion()); } #endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) diff --git a/src/core/common/settings.hpp b/src/core/common/settings.hpp index b53cd29ff..b881da3eb 100644 --- a/src/core/common/settings.hpp +++ b/src/core/common/settings.hpp @@ -92,7 +92,11 @@ public: * This method clears the struct object (setting all the fields to zero). * */ - void Init(void) { memset(this, 0, sizeof(*this)); } + void Init(void) + { + memset(this, 0, sizeof(*this)); + SetVersion(OT_THREAD_VERSION_1_1); + } /** * This method returns the Thread role. @@ -247,6 +251,22 @@ public: */ void SetMeshLocalIid(const uint8_t *aMeshLocalIid) { memcpy(mMlIid, aMeshLocalIid, sizeof(mMlIid)); } + /** + * This method returns the Thread version. + * + * @returns The Thread version. + * + */ + uint16_t GetVersion(void) const { return Encoding::LittleEndian::HostSwap16(mVersion); } + + /** + * This method sets the Thread version. + * + * @param[in] aVersion The Thread version. + * + */ + void SetVersion(uint16_t aVersion) { mVersion = Encoding::LittleEndian::HostSwap16(aVersion); } + private: uint8_t mRole; ///< Current Thread role. uint8_t mDeviceMode; ///< Device mode setting. @@ -257,6 +277,7 @@ public: uint32_t mPreviousPartitionId; ///< PartitionId Mac::ExtAddress mExtAddress; ///< Extended Address uint8_t mMlIid[OT_IP6_IID_SIZE]; ///< IID from ML-EID + uint16_t mVersion; ///< Version } OT_TOOL_PACKED_END; /** @@ -271,7 +292,11 @@ public: * This method clears the struct object (setting all the fields to zero). * */ - void Init(void) { memset(this, 0, sizeof(*this)); } + void Init(void) + { + memset(this, 0, sizeof(*this)); + SetVersion(OT_THREAD_VERSION_1_1); + } /** * This method returns the extended address. @@ -289,8 +314,25 @@ public: */ void SetExtAddress(const Mac::ExtAddress &aExtAddress) { mExtAddress = aExtAddress; } + /** + * This method returns the Thread version. + * + * @returns The Thread version. + * + */ + uint16_t GetVersion(void) const { return Encoding::LittleEndian::HostSwap16(mVersion); } + + /** + * This method sets the Thread version. + * + * @param[in] aVersion The Thread version. + * + */ + void SetVersion(uint16_t aVersion) { mVersion = Encoding::LittleEndian::HostSwap16(aVersion); } + private: Mac::ExtAddress mExtAddress; ///< Extended Address + uint16_t mVersion; ///< Version } OT_TOOL_PACKED_END; /** @@ -305,7 +347,11 @@ public: * This method clears the struct object (setting all the fields to zero). * */ - void Init(void) { memset(this, 0, sizeof(*this)); } + void Init(void) + { + memset(this, 0, sizeof(*this)); + SetVersion(OT_THREAD_VERSION_1_1); + } /** * This method returns the extended address. @@ -371,11 +417,28 @@ public: */ void SetMode(uint8_t aMode) { mMode = aMode; } + /** + * This method returns the Thread version. + * + * @returns The Thread version. + * + */ + uint16_t GetVersion(void) const { return Encoding::LittleEndian::HostSwap16(mVersion); } + + /** + * This method sets the Thread version. + * + * @param[in] aVersion The Thread version. + * + */ + void SetVersion(uint16_t aVersion) { mVersion = Encoding::LittleEndian::HostSwap16(aVersion); } + private: Mac::ExtAddress mExtAddress; ///< Extended Address uint32_t mTimeout; ///< Timeout uint16_t mRloc16; ///< RLOC16 uint8_t mMode; ///< The MLE device mode + uint16_t mVersion; ///< Version } OT_TOOL_PACKED_END; /** diff --git a/src/core/config/mac.h b/src/core/config/mac.h index bee1dea9f..26c6ecd99 100644 --- a/src/core/config/mac.h +++ b/src/core/config/mac.h @@ -200,7 +200,7 @@ * */ #ifndef OPENTHREAD_CONFIG_MAC_JOIN_BEACON_VERSION -#define OPENTHREAD_CONFIG_MAC_JOIN_BEACON_VERSION OPENTHREAD_THREAD_VERSION +#define OPENTHREAD_CONFIG_MAC_JOIN_BEACON_VERSION OPENTHREAD_CONFIG_THREAD_VERSION #endif /** diff --git a/src/core/mac/data_poll_sender.cpp b/src/core/mac/data_poll_sender.cpp index e1395d16c..710032c01 100644 --- a/src/core/mac/data_poll_sender.cpp +++ b/src/core/mac/data_poll_sender.cpp @@ -394,6 +394,15 @@ exit: return error; } +void DataPollSender::ResetKeepAliveTimer(void) +{ + if (mTimer.IsRunning() && mPollPeriod == GetDefaultPollPeriod()) + { + mTimerStartTime = TimerMilli::GetNow(); + mTimer.StartAt(mTimerStartTime, mPollPeriod); + } +} + void DataPollSender::ScheduleNextPoll(PollPeriodSelector aPollPeriodSelector) { TimeMilli now; diff --git a/src/core/mac/data_poll_sender.hpp b/src/core/mac/data_poll_sender.hpp index 4d6dc4035..d24dbd010 100644 --- a/src/core/mac/data_poll_sender.hpp +++ b/src/core/mac/data_poll_sender.hpp @@ -232,6 +232,12 @@ public: */ uint32_t GetKeepAlivePollPeriod(void) const; + /** + * This method resets the timer for sending keep-alive messages. + * + */ + void ResetKeepAliveTimer(void); + /** * This method returns the default maximum poll period. * diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index 51c0d10c1..95258fe4c 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -1303,6 +1303,13 @@ void Mac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError otDumpDebgMac("TX", aFrame.GetHeader(), aFrame.GetLength()); FinishOperation(); Get().HandleSentFrame(aFrame, aError); +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 + if (aError == OT_ERROR_NONE && Get().GetParent().IsEnhancedKeepAliveSupported() && + aFrame.GetSecurityEnabled() && aAckFrame != NULL) + { + Get().ResetKeepAliveTimer(); + } +#endif PerformNextOperation(); break; @@ -1667,6 +1674,15 @@ void Mac::HandleReceivedFrame(RxFrame *aFrame, otError aError) default: ExitNow(error = OT_ERROR_UNKNOWN_NEIGHBOR); } + +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 && OPENTHREAD_FTD + // From Thread 1.2, MAC Data Frame can also act as keep-alive message if child supports + if (aFrame->GetType() == Frame::kFcfFrameData && !neighbor->IsRxOnWhenIdle() && + neighbor->IsEnhancedKeepAliveSupported()) + { + neighbor->SetLastHeard(TimerMilli::GetNow()); + } +#endif } } diff --git a/src/core/openthread-core-config.h b/src/core/openthread-core-config.h index 284718768..d62ec67a5 100644 --- a/src/core/openthread-core-config.h +++ b/src/core/openthread-core-config.h @@ -36,13 +36,19 @@ #include +#define OT_THREAD_VERSION_INVALID 0 +#define OT_THREAD_VERSION_1_1 2 +#define OT_THREAD_VERSION_1_2 3 + #define OPENTHREAD_CORE_CONFIG_H_IN #ifdef OPENTHREAD_PROJECT_CORE_CONFIG_FILE #include OPENTHREAD_PROJECT_CORE_CONFIG_FILE #endif -#define OPENTHREAD_THREAD_VERSION (2) +#ifndef OPENTHREAD_CONFIG_THREAD_VERSION +#define OPENTHREAD_CONFIG_THREAD_VERSION OT_THREAD_VERSION_1_1 +#endif #include "config/openthread-core-default-config.h" diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 1c1c80cc7..9e1185715 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -389,6 +389,9 @@ otError Mle::Restore(void) Get().SetMacFrameCounter(networkInfo.GetMacFrameCounter()); mDeviceMode.Set(networkInfo.GetDeviceMode()); + // force re-attach when version mismatch. + VerifyOrExit(networkInfo.GetVersion() == kThreadVersion); + switch (networkInfo.GetRole()) { case OT_DEVICE_ROLE_CHILD: @@ -430,6 +433,7 @@ otError Mle::Restore(void) mParent.Clear(); mParent.SetExtAddress(parentInfo.GetExtAddress()); + mParent.SetVersion(static_cast(parentInfo.GetVersion())); mParent.SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle | DeviceMode::kModeFullNetworkData | DeviceMode::kModeSecureDataRequest)); mParent.SetRloc16(Rloc16FromRouterId(RouterIdFromRloc16(networkInfo.GetRloc16()))); @@ -468,6 +472,7 @@ otError Mle::Store(void) networkInfo.SetPreviousPartitionId(mLeaderData.GetPartitionId()); networkInfo.SetExtAddress(Get().GetExtAddress()); networkInfo.SetMeshLocalIid(&mMeshLocal64.GetAddress().mFields.m8[OT_IP6_PREFIX_SIZE]); + networkInfo.SetVersion(kThreadVersion); if (mRole == OT_DEVICE_ROLE_CHILD) { @@ -475,6 +480,7 @@ otError Mle::Store(void) parentInfo.Init(); parentInfo.SetExtAddress(mParent.GetExtAddress()); + parentInfo.SetVersion(mParent.GetVersion()); SuccessOrExit(error = Get().SaveParentInfo(parentInfo)); } @@ -3167,6 +3173,7 @@ otError Mle::HandleParentResponse(const Message &aMessage, const Ip6::MessageInf otError error = OT_ERROR_NONE; const otThreadLinkInfo *linkInfo = static_cast(aMessageInfo.GetLinkInfo()); ResponseTlv response; + VersionTlv version; SourceAddressTlv sourceAddress; LeaderDataTlv leaderData; LinkMarginTlv linkMarginTlv; @@ -3187,6 +3194,10 @@ otError Mle::HandleParentResponse(const Message &aMessage, const Ip6::MessageInf LogMleMessage("Receive Parent Response", aMessageInfo.GetPeerAddr(), sourceAddress.GetRloc16()); + // Version + SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kVersion, sizeof(version), version)); + VerifyOrExit(version.IsValid(), error = OT_ERROR_PARSE); + // Response SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kResponse, sizeof(response), response)); VerifyOrExit(response.IsValid() && @@ -3337,6 +3348,7 @@ otError Mle::HandleParentResponse(const Message &aMessage, const Ip6::MessageInf mParentCandidate.SetRloc16(sourceAddress.GetRloc16()); mParentCandidate.SetLinkFrameCounter(linkFrameCounter.GetFrameCounter()); mParentCandidate.SetMleFrameCounter(mleFrameCounter.GetFrameCounter()); + mParentCandidate.SetVersion(static_cast(version.GetVersion())); mParentCandidate.SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle | DeviceMode::kModeFullNetworkData | DeviceMode::kModeSecureDataRequest)); mParentCandidate.GetLinkInfo().Clear(); diff --git a/src/core/thread/mle_constants.hpp b/src/core/thread/mle_constants.hpp index 50a389cf5..be73a46c5 100644 --- a/src/core/thread/mle_constants.hpp +++ b/src/core/thread/mle_constants.hpp @@ -57,12 +57,12 @@ enum */ enum { - kThreadVersion = OPENTHREAD_THREAD_VERSION, ///< Thread Version - kUdpPort = 19788, ///< MLE UDP Port - kParentRequestRouterTimeout = 750, ///< Router Parent Request timeout - kParentRequestDuplicateMargin = 50, ///< Margin for duplicate parent request - kParentRequestReedTimeout = 1250, ///< Router and REEDs Parent Request timeout - kAttachStartJitter = 50, ///< Maximum jitter time added to start of attach. + kThreadVersion = OPENTHREAD_CONFIG_THREAD_VERSION, ///< Thread Version + kUdpPort = 19788, ///< MLE UDP Port + kParentRequestRouterTimeout = 750, ///< Router Parent Request timeout + kParentRequestDuplicateMargin = 50, ///< Margin for duplicate parent request + kParentRequestReedTimeout = 1250, ///< Router and REEDs Parent Request timeout + kAttachStartJitter = 50, ///< Maximum jitter time added to start of attach. kAnnounceProcessTimeout = 250, ///< Timeout after receiving Announcement before channel/pan-id change kAnnounceTimeout = 1400, ///< Total timeout used for sending Announcement messages kMinAnnounceDelay = 80, ///< Minimum delay between Announcement messages diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index bb9012e00..2737c9a58 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -2087,6 +2087,7 @@ otError MleRouter::HandleChildIdRequest(const Message & aMessage, otError error = OT_ERROR_NONE; const otThreadLinkInfo *linkInfo = static_cast(aMessageInfo.GetLinkInfo()); Mac::ExtAddress macAddr; + VersionTlv version; ResponseTlv response; LinkFrameCounterTlv linkFrameCounter; MleFrameCounterTlv mleFrameCounter; @@ -2113,6 +2114,10 @@ otError MleRouter::HandleChildIdRequest(const Message & aMessage, child = mChildTable.FindChild(macAddr, Child::kInStateAnyExceptInvalid); VerifyOrExit(child != NULL, error = OT_ERROR_ALREADY); + // Version + SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kVersion, sizeof(version), version)); + VerifyOrExit(version.IsValid(), error = OT_ERROR_PARSE); + // Response SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kResponse, sizeof(response), response)); VerifyOrExit(response.IsValid() && @@ -2194,6 +2199,7 @@ otError MleRouter::HandleChildIdRequest(const Message & aMessage, child->SetMleFrameCounter(mleFrameCounter.GetFrameCounter()); child->SetKeySequence(aKeySequence); child->SetDeviceMode(mode.GetMode()); + child->SetVersion(static_cast(version.GetVersion())); child->GetLinkInfo().AddRss(linkInfo->mRss); child->SetTimeout(timeout.GetTimeout()); @@ -3594,6 +3600,7 @@ void MleRouter::RestoreChildren(void) child->SetDeviceMode(DeviceMode(childInfo.GetMode())); child->SetState(Neighbor::kStateRestored); child->SetLastHeard(TimerMilli::GetNow()); + child->SetVersion(static_cast(childInfo.GetVersion())); Get().SetChildUseShortAddress(*child, true); numChildren++; } @@ -3640,6 +3647,7 @@ otError MleRouter::StoreChild(const Child &aChild) childInfo.SetTimeout(aChild.GetTimeout()); childInfo.SetRloc16(aChild.GetRloc16()); childInfo.SetMode(aChild.GetDeviceMode().Get()); + childInfo.SetVersion(aChild.GetVersion()); return Get().AddChildInfo(childInfo); } diff --git a/src/core/thread/mle_tlvs.hpp b/src/core/thread/mle_tlvs.hpp index 102657e06..c4fdbdeac 100644 --- a/src/core/thread/mle_tlvs.hpp +++ b/src/core/thread/mle_tlvs.hpp @@ -1629,13 +1629,16 @@ public: } /** - * This method indicates whether or not the TLV appears to be well-formed. + * This method indicates whether or not the TLV appears to be well-formed and valid version. * - * @retval TRUE If the TLV appears to be well-formed. - * @retval FALSE If the TLV does not appear to be well-formed. + * @retval TRUE If the TLV appears to be well-formed and with valid version. + * @retval FALSE If the TLV does not appear to be well-formed or with invalid version. * */ - bool IsValid(void) const { return GetLength() >= sizeof(*this) - sizeof(Tlv); } + bool IsValid(void) const + { + return (GetLength() >= sizeof(*this) - sizeof(Tlv)) && mVersion >= OT_THREAD_VERSION_1_1; + } /** * This method returns the Version value. diff --git a/src/core/thread/topology.hpp b/src/core/thread/topology.hpp index 95975c2b5..632dd6b91 100644 --- a/src/core/thread/topology.hpp +++ b/src/core/thread/topology.hpp @@ -351,6 +351,31 @@ public: */ void SetRloc16(uint16_t aRloc16) { mRloc16 = aRloc16; } + /** + * This method indicates whether Enhanced Keep-Alive is supported or not. + * + * @returns TRUE if Enhanced Keep-Alive is supported, FALSE otherwise. + * + */ + bool IsEnhancedKeepAliveSupported(void) const + { + return mState != kStateInvalid && mVersion >= OT_THREAD_VERSION_1_2; + } + + /** + * This method gets the device MLE version. + * + */ + uint8_t GetVersion(void) const { return mVersion; } + + /** + * This method sets the device MLE version. + * + * @param[in] aVersion The device MLE version. + * + */ + void SetVersion(uint8_t aVersion) { mVersion = aVersion; } + /** * This method gets the number of consecutive link failures. * @@ -454,6 +479,7 @@ private: #else uint8_t mLinkFailures; ///< Consecutive link failure count #endif + uint8_t mVersion; ///< The MLE version LinkQualityInfo mLinkInfo; ///< Link quality info (contains average RSS, link margin and link quality) }; diff --git a/tests/scripts/thread-cert/message.py b/tests/scripts/thread-cert/message.py index 567ba3e79..b41ce8c07 100644 --- a/tests/scripts/thread-cert/message.py +++ b/tests/scripts/thread-cert/message.py @@ -354,6 +354,10 @@ class Message(object): ipv6.UDPDatagram) return self.ipv6_packet.upper_layer_protocol.header.dst_port + def is_data_poll(self): + return self._type == MessageType.COMMAND and \ + self._mac_header.command_type == mac802154.MacHeader.CommandIdentifier.DATA_REQUEST + def __repr__(self): if (self.type == MessageType.DTLS and self.dtls.content_type == dtls.ContentType.HANDSHAKE): @@ -375,6 +379,14 @@ class MessagesSet(object): def commissioning_messages(self): return self._commissioning_messages + def next_data_poll(self): + while True: + message = self.next_message_of(MessageType.COMMAND, False) + if not message: + break + elif message.is_data_poll(): + return message + def next_coap_message(self, code, uri_path=None, assert_enabled=True): message = None diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index aee76b7e9..01158192f 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -338,6 +338,10 @@ class Node: self._expect('Done') return addr64 + def set_addr64(self, addr64): + self.send_command('extaddr %s' % addr64) + self._expect('Done') + def get_eui64(self): self.send_command('eui64') i = self._expect('([0-9a-fA-F]{16})') @@ -445,6 +449,18 @@ class Node: self.send_command(cmd) self._expect('Done') + def get_pollperiod(self): + self.send_command('pollperiod') + i = self._expect(r'(\d+)\r?\n') + if i == 0: + pollperiod = self.pexpect.match.groups()[0] + self._expect('Done') + return pollperiod + + def set_pollperiod(self, pollperiod): + self.send_command('pollperiod %d' % pollperiod) + self._expect('Done') + def set_router_upgrade_threshold(self, threshold): cmd = 'routerupgradethreshold %d' % threshold self.send_command(cmd) @@ -772,10 +788,14 @@ class Node: result = True try: responders = {} - while len(responders) < num_responses: - i = self._expect([r'from (\S+):']) + # ncp-sim doesn't print Done + done = (self.node_type == 'ncp-sim') + while len(responders) < num_responses or not done: + i = self._expect([r'from (\S+):', 'Done']) if i == 0: responders[self.pexpect.match.groups()[0]] = 1 + elif i == 1: + done = True self._expect('\n') except (pexpect.TIMEOUT, socket.timeout): result = False diff --git a/tests/scripts/thread-cert/requirements.txt b/tests/scripts/thread-cert/requirements.txt index 684b969b6..1d8ca8c95 100644 --- a/tests/scripts/thread-cert/requirements.txt +++ b/tests/scripts/thread-cert/requirements.txt @@ -1,4 +1,3 @@ -enum34 ipaddress pexpect pycryptodome diff --git a/tests/scripts/thread-cert/thread_cert.py b/tests/scripts/thread-cert/thread_cert.py new file mode 100644 index 000000000..f39df66ef --- /dev/null +++ b/tests/scripts/thread-cert/thread_cert.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2019, 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. +# + +import unittest + +import config +import debug +from node import Node + +DEFAULT_PARAMS = { + 'is_mtd': False, + 'mode': 'rsdn', + 'panid': 0xface, + 'whitelist': None, + 'version': '1.2' +} +"""Default configurations when creating nodes.""" + +EXTENDED_ADDRESS_BASE = 0x166e0a0000000000 +"""Extended address base to keep U/L bit 1. The value is borrowed from Thread Test Harness.""" + + +class TestCase(unittest.TestCase): + """The base class for all thread certification test cases. + + The `topology` member of sub-class is used to create test topology. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.simulator = config.create_default_simulator() + self.nodes = {} + + def setUp(self): + """Create simulator, nodes and apply configurations. + """ + initial_topology = {} + for i, params in self.topology.items(): + if params: + params = dict(DEFAULT_PARAMS, **params) + else: + params = DEFAULT_PARAMS.copy() + initial_topology[i] = params + + self.nodes[i] = Node(i, params['is_mtd'], simulator=self.simulator) + self.nodes[i].set_panid(params['panid']) + self.nodes[i].set_mode(params['mode']) + self.nodes[i].set_addr64(format(EXTENDED_ADDRESS_BASE + i, '016x')) + + # we have to add whitelist after nodes are all created + for i, params in initial_topology.items(): + whitelist = params['whitelist'] + if not whitelist: + continue + + for j in whitelist: + self.nodes[i].add_whitelist(self.nodes[j].get_addr64()) + self.nodes[i].enable_whitelist() + + self._inspector = debug.Inspector(self) + + def inspect(self): + self._inspector.inspect() + + def tearDown(self): + """Destroy nodes and simulator. + """ + for node in list(self.nodes.values()): + node.stop() + node.destroy() + self.simulator.stop() + + def flush_all(self): + """Flush away all captured messages of all nodes. + """ + for i in list(self.nodes.keys()): + self.simulator.get_messages_sent_by(i) diff --git a/tests/scripts/thread-cert/v1_2_router_5_1_1.py b/tests/scripts/thread-cert/v1_2_router_5_1_1.py new file mode 100755 index 000000000..20c089209 --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_router_5_1_1.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2019, 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. +# + +import unittest + +import thread_cert +import mle +import network_layer + +LEADER = 1 +ROUTER_1 = 2 + + +class Router_5_1_01(thread_cert.TestCase): + topology = { + LEADER: None, + ROUTER_1: None, + } + """All nodes are created with default configurations""" + + def test(self): + self.nodes[ROUTER_1].set_router_selection_jitter(1) + + self.nodes[LEADER].start() + self.simulator.go(5) + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + + self.nodes[ROUTER_1].start() + self.simulator.go(7) + self.assertEqual(self.nodes[ROUTER_1].get_state(), 'router') + + leader_messages = self.simulator.get_messages_sent_by(LEADER) + router_messages = self.simulator.get_messages_sent_by(ROUTER_1) + + # 1 - Leader transmits MLE advertisements + msg = leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress('ff02::1') + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Route64) + + # 2 - Router_1 begins attach process by sending a multicast MLE Parent Request + msg = router_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress('ff02::2') + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + assert msg.get_mle_message_tlv(mle.Version).version == 3 + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(0, scan_mask_tlv.end_device) + + # 3 - Leader sends a MLE Parent Response + msg = leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER_1]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.LinkMargin) + msg.assertMleMessageContainsTlv(mle.Connectivity) + msg.assertMleMessageContainsTlv(mle.Version) + assert msg.get_mle_message_tlv(mle.Version).version == 3 + + # 4 - Router_1 receives the MLE Parent Response and sends a Child ID Request + msg = router_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[LEADER]) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Timeout) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.TlvRequest) + msg.assertMleMessageDoesNotContainTlv(mle.AddressRegistration) + assert msg.get_mle_message_tlv(mle.Version).version == 3 + + # 5 - Leader responds with a Child ID Response + msg = leader_messages.next_mle_message( + mle.CommandType.CHILD_ID_RESPONSE) + msg.assertSentToNode(self.nodes[ROUTER_1]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Address16) + msg.assertMleMessageContainsOptionalTlv(mle.NetworkData) + msg.assertMleMessageContainsOptionalTlv(mle.Route64) + msg.assertMleMessageContainsOptionalTlv(mle.AddressRegistration) + + # 6 - Router_1 sends an Address Solicit Request + msg = router_messages.next_coap_message('0.02') + msg.assertCoapMessageRequestUriPath('/a/as') + msg.assertCoapMessageContainsTlv(network_layer.MacExtendedAddress) + msg.assertCoapMessageContainsTlv(network_layer.Status) + + # 7 - Leader sends an Address Solicit Response + msg = leader_messages.next_coap_message('2.04') + msg.assertCoapMessageContainsTlv(network_layer.Status) + msg.assertCoapMessageContainsOptionalTlv(network_layer.RouterMask) + + status_tlv = msg.get_coap_message_tlv(network_layer.Status) + self.assertEqual(network_layer.StatusValues.SUCCESS, status_tlv.status) + + # 8 - Router_1 sends a multicast Link Request Message + msg = router_messages.next_mle_message(mle.CommandType.LINK_REQUEST) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.TlvRequest) + assert msg.get_mle_message_tlv(mle.Version).version >= 3 + + tlv_request = msg.get_mle_message_tlv(mle.TlvRequest) + self.assertIn(mle.TlvType.LINK_MARGIN, tlv_request.tlvs) + + # 9 - Leader sends a Unicast Link Accept + msg = leader_messages.next_mle_message( + mle.CommandType.LINK_ACCEPT_AND_REQUEST) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.LinkMargin) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.Challenge) + assert msg.get_mle_message_tlv(mle.Version).version >= 3 + + # 10 - Router_1 Transmit MLE advertisements + msg = router_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress('ff02::1') + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Route64) + + # 11 - Verify connectivity by sending an ICMPv6 Echo Request to the DUT link local address + self.assertTrue(self.nodes[LEADER].ping( + self.nodes[ROUTER_1].get_linklocal())) + self.assertTrue(self.nodes[ROUTER_1].ping( + self.nodes[LEADER].get_linklocal())) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/v1_2_test_enhanced_keep_alive.py b/tests/scripts/thread-cert/v1_2_test_enhanced_keep_alive.py new file mode 100755 index 000000000..6bfd8bf4e --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_test_enhanced_keep_alive.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2019, 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. +# + +import unittest + +import thread_cert +import mle + +LEADER = 1 +SED_1 = 2 + +CHILD_TIMEOUT = 30 + +DEFAULT_POLL_PERIOD = CHILD_TIMEOUT - 4 +"""The default poll period calculated by ot::Mac::DataPollSender::GetDefaultPollPeriod().""" + +USER_POLL_PERIOD = CHILD_TIMEOUT // 3 +"""The poll period explicitly set by this test for verifying enhanced keep-alive.""" + + +class SED_EnhancedKeepAlive(thread_cert.TestCase): + topology = { + LEADER: None, + SED_1: { + 'mode': 's', + }, + } + """All nodes are created with default configurations""" + + def test(self): + self.nodes[SED_1].set_timeout(CHILD_TIMEOUT) + self.nodes[SED_1].set_pollperiod(USER_POLL_PERIOD * 1000) + + self.nodes[LEADER].start() + self.simulator.go(5) + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + + self.nodes[SED_1].start() + self.simulator.go(7) + self.assertEqual(self.nodes[SED_1].get_state(), 'child') + + leader_messages = self.simulator.get_messages_sent_by(LEADER) + sed_messages = self.simulator.get_messages_sent_by(SED_1) + + # 1 - Leader transmits MLE advertisements + msg = leader_messages.next_mle_message(mle.CommandType.ADVERTISEMENT) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress('ff02::1') + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Route64) + + # 2 - SED_1 begins attach process by sending a multicast MLE Parent Request + msg = sed_messages.next_mle_message(mle.CommandType.PARENT_REQUEST) + msg.assertSentWithHopLimit(255) + msg.assertSentToDestinationAddress('ff02::2') + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.ScanMask) + msg.assertMleMessageContainsTlv(mle.Version) + self.assertEqual(msg.get_mle_message_tlv(mle.Version).version, 3) + + scan_mask_tlv = msg.get_mle_message_tlv(mle.ScanMask) + self.assertEqual(1, scan_mask_tlv.router) + self.assertEqual(0, scan_mask_tlv.end_device) + + # 3 - Leader sends a MLE Parent Response + msg = leader_messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + msg.assertSentToNode(self.nodes[SED_1]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.Challenge) + msg.assertMleMessageContainsTlv(mle.LinkMargin) + msg.assertMleMessageContainsTlv(mle.Connectivity) + msg.assertMleMessageContainsTlv(mle.Version) + self.assertEqual(msg.get_mle_message_tlv(mle.Version).version, 3) + + # 4 - SED_1 receives the MLE Parent Response and sends a Child ID Request + msg = sed_messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[LEADER]) + msg.assertMleMessageContainsTlv(mle.Response) + msg.assertMleMessageContainsTlv(mle.LinkLayerFrameCounter) + msg.assertMleMessageContainsOptionalTlv(mle.MleFrameCounter) + msg.assertMleMessageContainsTlv(mle.Mode) + msg.assertMleMessageContainsTlv(mle.Timeout) + msg.assertMleMessageContainsTlv(mle.Version) + msg.assertMleMessageContainsTlv(mle.TlvRequest) + self.assertEqual(msg.get_mle_message_tlv(mle.Version).version, 3) + + # 5 - Leader responds with a Child ID Response + msg = leader_messages.next_mle_message( + mle.CommandType.CHILD_ID_RESPONSE) + msg.assertSentToNode(self.nodes[SED_1]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Address16) + msg.assertMleMessageContainsOptionalTlv(mle.NetworkData) + msg.assertMleMessageContainsOptionalTlv(mle.Route64) + msg.assertMleMessageContainsOptionalTlv(mle.AddressRegistration) + + leader_aloc = self.nodes[LEADER].get_addr_leader_aloc() + self.assertTrue(self.nodes[SED_1].ping(leader_aloc, + timeout=USER_POLL_PERIOD * 2)) + + # 6 - Timeout Child + self.nodes[LEADER].enable_whitelist() + self.nodes[SED_1].enable_whitelist() + self.nodes[SED_1].set_pollperiod(CHILD_TIMEOUT * 1000 * 2) + self.simulator.go(CHILD_TIMEOUT) + self.assertEqual(self.nodes[SED_1].get_state(), 'child') + self.nodes[SED_1].set_pollperiod(USER_POLL_PERIOD * 1000) + self.nodes[LEADER].disable_whitelist() + self.nodes[SED_1].disable_whitelist() + self.assertFalse(self.nodes[SED_1].ping(leader_aloc, + timeout=USER_POLL_PERIOD * 2)) + self.flush_all() + + # 7 - Wait SED_1 to re-attach + self.simulator.go(240) + leader_messages = self.simulator.get_messages_sent_by(LEADER) + msg = leader_messages.next_mle_message( + mle.CommandType.CHILD_ID_RESPONSE) + msg.assertSentToNode(self.nodes[SED_1]) + msg.assertMleMessageContainsTlv(mle.SourceAddress) + msg.assertMleMessageContainsTlv(mle.LeaderData) + msg.assertMleMessageContainsTlv(mle.Address16) + msg.assertMleMessageContainsOptionalTlv(mle.NetworkData) + msg.assertMleMessageContainsOptionalTlv(mle.Route64) + msg.assertMleMessageContainsOptionalTlv(mle.AddressRegistration) + self.assertTrue(self.nodes[SED_1].ping(leader_aloc, + timeout=USER_POLL_PERIOD * 2)) + self.flush_all() + + # 8 - Verify enhanced keep-alive works + self.nodes[LEADER].enable_whitelist() + self.nodes[SED_1].enable_whitelist() + self.nodes[SED_1].set_pollperiod(CHILD_TIMEOUT * 1000 * 2) + self.simulator.go(CHILD_TIMEOUT // 2) + self.assertEqual(self.nodes[SED_1].get_state(), 'child') + self.nodes[LEADER].disable_whitelist() + self.nodes[SED_1].disable_whitelist() + non_exist_addr = leader_aloc.replace('fc00', 'fc12') + self.assertFalse(self.nodes[SED_1].ping(non_exist_addr)) + self.nodes[LEADER].enable_whitelist() + self.nodes[SED_1].enable_whitelist() + self.simulator.go(CHILD_TIMEOUT // 2) + self.nodes[LEADER].disable_whitelist() + self.nodes[SED_1].disable_whitelist() + self.nodes[SED_1].set_pollperiod(USER_POLL_PERIOD * 1000) + self.assertTrue(self.nodes[SED_1].ping(leader_aloc, + timeout=USER_POLL_PERIOD * 2)) + + # 9 - Verify child resets keep-alive timer + self.nodes[SED_1].set_pollperiod(DEFAULT_POLL_PERIOD * 1000) + self.simulator.go(DEFAULT_POLL_PERIOD // 3 * 2) + self.flush_all() + self.nodes[SED_1].ping(leader_aloc, timeout=1) + self.simulator.go(DEFAULT_POLL_PERIOD // 3 * 2) + sed_messages = self.simulator.get_messages_sent_by(SED_1) + self.assertEqual(sed_messages.next_data_poll(), None) + + +if __name__ == '__main__': + unittest.main()