From 733750b3de73311feb5e37d6deaa0b71a5396f15 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Fri, 24 Jun 2022 12:21:04 -0700 Subject: [PATCH] [mle] handle rx key seq update based on MLE message class (#7672) This commit implements new mechanism in `Mle` related to key sequence update when an MLE message is received with a larger key sequence compared to the one being used by `KeyManager`. The MLE messages are categorized into classes of Authoritative, or Peer depending on the MLE command type and included TLVs. The class determines different actions: Authoritative indicates sender is confident that its key seq is in sync, so receiver will adopt the larger key seq. Peer class is used when both sender and receiver think they are in sync. In this case, if the MLE message is from a known neighbor, receiver will adopt the larger key seq if the difference is one, otherwise it will try to re-establish link with the neighbor using Authoritative message exchanges (e.g. sending Link Request or Child Update Request). --- src/core/thread/child_table.hpp | 16 ++ src/core/thread/mle.cpp | 130 +++++++++- src/core/thread/mle.hpp | 24 +- src/core/thread/mle_router.cpp | 17 ++ tests/scripts/thread-cert/Makefile.am | 2 + .../thread-cert/test_mle_msg_key_seq_jump.py | 240 ++++++++++++++++++ 6 files changed, 411 insertions(+), 18 deletions(-) create mode 100755 tests/scripts/thread-cert/test_mle_msg_key_seq_jump.py 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()