diff --git a/src/core/thread/child_table.hpp b/src/core/thread/child_table.hpp index 93cdfe00b..7df7dee34 100644 --- a/src/core/thread/child_table.hpp +++ b/src/core/thread/child_table.hpp @@ -304,6 +304,22 @@ public: */ bool HasSleepyChildWithAddress(const Ip6::Address &aIp6Address) const; + /** + * This method indicates whether the child table contains a given `Neighbor` instance. + * + * @param[in] aNeighbor A reference to a `Neighbor`. + * + * @retval TRUE if @p aNeighbor is a `Child` in the child table. + * @retval FALSE if @p aNeighbor is not a `Child` in the child table. + * + */ + bool Contains(const Neighbor &aNeighbor) const + { + const Child *child = static_cast(&aNeighbor); + + return (mChildren <= child) && (child < GetArrayEnd(mChildren)); + } + private: static constexpr uint16_t kMaxChildren = OPENTHREAD_CONFIG_MLE_MAX_CHILDREN; diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index c9f671197..055133e1d 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -1947,12 +1947,12 @@ exit: return; } -Error Mle::SendChildUpdateRequest(void) +Error Mle::SendChildUpdateRequest(bool aAppendChallenge) { - return SendChildUpdateRequest(mTimeout); + return SendChildUpdateRequest(aAppendChallenge, mTimeout); } -Error Mle::SendChildUpdateRequest(uint32_t aTimeout) +Error Mle::SendChildUpdateRequest(bool aAppendChallenge, uint32_t aTimeout) { Error error = kErrorNone; Ip6::Address destination; @@ -1972,11 +1972,15 @@ Error Mle::SendChildUpdateRequest(uint32_t aTimeout) VerifyOrExit((message = NewMleMessage(kCommandChildUpdateRequest)) != nullptr, error = kErrorNoBufs); SuccessOrExit(error = message->AppendModeTlv(mDeviceMode)); + if (aAppendChallenge || IsDetached()) + { + mParentRequestChallenge.GenerateRandom(); + SuccessOrExit(error = message->AppendChallengeTlv(mParentRequestChallenge)); + } + switch (mRole) { case kRoleDetached: - mParentRequestChallenge.GenerateRandom(); - SuccessOrExit(error = message->AppendChallengeTlv(mParentRequestChallenge)); mode = kAppendMeshLocalOnly; break; @@ -2381,11 +2385,6 @@ void Mle::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageIn SuccessOrExit( error = ProcessMessageSecurity(Crypto::AesCcm::kDecrypt, aMessage, aMessageInfo, aMessage.GetOffset(), header)); - if (keySequence > Get().GetCurrentKeySequence()) - { - Get().SetCurrentKeySequence(keySequence); - } - IgnoreError(aMessage.Read(aMessage.GetOffset(), command)); aMessage.MoveOffset(sizeof(command)); @@ -2548,6 +2547,48 @@ void Mle::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageIn ExitNow(error = kErrorDrop); } + // In case key sequence is larger, we determine whether to adopt it + // or not. The `Handle{MleMsg}()` methods set the `rxInfo.mClass` + // based on the message command type and the included TLVs. If + // there is any error during parsing of the message the `mClass` + // remains as its default value of `RxInfo::kUnknown`. Message + // classes are determined based on this: + // + // Authoritative : Larger key seq MUST be adopted. + // Peer : If from a known neighbor + // If difference is 1, adopt + // Otherwise don't adopt and try to re-sync with + // neighbor. + // Otherwise larger key seq MUST NOT be adopted. + + if (keySequence > Get().GetCurrentKeySequence()) + { + switch (rxInfo.mClass) + { + case RxInfo::kAuthoritativeMessage: + Get().SetCurrentKeySequence(keySequence); + break; + + case RxInfo::kPeerMessage: + if ((neighbor != nullptr) && neighbor->IsStateValid()) + { + if (keySequence - Get().GetCurrentKeySequence() == 1) + { + Get().SetCurrentKeySequence(keySequence); + } + else + { + LogInfo("Large key seq jump in peer class msg from 0x%04x ", neighbor->GetRloc16()); + ReestablishLinkWithNeighbor(*neighbor); + } + } + break; + + case RxInfo::kUnknown: + break; + } + } + #if OPENTHREAD_CONFIG_MULTI_RADIO // If we could not find a neighbor matching the MAC address of the // received MLE messages, or if the neighbor is now invalid, we @@ -2579,6 +2620,36 @@ exit: } } +void Mle::ReestablishLinkWithNeighbor(Neighbor &aNeighbor) +{ + VerifyOrExit(IsAttached() && aNeighbor.IsStateValid()); + + if (IsChild() && (&aNeighbor == &mParent)) + { + IgnoreError(SendChildUpdateRequest(/* aAppendChallenge */ true)); + ExitNow(); + } + +#if OPENTHREAD_FTD + VerifyOrExit(IsFullThreadDevice()); + + if (IsActiveRouter(aNeighbor.GetRloc16())) + { + IgnoreError(Get().SendLinkRequest(&aNeighbor)); + } + else if (Get().Contains(aNeighbor)) + { + Child &child = static_cast(aNeighbor); + + child.SetState(Child::kStateChildUpdateRequest); + IgnoreError(Get().SendChildUpdateRequest(child)); + } +#endif + +exit: + return; +} + void Mle::HandleAdvertisement(RxInfo &aRxInfo) { Error error = kErrorNone; @@ -2659,6 +2730,8 @@ void Mle::HandleAdvertisement(RxInfo &aRxInfo) IgnoreError(SendDataRequest(aRxInfo.mMessageInfo.GetPeerAddr(), tlvs, sizeof(tlvs), delay)); } + aRxInfo.mClass = RxInfo::kPeerMessage; + exit: LogProcessError(kTypeAdvertisement, error); } @@ -2696,6 +2769,9 @@ void Mle::HandleDataResponse(RxInfo &aRxInfo) Get().StopFastPolls(); } + SuccessOrExit(error); + aRxInfo.mClass = RxInfo::kPeerMessage; + exit: LogProcessError(kTypeDataResponse, error); } @@ -3050,6 +3126,8 @@ void Mle::HandleParentResponse(RxInfo &aRxInfo) mParentResponseCb(&parentinfo, mParentResponseCbContext); } + aRxInfo.mClass = RxInfo::kAuthoritativeMessage; + #if OPENTHREAD_FTD if (IsFullThreadDevice() && !IsDetached()) { @@ -3204,8 +3282,7 @@ void Mle::HandleChildIdResponse(RxInfo &aRxInfo) SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData)); // Network Data - error = Tlv::FindTlvOffset(aRxInfo.mMessage, Tlv::kNetworkData, networkDataOffset); - SuccessOrExit(error); + SuccessOrExit(error = Tlv::FindTlvOffset(aRxInfo.mMessage, Tlv::kNetworkData, networkDataOffset)); // Active Timestamp switch (Tlv::Find(aRxInfo.mMessage, timestamp)) @@ -3308,6 +3385,8 @@ void Mle::HandleChildIdResponse(RxInfo &aRxInfo) Get().SetRxOnWhenIdle(true); } + aRxInfo.mClass = RxInfo::kPeerMessage; + exit: LogProcessError(kTypeChildIdResponse, error); } @@ -3403,6 +3482,8 @@ void Mle::HandleChildUpdateRequest(RxInfo &aRxInfo) ExitNow(error = kErrorParse); } + aRxInfo.mClass = RxInfo::kPeerMessage; + #if OPENTHREAD_CONFIG_MULTI_RADIO if ((aRxInfo.mNeighbor != nullptr) && (challenge.mLength != 0)) { @@ -3432,10 +3513,20 @@ void Mle::HandleChildUpdateResponse(RxInfo &aRxInfo) Log(kMessageReceive, kTypeChildUpdateResponseOfParent, aRxInfo.mMessageInfo.GetPeerAddr()); + switch (aRxInfo.mMessage.ReadResponseTlv(response)) + { + case kErrorNone: + break; + case kErrorNotFound: + response.mLength = 0; + break; + default: + ExitNow(error = kErrorParse); + } + switch (mRole) { case kRoleDetached: - SuccessOrExit(error = aRxInfo.mMessage.ReadResponseTlv(response)); VerifyOrExit(response == mParentRequestChallenge, error = kErrorSecurity); break; @@ -3533,6 +3624,8 @@ void Mle::HandleChildUpdateResponse(RxInfo &aRxInfo) OT_UNREACHABLE_CODE(break); } + aRxInfo.mClass = (response.mLength == 0) ? RxInfo::kPeerMessage : RxInfo::kAuthoritativeMessage; + exit: if (error == kErrorNone) @@ -3567,6 +3660,8 @@ void Mle::HandleAnnounce(RxInfo &aRxInfo) SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, timestamp)); SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, panId)); + aRxInfo.mClass = RxInfo::kPeerMessage; + localTimestamp = Get().GetTimestamp(); if (timestamp.IsOrphanTimestamp() || MeshCoP::Timestamp::Compare(×tamp, localTimestamp) < 0) @@ -3628,8 +3723,11 @@ void Mle::HandleLinkMetricsManagementRequest(RxInfo &aRxInfo) SuccessOrExit( error = Get().HandleManagementRequest(aRxInfo.mMessage, *aRxInfo.mNeighbor, status)); + error = SendLinkMetricsManagementResponse(aRxInfo.mMessageInfo.GetPeerAddr(), status); + aRxInfo.mClass = RxInfo::kPeerMessage; + exit: LogProcessError(kTypeLinkMetricsManagementRequest, error); } @@ -3648,6 +3746,8 @@ void Mle::HandleLinkMetricsManagementResponse(RxInfo &aRxInfo) error = Get().HandleManagementResponse(aRxInfo.mMessage, aRxInfo.mMessageInfo.GetPeerAddr()); + aRxInfo.mClass = RxInfo::kPeerMessage; + exit: LogProcessError(kTypeLinkMetricsManagementResponse, error); } @@ -3665,6 +3765,8 @@ void Mle::HandleLinkProbe(RxInfo &aRxInfo) aRxInfo.mNeighbor->AggregateLinkMetrics(seriesId, LinkMetrics::SeriesInfo::kSeriesTypeLinkProbe, aRxInfo.mMessage.GetAverageLqi(), aRxInfo.mMessage.GetAverageRss()); + aRxInfo.mClass = RxInfo::kPeerMessage; + exit: LogProcessError(kTypeLinkProbe, error); } @@ -4225,7 +4327,7 @@ Error Mle::DetachGracefully(otDetachGracefullyCallback aCallback, void *aContext if (IsChild()) { - IgnoreError(SendChildUpdateRequest(/*aTimeout=*/0)); + IgnoreError(SendChildUpdateRequest(/* aAppendChallenge */ false, /* aTimeout */ 0)); } #if OPENTHREAD_FTD else if (IsRouter()) diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index ddd8b91cd..78c1f1c5d 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -1378,7 +1378,18 @@ protected: struct RxInfo { /** - * This constructor initializes the `RxInfo` + * This enumeration represents a received MLE message class. + * + */ + enum Class : uint8_t + { + kUnknown, ///< Unknown (default value, also indicates MLE message parse error). + kAuthoritativeMessage, ///< Authoritative message (larger received key seq MUST be adopted). + kPeerMessage, ///< Peer message (adopt only if from a known neighbor and is greater by one). + }; + + /** + * This constructor initializes the `RxInfo`. * * @param[in] aMessage The received MLE message. * @param[in] aMessageInfo The `Ip6::MessageInfo` associated with message. @@ -1390,14 +1401,16 @@ protected: , mFrameCounter(0) , mKeySequence(0) , mNeighbor(nullptr) + , mClass(kUnknown) { } RxMessage & mMessage; ///< The MLE message. const Ip6::MessageInfo &mMessageInfo; ///< The `MessageInfo` associated with the message. uint32_t mFrameCounter; ///< The frame counter from aux security header. - uint32_t mKeySequence; ///< The key sequence from the aux security header. + uint32_t mKeySequence; ///< The key sequence from aux security header. Neighbor * mNeighbor; ///< Neighbor from which message was received (can be `nullptr`). + Class mClass; ///< The message class (authoritative, peer, or unknown). }; /** @@ -1480,11 +1493,13 @@ protected: /** * This method generates an MLE Child Update Request message. * + * @param[in] aAppendChallenge Indicates whether or not to include a Challenge TLV (even when already attached). + * * @retval kErrorNone Successfully generated an MLE Child Update Request message. * @retval kErrorNoBufs Insufficient buffers to generate the MLE Child Update Request message. * */ - Error SendChildUpdateRequest(void); + Error SendChildUpdateRequest(bool aAppendChallenge = false); /** * This method generates an MLE Child Update Response message. @@ -1824,9 +1839,10 @@ private: static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); void ScheduleMessageTransmissionTimer(void); + void ReestablishLinkWithNeighbor(Neighbor &aNeighbor); static void HandleDetachGracefullyTimer(Timer &aTimer); void HandleDetachGracefullyTimer(void); - Error SendChildUpdateRequest(uint32_t aTimeout); + Error SendChildUpdateRequest(bool aAppendChallenge, uint32_t aTimeout); #if OPENTHREAD_FTD static void HandleDetachGracefullyAddressReleaseResponse(void * aContext, diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index b18f21ba1..f21cef38a 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -724,6 +724,8 @@ void MleRouter::HandleLinkRequest(RxInfo &aRxInfo) } #endif + aRxInfo.mClass = RxInfo::kPeerMessage; + SuccessOrExit(error = SendLinkAccept(aRxInfo.mMessageInfo, neighbor, requestedTlvs, challenge)); exit: @@ -1018,6 +1020,8 @@ Error MleRouter::HandleLinkAccept(RxInfo &aRxInfo, bool aRequest) mNeighborTable.Signal(NeighborTable::kRouterAdded, *router); + aRxInfo.mClass = RxInfo::kAuthoritativeMessage; + if (aRequest) { Challenge challenge; @@ -1762,6 +1766,8 @@ void MleRouter::HandleParentRequest(RxInfo &aRxInfo) child->SetTimeout(Time::MsecToSec(kMaxChildIdRequestTimeout)); } + aRxInfo.mClass = RxInfo::kPeerMessage; + SendParentResponse(child, challenge, !ScanMaskTlv::IsEndDeviceFlagSet(scanMask)); exit: @@ -2465,6 +2471,8 @@ void MleRouter::HandleChildIdRequest(RxInfo &aRxInfo) child->SetRequestTlv(numTlvs++, Tlv::kPendingDataset); } + aRxInfo.mClass = RxInfo::kAuthoritativeMessage; + switch (mRole) { case kRoleDisabled: @@ -2692,6 +2700,8 @@ void MleRouter::HandleChildUpdateRequest(RxInfo &aRxInfo) SendChildUpdateResponse(child, aRxInfo.mMessageInfo, tlvs, tlvslength, challenge); + aRxInfo.mClass = RxInfo::kPeerMessage; + exit: LogProcessError(kTypeChildUpdateRequestOfChild, error); } @@ -2725,6 +2735,7 @@ void MleRouter::HandleChildUpdateResponse(RxInfo &aRxInfo) break; case kErrorNotFound: VerifyOrExit(child->IsStateValid(), error = kErrorSecurity); + response.mLength = 0; break; default: ExitNow(error = kErrorNone); @@ -2824,6 +2835,8 @@ void MleRouter::HandleChildUpdateResponse(RxInfo &aRxInfo) child->SetKeySequence(aRxInfo.mKeySequence); child->GetLinkInfo().AddRss(aRxInfo.mMessageInfo.GetThreadLinkInfo()->GetRss()); + aRxInfo.mClass = (response.mLength == 0) ? RxInfo::kPeerMessage : RxInfo::kAuthoritativeMessage; + exit: LogProcessError(kTypeChildUpdateResponseOfChild, error); } @@ -2886,6 +2899,8 @@ void MleRouter::HandleDataRequest(RxInfo &aRxInfo) ExitNow(error = kErrorParse); } + aRxInfo.mClass = RxInfo::kPeerMessage; + SendDataResponse(aRxInfo.mMessageInfo.GetPeerAddr(), tlvs, numTlvs, 0, &aRxInfo.mMessage); exit: @@ -4444,6 +4459,8 @@ void MleRouter::HandleTimeSync(RxInfo &aRxInfo) VerifyOrExit(aRxInfo.mNeighbor && aRxInfo.mNeighbor->IsStateValid()); + aRxInfo.mClass = RxInfo::kPeerMessage; + Get().HandleTimeSyncMessage(aRxInfo.mMessage); exit: diff --git a/tests/scripts/thread-cert/Makefile.am b/tests/scripts/thread-cert/Makefile.am index 6af656aa6..d6e4210c6 100644 --- a/tests/scripts/thread-cert/Makefile.am +++ b/tests/scripts/thread-cert/Makefile.am @@ -175,6 +175,7 @@ EXTRA_DIST = \ test_mac802154.py \ test_mac_scan.py \ test_mle.py \ + test_mle_msg_key_seq_jump.py \ test_netdata_publisher.py \ test_network_data.py \ test_network_layer.py \ @@ -255,6 +256,7 @@ check_SCRIPTS = \ test_mac802154.py \ test_mac_scan.py \ test_mle.py \ + test_mle_msg_key_seq_jump.py \ test_netdata_publisher.py \ test_network_data.py \ test_network_layer.py \ diff --git a/tests/scripts/thread-cert/test_mle_msg_key_seq_jump.py b/tests/scripts/thread-cert/test_mle_msg_key_seq_jump.py new file mode 100755 index 000000000..52ddaaaaa --- /dev/null +++ b/tests/scripts/thread-cert/test_mle_msg_key_seq_jump.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022, 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 ipaddress +import unittest + +import command +import config +import thread_cert + +# Test description: +# +# This test verifies behavior of MLE related to handling of received +# larger key sequence based on the MLE message class (authoritative, +# or peer). +# +# +# Topology: +# +# leader --- router +# | \ +# | \ +# child reed +# + +LEADER = 1 +CHILD = 2 +REED = 3 +ROUTER = 4 + + +class MleMsgKeySeqJump(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + SUPPORT_NCP = False + + TOPOLOGY = { + LEADER: { + 'name': 'LEADER', + 'mode': 'rdn', + }, + CHILD: { + 'name': 'CHILD', + 'is_mtd': True, + 'mode': 'rn', + }, + REED: { + 'name': 'REED', + 'mode': 'rn' + }, + ROUTER: { + 'name': 'ROUTER', + 'mode': 'rdn', + }, + } + + def test(self): + leader = self.nodes[LEADER] + child = self.nodes[CHILD] + reed = self.nodes[REED] + router = self.nodes[ROUTER] + + nodes = [leader, child, reed, router] + + #------------------------------------------------------------------- + # Form the network. + + for node in nodes: + node.set_key_sequence_counter(0) + + leader.start() + self.simulator.go(15) + self.assertEqual(leader.get_state(), 'leader') + + child.start() + reed.start() + self.simulator.go(5) + self.assertEqual(child.get_state(), 'child') + self.assertEqual(reed.get_state(), 'child') + + router.start() + self.simulator.go(5) + self.assertEqual(router.get_state(), 'router') + + #------------------------------------------------------------------- + # Validate the initial key seq counter on all nodes + + for node in nodes: + self.assertEqual(node.get_key_sequence_counter(), 0) + + #------------------------------------------------------------------- + # Manually increase the key seq on child. Then change MLE mode on + # child which triggers a "Child Update Request" to its parent + # (leader). The key jump noticed on parent side would trigger an + # authoritative MLE Child Update exchange (including challenge and + # response TLVs) and causes the parent (leader) to also adopt the + # larger key seq. + + child.set_key_sequence_counter(5) + self.assertEqual(child.get_key_sequence_counter(), 5) + + child.set_mode('r') + self.simulator.go(1) + + self.assertEqual(child.get_key_sequence_counter(), 5) + self.assertEqual(leader.get_key_sequence_counter(), 5) + + #------------------------------------------------------------------- + # Wait long enough for MLE Advertisement to be sent. This would + # trigger reed and router to also notice key seq jump and try to + # re-establish link again (using authoritative exchanges). Validate + # that all nodes are using the new key seq. + + self.simulator.go(40) + for node in nodes: + self.assertEqual(node.get_key_sequence_counter(), 5) + + #------------------------------------------------------------------- + # Manually increase the key seq on leader. Wait for advertisement + # interval. This would trigger both reed and router to notice key + # seq jump and try to re-establish link (link request/accept exchange). + # Validate that they all adopt the new key seq. + + leader.set_key_sequence_counter(10) + self.assertEqual(leader.get_key_sequence_counter(), 10) + + self.simulator.go(40) + + self.assertEqual(router.get_key_sequence_counter(), 10) + self.assertEqual(reed.get_key_sequence_counter(), 10) + + #------------------------------------------------------------------- + # Change MLE mode on child to trigger a "Child Update Request" exchange + # which should then update the key seq on child as well. + + child.set_mode('rn') + self.simulator.go(5) + self.assertEqual(child.get_key_sequence_counter(), 10) + + #------------------------------------------------------------------- + # Stop all other nodes except for leader. Move the leader key seq + # forward and then restart all other node. Validate that router, + # reed and child all re-attach successfully and adopt the higher key + # sequence. + + router.stop() + reed.stop() + child.stop() + + leader.set_key_sequence_counter(15) + self.assertEqual(leader.get_key_sequence_counter(), 15) + + child.start() + reed.start() + router.start() + self.simulator.go(5) + + self.assertEqual(child.get_state(), 'child') + self.assertEqual(reed.get_state(), 'child') + self.assertEqual(router.get_state(), 'router') + + for node in nodes: + self.assertEqual(node.get_key_sequence_counter(), 15) + + #------------------------------------------------------------------- + # Stop all other nodes except for leader. Move the child key seq + # forward and then restart child. Ensure it re-attached successfully + # to leader and that leader adopts the higher key seq counter. + + router.stop() + reed.stop() + child.stop() + + child.set_key_sequence_counter(20) + self.assertEqual(child.get_key_sequence_counter(), 20) + + child.start() + self.simulator.go(5) + + self.assertEqual(child.get_state(), 'child') + self.assertEqual(leader.get_key_sequence_counter(), 20) + + #------------------------------------------------------------------- + # Restart router and reed and ensure they are re-attached and get the + # higher key seq counter. + + router.start() + reed.start() + + self.simulator.go(5) + self.assertEqual(router.get_state(), 'router') + self.assertEqual(reed.get_state(), 'child') + + self.assertEqual(router.get_key_sequence_counter(), 20) + self.assertEqual(reed.get_key_sequence_counter(), 20) + + #------------------------------------------------------------------- + # Move forward the key seq counter by one on router. Wait for + # advertisement interval. Validate that leader adopts the higher + # counter value. + + router.set_key_sequence_counter(21) + self.assertEqual(router.get_key_sequence_counter(), 21) + + self.simulator.go(40) + self.assertEqual(leader.get_key_sequence_counter(), 21) + self.assertEqual(reed.get_key_sequence_counter(), 21) + + child.set_mode('r') + self.simulator.go(2) + self.assertEqual(child.get_key_sequence_counter(), 21) + + +if __name__ == '__main__': + unittest.main()