From b339a8946bfa8aa6fbf5ed967471f18a85fbad87 Mon Sep 17 00:00:00 2001 From: Rongli Sun Date: Wed, 18 Dec 2019 16:45:55 +0800 Subject: [PATCH] [travis] test parent selection rules (#4423) This commit has five scenarios to test the four Mesh Impacting Criteria and one Child Impacting Criterion (version). --- .travis/script.sh | 8 +- script/test | 53 ++- tests/scripts/thread-cert/common.py | 8 + tests/scripts/thread-cert/mle.py | 14 +- tests/scripts/thread-cert/node.py | 60 +++- tests/scripts/thread-cert/test_mle.py | 24 +- tests/scripts/thread-cert/thread_cert.py | 20 +- .../thread-cert/v1_2_test_parent_selection.py | 307 ++++++++++++++++++ 8 files changed, 448 insertions(+), 46 deletions(-) create mode 100755 tests/scripts/thread-cert/v1_2_test_parent_selection.py diff --git a/.travis/script.sh b/.travis/script.sh index 826522e74..f4b4f8d3a 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -583,8 +583,12 @@ build_samr21() { [ $BUILD_TARGET != v1.2 ] || { ./bootstrap || die - REFERENCE_DEVICE=1 make -f examples/Makefile-posix || die - ./script/test v1.2 || die + ./script/test build || die + + ls tests/scripts/thread-cert/v1_2_*.py | while read test_case; do + [[ ! -d tmp ]] || rm -rvf tmp + PYTHONUNBUFFERED=1 "${test_case}" + done } [ $BUILD_TARGET != posix-app-pty ] || { diff --git a/script/test b/script/test index ff3263404..1ef08e3d8 100755 --- a/script/test +++ b/script/test @@ -37,19 +37,35 @@ readonly SYSTEM_TRIPLET="$(third_party/nlbuild-autotools/repo/third_party/autoco export top_builddir="build/${SYSTEM_TRIPLET}" do_build() { - if [[ -n "${RADIO_DEVICE}" && "${VIRTUAL_TIME}" = 1 ]]; then - VIRTUAL_TIME_UART=1 make -f examples/Makefile-posix - else - make -f examples/Makefile-posix + ./bootstrap + if [ "${VIRTUAL_TIME}" = 1 ]; then + OPTIONS="VIRTUAL_TIME_UART=1" fi + make -f examples/Makefile-posix ${OPTIONS} + if [[ -n "${RADIO_DEVICE}" ]]; then make -f src/posix/Makefile-posix fi + + # Extra 1.1 build for 1.2 env + if [ "${THREAD_VERSION}" = "1.2" ]; then + ./bootstrap + OPTIONS="TargetTuple=${SYSTEM_TRIPLET}-1.1 THREAD_VERSION=1.1 ${OPTIONS}" + make -f examples/Makefile-posix ${OPTIONS} + + if [[ -n "${RADIO_DEVICE}" ]]; then + make -f src/posix/Makefile-posix + fi + fi } do_clean() { rm -rfv "build/${SYSTEM_TRIPLET}" "build/posix/${SYSTEM_TRIPLET}" + # Extra 1.1 clean for 1.2 env + if [ "${THREAD_VERSION}" = "1.2" ]; then + rm -rfv "build/${SYSTEM_TRIPLET}-1.1" "build/posix/${SYSTEM_TRIPLET}-1.1" + fi } do_cert() { @@ -57,13 +73,6 @@ 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 @@ -71,6 +80,7 @@ ENVIRONMENTS: NODE_TYPE 'sim' for CLI, 'ncp-sim' for NCP. The default is 'sim'. NODE_MODE 'transceiver' for transceiver mode, otherwise for standalone mode. The default is standalone mode. VIRTUAL_TIME 1 for virtual time, otherwise real time. The default is 1 + THREAD_VERSION 1.1 for Thread 1.1 stack, 1.2 for Thread 1.2 stack. The default is 1.1. COMMANDS: clean Clean built files to prepare for new build. @@ -94,7 +104,11 @@ EXAMPLES: # Test CLI with real time VIRTUAL_TIME=0 $0 clean build cert tests/scripts/thread-cert/Cert_5_1_01_RouterAttach.py VIRTUAL_TIME=0 $0 cert tests/scripts/thread-cert/Cert_5_1_02_ChildAddressTimeout.py -" + + # Test Thread 1.2 with real time + THREAD_VERSION=1.2 VIRTUAL_TIME=0 $0 clean build cert tests/scripts/thread-cert/v1_2_router_5_1_1.py + THREAD_VERSION=1.2 VIRTUAL_TIME=0 $0 clean build cert tests/scripts/thread-cert/v1_2_test_parent_selection.py + " exit $1 } @@ -104,11 +118,21 @@ main() export NODE_TYPE="${NODE_TYPE:-sim}" export VERBOSE="${VERBOSE:-1}" export VIRTUAL_TIME="${VIRTUAL_TIME:-1}" + export THREAD_VERSION="${THREAD_VERSION:-1.1}" + if [ "${THREAD_VERSION}" = "1.2" ]; then + export top_builddir_1_1="build/${SYSTEM_TRIPLET}-1.1" + fi if [[ "${NODE_MODE}" = 'transceiver' ]] ; then export RADIO_DEVICE="build/${SYSTEM_TRIPLET}/examples/apps/ncp/ot-rcp" export OT_CLI_PATH="build/posix/${SYSTEM_TRIPLET}/src/posix/ot-cli" export OT_NCP_PATH="build/posix/${SYSTEM_TRIPLET}/src/posix/ot-ncp" + + if [ "${THREAD_VERSION}" = "1.2" ]; then + export RADIO_DEVICE_1_1="build/${SYSTEM_TRIPLET}-1.1/examples/apps/ncp/ot-rcp" + export OT_CLI_PATH_1_1="build/posix/${SYSTEM_TRIPLET}-1.1/src/posix/ot-cli" + export OT_NCP_PATH_1_1="build/posix/${SYSTEM_TRIPLET}-1.1/src/posix/ot-ncp" + fi fi if [[ -z "$1" ]]; then @@ -118,6 +142,7 @@ main() [[ -n "${RADIO_DEVICE}" ]] && echo "Using transceiver mode" || echo "Using standalone mode" [[ "${NODE_TYPE}" = "ncp-sim" ]] && echo "Using NCP node" || echo "Using CLI node" [[ "${VIRTUAL_TIME}" = 1 ]] && echo "Using virtual time" || echo "Using real time" + [[ "${THREAD_VERSION}" = "1.2" ]] && echo "Using Thread 1.2 stack" || echo "Using Thread 1.1 stack" while [[ -n "$1" ]]; do case "$1" in @@ -131,10 +156,6 @@ main() shift do_cert "$1" ;; - v1.2) - do_v1_2 - break - ;; help) print_usage ;; diff --git a/tests/scripts/thread-cert/common.py b/tests/scripts/thread-cert/common.py index fe68ad36f..c69a9a46d 100644 --- a/tests/scripts/thread-cert/common.py +++ b/tests/scripts/thread-cert/common.py @@ -34,6 +34,14 @@ from enum import IntEnum import ipaddress +# Map of 2 bits of parent priority. +pp_map = {1: 1, 0: 0, 3: -1, 2: -2} + + +# Get the signed parent priority from the byte that parent priority is in. +def map_pp(self, pp_byte): + return pp_map[((pp_byte & 0xC0) >> 6)] + def expect_the_same_class(self, other): if not isinstance(other, self.__class__): diff --git a/tests/scripts/thread-cert/mle.py b/tests/scripts/thread-cert/mle.py index 8a91dc91c..5e9784103 100644 --- a/tests/scripts/thread-cert/mle.py +++ b/tests/scripts/thread-cert/mle.py @@ -573,7 +573,7 @@ class Connectivity(object): def __init__( self, - pp, + pp_byte, link_quality_3, link_quality_2, link_quality_1, @@ -583,7 +583,7 @@ class Connectivity(object): sed_buffer_size=None, sed_datagram_count=None, ): - self._pp = pp + self._pp_byte = pp_byte self._link_quality_3 = link_quality_3 self._link_quality_2 = link_quality_2 self._link_quality_1 = link_quality_1 @@ -593,9 +593,13 @@ class Connectivity(object): self._sed_buffer_size = sed_buffer_size self._sed_datagram_count = sed_datagram_count + @property + def pp_byte(self): + return self._pp_byte + @property def pp(self): - return self._pp + return common.map_pp(self, self._pp_byte) @property def link_quality_3(self): @@ -667,7 +671,7 @@ class Connectivity(object): class ConnectivityFactory: def parse(self, data, message_info): - pp = ord(data.read(1)) & 0x03 + pp_byte = ord(data.read(1)) link_quality_3 = ord(data.read(1)) link_quality_2 = ord(data.read(1)) link_quality_1 = ord(data.read(1)) @@ -685,7 +689,7 @@ class ConnectivityFactory: sed_datagram_count = None return Connectivity( - pp, + pp_byte, link_quality_3, link_quality_2, link_quality_1, diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 01158192f..0d2cce5d9 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -42,10 +42,17 @@ import unittest class Node: - def __init__(self, nodeid, is_mtd=False, simulator=None): + def __init__(self, nodeid, is_mtd=False, simulator=None, version=None): self.nodeid = nodeid self.verbose = int(float(os.getenv('VERBOSE', 0))) self.node_type = os.getenv('NODE_TYPE', 'sim') + self.env_version = os.getenv('THREAD_VERSION', '1.1') + + if version is not None: + self.version = version + else: + self.version = self.env_version + self.simulator = simulator if self.simulator: self.simulator.add_node(self) @@ -69,11 +76,18 @@ class Node: """ Initialize a simulation node. """ if 'OT_CLI_PATH' in os.environ: cmd = os.environ['OT_CLI_PATH'] - elif 'top_builddir' in os.environ: + elif (self.version == '1.1' and self.version != self.env_version): + # Posix app + if 'OT_CLI_PATH_1_1' in os.environ: + cmd = os.environ['OT_CLI_PATH_1_1'] + elif ('top_builddir_1_1') in os.environ: + srcdir = os.environ['top_builddir_1_1'] + cmd = '%s/examples/apps/cli/ot-cli-%s' % (srcdir, mode) + elif ('top_builddir') in os.environ: srcdir = os.environ['top_builddir'] cmd = '%s/examples/apps/cli/ot-cli-%s' % (srcdir, mode) else: - cmd = './ot-cli-%s' % mode + cmd = '%s/ot-cli-%s' % (self.version, mode) if 'RADIO_DEVICE' in os.environ: cmd += ' -v %s' % os.environ['RADIO_DEVICE'] @@ -107,15 +121,33 @@ class Node: os.environ['OT_NCP_PATH'], args, ) - elif "top_builddir" in os.environ: - builddir = os.environ['top_builddir'] - cmd = 'spinel-cli.py -p "%s/examples/apps/ncp/ot-ncp-%s%s" -n' % ( - builddir, - mode, + elif (self.version == '1.1' and self.version != self.env_version): + if 'OT_NCP_PATH_1_1' in os.environ: + cmd = 'spinel-cli.py -p "%s%s" -n' % ( + os.environ['OT_NCP_PATH_1_1'], + args, + ) + elif ('top_builddir_1_1') in os.environ: + srcdir = os.environ['top_builddir_1_1'] + cmd = '%s/examples/apps/ncp/ot-ncp-%s' % (srcdir, mode) + cmd = 'spinel-cli.py -p "%s%s" -n' % ( + cmd, + args, + ) + elif ('top_builddir') in os.environ: + srcdir = os.environ['top_builddir'] + cmd = '%s/examples/apps/ncp/ot-ncp-%s' % (srcdir, mode) + cmd = 'spinel-cli.py -p "%s%s" -n' % ( + cmd, args, ) else: - cmd = 'spinel-cli.py -p "./ot-ncp-%s%s" -n' % (mode, args) + cmd = 'spinel-cli.py -p "%s/ot-ncp-%s%s" -n' % ( + self.version, + mode, + args, + ) + cmd += ' %d' % nodeid print("%s" % cmd) @@ -312,6 +344,11 @@ class Node: self.send_command(cmd) self._expect('Done') + def set_link_quality(self, addr, lqi): + cmd = 'macfilter rss add-lqi %s %s' % (addr, lqi) + self.send_command(cmd) + self._expect('Done') + def remove_whitelist(self, addr): cmd = 'macfilter addr remove %s' % addr self.send_command(cmd) @@ -436,6 +473,11 @@ class Node: self.send_command(cmd) self._expect('Done') + def set_parent_priority(self, priority): + cmd = 'parentpriority %d' % priority + self.send_command(cmd) + self._expect('Done') + def get_partition_id(self): self.send_command('leaderpartitionid') i = self._expect(r'(\d+)\r?\n') diff --git a/tests/scripts/thread-cert/test_mle.py b/tests/scripts/thread-cert/test_mle.py index e00595d1a..d577fad74 100755 --- a/tests/scripts/thread-cert/test_mle.py +++ b/tests/scripts/thread-cert/test_mle.py @@ -294,7 +294,7 @@ def any_u(): def any_pp(): - return random.getrandbits(2) + return (random.getrandbits(2) << 6) def any_link_quality_3(): @@ -1115,9 +1115,9 @@ class TestConnectivity(unittest.TestCase): def test_should_return_pp_value_when_pp_property_is_called(self): # GIVEN - pp = any_pp() + pp_byte = any_pp() - connectivity_obj = mle.Connectivity(pp, any_link_quality_3(), + connectivity_obj = mle.Connectivity(pp_byte, any_link_quality_3(), any_link_quality_2(), any_link_quality_1(), any_leader_cost(), @@ -1130,7 +1130,7 @@ class TestConnectivity(unittest.TestCase): actual_pp = connectivity_obj.pp # THEN - self.assertEqual(pp, actual_pp) + self.assertEqual(common.map_pp(self, pp_byte), actual_pp) def test_should_return_link_quality_3_value_when_link_quality_3_property_is_called( self): @@ -1294,7 +1294,7 @@ class TestConnectivityFactory(unittest.TestCase): def test_should_create_Connectivity_from_bytearray_when_parse_method_is_called( self): # GIVEN - pp = any_pp() + pp_byte = any_pp() link_quality_3 = any_link_quality_3() link_quality_2 = any_link_quality_2() link_quality_1 = any_link_quality_1() @@ -1307,8 +1307,8 @@ class TestConnectivityFactory(unittest.TestCase): factory = mle.ConnectivityFactory() data = bytearray([ - pp, link_quality_3, link_quality_2, link_quality_1, leader_cost, - id_sequence, active_routers + pp_byte, link_quality_3, link_quality_2, link_quality_1, + leader_cost, id_sequence, active_routers ]) + struct.pack(">H", sed_buffer_size) + bytearray( [sed_datagram_count]) @@ -1317,7 +1317,7 @@ class TestConnectivityFactory(unittest.TestCase): # THEN self.assertTrue(isinstance(actual_connectivity, mle.Connectivity)) - self.assertEqual(pp, actual_connectivity.pp) + self.assertEqual(common.map_pp(self, pp_byte), actual_connectivity.pp) self.assertEqual(link_quality_3, actual_connectivity.link_quality_3) self.assertEqual(link_quality_2, actual_connectivity.link_quality_2) self.assertEqual(link_quality_1, actual_connectivity.link_quality_1) @@ -1331,7 +1331,7 @@ class TestConnectivityFactory(unittest.TestCase): def test_should_create_Connectivity_without_sed_data_when_parse_method_is_called( self): # GIVEN - pp = any_pp() + pp_byte = any_pp() link_quality_3 = any_link_quality_3() link_quality_2 = any_link_quality_2() link_quality_1 = any_link_quality_1() @@ -1344,8 +1344,8 @@ class TestConnectivityFactory(unittest.TestCase): factory = mle.ConnectivityFactory() data = bytearray([ - pp, link_quality_3, link_quality_2, link_quality_1, leader_cost, - id_sequence, active_routers + pp_byte, link_quality_3, link_quality_2, link_quality_1, + leader_cost, id_sequence, active_routers ]) # WHEN @@ -1353,7 +1353,7 @@ class TestConnectivityFactory(unittest.TestCase): # THEN self.assertTrue(isinstance(actual_connectivity, mle.Connectivity)) - self.assertEqual(pp, actual_connectivity.pp) + self.assertEqual(common.map_pp(self, pp_byte), actual_connectivity.pp) self.assertEqual(link_quality_3, actual_connectivity.link_quality_3) self.assertEqual(link_quality_2, actual_connectivity.link_quality_2) self.assertEqual(link_quality_1, actual_connectivity.link_quality_1) diff --git a/tests/scripts/thread-cert/thread_cert.py b/tests/scripts/thread-cert/thread_cert.py index f39df66ef..674459456 100644 --- a/tests/scripts/thread-cert/thread_cert.py +++ b/tests/scripts/thread-cert/thread_cert.py @@ -38,7 +38,7 @@ DEFAULT_PARAMS = { 'mode': 'rsdn', 'panid': 0xface, 'whitelist': None, - 'version': '1.2' + 'version': '1.2', } """Default configurations when creating nodes.""" @@ -68,7 +68,12 @@ class TestCase(unittest.TestCase): params = DEFAULT_PARAMS.copy() initial_topology[i] = params - self.nodes[i] = Node(i, params['is_mtd'], simulator=self.simulator) + self.nodes[i] = Node( + i, + params['is_mtd'], + simulator=self.simulator, + version=params['version'], + ) 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')) @@ -101,3 +106,14 @@ class TestCase(unittest.TestCase): """ for i in list(self.nodes.keys()): self.simulator.get_messages_sent_by(i) + + def flush_nodes(self, nodes): + """Flush away all captured messages of specified nodes. + + Args: + nodes (list): nodes whose messages to flush. + + """ + for i in nodes: + if i in list(self.nodes.keys()): + self.simulator.get_messages_sent_by(i) diff --git a/tests/scripts/thread-cert/v1_2_test_parent_selection.py b/tests/scripts/thread-cert/v1_2_test_parent_selection.py new file mode 100755 index 000000000..3d7aae433 --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_test_parent_selection.py @@ -0,0 +1,307 @@ +#!/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_2 = 1 +ROUTER_1_1 = 2 +REED_1_2 = 3 +ROUTER_1_2 = 4 +REED_1_1 = 5 +MED_1_1 = 6 +MED_1_2 = 7 + +# Topology +# (lq:2) (pp:1) +# REED_1_2 ----- ROUTER_1_2 +# | \ / | \ +# | \/ REED_1_1 \ +# (lq:2) | / \ / `router` \ +# | (lq:2) \ \ +# | / / \ \ +# LEADER_1_2 --- ROUTER_1_1 -- MED_1_2 +# \ | +# \ | +# \ | +# MED_1_1 +# +# 1) Bring up LEADER_1_2 and ROUTER_1_1, +# 2) Config link quality (LEADER_1_2->REED_1_2) as 2, bring up REED_1_2 which would attach to ROUTER_1_1 +# due to higher two-way link quality, +# 3) Config link quality(LEADER_1_2->ROUTER_1_2) and link quality(REED_1_2->ROUTER_1_2) as 2, bring up +# ROUTER_1_2 which would attach to LEADER_1_2 due to active router is preferred, +# 4) Config parent priority as 1 on ROUTER_1_2, bring up REED_1_1 which would attach to ROUTER_1_2 due to +# higher parent priority, +# 5) Upgrade REED_1_1 to `router` role, bring up MED_1_1 which would attach to LEADER_1_2 which has higher +# link quality of 3, +# 6) Config parent priority as 1 on ROUTER_1_1, bring up MED_1_2 which would attach to ROUTER_1_2 due to +# higher version +# + + +class TestParentSelection(thread_cert.TestCase): + topology = { + LEADER_1_2: { + 'version': '1.2', + 'whitelist': [REED_1_2, ROUTER_1_2, REED_1_1, ROUTER_1_1, MED_1_1], + }, + ROUTER_1_1: { + 'version': '1.1', + 'whitelist': [LEADER_1_2, REED_1_2, MED_1_2, MED_1_1], + }, + REED_1_2: { + 'version': '1.2', + 'whitelist': [ROUTER_1_2, ROUTER_1_1, LEADER_1_2], + }, + ROUTER_1_2: { + 'version': '1.2', + 'whitelist': [REED_1_2, MED_1_2, REED_1_1, LEADER_1_2], + }, + REED_1_1: { + 'version': '1.1', + 'whitelist': [ROUTER_1_2, LEADER_1_2] + }, + MED_1_1: { + 'mode': 'rs', + 'version': '1.1', + 'whitelist': [LEADER_1_2, ROUTER_1_1], + }, + MED_1_2: { + 'mode': 'rs', + 'version': '1.2', + 'whitelist': [ROUTER_1_1, ROUTER_1_2], + }, + } + """All nodes are created with default configurations""" + + def test(self): + + self.nodes[LEADER_1_2].start() + self.simulator.go(5) + self.assertEqual(self.nodes[LEADER_1_2].get_state(), 'leader') + + self.nodes[ROUTER_1_1].set_router_selection_jitter(1) + self.nodes[ROUTER_1_1].start() + self.simulator.go(5) + self.assertEqual(self.nodes[ROUTER_1_1].get_state(), 'router') + + # Mesh Impacting Criteria - Highest Two-way link quality + # REED_1_2 would attach to ROUTER_1_1 + # Attach to ROUTER_1_1 which has highest two-way link quality + + # Flush relative message queues + self.flush_nodes([LEADER_1_2, ROUTER_1_1]) + + self.nodes[LEADER_1_2].set_link_quality( + self.nodes[REED_1_2].get_addr64(), 2) + self.nodes[REED_1_2].set_router_selection_jitter(1) + self.nodes[REED_1_2].set_router_upgrade_threshold(1) + self.nodes[REED_1_2].start() + self.simulator.go(5) + self.assertEqual(self.nodes[REED_1_2].get_state(), 'child') + + # Check Parent Response + messages = self.simulator.get_messages_sent_by(ROUTER_1_1) + parent_prefer = messages.next_mle_message( + mle.CommandType.PARENT_RESPONSE) + assert (parent_prefer), "Error: Expected parent response not found" + + messages = self.simulator.get_messages_sent_by(LEADER_1_2) + parent_cmp = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + assert (parent_cmp), "Error: Expected parent response not found" + + # Known that link margin for link quality 3 is 80 and link quality 2 is 15 + assert ((parent_prefer.get_mle_message_tlv(mle.LinkMargin).link_margin - + parent_cmp.get_mle_message_tlv(mle.LinkMargin).link_margin) > + 20) + + # Check Child Id Request + messages = self.simulator.get_messages_sent_by(REED_1_2) + msg = messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[ROUTER_1_1]) + + # Mesh Impacting Criteria - Active Routers over REEDs + # ROUTER_1_2 would attach to LEADER_1_2 + # Link quality configuration, so that REED_1_2 has the chance to respond + + # Flush relative message queues + self.flush_nodes([LEADER_1_2, REED_1_2]) + + self.nodes[LEADER_1_2].set_link_quality( + self.nodes[ROUTER_1_2].get_addr64(), 2) + self.nodes[REED_1_2].set_link_quality( + self.nodes[ROUTER_1_2].get_addr64(), 2) + self.nodes[ROUTER_1_2].set_router_selection_jitter(1) + self.nodes[ROUTER_1_2].start() + self.simulator.go(5) + self.assertEqual(self.nodes[ROUTER_1_2].get_state(), 'router') + + # Check Parent Response + messages = self.simulator.get_messages_sent_by(LEADER_1_2) + + # Skip first response for first parent request + assert messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + + parent_prefer = messages.next_mle_message( + mle.CommandType.PARENT_RESPONSE) + assert (parent_prefer), "Error: Expected parent response not found" + + messages = self.simulator.get_messages_sent_by(REED_1_2) + parent_cmp = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + assert (parent_cmp), "Error: Expected parent response not found" + + assert (parent_prefer.get_mle_message_tlv( + mle.LinkMargin).link_margin == parent_cmp.get_mle_message_tlv( + mle.LinkMargin).link_margin) + + # Check Child Id Request + messages = self.simulator.get_messages_sent_by(ROUTER_1_2) + msg = messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[LEADER_1_2]) + + # Mesh Impacting Criteria - Highest Parent Priority value in the Connectivity TLV + # REED_1_1 would attach to ROUTER_1_2 + + # Flush relative message queues + self.flush_nodes([LEADER_1_2, ROUTER_1_2]) + + self.nodes[ROUTER_1_2].set_parent_priority(1) + self.nodes[REED_1_1].set_router_selection_jitter(1) + self.nodes[REED_1_1].set_router_upgrade_threshold(1) + self.nodes[REED_1_1].start() + self.simulator.go(5) + self.assertEqual(self.nodes[REED_1_1].get_state(), 'child') + + # Check Parent Response + messages = self.simulator.get_messages_sent_by(ROUTER_1_2) + parent_prefer = messages.next_mle_message( + mle.CommandType.PARENT_RESPONSE) + assert (parent_prefer), "Error: Expected parent response not found" + + messages = self.simulator.get_messages_sent_by(LEADER_1_2) + parent_cmp = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + assert (parent_cmp), "Error: Expected parent response not found" + + assert (parent_prefer.get_mle_message_tlv( + mle.LinkMargin).link_margin == parent_cmp.get_mle_message_tlv( + mle.LinkMargin).link_margin) + + assert (parent_prefer.get_mle_message_tlv(mle.Connectivity).pp > + parent_cmp.get_mle_message_tlv(mle.Connectivity).pp) + + # Check Child Id Request + messages = self.simulator.get_messages_sent_by(REED_1_1) + msg = messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[ROUTER_1_2]) + + # Mesh Impacting Criteria - Router with the most high-quality neighbors + # (Link Quality 3 field in the Connectivity TLV) + # MED_1_1 would attach to LEADER_1_2 + + self.nodes[REED_1_1].set_state('router') + self.simulator.go(5) + self.assertEqual(self.nodes[REED_1_1].get_state(), 'router') + + # Flush relative message queues + self.flush_nodes([LEADER_1_2, ROUTER_1_1]) + + self.nodes[MED_1_1].start() + self.simulator.go(5) + self.assertEqual(self.nodes[MED_1_1].get_state(), 'child') + + # Check Parent Response + messages = self.simulator.get_messages_sent_by(LEADER_1_2) + parent_prefer = messages.next_mle_message( + mle.CommandType.PARENT_RESPONSE) + assert (parent_prefer), "Error: Expected parent response not found" + + messages = self.simulator.get_messages_sent_by(ROUTER_1_1) + parent_cmp = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + assert (parent_cmp), "Error: Expected parent response not found" + + assert (parent_prefer.get_mle_message_tlv( + mle.LinkMargin).link_margin == parent_cmp.get_mle_message_tlv( + mle.LinkMargin).link_margin) + assert (parent_prefer.get_mle_message_tlv( + mle.Connectivity).pp == parent_cmp.get_mle_message_tlv( + mle.Connectivity).pp) + assert (parent_prefer.get_mle_message_tlv( + mle.Connectivity).link_quality_3 > parent_cmp.get_mle_message_tlv( + mle.Connectivity).link_quality_3) + + # Check Child Id Request + messages = self.simulator.get_messages_sent_by(MED_1_1) + msg = messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[LEADER_1_2]) + + # Child Impacting Criteria - A Version number in the Version TLV + # equal to or higher than the version that implements features + # desirable to the Child MED_1_2 would attach to ROUTER_1_2 + + # Flush relative message queues + self.flush_nodes([ROUTER_1_2, ROUTER_1_1]) + + self.nodes[ROUTER_1_1].set_parent_priority(1) + self.nodes[MED_1_2].start() + self.simulator.go(5) + self.assertEqual(self.nodes[MED_1_2].get_state(), 'child') + + # Check Parent Response + messages = self.simulator.get_messages_sent_by(ROUTER_1_2) + parent_prefer = messages.next_mle_message( + mle.CommandType.PARENT_RESPONSE) + assert (parent_prefer), "Error: Expected parent response not found" + + messages = self.simulator.get_messages_sent_by(ROUTER_1_1) + parent_cmp = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) + assert (parent_cmp), "Error: Expected parent response not found" + + assert (parent_prefer.get_mle_message_tlv( + mle.LinkMargin).link_margin == parent_cmp.get_mle_message_tlv( + mle.LinkMargin).link_margin) + assert (parent_prefer.get_mle_message_tlv( + mle.Connectivity).pp == parent_cmp.get_mle_message_tlv( + mle.Connectivity).pp) + assert (parent_prefer.get_mle_message_tlv( + mle.Connectivity).link_quality_3 == parent_cmp.get_mle_message_tlv( + mle.Connectivity).link_quality_3) + assert (parent_prefer.get_mle_message_tlv(mle.Version).version > + parent_cmp.get_mle_message_tlv(mle.Version).version) + + # Check Child Id Request + messages = self.simulator.get_messages_sent_by(MED_1_2) + msg = messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) + msg.assertSentToNode(self.nodes[ROUTER_1_2]) + + +if __name__ == '__main__': + unittest.main()