From 3d21a846d92d6df4ae3559a184462537fa1413bd Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Thu, 30 Apr 2020 15:54:26 -0700 Subject: [PATCH] [test] add multi-radio and TREL test cases (#4440) This commit adds new builds under "simulation-build-autotools" to cover multi-radio configurations: TREL only, 15.4+TREL, 15.4+TREL+ToBLE. It also adds new GitHub action tests covering all `toranj` test-cases with different configurations: 1) NCP model with TREL radio only, 2) POSIX App and RCP model with TREL radio only (emulated over a virtual Ethernet netif). --- .github/workflows/toranj.yml | 103 +++++++++ script/check-simulation-build-autotools | 56 +++++ tests/toranj/build.sh | 3 + tests/toranj/start.sh | 32 ++- tests/toranj/test-700-multi-radio-join.py | 131 +++++++++++ tests/toranj/test-701-multi-radio-probe.py | 205 +++++++++++++++++ .../test-702-multi-radio-discovery-by-rx.py | 206 ++++++++++++++++++ .../test-703-multi-radio-mesh-header-msg.py | 142 ++++++++++++ tests/toranj/test-704-multi-radio-scan.py | 115 ++++++++++ .../test-705-multi-radio-discover-scan.py | 115 ++++++++++ tests/toranj/wpan.py | 87 +++++++- 11 files changed, 1186 insertions(+), 9 deletions(-) create mode 100644 tests/toranj/test-700-multi-radio-join.py create mode 100644 tests/toranj/test-701-multi-radio-probe.py create mode 100644 tests/toranj/test-702-multi-radio-discovery-by-rx.py create mode 100644 tests/toranj/test-703-multi-radio-mesh-header-msg.py create mode 100644 tests/toranj/test-704-multi-radio-scan.py create mode 100644 tests/toranj/test-705-multi-radio-discover-scan.py diff --git a/.github/workflows/toranj.yml b/.github/workflows/toranj.yml index 0a95d0d4a..664631233 100644 --- a/.github/workflows/toranj.yml +++ b/.github/workflows/toranj.yml @@ -44,6 +44,7 @@ jobs: runs-on: ubuntu-18.04 env: COVERAGE: 1 + TORANJ_RADIO : 15.4 steps: - uses: actions/checkout@v2 - name: Bootstrap @@ -81,6 +82,7 @@ jobs: env: COVERAGE: 1 TORANJ_POSIX_RCP_MODEL: 1 + TORANJ_RADIO : 15.4 steps: - uses: actions/checkout@v2 - name: Bootstrap @@ -113,10 +115,111 @@ jobs: name: cov-toranj-rcp path: tmp/coverage.info + toranj-ncp-trel: + runs-on: ubuntu-18.04 + env: + COVERAGE: 1 + TORANJ_RADIO : trel + steps: + - uses: actions/checkout@v2 + - name: Bootstrap + run: | + sudo rm /etc/apt/sources.list.d/* && sudo apt-get update + sudo apt-get --no-install-recommends install -y dbus libdbus-1-dev + sudo apt-get --no-install-recommends install -y autoconf-archive + sudo apt-get --no-install-recommends install -y bsdtar + sudo apt-get --no-install-recommends install -y libtool + sudo apt-get --no-install-recommends install -y libglib2.0-dev + sudo apt-get --no-install-recommends install -y libboost-dev libboost-signals-dev + sudo apt-get --no-install-recommends install -y lcov + + git clone --depth=1 --branch=master https://github.com/openthread/wpantund.git + cd wpantund + ./bootstrap.sh + ./configure + sudo make -j2 + sudo make install + - name: Build & Run + run: | + top_builddir=$(pwd)/build/toranj ./tests/toranj/start.sh + - name: Generate Coverage + run: | + ./script/test generate_coverage gcc + - uses: actions/upload-artifact@v2 + with: + name: cov-toranj-ncp-trel + path: tmp/coverage.info + + toranj-rcp-trel: + runs-on: ubuntu-18.04 + env: + COVERAGE: 1 + TORANJ_POSIX_RCP_MODEL: 1 + TORANJ_RADIO : trel + steps: + - uses: actions/checkout@v2 + - name: Bootstrap + run: | + sudo rm /etc/apt/sources.list.d/* && sudo apt-get update + sudo apt-get --no-install-recommends install -y dbus libdbus-1-dev + sudo apt-get --no-install-recommends install -y autoconf-archive + sudo apt-get --no-install-recommends install -y bsdtar + sudo apt-get --no-install-recommends install -y libtool + sudo apt-get --no-install-recommends install -y libglib2.0-dev + sudo apt-get --no-install-recommends install -y libboost-dev libboost-signals-dev + sudo apt-get --no-install-recommends install -y lcov + + git clone --depth=1 --branch=master https://github.com/openthread/wpantund.git + cd wpantund + ./bootstrap.sh + ./configure + sudo make -j2 + sudo make install + - name: Build & Run + run: | + top_builddir=$(pwd)/build/toranj ./tests/toranj/start.sh + - name: Generate Coverage + run: | + ./script/test generate_coverage gcc + - uses: actions/upload-artifact@v2 + with: + name: cov-toranj-rcp-trel + path: tmp/coverage.info + + toranj-ncp-multi: + runs-on: ubuntu-18.04 + env: + COVERAGE: 1 + TORANJ_RADIO : multi + steps: + - uses: actions/checkout@v2 + - name: Bootstrap + run: | + sudo rm /etc/apt/sources.list.d/* && sudo apt-get update + sudo apt-get --no-install-recommends install -y dbus libdbus-1-dev + sudo apt-get --no-install-recommends install -y autoconf-archive + sudo apt-get --no-install-recommends install -y bsdtar + sudo apt-get --no-install-recommends install -y libtool + sudo apt-get --no-install-recommends install -y libglib2.0-dev + sudo apt-get --no-install-recommends install -y libboost-dev libboost-signals-dev + sudo apt-get --no-install-recommends install -y lcov + + git clone --depth=1 --branch=master https://github.com/openthread/wpantund.git + cd wpantund + ./bootstrap.sh + ./configure + sudo make -j2 + sudo make install + - name: Build & Run + run: | + top_builddir=$(pwd)/build/toranj ./tests/toranj/start.sh + upload-coverage: needs: - toranj-ncp - toranj-rcp + - toranj-ncp-trel + - toranj-rcp-trel runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 diff --git a/script/check-simulation-build-autotools b/script/check-simulation-build-autotools index eadb3ce29..590c3c072 100755 --- a/script/check-simulation-build-autotools +++ b/script/check-simulation-build-autotools @@ -192,12 +192,68 @@ build_nest_common() cd .. } +build_multi_radio_links() +{ + # TREL radio link only. + local options=( + "-DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1" + "-DOPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE=1" + "-DOPENTHREAD_CONFIG_LOG_LEVEL=OT_LOG_LEVEL_DEBG" + "-DOPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE=0" + "-DOPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE=1" + ) + export CPPFLAGS="${options[*]}" + + reset_source + mkdir build && cd build + ../configure \ + --enable-cli \ + --enable-ftd \ + --enable-mtd \ + --enable-ncp \ + --enable-radio-only \ + --with-examples=simulation \ + --disable-docs \ + --disable-tests + make -j"${OT_BUILD_JOBS}" + cd .. + + # Multi radio link - 15.4 and TREL. + options=( + "-DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1" + "-DOPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE=1" + "-DOPENTHREAD_CONFIG_DIAG_ENABLE=1" + "-DOPENTHREAD_CONFIG_LINK_RAW_ENABLE=1" + "-DOPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE=1" + "-DOPENTHREAD_CONFIG_LOG_LEVEL=OT_LOG_LEVEL_DEBG" + "-DOPENTHREAD_CONFIG_MAC_FILTER_ENABLE=1" + "-DOPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE=1" + "-DOPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE=1" + ) + export CPPFLAGS="${options[*]}" + + reset_source + mkdir build && cd build + ../configure \ + --enable-cli \ + --enable-ftd \ + --enable-mtd \ + --enable-ncp \ + --enable-radio-only \ + --with-examples=simulation \ + --disable-docs \ + --disable-tests + make -j"${OT_BUILD_JOBS}" + cd .. +} + main() { ./bootstrap build_all_features build_nest_common + build_multi_radio_links } main "$@" diff --git a/tests/toranj/build.sh b/tests/toranj/build.sh index bc328a5fa..d2fc77026 100755 --- a/tests/toranj/build.sh +++ b/tests/toranj/build.sh @@ -134,6 +134,7 @@ case ${build_config} in --with-examples=simulation \ "${configure_options[@]}" || die make -j 8 || die + cp -p ${top_builddir}/examples/apps/ncp/ot-ncp-ftd ${top_builddir}/examples/apps/ncp/ot-ncp-ftd-15.4 ;; ncp-trel) @@ -150,6 +151,7 @@ case ${build_config} in --with-examples=simulation \ "${configure_options[@]}" || die make -j 8 || die + cp -p ${top_builddir}/examples/apps/ncp/ot-ncp-ftd ${top_builddir}/examples/apps/ncp/ot-ncp-ftd-trel ;; ncp-15.4+trel | ncp-trel+15.4) @@ -166,6 +168,7 @@ case ${build_config} in --with-examples=simulation \ "${configure_options[@]}" || die make -j 8 || die + cp -p ${top_builddir}/examples/apps/ncp/ot-ncp-ftd ${top_builddir}/examples/apps/ncp/ot-ncp-ftd-15.4-trel ;; rcp) diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 16cc14d02..785a7058e 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -75,8 +75,8 @@ run() return fi - # We allow a failed test to be retried up to 3 attempts. - if [ "$counter" -lt 2 ]; then + # We allow a failed test to be retried up to 7 attempts. + if [ "$counter" -lt 7 ]; then counter=$((counter + 1)) echo Attempt $counter running "$1" failed. Trying again. cleanup @@ -92,6 +92,10 @@ run() cd "$(dirname "$0")" || die "cd failed" +if [ -z "${top_builddir}" ]; then + top_builddir=. +fi + if [ "$COVERAGE" = 1 ]; then coverage_option="--enable-coverage" else @@ -108,8 +112,17 @@ case $TORANJ_POSIX_RCP_MODEL in esac if [ "$use_posix_with_rcp" = "no" ]; then - ./build.sh ${coverage_option} ncp-"${TORANJ_RADIO}" || die "ncp build failed" - + if [ "$TORANJ_RADIO" = "multi" ]; then + # Build all combinations + ./build.sh ${coverage_option} ncp-15.4 || die "ncp-15.4 build failed" + (cd ${top_builddir} && make clean) || die "cd and clean failed" + ./build.sh ${coverage_option} ncp-trel || die "ncp-trel build failed" + (cd ${top_builddir} && make clean) || die "cd and clean failed" + ./build.sh ${coverage_option} ncp-15.4+trel || die "ncp-15.4+trel build failed" + (cd ${top_builddir} && make clean) || die "cd and clean failed" + else + ./build.sh ${coverage_option} ncp-"${TORANJ_RADIO}" || die "ncp build failed" + fi else ./build.sh ${coverage_option} rcp || die "rcp build failed" ./build.sh ${coverage_option} posix-"${TORANJ_RADIO}" || die "posix build failed" @@ -121,6 +134,17 @@ fi cleanup +if [ "$TORANJ_RADIO" = "multi" ]; then + run test-700-multi-radio-join.py + run test-701-multi-radio-probe.py + run test-702-multi-radio-discovery-by-rx.py + run test-703-multi-radio-mesh-header-msg.py + run test-704-multi-radio-scan.py + run test-705-multi-radio-discover-scan.py + + exit 0 +fi + run test-001-get-set.py run test-002-form.py run test-003-join.py diff --git a/tests/toranj/test-700-multi-radio-join.py b/tests/toranj/test-700-multi-radio-join.py new file mode 100644 index 000000000..518b89d55 --- /dev/null +++ b/tests/toranj/test-700-multi-radio-join.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, 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: This test covers joining of nodes with different radio links +# +# Parent node with trel and 15.4 with 3 children. +# +# parent +# (trel + 15.4) +# / | \ +# / | \ +# / | \ +# c1 c2 c3 +# (15.4) (trel) (trel+15.4) + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +speedup = 4 +wpan.Node.set_time_speedup_factor(speedup) + +parent = wpan.Node(wpan.NODE_15_4_TREL) +c1 = wpan.Node(wpan.NODE_15_4) +c2 = wpan.Node(wpan.NODE_TREL) +c3 = wpan.Node(wpan.NODE_15_4_TREL) + +# ----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +# ----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +WAIT_TIME = 5 + +# Verify that each node supports the correct radio links: + +parent_radios = wpan.parse_list(parent.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) +verify( + len(parent_radios) == 2 and (wpan.RADIO_LINK_IEEE_802_15_4 in parent_radios) and + (wpan.RADIO_LINK_TREL_UDP6 in parent_radios)) + +c1_radios = wpan.parse_list(c1.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) +verify(len(c1_radios) == 1 and wpan.RADIO_LINK_IEEE_802_15_4 in c1_radios) + +c2_radios = wpan.parse_list(c2.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) +verify(len(c2_radios) == 1 and wpan.RADIO_LINK_TREL_UDP6 in c2_radios) + +c3_radios = wpan.parse_list(c3.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) +verify( + len(c3_radios) == 2 and (wpan.RADIO_LINK_IEEE_802_15_4 in c3_radios) and (wpan.RADIO_LINK_TREL_UDP6 in c3_radios)) + +parent.form("multi-radio") + +c1.join_node(parent, wpan.JOIN_TYPE_END_DEVICE) +c2.join_node(parent, wpan.JOIN_TYPE_END_DEVICE) +c3.join_node(parent, wpan.JOIN_TYPE_END_DEVICE) + +# Verify that parent correctly knows about all the children and their supported radio links + +radio_info_entries = wpan.parse_multi_radio_result(parent.get(wpan.WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO)) +verify(len(radio_info_entries) == 3) + +for node in [c1, c2, c3]: + # Find the entry matching node ext address + address = node.get(wpan.WPAN_EXT_ADDRESS)[1:-1] + filtred_list = [entry for entry in radio_info_entries if entry.ext_address == address] + verify(len(filtred_list) == 1) + entry = filtred_list[0] + + # Verify that the multi radio info matches the radio links supported by the node + node_radios = wpan.parse_list(node.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) + verify(len(node_radios) == len(entry.radios)) + for radio in node_radios: + verify(entry.supports(radio)) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# Reset the parent and check that all children are attached back successfully + +parent.reset() + + +def check_all_children_are_attached(): + neighbor_table = wpan.parse_neighbor_table_result(parent.get(wpan.WPAN_THREAD_NEIGHBOR_TABLE)) + verify(len(neighbor_table) == 3) + + +wpan.verify_within(check_all_children_are_attached, WAIT_TIME) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +wpan.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/test-701-multi-radio-probe.py b/tests/toranj/test-701-multi-radio-probe.py new file mode 100644 index 000000000..7a39d9b20 --- /dev/null +++ b/tests/toranj/test-701-multi-radio-probe.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, 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: This test covers behavior of device after trel network is temporarily disabled +# and rediscovery of trel radio using probe mechanism. +# +# r1 ---------- r2 +# (15.4+trel) (15.4+trel) +# +# On r2 we disable trel temporarily. +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +speedup = 4 +wpan.Node.set_time_speedup_factor(speedup) + +r1 = wpan.Node(wpan.NODE_15_4_TREL) +r2 = wpan.Node(wpan.NODE_15_4_TREL) +c2 = wpan.Node(wpan.NODE_15_4) + +# ----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +# ----------------------------------------------------------------------------------------------------------------------- +# Build network topology +# + +r1.allowlist_node(r2) +r2.allowlist_node(r1) + +r2.allowlist_node(c2) +c2.allowlist_node(r2) + +r1.form("discover-by-rx") + +r2.join_node(r1, wpan.JOIN_TYPE_ROUTER) +c2.join_node(r2, wpan.JOIN_TYPE_SLEEPY_END_DEVICE) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +WAIT_TIME = 5 +HIGH_PREFERENCE_THRESHOLD = 220 +MIN_PREFERENCE_THRESHOLD = 0 + +r1_ext_address = r1.get(wpan.WPAN_EXT_ADDRESS)[1:-1] +r1_rloc = int(r1.get(wpan.WPAN_THREAD_RLOC16), 16) +r1_ml_address = r1.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1] +r2_ext_address = r2.get(wpan.WPAN_EXT_ADDRESS)[1:-1] +r2_rloc = int(r2.get(wpan.WPAN_THREAD_RLOC16), 16) +r2_ml_address = r2.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1] + +# Wait for r2 to become router and r1 establishes a link with it + + +def check_r1_router_table(): + router_table = wpan.parse_router_table_result(r1.get(wpan.WPAN_THREAD_ROUTER_TABLE)) + verify(len(router_table) == 2) + for entry in router_table: + verify(entry.rloc16 == r1_rloc or entry.is_link_established()) + + +wpan.verify_within(check_r1_router_table, WAIT_TIME) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Check that r1 detected both trel and 15.4 as supported radio by r2 + +r1_radios = wpan.parse_list(r1.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) +verify( + len(r1_radios) == 2 and (wpan.RADIO_LINK_IEEE_802_15_4 in r1_radios) and (wpan.RADIO_LINK_TREL_UDP6 in r1_radios)) + +r2_radios = wpan.parse_list(r2.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) +verify( + len(r2_radios) == 2 and (wpan.RADIO_LINK_IEEE_802_15_4 in r2_radios) and (wpan.RADIO_LINK_TREL_UDP6 in r2_radios)) + + +def check_r1_sees_r2_has_two_radio_links(): + r1_neighbor_radios = wpan.parse_multi_radio_result(r1.get(wpan.WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO)) + verify(len(r1_neighbor_radios) == 1) + verify(len(r1_neighbor_radios[0].radios) == 2) + + +wpan.verify_within(check_r1_sees_r2_has_two_radio_links, WAIT_TIME) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Send from r1 to r2 and verify that r1 prefers trel radio link for sending to r2. + +sender = r1.prepare_tx(r1_ml_address, r2_ml_address, "Hi r2 from r1", 5) +recver = r2.prepare_rx(sender) +wpan.Node.perform_async_tx_rx() +verify(sender.was_successful) +verify(recver.was_successful) + +r1_neighbor_radios = wpan.parse_multi_radio_result(r1.get(wpan.WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO)) +verify(len(r1_neighbor_radios) == 1) +r2_radio_info = r1_neighbor_radios[0] +verify(r2_radio_info.supports(wpan.RADIO_LINK_TREL_UDP6)) +verify(r2_radio_info.preference(wpan.RADIO_LINK_TREL_UDP6) >= HIGH_PREFERENCE_THRESHOLD) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Now disable trel link on r2 and send again. We expect that r1 would quickly learn that trel is +# no longer supported by r2 and prefer 15.4 for tx to r2. + +r2.set(wpan.WPAN_OT_TREL_TEST_MODE_ENABLE, 'false') +verify(r2.get(wpan.WPAN_OT_TREL_TEST_MODE_ENABLE) == 'false') + +sender = r1.prepare_tx(r1_ml_address, r2_ml_address, "Hi again r2 from r1", 5) +wpan.Node.perform_async_tx_rx() +verify(sender.was_successful) + + +def check_r1_does_not_prefer_trel_for_r2(): + r1_neighbor_radios = wpan.parse_multi_radio_result(r1.get(wpan.WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO)) + verify(len(r1_neighbor_radios) == 1) + r2_radio_info = r1_neighbor_radios[0] + verify(r2_radio_info.supports(wpan.RADIO_LINK_TREL_UDP6)) + verify(r2_radio_info.preference(wpan.RADIO_LINK_TREL_UDP6) <= MIN_PREFERENCE_THRESHOLD) + + +wpan.verify_within(check_r1_does_not_prefer_trel_for_r2, WAIT_TIME) + +# Check that we can send between r1 and r2 (now all tx should use 15.4) + +sender = r1.prepare_tx(r1_ml_address, r2_ml_address, "Hi on 15.4 r2 from r1", 5) +recver = r2.prepare_rx(sender) +wpan.Node.perform_async_tx_rx() +verify(sender.was_successful) +verify(recver.was_successful) + +r1_neighbor_radios = wpan.parse_multi_radio_result(r1.get(wpan.WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO)) +verify(len(r1_neighbor_radios) == 1) +r2_radio_info = r1_neighbor_radios[0] +verify(r2_radio_info.supports(wpan.RADIO_LINK_TREL_UDP6)) +verify(r2_radio_info.preference(wpan.RADIO_LINK_TREL_UDP6) <= MIN_PREFERENCE_THRESHOLD) +verify(r2_radio_info.supports(wpan.RADIO_LINK_IEEE_802_15_4)) +verify(r2_radio_info.preference(wpan.RADIO_LINK_IEEE_802_15_4) >= HIGH_PREFERENCE_THRESHOLD) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Enable trel back on r2, start sending traffic from r1 to r2 +# The probe mechanism should kick and detect that r2 has trel again. + +r2.set(wpan.WPAN_OT_TREL_TEST_MODE_ENABLE, 'true') +verify(r2.get(wpan.WPAN_OT_TREL_TEST_MODE_ENABLE) == 'true') + +sender = r1.prepare_tx(r1_ml_address, r2_ml_address, "Probing r2 from r1", 80) +recver = r2.prepare_rx(sender) +wpan.Node.perform_async_tx_rx() +verify(sender.was_successful) +verify(recver.was_successful) + + +def check_r1_again_prefers_trel_for_r2(): + r1_neighbor_radios = wpan.parse_multi_radio_result(r1.get(wpan.WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO)) + verify(len(r1_neighbor_radios) == 1) + r2_radio_info = r1_neighbor_radios[0] + verify(r2_radio_info.supports(wpan.RADIO_LINK_TREL_UDP6)) + verify(r2_radio_info.preference(wpan.RADIO_LINK_TREL_UDP6) >= HIGH_PREFERENCE_THRESHOLD) + + +wpan.verify_within(check_r1_again_prefers_trel_for_r2, WAIT_TIME) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +wpan.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/test-702-multi-radio-discovery-by-rx.py b/tests/toranj/test-702-multi-radio-discovery-by-rx.py new file mode 100644 index 000000000..ec6f6e689 --- /dev/null +++ b/tests/toranj/test-702-multi-radio-discovery-by-rx.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, 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: This test covers behavior of device after TREL network is temporarily disabled +# and rediscovery of TREL by receiving message over that radio from the neighbor. +# +# r1 ---------- r2 +# (15.4+trel) (15.4+trel) +# +# On r2 we disable trel temporarily. +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +speedup = 4 +wpan.Node.set_time_speedup_factor(speedup) + +r1 = wpan.Node(wpan.NODE_15_4_TREL) +r2 = wpan.Node(wpan.NODE_15_4_TREL) +c2 = wpan.Node(wpan.NODE_15_4) + +# ----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +# ----------------------------------------------------------------------------------------------------------------------- +# Build network topology +# + +r1.allowlist_node(r2) +r2.allowlist_node(r1) + +r2.allowlist_node(c2) +c2.allowlist_node(r2) + +r1.form("discover-by-rx") + +r2.join_node(r1, wpan.JOIN_TYPE_ROUTER) +c2.join_node(r2, wpan.JOIN_TYPE_SLEEPY_END_DEVICE) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +WAIT_TIME = 5 +HIGH_PREFERENCE_THRESHOLD = 220 +MIN_PREFERENCE_THRESHOLD = 0 + +r1_ext_address = r1.get(wpan.WPAN_EXT_ADDRESS)[1:-1] +r1_rloc = int(r1.get(wpan.WPAN_THREAD_RLOC16), 16) +r1_ml_address = r1.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1] +r2_ext_address = r2.get(wpan.WPAN_EXT_ADDRESS)[1:-1] +r2_rloc = int(r2.get(wpan.WPAN_THREAD_RLOC16), 16) +r2_ml_address = r2.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1] + +# Wait for r2 to become router and r1 establishes a link with it + + +def check_r1_router_table(): + router_table = wpan.parse_router_table_result(r1.get(wpan.WPAN_THREAD_ROUTER_TABLE)) + verify(len(router_table) == 2) + for entry in router_table: + verify(entry.rloc16 == r1_rloc or entry.is_link_established()) + + +wpan.verify_within(check_r1_router_table, WAIT_TIME) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Check that r1 detected both TREL and 15.4 as supported radio by r2 + +r1_radios = wpan.parse_list(r1.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) +verify( + len(r1_radios) == 2 and (wpan.RADIO_LINK_IEEE_802_15_4 in r1_radios) and (wpan.RADIO_LINK_TREL_UDP6 in r1_radios)) + +r2_radios = wpan.parse_list(r2.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) +verify( + len(r2_radios) == 2 and (wpan.RADIO_LINK_IEEE_802_15_4 in r2_radios) and (wpan.RADIO_LINK_TREL_UDP6 in r2_radios)) + + +def check_r1_sees_r2_has_two_radio_links(): + r1_neighbor_radios = wpan.parse_multi_radio_result(r1.get(wpan.WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO)) + verify(len(r1_neighbor_radios) == 1) + verify(len(r1_neighbor_radios[0].radios) == 2) + + +wpan.verify_within(check_r1_sees_r2_has_two_radio_links, WAIT_TIME) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Send from r1 to r2 and verify that r1 prefers TREL radio link for sending to r2. + +sender = r1.prepare_tx(r1_ml_address, r2_ml_address, "Hi r2 from r1", 5) +recver = r2.prepare_rx(sender) +wpan.Node.perform_async_tx_rx() +verify(sender.was_successful) +verify(recver.was_successful) + +r1_neighbor_radios = wpan.parse_multi_radio_result(r1.get(wpan.WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO)) +verify(len(r1_neighbor_radios) == 1) +r2_radio_info = r1_neighbor_radios[0] +verify(r2_radio_info.supports(wpan.RADIO_LINK_TREL_UDP6)) +verify(r2_radio_info.preference(wpan.RADIO_LINK_TREL_UDP6) >= HIGH_PREFERENCE_THRESHOLD) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Now disable TREL link on r2 and send again. We expect that r1 would quickly learn that trel is +# no longer supported by r2 and prefer 15.4 for tx to r2. + +r2.set(wpan.WPAN_OT_TREL_TEST_MODE_ENABLE, 'false') +verify(r2.get(wpan.WPAN_OT_TREL_TEST_MODE_ENABLE) == 'false') + +sender = r1.prepare_tx(r1_ml_address, r2_ml_address, "Hi again r2 from r1", 5) +wpan.Node.perform_async_tx_rx() +verify(sender.was_successful) + + +def check_r1_does_not_prefer_trel_for_r2(): + r1_neighbor_radios = wpan.parse_multi_radio_result(r1.get(wpan.WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO)) + verify(len(r1_neighbor_radios) == 1) + r2_radio_info = r1_neighbor_radios[0] + verify(r2_radio_info.supports(wpan.RADIO_LINK_TREL_UDP6)) + verify(r2_radio_info.preference(wpan.RADIO_LINK_TREL_UDP6) <= MIN_PREFERENCE_THRESHOLD) + + +wpan.verify_within(check_r1_does_not_prefer_trel_for_r2, WAIT_TIME) + +# Check that we can send between r1 and r2 (now all tx should use 15.4) + +sender = r1.prepare_tx(r1_ml_address, r2_ml_address, "Hi on 15.4 r2 from r1", 5) +recver = r2.prepare_rx(sender) +wpan.Node.perform_async_tx_rx() +verify(sender.was_successful) +verify(recver.was_successful) + +r1_neighbor_radios = wpan.parse_multi_radio_result(r1.get(wpan.WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO)) +verify(len(r1_neighbor_radios) == 1) +r2_radio_info = r1_neighbor_radios[0] +verify(r2_radio_info.supports(wpan.RADIO_LINK_TREL_UDP6)) +verify(r2_radio_info.preference(wpan.RADIO_LINK_TREL_UDP6) <= MIN_PREFERENCE_THRESHOLD) +verify(r2_radio_info.supports(wpan.RADIO_LINK_IEEE_802_15_4)) +verify(r2_radio_info.preference(wpan.RADIO_LINK_IEEE_802_15_4) >= HIGH_PREFERENCE_THRESHOLD) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Enable trel back on r2, start sending traffic from r2 to r1. +# r2 would use probe mehcnasim to discover that trel is enabled again +# r1 should notice new rx on trel and update trel preference for r1 + +r2.set(wpan.WPAN_OT_TREL_TEST_MODE_ENABLE, 'true') +verify(r2.get(wpan.WPAN_OT_TREL_TEST_MODE_ENABLE) == 'true') + +sender = r2.prepare_tx(r2_ml_address, r1_ml_address, "Probing r1 from r2", 80) +recver = r1.prepare_rx(sender) +wpan.Node.perform_async_tx_rx() +verify(sender.was_successful) +verify(recver.was_successful) + + +def check_r1_again_prefers_trel_for_r2(): + r1_neighbor_radios = wpan.parse_multi_radio_result(r1.get(wpan.WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO)) + verify(len(r1_neighbor_radios) == 1) + r2_radio_info = r1_neighbor_radios[0] + verify(r2_radio_info.supports(wpan.RADIO_LINK_TREL_UDP6)) + verify(r2_radio_info.preference(wpan.RADIO_LINK_TREL_UDP6) >= HIGH_PREFERENCE_THRESHOLD) + + +wpan.verify_within(check_r1_again_prefers_trel_for_r2, WAIT_TIME) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +wpan.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/test-703-multi-radio-mesh-header-msg.py b/tests/toranj/test-703-multi-radio-mesh-header-msg.py new file mode 100644 index 000000000..cdc9764fc --- /dev/null +++ b/tests/toranj/test-703-multi-radio-mesh-header-msg.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, 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: This test covers behavior of MeshHeader messages (message sent over multi-hop) when the +# underlying devices (in the path) support different radio types with different frame MTU length. +# +# r1 --------- r2 ---------- r3 +# (trel) (15.4+trel) (15.4) +# | +# | +# c3 (15.4) +# +# c3 is used for quick router promotion of r3. +# +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +speedup = 4 +wpan.Node.set_time_speedup_factor(speedup) + +r1 = wpan.Node(wpan.NODE_TREL) +r2 = wpan.Node(wpan.NODE_15_4_TREL) +r3 = wpan.Node(wpan.NODE_15_4) +c3 = wpan.Node(wpan.NODE_15_4) + +# ----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +# ----------------------------------------------------------------------------------------------------------------------- +# Build network topology +# + +r1.allowlist_node(r2) +r2.allowlist_node(r1) + +r2.allowlist_node(r3) +r3.allowlist_node(r2) + +r3.allowlist_node(c3) +c3.allowlist_node(r3) + +r1.form("mesh-header") + +r2.join_node(r1, wpan.JOIN_TYPE_ROUTER) +r3.join_node(r2, wpan.JOIN_TYPE_ROUTER) +c3.join_node(r3, wpan.JOIN_TYPE_END_DEVICE) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +WAIT_TIME = 5 +INVALID_ROUTER_ID = 63 +MSG_LEN = 1000 +MSG_COUNT = 7 + +# Verify that each node supports the correct radio links. + +r1_radios = wpan.parse_list(r1.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) +verify(len(r1_radios) == 1 and (wpan.RADIO_LINK_TREL_UDP6 in r1_radios)) + +r2_radios = wpan.parse_list(r2.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) +verify( + len(r2_radios) == 2 and (wpan.RADIO_LINK_IEEE_802_15_4 in r2_radios) and (wpan.RADIO_LINK_TREL_UDP6 in r2_radios)) + +r3_radios = wpan.parse_list(r3.get(wpan.WPAN_OT_SUPPORTED_RADIO_LINKS)) +verify(len(r3_radios) == 1 and (wpan.RADIO_LINK_IEEE_802_15_4 in r3_radios)) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Wait for all router entries are updated + +r1_rloc = int(r1.get(wpan.WPAN_THREAD_RLOC16), 16) + + +def check_r1_router_table(): + router_table = wpan.parse_router_table_result(r1.get(wpan.WPAN_THREAD_ROUTER_TABLE)) + verify(len(router_table) == 3) + for entry in router_table: + verify(entry.rloc16 == r1_rloc or entry.is_link_established() or entry.next_hop != INVALID_ROUTER_ID) + + +wpan.verify_within(check_r1_router_table, WAIT_TIME) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Send large message from r1 to r3. Such a messag would be sent as a +# Mesh Header message. Note that the origin r1 is a trel-only device +# whereas the final destination r3 is a 15.4-only device. The originator +# should use smaller MTU size (which then fragment the message into +# multiple frames) otherwise the 15.4 link won't be able to handle it. + +r1_ml_address = r1.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1] +r3_ml_address = r3.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1] + +sender = r1.prepare_tx(r1_ml_address, r3_ml_address, MSG_LEN, MSG_COUNT) +recver = r3.prepare_rx(sender) + +wpan.Node.perform_async_tx_rx() + +verify(sender.was_successful) +verify(recver.was_successful) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +wpan.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/test-704-multi-radio-scan.py b/tests/toranj/test-704-multi-radio-scan.py new file mode 100644 index 000000000..a4d406d0b --- /dev/null +++ b/tests/toranj/test-704-multi-radio-scan.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, 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. + +from wpan import verify +import wpan + +# ----------------------------------------------------------------------------------------------------------------------- +# Test description: Test active scan with nodes supporting different radios +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +speedup = 1 +wpan.Node.set_time_speedup_factor(speedup) + +n1 = wpan.Node(wpan.NODE_15_4) +n2 = wpan.Node(wpan.NODE_TREL) +n3 = wpan.Node(wpan.NODE_15_4_TREL) +s1 = wpan.Node(wpan.NODE_15_4) +s2 = wpan.Node(wpan.NODE_TREL) +s3 = wpan.Node(wpan.NODE_15_4_TREL) + +# ----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +# ----------------------------------------------------------------------------------------------------------------------- +# Build network topology + +n1.form("n1", channel='20') +n2.form("n2", channel='21') +n3.form("n3", channel='22') + +# ----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Scan by scanner nodes (no network) + +# Scan by s1 (15.4 only), expect to see n1(15.4) and n3(15.4+trel) +result = wpan.parse_scan_result(s1.active_scan()) +verify(n1.is_in_scan_result(result)) +verify(not n2.is_in_scan_result(result)) +verify(n3.is_in_scan_result(result)) + +# Scan by s2 (trel only), expect to see n2(trel) and n3(15.4+trel) +result = wpan.parse_scan_result(s2.active_scan()) +verify(not n1.is_in_scan_result(result)) +verify(n2.is_in_scan_result(result)) +verify(n3.is_in_scan_result(result)) + +# Scan by s3 (trel+15.4), expect to see all nodes +result = wpan.parse_scan_result(s3.active_scan()) +verify(n1.is_in_scan_result(result)) +verify(n2.is_in_scan_result(result)) +verify(n3.is_in_scan_result(result)) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Scan by the nodes + +# Scan by n1 (15.4 only), expect to see only n3(15.4+trel) +result = wpan.parse_scan_result(n1.active_scan()) +verify(not n1.is_in_scan_result(result)) +verify(not n2.is_in_scan_result(result)) +verify(n3.is_in_scan_result(result)) + +# Scan by n2 (trel only), expect to see only n3(15.4+trel) +result = wpan.parse_scan_result(n2.active_scan()) +verify(not n1.is_in_scan_result(result)) +verify(not n2.is_in_scan_result(result)) +verify(n3.is_in_scan_result(result)) + +# Scan by n3 (15.4+trel), expect to see n1(15.4) and n2(trel) +result = wpan.parse_scan_result(n3.active_scan()) +verify(n1.is_in_scan_result(result)) +verify(n2.is_in_scan_result(result)) +verify(not n3.is_in_scan_result(result)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +wpan.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/test-705-multi-radio-discover-scan.py b/tests/toranj/test-705-multi-radio-discover-scan.py new file mode 100644 index 000000000..528c01240 --- /dev/null +++ b/tests/toranj/test-705-multi-radio-discover-scan.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, 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. + +from wpan import verify +import wpan + +# ----------------------------------------------------------------------------------------------------------------------- +# Test description: Test MLE discover scan with nodes supporting different radios +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +speedup = 1 +wpan.Node.set_time_speedup_factor(speedup) + +n1 = wpan.Node(wpan.NODE_15_4) +n2 = wpan.Node(wpan.NODE_TREL) +n3 = wpan.Node(wpan.NODE_15_4_TREL) +s1 = wpan.Node(wpan.NODE_15_4) +s2 = wpan.Node(wpan.NODE_TREL) +s3 = wpan.Node(wpan.NODE_15_4_TREL) + +# ----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +# ----------------------------------------------------------------------------------------------------------------------- +# Build network topology + +n1.form("n1", channel='20') +n2.form("n2", channel='21') +n3.form("n3", channel='22') + +# ----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Scan by scanner nodes (no network) + +# Scan by s1 (15.4 only), expect to see n1(15.4) and n3(15.4+trel) +result = wpan.parse_scan_result(s1.discover_scan()) +verify(n1.is_in_scan_result(result)) +verify(not n2.is_in_scan_result(result)) +verify(n3.is_in_scan_result(result)) + +# Scan by s2 (trel only), expect to see n2(trel) and n3(15.4+trel) +result = wpan.parse_scan_result(s2.discover_scan()) +verify(not n1.is_in_scan_result(result)) +verify(n2.is_in_scan_result(result)) +verify(n3.is_in_scan_result(result)) + +# Scan by s3 (trel+15.4), expect to see all nodes +result = wpan.parse_scan_result(s3.discover_scan()) +verify(n1.is_in_scan_result(result)) +verify(n2.is_in_scan_result(result)) +verify(n3.is_in_scan_result(result)) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Scan by the nodes + +# Scan by n1 (15.4 only), expect to see only n3(15.4+trel) +result = wpan.parse_scan_result(n1.discover_scan()) +verify(not n1.is_in_scan_result(result)) +verify(not n2.is_in_scan_result(result)) +verify(n3.is_in_scan_result(result)) + +# Scan by n2 (trel only), expect to see only n3(15.4+trel) +result = wpan.parse_scan_result(n2.discover_scan()) +verify(not n1.is_in_scan_result(result)) +verify(not n2.is_in_scan_result(result)) +verify(n3.is_in_scan_result(result)) + +# Scan by n3 (15.4+trel), expect to see n1(15.4) and n2(trel) +result = wpan.parse_scan_result(n3.discover_scan()) +verify(n1.is_in_scan_result(result)) +verify(n2.is_in_scan_result(result)) +verify(not n3.is_in_scan_result(result)) + +# ----------------------------------------------------------------------------------------------------------------------- +# 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 d72c9ba3d..0780a9755 100644 --- a/tests/toranj/wpan.py +++ b/tests/toranj/wpan.py @@ -122,6 +122,9 @@ WPAN_OT_MSG_BUFFER_COUNTERS = "OpenThread:MsgBufferCounters" WPAN_OT_MSG_BUFFER_COUNTERS_AS_STRING = "OpenThread:MsgBufferCounters:AsString" WPAN_OT_DEBUG_TEST_ASSERT = "OpenThread:Debug:TestAssert" WPAN_OT_DEBUG_TEST_WATCHDOG = "OpenThread:Debug:TestWatchdog" +WPAN_OT_SUPPORTED_RADIO_LINKS = "OpenThread:SupportedRadioLinks" +WPAN_OT_NEIGHBOR_TABLE_MULTI_RADIO_INFO = "OpenThread:NeighborTable::MultiRadioInfo" +WPAN_OT_TREL_TEST_MODE_ENABLE = "OpenThread:Trel:TestMode:Enable" WPAN_MAC_ALLOWLIST_ENABLED = "MAC:Allowlist:Enabled" WPAN_MAC_ALLOWLIST_ENTRIES = "MAC:Allowlist:Entries" @@ -228,6 +231,13 @@ MCU_POWER_STATE_ON = '"on"' MCU_POWER_STATE_LOW_POWER = '"low-power"' MCU_POWER_STATE_OFF = '"off"' +# ----------------------------------------------------------------------------------------------------------------------- +# Node Radio Link Types (Use as input to `Node()` initializer) + +NODE_15_4 = "-15.4" +NODE_TREL = "-trel" +NODE_15_4_TREL = "-15.4-trel" + # ----------------------------------------------------------------------------------------------------------------------- # Node types (from `WPAN_NODE_TYPE` property) @@ -261,6 +271,13 @@ THREAD_MODE_FLAG_FULL_NETWORK_DATA = (1 << 0) THREAD_MODE_FLAG_FULL_THREAD_DEV = (1 << 1) THREAD_MODE_FLAG_RX_ON_WHEN_IDLE = (1 << 3) +# ----------------------------------------------------------------------------------------------------------------------- +# Radio Link type + +RADIO_LINK_IEEE_802_15_4 = "IEEE_802_15_4" +RADIO_LINK_TREL_UDP6 = "TREL_UDP6" +RADIO_LINK_TOBLE = "TOBLE" + _OT_BUILDDIR = os.getenv('top_builddir', '../..') _WPANTUND_PREFIX = os.getenv('WPANTUND_PREFIX', '/usr/local') @@ -281,7 +298,6 @@ def _log(text, new_line=True, flush=True): class Node(object): """ A wpantund OT NCP instance """ - # defines the default verbosity setting (can be changed per `Node`) _VERBOSE = os.getenv('TORANJ_VERBOSE', 'no').lower() in ['true', '1', 't', 'y', 'yes', 'on'] _SPEED_UP_FACTOR = 1 # defines the default time speed up factor @@ -312,7 +328,7 @@ class Node(object): _cur_index = _START_INDEX _all_nodes = weakref.WeakSet() - def __init__(self, verbose=_VERBOSE): + def __init__(self, radios=None, verbose=_VERBOSE): """Creates a new `Node` instance""" index = Node._cur_index @@ -330,10 +346,11 @@ class Node(object): self._use_posix_with_rcp = False if self._use_posix_with_rcp: - ncp_socket_path = 'system:{} -s {} spinel+hdlc+uart://{}?forkpty-arg={}'.format( - self._OT_NCP_FTD_POSIX, self._SPEED_UP_FACTOR, self._OT_RCP, index) + ncp_socket_path = 'system:{}{} -s {} spinel+hdlc+uart://{}?forkpty-arg={}'.format( + self._OT_NCP_FTD_POSIX, '' if radios is None else radios, self._SPEED_UP_FACTOR, self._OT_RCP, index) else: - ncp_socket_path = 'system:{} {} {}'.format(self._OT_NCP_FTD, index, self._SPEED_UP_FACTOR) + ncp_socket_path = 'system:{}{} {} {}'.format(self._OT_NCP_FTD, '' if radios is None else radios, index, + self._SPEED_UP_FACTOR) cmd = self._WPANTUND + \ ' -o Config:NCP:SocketPath \"{}\"'.format(ncp_socket_path) + \ @@ -1536,3 +1553,63 @@ class InterfaceRoute(object): def parse_interface_routes_result(interface_routes_list): """ Parses interface routes list string and returns an array of `InterfaceRoute` objects""" return [InterfaceRoute(item) for item in interface_routes_list.split('\n')[1:-1]] + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +class MultiRadioEntry(object): + """ This object encapsulates a multi radio info entry""" + + def __init__(self, text): + + # Example of expected text: + # + # `\t"0EB758375B4976E7, RLOC16:f403, Radios:[IEEE_802_15_4(200), TREL_UDP6(255)]"` + # + + # We get rid of the first two chars `\t"' and last char '"', split the rest using whitespace as separator. + # Then remove any ',' at end of items in the list. + items = [item[:-1] if item[-1] == ',' else item for item in text[2:-1].split()] + + # First item is the extended address + self._ext_address = items[0] + + # Second items is 'RLCO16:{rloc}' + self._rloc16 = items[1].split(':')[1] + + # Join back rest of items, split using ":" to get list of radios of form "[IEEE_802_15_4(200) TREL_UDP6(255)]" + radios = " ".join(items[2:]).split(":")[1] + + if radios != "[]": + # Get rid of `[ and `]`, then split using " ", then convert to dictionary mapping radio type + # to its preference value. + self._radios = {radio.split("(")[0]: radio.split("(")[1][:-1] for radio in radios[1:-1].split(' ')} + else: + self._radios = {} + + @property + def ext_address(self): + return self._ext_address + + @property + def rloc16(self): + return self._rloc16 + + @property + def radios(self): + return self._radios + + def supports(self, radio_type): + return radio_type in self._radios + + def preference(self, radio_type): + return int(self._radios[radio_type], 0) if self.supports(radio_type) else None + + def __repr__(self): + return 'MultiRadioEntry({})'.format(self.__dict__) + + +def parse_multi_radio_result(multi_radio_list): + """ Parses multi radio neighbor list string and returns an array of `MultiRadioEntry` objects""" + return [MultiRadioEntry(item) for item in multi_radio_list.split('\n')[1:-1]]