From 7b7f33204977312a11178ca6cf2644f8c7f8fae8 Mon Sep 17 00:00:00 2001 From: Li Cao Date: Thu, 20 Jun 2024 11:42:41 +0800 Subject: [PATCH] [controller] add ncp host (#2329) This commit adds the `NcpHost` class which is the implementation of `ThreadController` under NCP case. This commit updates `ThreadController::Create` to create `NcpHost` under the NCP case, allowing `otbr-agent` to start when the co-processor is a NCP, though no functionalities have been implemented yet. This commit also adds a CI workflow to use expect script to test otbr-agent in NCP case. For this PR, a simple test is added to start otbr-agent with NCP (dry run) and check the co-processor version. --- .github/workflows/ncp_mode.yml | 67 ++++++++ script/test | 3 + src/ncp/CMakeLists.txt | 2 + src/ncp/ncp_host.cpp | 89 +++++++++++ src/ncp/ncp_host.hpp | 82 ++++++++++ src/ncp/thread_host.cpp | 16 +- tests/scripts/expect/ncp_version.exp | 16 ++ tests/scripts/ncp_mode | 224 +++++++++++++++++++++++++++ 8 files changed, 494 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/ncp_mode.yml create mode 100644 src/ncp/ncp_host.cpp create mode 100644 src/ncp/ncp_host.hpp create mode 100644 tests/scripts/expect/ncp_version.exp create mode 100755 tests/scripts/ncp_mode diff --git a/.github/workflows/ncp_mode.yml b/.github/workflows/ncp_mode.yml new file mode 100644 index 00000000000..eba5a31003c --- /dev/null +++ b/.github/workflows/ncp_mode.yml @@ -0,0 +1,67 @@ +# +# Copyright (c) 2024, 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. +# + +name: NcpMode + +on: + push: + branches-ignore: + - 'dependabot/**' + pull_request: + branches: + - 'main' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || (github.repository == 'openthread/ot-br-posix' && github.run_id) || github.ref }} + cancel-in-progress: true + +jobs: + + ncp_mode: + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + mdns: ["mDNSResponder", "avahi"] + env: + BUILD_TARGET: check + OTBR_MDNS: ${{ matrix.mdns }} + OTBR_COVERAGE: 1 + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Bootstrap + run: tests/scripts/bootstrap.sh + - name: Build + run: | + script/test build + - name: Run + run: OTBR_VERBOSE=${RUNNER_DEBUG:-0} script/test ncp_mode + - name: Codecov + uses: codecov/codecov-action@v4 diff --git a/script/test b/script/test index 93f642cb530..ead7d752232 100755 --- a/script/test +++ b/script/test @@ -236,6 +236,9 @@ main() meshcop) top_builddir="${OTBR_TOP_BUILDDIR}" print_result ./tests/scripts/meshcop ;; + ncp_mode) + top_builddir="${OTBR_TOP_BUILDDIR}" print_result ./tests/scripts/ncp_mode + ;; openwrt) print_result ./tests/scripts/openwrt ;; diff --git a/src/ncp/CMakeLists.txt b/src/ncp/CMakeLists.txt index 4fcde6bcf66..10b163b75b8 100644 --- a/src/ncp/CMakeLists.txt +++ b/src/ncp/CMakeLists.txt @@ -27,6 +27,8 @@ # add_library(otbr-ncp + ncp_host.cpp + ncp_host.hpp rcp_host.cpp rcp_host.hpp thread_host.cpp diff --git a/src/ncp/ncp_host.cpp b/src/ncp/ncp_host.cpp new file mode 100644 index 00000000000..1b498ca8daa --- /dev/null +++ b/src/ncp/ncp_host.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024, 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. + */ + +#define OTBR_LOG_TAG "NCP_HOST" + +#include "ncp_host.hpp" + +#include +#include + +#include + +#include "lib/spinel/spinel_driver.hpp" + +namespace otbr { +namespace Ncp { + +NcpHost::NcpHost(bool aDryRun) + : mSpinelDriver(*static_cast(otSysGetSpinelDriver())) +{ + memset(&mConfig, 0, sizeof(mConfig)); + mConfig.mDryRun = aDryRun; + mConfig.mSpeedUpFactor = 1; +} + +const char *NcpHost::GetCoprocessorVersion(void) +{ + return mSpinelDriver.GetVersion(); +} + +void NcpHost::Init(void) +{ + otSysInit(&mConfig); +} + +void NcpHost::Deinit(void) +{ + otSysDeinit(); +} + +void NcpHost::GetDeviceRole(DeviceRoleHandler aHandler) +{ + // TODO: Implement the API with NCP Spinel + aHandler(OT_ERROR_NOT_IMPLEMENTED, OT_DEVICE_ROLE_DISABLED); +} + +void NcpHost::Process(const MainloopContext &aMainloop) +{ + mSpinelDriver.Process(&aMainloop); +} + +void NcpHost::Update(MainloopContext &aMainloop) +{ + mSpinelDriver.GetSpinelInterface()->UpdateFdSet(&aMainloop); + + if (mSpinelDriver.HasPendingFrame()) + { + aMainloop.mTimeout.tv_sec = 0; + aMainloop.mTimeout.tv_usec = 0; + } +} + +} // namespace Ncp +} // namespace otbr diff --git a/src/ncp/ncp_host.hpp b/src/ncp/ncp_host.hpp new file mode 100644 index 00000000000..b261f54c3dd --- /dev/null +++ b/src/ncp/ncp_host.hpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024, 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. + */ + +/** + * @file + * This file includes definitions of OpenThead Host for NCP. + */ + +#ifndef OTBR_AGENT_NCP_HOST_HPP_ +#define OTBR_AGENT_NCP_HOST_HPP_ + +#include "lib/spinel/coprocessor_type.h" +#include "lib/spinel/spinel_driver.hpp" + +#include "common/mainloop.hpp" +#include "ncp/thread_host.hpp" + +namespace otbr { +namespace Ncp { + +class NcpHost : public MainloopProcessor, public ThreadHost +{ +public: + /** + * Constructor. + * + * @param[in] aDryRun TRUE to indicate dry-run mode. FALSE otherwise. + * + */ + NcpHost(bool aDryRun); + + /** + * Destructor. + * + */ + ~NcpHost(void) override = default; + + // ThreadHost methods + void GetDeviceRole(const DeviceRoleHandler aHandler) override; + CoprocessorType GetCoprocessorType(void) override { return OT_COPROCESSOR_NCP; } + const char *GetCoprocessorVersion(void) override; + void Init(void) override; + void Deinit(void) override; + + // MainloopProcessor methods + void Update(MainloopContext &aMainloop) override; + void Process(const MainloopContext &aMainloop) override; + +private: + ot::Spinel::SpinelDriver &mSpinelDriver; + otPlatformConfig mConfig; +}; + +} // namespace Ncp +} // namespace otbr + +#endif // OTBR_AGENT_NCP_HOST_HPP_ diff --git a/src/ncp/thread_host.cpp b/src/ncp/thread_host.cpp index 13cc31d17b1..c7baf97188e 100644 --- a/src/ncp/thread_host.cpp +++ b/src/ncp/thread_host.cpp @@ -35,6 +35,7 @@ #include "lib/spinel/coprocessor_type.h" +#include "ncp_host.hpp" #include "rcp_host.hpp" namespace otbr { @@ -63,14 +64,19 @@ std::unique_ptr ThreadHost::Create(const char * coprocessorType = otSysInitCoprocessor(&urls); - if (coprocessorType == OT_COPROCESSOR_RCP) + switch (coprocessorType) { + case OT_COPROCESSOR_RCP: host = MakeUnique(aInterfaceName, aRadioUrls, aBackboneInterfaceName, aDryRun, aEnableAutoAttach); - } - else - { - // TODO: add NCP type + break; + + case OT_COPROCESSOR_NCP: + host = MakeUnique(aDryRun); + break; + + default: DieNow("Unknown coprocessor type!"); + break; } return host; diff --git a/tests/scripts/expect/ncp_version.exp b/tests/scripts/expect/ncp_version.exp new file mode 100644 index 00000000000..aeea4fd95f7 --- /dev/null +++ b/tests/scripts/expect/ncp_version.exp @@ -0,0 +1,16 @@ +#!/usr/bin/expect -f + +set timeout 1 + +# Spawn the otbr-agent with NCP in Dry Run mode +spawn $::env(EXP_OTBR_AGENT_PATH) -I $::env(EXP_TUN_NAME) -v -d7 --radio-version "spinel+hdlc+forkpty://$::env(EXP_OT_NCP_PATH)?forkpty-arg=$::env(EXP_LEADER_NODE_ID)" + +# Expect the NCP version +expect -re {OPENTHREAD/[0-9a-z]{9}; SIMULATION} { +} timeout { + puts "timeout!" + exit 1 +} + +# Wait for the spawned process to terminate +expect eof diff --git a/tests/scripts/ncp_mode b/tests/scripts/ncp_mode new file mode 100755 index 00000000000..1c157e4482d --- /dev/null +++ b/tests/scripts/ncp_mode @@ -0,0 +1,224 @@ +#!/bin/bash +# +# Copyright (c) 2024, 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. +# +# Test basic functionality of otbr-agent under NCP mode. +# +# Usage: +# ./ncp_mode +set -euxo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +readonly SCRIPT_DIR +EXPECT_SCRIPT_DIR="${SCRIPT_DIR}/expect" +readonly EXPECT_SCRIPT_DIR + +#--------------------------------------- +# Configurations +#--------------------------------------- +OT_NCP="${OT_NCP:-ot-ncp-ftd}" +readonly OT_NCP + +ABS_TOP_BUILDDIR="$(cd "${top_builddir:-"${SCRIPT_DIR}"/../../}" && pwd)" +readonly ABS_TOP_BUILDDIR + +ABS_TOP_SRCDIR="$(cd "${top_srcdir:-"${SCRIPT_DIR}"/../../}" && pwd)" +readonly ABS_TOP_SRCDIR + +ABS_TOP_OT_SRCDIR=${ABS_TOP_SRCDIR}/third_party/openthread/repo +readonly ABS_TOP_OT_SRCDIR + +ABS_TOP_OT_BUILDDIR=${ABS_TOP_BUILDDIR}/../simulation +readonly ABS_TOP_BUILDDIR + +OTBR_COLOR_PASS='\033[0;32m' +readonly OTBR_COLOR_PASS + +OTBR_COLOR_FAIL='\033[0;31m' +readonly OTBR_COLOR_FAIL + +OTBR_COLOR_NONE='\033[0m' +readonly OTBR_COLOR_NONE + +readonly OTBR_VERBOSE=${OTBR_VERBOSE:-0} + +#---------------------------------------- +# Helper functions +#---------------------------------------- +die() +{ + exit_message="$*" + echo " *** ERROR: $*" + exit 1 +} + +exists_or_die() +{ + [[ -f $1 ]] || die "Missing file: $1" +} + +executable_or_die() +{ + [[ -x $1 ]] || die "Missing executable: $1" +} + +write_syslog() +{ + logger -s -p syslog.alert "OTBR_TEST: $*" +} + +#---------------------------------------- +# Test constants +#---------------------------------------- +TEST_BASE=/tmp/test-otbr +readonly TEST_BASE + +OTBR_AGENT=otbr-agent +readonly OTBR_AGENT + +STAGE_DIR="${TEST_BASE}/stage" +readonly STAGE_DIR + +BUILD_DIR="${TEST_BASE}/build" +readonly BUILD_DIR + +OTBR_DBUS_CONF="${ABS_TOP_BUILDDIR}/src/agent/otbr-agent.conf" +readonly OTBR_DBUS_CONF + +OTBR_AGENT_PATH="${ABS_TOP_BUILDDIR}/src/agent/${OTBR_AGENT}" +readonly OTBR_AGENT_PATH + +# The node ids +LEADER_NODE_ID=1 +readonly LEADER_NODE_ID + +# The TUN device for OpenThread border router. +TUN_NAME=wpan0 +readonly TUN_NAME + +#---------------------------------------- +# Test steps +#---------------------------------------- +build_ot_simulation() +{ + "${ABS_TOP_OT_SRCDIR}"/script/cmake-build simulation -DOT_APP_CLI=OFF -DOT_MTD=OFF + ot_ncp=$(find "${ABS_TOP_OT_BUILDDIR}" -name "${OT_NCP}") +} + +test_setup() +{ + executable_or_die "${OTBR_AGENT_PATH}" + + # Remove flashes + sudo rm -vrf "${TEST_BASE}/tmp" + # OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK + sudo rm -vf "/tmp/openthread.lock" + + build_ot_simulation + + # We will be creating a lot of log information + # Rotate logs so we have a clean and empty set of logs uncluttered with other stuff + if [[ -f /etc/logrotate.conf ]]; then + sudo logrotate -f /etc/logrotate.conf || true + fi + + # Preparation for otbr-agent + exists_or_die "${OTBR_DBUS_CONF}" + sudo cp "${OTBR_DBUS_CONF}" /etc/dbus-1/system.d + + write_syslog "AGENT: kill old" + sudo killall "${OTBR_AGENT}" || true + + # From now on - all exits are TRAPPED + # When they occur, we call the function: output_logs'. + trap test_teardown EXIT +} + +test_teardown() +{ + exit_message="Test teardown" + + # Capture the exit code so we can return it below + EXIT_CODE=$? + readonly EXIT_CODE + write_syslog "EXIT ${EXIT_CODE} - output logs" + + sudo pkill -f "${OTBR_AGENT}" || true + wait + + echo 'clearing all' + sudo rm /etc/dbus-1/system.d/otbr-agent.conf || true + sudo rm -rf "${STAGE_DIR}" || true + sudo rm -rf "${BUILD_DIR}" || true + sudo rm -rf "${ABS_TOP_OT_BUILDDIR}" || true + + echo "EXIT ${EXIT_CODE}: MESSAGE: ${exit_message}" + exit ${EXIT_CODE} +} + +otbr_exec_expect_script() +{ + local log_file="tmp/log_expect" + + for script in "$@"; do + echo -e "\n${OTBR_COLOR_PASS}EXEC${OTBR_COLOR_NONE} ${script}" + sudo killall ot-rcp || true + sudo killall ot-cli || true + sudo killall ot-cli-ftd || true + sudo killall ot-cli-mtd || true + sudo killall ot-ncp-ftd || true + sudo killall ot-ncp-mtd || true + sudo rm -rf tmp + mkdir tmp + { + sudo -E expect -df "${script}" 2>"${log_file}" + } || { + local EXIT_CODE=$? + + echo -e "\n${OTBR_COLOR_FAIL}FAIL${OTBR_COLOR_NONE} ${script}" + cat "${log_file}" >&2 + return "${EXIT_CODE}" + } + echo -e "\n${OTBR_COLOR_PASS}PASS${OTBR_COLOR_NONE} ${script}" + if [[ ${OTBR_VERBOSE} == 1 ]]; then + cat "${log_file}" >&2 + fi + done +} + +main() +{ + test_setup + + export EXP_OTBR_AGENT_PATH="${OTBR_AGENT_PATH}" + export EXP_TUN_NAME="${TUN_NAME}" + export EXP_LEADER_NODE_ID="${LEADER_NODE_ID}" + export EXP_OT_NCP_PATH="${ot_ncp}" + otbr_exec_expect_script "${EXPECT_SCRIPT_DIR}/ncp_version.exp" +} + +main "$@"