diff --git a/src/core/thread/meshcop_tlvs.hpp b/src/core/thread/meshcop_tlvs.hpp index 4a2955742..05d83591e 100644 --- a/src/core/thread/meshcop_tlvs.hpp +++ b/src/core/thread/meshcop_tlvs.hpp @@ -1349,6 +1349,26 @@ public: bool IsValid(void) const { return true; } } OT_TOOL_PACKED_END; +/** + * This class implements Channel Mask TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class ChannelMask0Tlv: public ChannelMaskTlv, public ChannelMaskEntry +{ +public: + /** + * This method returns the Channel Mask value. + * + * @returns The Channel Mask value. + * + */ + uint32_t GetMask(void) { return HostSwap32(mMask); } + +private: + uint32_t mMask; +} OT_TOOL_PACKED_END; + /** * This class implements Count TLV generation and parsing. * diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index b5c00d686..c9acc6e5b 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -162,6 +162,7 @@ Mle::Mle(ThreadNetif &aThreadNetif) : mRouterSelectionJitterTimeout = 0; mRouterSelectionJitter = kRouterSelectionJitter; + mAnnounceChannel = kPhyMinChannel; mPreviousPanId = Mac::kPanIdBroadcast; } @@ -1093,6 +1094,11 @@ void Mle::HandleParentRequestTimer(void) mPreviousPanId = Mac::kPanIdBroadcast; BecomeDetached(); } + else if ((mDeviceMode & ModeTlv::kModeFFD) == 0) + { + SendOrphanAnnounce(); + BecomeDetached(); + } else if (mMleRouter.BecomeLeader() != kThreadError_None) { mParentRequestState = kParentIdle; @@ -1397,6 +1403,48 @@ exit: return error; } +void Mle::SendOrphanAnnounce(void) +{ + MeshCoP::ChannelMask0Tlv *channelMask; + uint8_t channel; + + channelMask = static_cast(mNetif.GetActiveDataset().GetNetwork().Get( + MeshCoP::Tlv::kChannelMask)); + + VerifyOrExit(channelMask != NULL,); + + // find next channel in the Active Operational Dataset Channel Mask + channel = mAnnounceChannel; + + while (!channelMask->IsChannelSet(channel)) + { + channel++; + + if (channel > kPhyMaxChannel) + { + channel = kPhyMinChannel; + } + + VerifyOrExit(channel != mAnnounceChannel,); + } + + // Send Annuonce message + SendAnnounce(channel); + + // Move to next channel + mAnnounceChannel++; + + if (mAnnounceChannel > kPhyMaxChannel) + { + mAnnounceChannel = kPhyMinChannel; + } + +exit: + return; +} + + + ThreadError Mle::SendMessage(Message &aMessage, const Ip6::Address &aDestination) { ThreadError error = kThreadError_None; @@ -2318,14 +2366,19 @@ ThreadError Mle::HandleAnnounce(const Message &aMessage, const Ip6::MessageInfo localTimestamp = mNetif.GetActiveDataset().GetNetwork().GetTimestamp(); - VerifyOrExit(localTimestamp == NULL || localTimestamp->Compare(timestamp) > 0,); - - Stop(); - mPreviousChannel = mMac.GetChannel(); - mPreviousPanId = mMac.GetPanId(); - mMac.SetChannel(static_cast(channel.GetChannel())); - mMac.SetPanId(panid.GetPanId()); - Start(); + if (localTimestamp == NULL || localTimestamp->Compare(timestamp) > 0) + { + Stop(); + mPreviousChannel = mMac.GetChannel(); + mPreviousPanId = mMac.GetPanId(); + mMac.SetChannel(static_cast(channel.GetChannel())); + mMac.SetPanId(panid.GetPanId()); + Start(); + } + else + { + SendAnnounce(static_cast(channel.GetChannel())); + } exit: (void)aMessageInfo; diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index f6f144718..ca8e6e700 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -1139,6 +1139,7 @@ private: ThreadError SendParentRequest(void); ThreadError SendChildIdRequest(void); ThreadError SendDiscoveryResponse(const Ip6::Address &aDestination, uint16_t aPanId); + void SendOrphanAnnounce(void); bool IsBetterParent(uint16_t aRloc16, uint8_t aLinkQuality, ConnectivityTlv &aConnectivityTlv) const; @@ -1171,6 +1172,7 @@ private: void *mDiscoverContext; bool mIsDiscoverInProgress; + uint8_t mAnnounceChannel; uint8_t mPreviousChannel; uint16_t mPreviousPanId; diff --git a/tests/scripts/Makefile.am b/tests/scripts/Makefile.am index faf046f62..20d3e2f00 100644 --- a/tests/scripts/Makefile.am +++ b/tests/scripts/Makefile.am @@ -114,6 +114,7 @@ EXTRA_DIST = \ thread-cert/Cert_9_2_14_PanIdQuery.py \ thread-cert/Cert_9_2_15_PendingPartition.py \ thread-cert/Cert_9_2_16_ActivePendingPartition.py \ + thread-cert/Cert_9_2_17_Orphan.py \ thread-cert/Cert_9_2_18_RollBackActiveTimestamp.py \ thread-cert/node.py \ $(NULL) @@ -241,6 +242,7 @@ check_SCRIPTS += \ thread-cert/Cert_9_2_14_PanIdQuery.py \ thread-cert/Cert_9_2_15_PendingPartition.py \ thread-cert/Cert_9_2_16_ActivePendingPartition.py \ + thread-cert/Cert_9_2_17_Orphan.py \ thread-cert/Cert_9_2_18_RollBackActiveTimestamp.py \ $(NULL) @@ -269,6 +271,7 @@ XFAIL_NCP_TESTS = \ thread-cert/Cert_9_2_14_PanIdQuery.py \ thread-cert/Cert_9_2_15_PendingPartition.py \ thread-cert/Cert_9_2_16_ActivePendingPartition.py \ + thread-cert/Cert_9_2_17_Orphan.py \ thread-cert/Cert_9_2_18_RollBackActiveTimestamp.py \ $(NULL) diff --git a/tests/scripts/thread-cert/Cert_9_2_17_Orphan.py b/tests/scripts/thread-cert/Cert_9_2_17_Orphan.py new file mode 100755 index 000000000..eef8b01c7 --- /dev/null +++ b/tests/scripts/thread-cert/Cert_9_2_17_Orphan.py @@ -0,0 +1,94 @@ +#!/usr/bin/python +# +# Copyright (c) 2016, 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 time +import unittest + +import node + +CHANNEL1 = 11 +CHANNEL2 = 18 +CHANNEL_MASK = 1 << 18 +PANID_INIT = 0xface + +LEADER1 = 1 +LEADER2 = 2 +ED1 = 3 + +class Cert_9_2_17_Orphan(unittest.TestCase): + def setUp(self): + self.nodes = {} + for i in range(1,4): + self.nodes[i] = node.Node(i) + + self.nodes[LEADER1].set_active_dataset(10, channel=CHANNEL1, panid=PANID_INIT, channel_mask=CHANNEL_MASK) + self.nodes[LEADER1].set_mode('rsdn') + self.nodes[LEADER1].add_whitelist(self.nodes[ED1].get_addr64()) + self.nodes[LEADER1].enable_whitelist() + self.nodes[LEADER1].set_router_selection_jitter(1) + + self.nodes[LEADER2].set_active_dataset(20, channel=CHANNEL2, panid=PANID_INIT, channel_mask=CHANNEL_MASK) + self.nodes[LEADER2].set_mode('rsdn') + self.nodes[LEADER2].enable_whitelist() + self.nodes[LEADER2].set_router_selection_jitter(1) + + self.nodes[ED1].set_active_dataset(10, channel=CHANNEL1, panid=PANID_INIT, channel_mask=CHANNEL_MASK) + self.nodes[ED1].set_mode('rsn') + self.nodes[ED1].add_whitelist(self.nodes[LEADER1].get_addr64()) + self.nodes[ED1].enable_whitelist() + self.nodes[ED1].set_timeout(3) + + def tearDown(self): + for node in list(self.nodes.values()): + node.stop() + del self.nodes + + def test(self): + self.nodes[LEADER1].start() + self.nodes[LEADER1].set_state('leader') + self.assertEqual(self.nodes[LEADER1].get_state(), 'leader') + + self.nodes[LEADER2].start() + self.nodes[LEADER2].set_state('leader') + self.assertEqual(self.nodes[LEADER2].get_state(), 'leader') + + self.nodes[ED1].start() + time.sleep(5) + self.assertEqual(self.nodes[ED1].get_state(), 'child') + + self.nodes[LEADER1].stop() + self.nodes[LEADER2].add_whitelist(self.nodes[ED1].get_addr64()) + self.nodes[ED1].add_whitelist(self.nodes[LEADER2].get_addr64()) + time.sleep(20) + + self.assertEqual(self.nodes[ED1].get_state(), 'child') + self.assertEqual(self.nodes[ED1].get_channel(), CHANNEL2) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index c90fafdfa..0b1c8c635 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -457,7 +457,7 @@ class Node: self.send_command(cmd) self.pexpect.expect('Done') - def set_active_dataset(self, timestamp, panid=None, channel=None, master_key=None): + def set_active_dataset(self, timestamp, panid=None, channel=None, channel_mask=None, master_key=None): self.send_command('dataset clear') self.pexpect.expect('Done') @@ -475,6 +475,11 @@ class Node: self.send_command(cmd) self.pexpect.expect('Done') + if channel_mask != None: + cmd = 'dataset channelmask %d' % channel_mask + self.send_command(cmd) + self.pexpect.expect('Done') + if master_key != None: cmd = 'dataset masterkey ' + master_key self.send_command(cmd) @@ -514,7 +519,7 @@ class Node: self.pexpect.expect('Done') def send_mgmt_active_set(self, active_timestamp=None, channel=None, panid=None, mesh_local=None, - network_name=None): + network_name=None): cmd = 'dataset mgmtsetcommand active ' if active_timestamp != None: