diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 3318bfb8c..aa1558743 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -191,6 +191,7 @@ run test-023-multicast-traffic.py run test-024-partition-merge.py run test-025-network-data-timeout.py run test-026-slaac-address.py +run test-027-child-mode-change.py run test-100-mcu-power-state.py run test-600-channel-manager-properties.py run test-601-channel-manager-channel-change.py diff --git a/tests/toranj/test-027-child-mode-change.py b/tests/toranj/test-027-child-mode-change.py new file mode 100644 index 000000000..a203f512f --- /dev/null +++ b/tests/toranj/test-027-child-mode-change.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# +# Copyright (c) 2018, 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 wpan +from wpan import verify + +#----------------------------------------------------------------------------------------------------------------------- +# Test description: Verify Thread mode change on children and recovery after parent reset +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print '-' * 120 +print 'Starting \'{}\''.format(test_name) + +#----------------------------------------------------------------------------------------------------------------------- +# Utility functions + +def verify_child_table(parent, children): + """ + This function verifies that child table on `parent` node contains all the entries in `children` list and the child + table entry's mode value matches the children Thread mode. + """ + child_table = wpan.parse_child_table_result(parent.get(wpan.WPAN_THREAD_CHILD_TABLE)) + verify(len(child_table) == len(children)) + for child in children: + ext_addr = child.get(wpan.WPAN_EXT_ADDRESS)[1:-1] + for entry in child_table: + if entry.ext_address == ext_addr: + break + else: + raise wpan.VerifyError('Failed to find a child entry for extended address {} in table'.format(ext_addr)) + exit(1) + + verify(int(entry.rloc16, 16) == int(child.get(wpan.WPAN_THREAD_RLOC16), 16)) + mode = int(child.get(wpan.WPAN_THREAD_DEVICE_MODE), 0) + verify(entry.is_rx_on_when_idle() == (mode & wpan.THREAD_MODE_FLAG_RX_ON_WHEN_IDLE != 0)) + verify(entry.is_ftd() == (mode & wpan.THREAD_MODE_FLAG_FULL_THREAD_DEV != 0)) + verify(entry.is_full_net_data() == (mode & wpan.THREAD_MODE_FLAG_FULL_NETWORK_DATA != 0)) + verify(entry.is_sec_data_req() == (mode & wpan.THREAD_MODE_FLAG_SECURE_DATA_REQUEST != 0)) + +#----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +speedup = 4 +wpan.Node.set_time_speedup_factor(speedup) + +parent = wpan.Node() +child1 = wpan.Node() +child2 = wpan.Node() + +#----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +#----------------------------------------------------------------------------------------------------------------------- +# Build network topology +# + +parent.form("ModeChangeReset") + +child1.join_node(parent, wpan.JOIN_TYPE_END_DEVICE) +child2.join_node(parent, wpan.JOIN_TYPE_SLEEPY_END_DEVICE) + +child1.set(wpan.WPAN_POLL_INTERVAL, '8000') +child2.set(wpan.WPAN_POLL_INTERVAL, '8000') + +children = [child1, child2] + +#----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +WAIT_INTERVAL = 6 + +# Thread Mode for end-device and sleepy end-device +DEVICE_MODE_SLEEPY_END_DEVICE = wpan.THREAD_MODE_FLAG_FULL_NETWORK_DATA | wpan.THREAD_MODE_FLAG_SECURE_DATA_REQUEST +DEVICE_MODE_END_DEVICE = wpan.THREAD_MODE_FLAG_FULL_NETWORK_DATA | wpan.THREAD_MODE_FLAG_FULL_THREAD_DEV \ + | wpan.THREAD_MODE_FLAG_SECURE_DATA_REQUEST | wpan.THREAD_MODE_FLAG_RX_ON_WHEN_IDLE + +# Disable child supervision on all devices +parent.set(wpan.WPAN_CHILD_SUPERVISION_INTERVAL, '0') +child1.set(wpan.WPAN_CHILD_SUPERVISION_CHECK_TIMEOUT, '0') +child2.set(wpan.WPAN_CHILD_SUPERVISION_CHECK_TIMEOUT, '0') + +# Verify Thread Device Mode on both children +verify(int(child1.get(wpan.WPAN_THREAD_DEVICE_MODE),0) == DEVICE_MODE_END_DEVICE) +verify(int(child2.get(wpan.WPAN_THREAD_DEVICE_MODE),0) == DEVICE_MODE_SLEEPY_END_DEVICE) + +def check_child_table(): + verify_child_table(parent, children) + +wpan.verify_within(check_child_table, WAIT_INTERVAL) + +# Reset parent and verify all children are recovered +parent.reset() +wpan.verify_within(check_child_table, WAIT_INTERVAL) + +# Change mode on both children (make child1 sleepy, and child2 non-sleepy) +child1.set(wpan.WPAN_THREAD_DEVICE_MODE, str(DEVICE_MODE_SLEEPY_END_DEVICE)) +verify(int(child1.get(wpan.WPAN_THREAD_DEVICE_MODE),0) == DEVICE_MODE_SLEEPY_END_DEVICE) + +child2.set(wpan.WPAN_THREAD_DEVICE_MODE, str(DEVICE_MODE_END_DEVICE)) +verify(int(child2.get(wpan.WPAN_THREAD_DEVICE_MODE),0) == DEVICE_MODE_END_DEVICE) + +# Verify that the child table on parent is also updated +wpan.verify_within(check_child_table, WAIT_INTERVAL) + +# Reset parent and verify all children are recovered +parent.reset() +wpan.verify_within(check_child_table, WAIT_INTERVAL) + +#----------------------------------------------------------------------------------------------------------------------- +# Test finished + +wpan.Node.finalize_all_nodes() + +print '\'{}\' passed.'.format(test_name) diff --git a/tests/toranj/wpan.py b/tests/toranj/wpan.py index 0c28e2710..b79128390 100644 --- a/tests/toranj/wpan.py +++ b/tests/toranj/wpan.py @@ -183,13 +183,20 @@ NODE_TYPE_COMMISSIONER = '"commissioner"' NODE_TYPE_NEST_LURKER = '"nl-lurker"' #----------------------------------------------------------------------------------------------------------------------- - # Node types used by `Node.join()` JOIN_TYPE_ROUTER = 'r' JOIN_TYPE_END_DEVICE = 'e' JOIN_TYPE_SLEEPY_END_DEVICE = 's' +#----------------------------------------------------------------------------------------------------------------------- +# Bit Flags for Thread Device Mode `WPAN_THREAD_DEVICE_MODE` + +THREAD_MODE_FLAG_FULL_NETWORK_DATA = (1 << 0) +THREAD_MODE_FLAG_FULL_THREAD_DEV = (1 << 1) +THREAD_MODE_FLAG_SECURE_DATA_REQUEST = (1 << 2) +THREAD_MODE_FLAG_RX_ON_WHEN_IDLE = (1 << 3) + #----------------------------------------------------------------------------------------------------------------------- def _log(text, new_line=True, flush=True): @@ -1126,10 +1133,12 @@ class ChildEntry(object): # Convert the rest into a dictionary by splitting using ':' as separator dict = {item.split(':')[0] : item.split(':')[1] for item in items[1:]} - self._rloc16 = dict['RLOC16'] - self._timeout = dict['Timeout'] - self._rx_on_idle = (dict['RxOnIdle'] == 'yes') - self._ftd = (dict['FTD'] == 'yes') + self._rloc16 = dict['RLOC16'] + self._timeout = dict['Timeout'] + self._rx_on_idle = (dict['RxOnIdle'] == 'yes') + self._ftd = (dict['FTD'] == 'yes') + self._sec_data_req = (dict['SecDataReq'] == 'yes') + self._full_net_data = (dict['FullNetData'] == 'yes') @property def ext_address(self): @@ -1149,6 +1158,12 @@ class ChildEntry(object): def is_ftd(self): return self._ftd + def is_sec_data_req(self): + return self._sec_data_req + + def is_full_net_data(self): + return self._full_net_data + def __repr__(self): return 'ChildEntry({})'.format(self.__dict__)