diff --git a/.github/workflows/toranj.yml b/.github/workflows/toranj.yml index bed59676b3e..3d95576fe76 100644 --- a/.github/workflows/toranj.yml +++ b/.github/workflows/toranj.yml @@ -190,6 +190,30 @@ jobs: run: | ./tests/toranj/build.sh posix-15.4 + nexus: + name: nexus + runs-on: ubuntu-20.04 + steps: + - name: Harden Runner + uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + submodules: true + - name: Bootstrap + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + sudo apt-get update + sudo apt-get --no-install-recommends install -y clang-10 clang++-10 ninja-build llvm lcov + sudo apt-get --no-install-recommends install -y g++-multilib + - name: Build & Run + run: | + ./tests/nexus/build.sh + ninja test + upload-coverage: needs: - toranj-cli diff --git a/CMakeLists.txt b/CMakeLists.txt index 54aa143b882..72cfd88815a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,11 @@ if(OT_PLATFORM STREQUAL "posix") add_subdirectory("${PROJECT_SOURCE_DIR}/src/posix/platform") elseif(OT_PLATFORM STREQUAL "external") # skip in this case +elseif(OT_PLATFORM STREQUAL "nexus") + if (OT_APP_CLI OR OT_APP_NCP OR OT_APP_RCP) + message(FATAL_ERROR "no app (cli/ncp/rcp) should be enabled with nexus simulation platform") + endif() + target_compile_definitions(ot-config INTERFACE OPENTHREAD_PLATFORM_NEXUS=1) else() target_include_directories(ot-config INTERFACE ${PROJECT_SOURCE_DIR}/examples/platforms/${OT_PLATFORM}) add_subdirectory("${PROJECT_SOURCE_DIR}/examples/platforms/${OT_PLATFORM}") diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index d85360b1e32..2a2d4c59828 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -265,7 +265,7 @@ message(STATUS "- - - - - - - - - - - - - - - - ") # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Get a list of the available platforms and output as a list to the 'arg_platforms' argument function(ot_get_platforms arg_platforms) - list(APPEND result "NO" "posix" "external") + list(APPEND result "NO" "posix" "external" "nexus") set(platforms_dir "${PROJECT_SOURCE_DIR}/examples/platforms") file(GLOB platforms RELATIVE "${platforms_dir}" "${platforms_dir}/*") foreach(platform IN LISTS platforms) diff --git a/src/core/instance/instance.hpp b/src/core/instance/instance.hpp index 7ad5bc1a033..188609b4b6c 100644 --- a/src/core/instance/instance.hpp +++ b/src/core/instance/instance.hpp @@ -252,6 +252,15 @@ class Instance : public otInstance, private NonCopyable */ uint32_t GetId(void) const { return mId; } +#if OPENTHREAD_PLATFORM_NEXUS + /** + * Sets the instance identifier. + * + * @param[in] aId The identifier to assign to the `Instance`. + */ + void SetId(uint32_t aId) { mId = aId; } +#endif + /** * Indicates whether or not the instance is valid/initialized and not yet finalized. * @@ -412,9 +421,23 @@ class Instance : public otInstance, private NonCopyable */ template inline Type &Get(void); +#if OPENTHREAD_PLATFORM_NEXUS + /** + * Constructor to initialize an `Instance` + */ + Instance(void); + + /** + * Called after constructor initialization. + */ + void AfterInit(void); +#endif + private: +#if !OPENTHREAD_PLATFORM_NEXUS Instance(void); void AfterInit(void); +#endif // Order of variables (their initialization in `Instance`) // is important. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 47424b8ee10..4fde2520839 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,7 +28,7 @@ option(OT_BUILD_GTEST "enable gtest") -if(OT_FTD AND BUILD_TESTING) +if(OT_FTD AND BUILD_TESTING AND (NOT OT_PLATFORM STREQUAL "nexus")) add_subdirectory(unit) if(OT_BUILD_GTEST) add_subdirectory(gtest) @@ -40,3 +40,7 @@ option(OT_FUZZ_TARGETS "enable fuzz targets" OFF) if(OT_FUZZ_TARGETS) add_subdirectory(fuzz) endif() + +if(OT_PLATFORM STREQUAL "nexus") + add_subdirectory(nexus) +endif() diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt new file mode 100644 index 00000000000..4421d9a444b --- /dev/null +++ b/tests/nexus/CMakeLists.txt @@ -0,0 +1,112 @@ +# +# 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. +# + +set(COMMON_INCLUDES + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/src/core + ${PROJECT_SOURCE_DIR}/tests/nexus/platform +) + +set(COMMON_COMPILE_OPTIONS + -DOPENTHREAD_FTD=1 + -DOPENTHREAD_MTD=0 + -DOPENTHREAD_RADIO=0 +) + +add_library(ot-nexus-platform + platform/nexus_alarm.cpp + platform/nexus_core.cpp + platform/nexus_misc.cpp + platform/nexus_node.cpp + platform/nexus_radio.cpp + platform/nexus_settings.cpp +) + +target_include_directories(ot-nexus-platform + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-nexus-platform + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(ot-nexus-platform + PRIVATE + ot-config + ${OT_MBEDTLS} +) + +set(COMMON_LIBS + ot-nexus-platform + openthread-ftd + ot-nexus-platform + ${OT_MBEDTLS} + ot-config + openthread-ftd +) + +#---------------------------------------------------------------------------------------------------------------------- + +macro(ot_nexus_test name) + + # Macro to add an OpenThread nexus test. + # + # Nexus test name will be `nexus_{name}`. Test source file of + # `test_{name}.cpp` is used. Optional extra arguments can be + # passed to provide additional source files. + + add_executable(nexus_${name} + test_${name}.cpp ${ARGN} + ) + + target_include_directories(nexus_${name} + PRIVATE + ${COMMON_INCLUDES} + ) + + target_link_libraries(nexus_${name} + PRIVATE + ${COMMON_LIBS} + ) + + target_compile_options(nexus_${name} + PRIVATE + ${COMMON_COMPILE_OPTIONS} + ) + + add_test(NAME nexus_${name} COMMAND nexus_${name}) +endmacro() + + +#---------------------------------------------------------------------------------------------------------------------- + +ot_nexus_test(form_join) +ot_nexus_test(large_network) diff --git a/tests/nexus/README.md b/tests/nexus/README.md new file mode 100644 index 00000000000..2e836037b04 --- /dev/null +++ b/tests/nexus/README.md @@ -0,0 +1,29 @@ +# Nexus test framework + +Nexus is a test framework for OpenThread testing. + +### Design Goals + +- **Faster and more scalable network simulation**: Enable faster and more efficient simulations of OpenThread networks involving a large number of nodes over extended durations. +- **Enhanced control**: Achieve greater control and scalability over simulated tests. + +### Features + +- Includes the Nexus platform implementation that emulates platform behavior, allowing multiple nodes running the OpenThread core stack to be simulated and interact with each other within the same process. +- Unlike the simulation platform (under `examples/platforms/simulation`), where nodes run in separate processes and interact via POSIX sockets, Nexus nodes are simulated within a single process. +- Nexus tests can interact directly with the C++ or C OT core APIs, providing more control than the simulation platform's CLI-based interactions. +- The flow of time in Nexus tests is directly controlled by the test itself, allowing for quick time interval advancement. + +### How to build and run tests + +To build Nexus test cases, the `build.sh` script can be used: + +```bash +./tests/nexus/build.sh +``` + +Afterwards, each test can be run directly: + +```bash +./tests/nexus/nexus_form_join +``` diff --git a/tests/nexus/build.sh b/tests/nexus/build.sh new file mode 100755 index 00000000000..738ef2adb58 --- /dev/null +++ b/tests/nexus/build.sh @@ -0,0 +1,66 @@ +#!/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. +# + +display_usage() +{ + echo "" + echo "Nexus build script " + echo "" + echo "" +} + +die() +{ + echo " *** ERROR: " "$*" + exit 1 +} + +cd "$(dirname "$0")" || die "cd failed" +cd ../.. || die "cd failed" + +if [ -n "${top_builddir}" ]; then + top_srcdir=$(pwd) + mkdir -p "${top_builddir}" +else + top_srcdir=. + top_builddir=. +fi + +echo "====================================================================================================" +echo "Building OpenThread Nexus test platform" +echo "====================================================================================================" +cd "${top_builddir}" || die "cd failed" +cmake -GNinja -DOT_PLATFORM=nexus -DOT_COMPILE_WARNING_AS_ERROR=ON \ + -DOT_MULTIPLE_INSTANCE=ON \ + -DOT_THREAD_VERSION=1.4 -DOT_APP_CLI=OFF -DOT_APP_NCP=OFF -DOT_APP_RCP=OFF \ + -DOT_PROJECT_CONFIG=../tests/nexus/openthread-core-nexus-config.h \ + "${top_srcdir}" || die +ninja || die + +exit 0 diff --git a/tests/nexus/openthread-core-nexus-config.h b/tests/nexus/openthread-core-nexus-config.h new file mode 100644 index 00000000000..8d0cb3bb193 --- /dev/null +++ b/tests/nexus/openthread-core-nexus-config.h @@ -0,0 +1,133 @@ +/* + * 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. + */ + +/** + * This header file defines the OpenThread core configuration for Nexus simulation + * + */ + +#ifndef OT_CORE_NEXUS_CONFIG_H_ +#define OT_CORE_NEXUS_CONFIG_H_ + +#ifndef OPENTHREAD_RADIO +#define OPENTHREAD_RADIO 0 +#endif + +#ifndef OPENTHREAD_RADIO +#define OPENTHREAD_RADIO_CLI 0 +#endif + +#define OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE 1 +#define OPENTHREAD_CONFIG_BLE_TCAT_ENABLE 0 +#define OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE 1 +#define OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE 1 +#define OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE 1 +#define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 1 +#define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE 0 +#define OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE 0 +#define OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE 0 +#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE 1 +#define OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE 1 +#define OPENTHREAD_CONFIG_COAP_API_ENABLE 1 +#define OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE 1 +#define OPENTHREAD_CONFIG_COMMISSIONER_ENABLE 1 +#define OPENTHREAD_CONFIG_COMMISSIONER_MAX_JOINER_ENTRIES 4 +#define OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE 1 +#define OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE 1 +#define OPENTHREAD_CONFIG_DIAG_ENABLE 0 +#define OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE 1 +#define OPENTHREAD_CONFIG_DNS_DSO_ENABLE 0 +#define OPENTHREAD_CONFIG_DNSSD_DISCOVERY_PROXY_ENABLE 0 +#define OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE 1 +#define OPENTHREAD_CONFIG_ECDSA_ENABLE 1 +#define OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE 1 +#define OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE 1 +#define OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE 1 +#define OPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS 4 +#define OPENTHREAD_CONFIG_IP6_MAX_EXT_UCAST_ADDRS 8 +#define OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE 1 +#define OPENTHREAD_CONFIG_IP6_SLAAC_NUM_ADDRESSES 4 +#define OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE 1 +#define OPENTHREAD_CONFIG_JOINER_ENABLE 1 +#define OPENTHREAD_CONFIG_LOG_LEVEL OT_LOG_LEVEL_INFO +#define OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE 1 +#define OPENTHREAD_CONFIG_LOG_LEVEL_INIT OT_LOG_LEVEL_CRIT +#define OPENTHREAD_CONFIG_LOG_OUTPUT OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED +#define OPENTHREAD_CONFIG_LOG_PLATFORM 0 +#define OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL 1 +#define OPENTHREAD_CONFIG_LOG_PREPEND_UPTIME 0 +#define OPENTHREAD_CONFIG_LOG_SUFFIX "" +#define OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE 0 +#define OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE 0 +#define OPENTHREAD_CONFIG_MAC_FILTER_ENABLE 1 +#define OPENTHREAD_CONFIG_MAC_FILTER_SIZE 80 +#define OPENTHREAD_CONFIG_MESH_DIAG_ENABLE 1 +#define OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE 1 +#define OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE 1 +#define OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH 1 +#define OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD 10 +#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE 0 +#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE 0 +#define OPENTHREAD_CONFIG_MLE_MAX_CHILDREN 128 +#define OPENTHREAD_CONFIG_MULTICAST_DNS_AUTO_ENABLE_ON_INFRA_IF 0 +#define OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE 0 +#define OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE 0 +#define OPENTHREAD_CONFIG_MULTICAST_DNS_PUBLIC_API_ENABLE 0 +#define OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE 0 +#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE OPENTHREAD_FTD +#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL "Nexus Simulation" +#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "OpenThread by Google Nest" +#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_SW_VERSION "OT-simul-nexus" +#define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE 1 +#define OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS 256 +#define OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION 0 +#define OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE 0 +#define OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE 0 +#define OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE 0 +#define OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE 0 +#define OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE 0 +#define OPENTHREAD_CONFIG_RADIO_STATS_ENABLE 0 +#define OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE 1 +#define OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE 0 +#define OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE 1 +#define OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE 0 +#define OPENTHREAD_CONFIG_SRP_SERVER_ENABLE 1 +#define OPENTHREAD_CONFIG_TCP_ENABLE 0 +#define OPENTHREAD_CONFIG_TLS_ENABLE 0 +#define OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_ENTRIES 256 +#define OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_MAX_SNOOP_ENTRIES 16 +#define OPENTHREAD_CONFIG_TMF_ADDRESS_QUERY_INITIAL_RETRY_DELAY 4 +#define OPENTHREAD_CONFIG_TMF_ADDRESS_QUERY_MAX_RETRY_DELAY 120 +#define OPENTHREAD_CONFIG_TMF_ADDRESS_QUERY_TIMEOUT 6 +#define OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE 1 +#define OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE 1 +#define OPENTHREAD_CONFIG_TMF_NETDIAG_CLIENT_ENABLE 1 +#define OPENTHREAD_CONFIG_TMF_SNOOP_CACHE_ENTRY_TIMEOUT 3 +#define OPENTHREAD_CONFIG_UPTIME_ENABLE 1 + +#endif /* OT_CORE_NEXUS_CONFIG_H_ */ diff --git a/tests/nexus/platform/nexus_alarm.cpp b/tests/nexus/platform/nexus_alarm.cpp new file mode 100644 index 00000000000..673f8652848 --- /dev/null +++ b/tests/nexus/platform/nexus_alarm.cpp @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#include + +#include "nexus_alarm.hpp" +#include "nexus_core.hpp" +#include "nexus_node.hpp" + +namespace ot { +namespace Nexus { + +//--------------------------------------------------------------------------------------------------------------------- +// otPlatAlarmMilli APIs + +extern "C" { + +uint32_t otPlatAlarmMilliGetNow(void) { return Core::Get().GetNow().GetValue(); } + +void otPlatAlarmMilliStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt) +{ + Alarm &alarm = AsNode(aInstance).mAlarm; + + alarm.mScheduled = true; + alarm.mAlarmTime.SetValue(aT0 + aDt); + + Core::Get().UpdateNextAlarmTime(alarm); +} + +void otPlatAlarmMilliStop(otInstance *aInstance) { AsNode(aInstance).mAlarm.mScheduled = false; } + +} // extern "C" + +} // namespace Nexus +} // namespace ot diff --git a/tests/nexus/platform/nexus_alarm.hpp b/tests/nexus/platform/nexus_alarm.hpp new file mode 100644 index 00000000000..baef030b38c --- /dev/null +++ b/tests/nexus/platform/nexus_alarm.hpp @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#ifndef OT_NEXUS_ALARM_HPP_ +#define OT_NEXUS_ALARM_HPP_ + +#include "instance/instance.hpp" + +namespace ot { +namespace Nexus { + +struct Alarm +{ + bool ShouldTrigger(Time aNow) const { return mScheduled && (aNow >= mAlarmTime); } + + bool mScheduled; + Time mAlarmTime; +}; + +} // namespace Nexus +} // namespace ot + +#endif // OT_NEXUS_ALARM_HPP_ diff --git a/tests/nexus/platform/nexus_core.cpp b/tests/nexus/platform/nexus_core.cpp new file mode 100644 index 00000000000..37b7eef9893 --- /dev/null +++ b/tests/nexus/platform/nexus_core.cpp @@ -0,0 +1,205 @@ +/* + * 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. + */ + +#include "nexus_core.hpp" +#include "nexus_node.hpp" + +namespace ot { +namespace Nexus { + +Core *Core::sCore = nullptr; + +Core::Core(void) + : mNow(0) + , mCurNodeId(0) + , mPendingAction(false) +{ + VerifyOrQuit(sCore == nullptr); + sCore = this; + + mNextAlarmTime = mNow.GetDistantFuture(); +} + +Node &Core::CreateNode(void) +{ + Node *node; + + node = Node::Allocate(); + VerifyOrQuit(node != nullptr); + + node->GetInstance().SetId(mCurNodeId++); + + mNodes.Push(*node); + + node->GetInstance().AfterInit(); + + return *node; +} + +void Core::UpdateNextAlarmTime(const Alarm &aAlarm) +{ + if (aAlarm.mScheduled) + { + mNextAlarmTime = Min(mNextAlarmTime, Max(mNow, aAlarm.mAlarmTime)); + } +} + +void Core::AdvanceTime(uint32_t aDuration) +{ + TimeMilli targetTime = mNow + aDuration; + + while (mPendingAction || (mNextAlarmTime <= targetTime)) + { + mNextAlarmTime = mNow.GetDistantFuture(); + mPendingAction = false; + + for (Node &node : mNodes) + { + Process(node); + UpdateNextAlarmTime(node.mAlarm); + } + + if (!mPendingAction) + { + mNow = Min(mNextAlarmTime, targetTime); + } + } + + mNow = targetTime; +} + +void Core::Process(Node &aNode) +{ + otTaskletsProcess(&aNode.GetInstance()); + + ProcessRadio(aNode); + + if (aNode.mAlarm.ShouldTrigger(mNow)) + { + otPlatAlarmMilliFired(&aNode.GetInstance()); + } +} + +void Core::ProcessRadio(Node &aNode) +{ + Mac::Address dstAddr; + uint16_t dstPanId; + bool ackRequested; + AckMode ackMode = kNoAck; + + VerifyOrExit(aNode.mRadio.mState == Radio::kStateTransmit); + + if (aNode.mRadio.mTxFrame.GetDstAddr(dstAddr) != kErrorNone) + { + dstAddr.SetNone(); + } + + if (aNode.mRadio.mTxFrame.GetDstPanId(dstPanId) != kErrorNone) + { + dstPanId = Mac::kPanIdBroadcast; + } + + ackRequested = aNode.mRadio.mTxFrame.GetAckRequest(); + + otPlatRadioTxStarted(&aNode.GetInstance(), &aNode.mRadio.mTxFrame); + + for (Node &rxNode : mNodes) + { + bool matchesDst; + + if ((&rxNode == &aNode) || !rxNode.mRadio.CanReceiveOnChannel(aNode.mRadio.mTxFrame.GetChannel())) + { + continue; + } + + matchesDst = rxNode.mRadio.Matches(dstAddr, dstPanId); + + if (matchesDst || rxNode.mRadio.mPromiscuous) + { + // `rxNode` should receive this frame. + + Radio::Frame rxFrame(aNode.mRadio.mTxFrame); + + rxFrame.mInfo.mRxInfo.mTimestamp = (mNow.GetValue() * 1000u); + rxFrame.mInfo.mRxInfo.mRssi = kDefaultRxRssi; + rxFrame.mInfo.mRxInfo.mLqi = 0; + + if (matchesDst && !dstAddr.IsNone() && !dstAddr.IsBroadcast() && ackRequested) + { + Mac::Address srcAddr; + + ackMode = kSendAckNoFramePending; + + if ((aNode.mRadio.mTxFrame.GetSrcAddr(srcAddr) == kErrorNone) && + rxNode.mRadio.HasFramePendingFor(srcAddr)) + { + ackMode = kSendAckFramePending; + rxFrame.mInfo.mRxInfo.mAckedWithFramePending = true; + } + } + + otPlatRadioReceiveDone(&rxNode.GetInstance(), &rxFrame, kErrorNone); + } + + if (ackMode != kNoAck) + { + // No need to go through rest of `mNodes` + // if already acked by a node. + break; + } + } + + aNode.mRadio.mChannel = aNode.mRadio.mTxFrame.mChannel; + aNode.mRadio.mState = Radio::kStateReceive; + + if (ackMode != kNoAck) + { + Mac::TxFrame ackFrame; + uint8_t ackPsdu[Mac::Frame::kImmAckLength]; + + ClearAllBytes(ackFrame); + ackFrame.mPsdu = ackPsdu; + + ackFrame.GenerateImmAck( + static_cast(static_cast(aNode.mRadio.mTxFrame)), + (ackMode == kSendAckFramePending)); + + otPlatRadioTxDone(&aNode.GetInstance(), &aNode.mRadio.mTxFrame, &ackFrame, kErrorNone); + } + else + { + otPlatRadioTxDone(&aNode.GetInstance(), &aNode.mRadio.mTxFrame, nullptr, + ackRequested ? kErrorNoAck : kErrorNone); + } + +exit: + return; +} + +} // namespace Nexus +} // namespace ot diff --git a/tests/nexus/platform/nexus_core.hpp b/tests/nexus/platform/nexus_core.hpp new file mode 100644 index 00000000000..f164e5bc33a --- /dev/null +++ b/tests/nexus/platform/nexus_core.hpp @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#ifndef OT_NEXUS_CORE_HPP_ +#define OT_NEXUS_CORE_HPP_ + +#include "common/owning_list.hpp" +#include "instance/instance.hpp" + +#include "nexus_alarm.hpp" +#include "nexus_radio.hpp" +#include "nexus_utils.hpp" + +namespace ot { +namespace Nexus { + +class Node; + +class Core +{ +public: + Core(void); + + static Core &Get(void) { return *sCore; } + + Node &CreateNode(void); + LinkedList &GetNodes(void) { return mNodes; } + + TimeMilli GetNow(void) { return mNow; } + void AdvanceTime(uint32_t aDuration); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Used by platform implementation + + void SetActiveNode(Node *aNode) { mActiveNode = aNode; } + Node *GetActiveNode(void) { return mActiveNode; } + + void UpdateNextAlarmTime(const Alarm &aAlarm); + void MarkPendingAction(void) { mPendingAction = true; } + +private: + static constexpr int8_t kDefaultRxRssi = -20; + + enum AckMode : uint8_t + { + kNoAck, + kSendAckNoFramePending, + kSendAckFramePending, + }; + + void Process(Node &aNode); + void ProcessRadio(Node &aNode); + + static Core *sCore; + + OwningList mNodes; + uint16_t mCurNodeId; + bool mPendingAction; + TimeMilli mNow; + TimeMilli mNextAlarmTime; + Node *mActiveNode; +}; + +void Log(const char *aFormat, ...); + +} // namespace Nexus +} // namespace ot + +#endif // OT_NEXUS_CORE_HPP_ diff --git a/tests/nexus/platform/nexus_misc.cpp b/tests/nexus/platform/nexus_misc.cpp new file mode 100644 index 00000000000..09919940f36 --- /dev/null +++ b/tests/nexus/platform/nexus_misc.cpp @@ -0,0 +1,154 @@ +/* + * 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. + */ + +#include +#include + +#include +#include +#include + +#include "nexus_core.hpp" +#include "nexus_node.hpp" + +namespace ot { +namespace Nexus { + +static void LogVarArgs(Node *aActiveNode, const char *aFormat, va_list aArgs); + +extern "C" { + +//--------------------------------------------------------------------------------------------------------------------- +// otTasklets + +void otTaskletsSignalPending(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + Core::Get().MarkPendingAction(); +} + +//--------------------------------------------------------------------------------------------------------------------- +// otPlatLog + +void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...) +{ + OT_UNUSED_VARIABLE(aLogLevel); + OT_UNUSED_VARIABLE(aLogRegion); + + va_list args; + + va_start(args, aFormat); + LogVarArgs(Core::Get().GetActiveNode(), aFormat, args); + va_end(args); +} + +//--------------------------------------------------------------------------------------------------------------------- +// Heap allocation APIs + +void *otPlatCAlloc(size_t aNum, size_t aSize) +{ + void *ptr = calloc(aNum, aSize); + return ptr; +} + +void otPlatFree(void *aPtr) { free(aPtr); } + +//--------------------------------------------------------------------------------------------------------------------- +// Entropy + +otError otPlatEntropyGet(uint8_t *aOutput, uint16_t aOutputLength) +{ + Error error = OT_ERROR_NONE; + FILE *file = nullptr; + size_t readLength; + + file = fopen("/dev/urandom", "rb"); + VerifyOrExit(file != nullptr, error = kErrorFailed); + + readLength = fread(aOutput, 1, aOutputLength, file); + + if (readLength != aOutputLength) + { + error = kErrorFailed; + } + + fclose(file); + +exit: + return error; +} + +//--------------------------------------------------------------------------------------------------------------------- +// Misc + +otError otPlatDiagProcess(otInstance *, uint8_t, char *[]) { return kErrorNotImplemented; } +void otPlatDiagModeSet(bool) {} +bool otPlatDiagModeGet() { return false; } +void otPlatDiagChannelSet(uint8_t) {} +void otPlatDiagTxPowerSet(int8_t) {} +void otPlatDiagRadioReceived(otInstance *, otRadioFrame *, otError) {} +void otPlatDiagAlarmCallback(otInstance *) {} +void otPlatUartSendDone(void) {} +void otPlatUartReceived(const uint8_t *, uint16_t) {} +void otPlatReset(otInstance *) {} +otError otPlatResetToBootloader(otInstance *) { return kErrorNotImplemented; } +otPlatResetReason otPlatGetResetReason(otInstance *) { return OT_PLAT_RESET_REASON_POWER_ON; } +void otPlatWakeHost(void) {} + +} // extern "C" + +//--------------------------------------------------------------------------------------------------------------------- +// Log related function + +void Log(const char *aFormat, ...) +{ + va_list args; + + va_start(args, aFormat); + LogVarArgs(nullptr, aFormat, args); + va_end(args); +} + +static void LogVarArgs(Node *aActiveNode, const char *aFormat, va_list aArgs) +{ + uint32_t now = Core::Get().GetNow().GetValue(); + + printf("%02u:%02u:%02u.%03u ", now / 3600000, (now / 60000) % 60, (now / 1000) % 60, now % 1000); + + if (aActiveNode != nullptr) + { + printf("%03u ", aActiveNode->GetInstance().GetId()); + } + + vprintf(aFormat, aArgs); + printf("\n"); +} + +} // namespace Nexus +} // namespace ot diff --git a/tests/nexus/platform/nexus_node.cpp b/tests/nexus/platform/nexus_node.cpp new file mode 100644 index 00000000000..23e4e37dfdd --- /dev/null +++ b/tests/nexus/platform/nexus_node.cpp @@ -0,0 +1,87 @@ +/* + * 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. + */ + +#include "nexus_node.hpp" +#include "nexus_utils.hpp" + +namespace ot { +namespace Nexus { + +void Node::Form(void) +{ + MeshCoP::Dataset::Info datasetInfo; + + SuccessOrQuit(datasetInfo.GenerateRandom(*this)); + Get().SaveLocal(datasetInfo); + + Get().Up(); + SuccessOrQuit(Get().Start()); +} + +void Node::Join(Node &aNode, JoinMode aJoinMode) +{ + MeshCoP::Dataset dataset; + Mle::DeviceMode mode(0); + + switch (aJoinMode) + { + case kAsFed: + SuccessOrQuit(Get().SetRouterEligible(false)); + OT_FALL_THROUGH; + + case kAsFtd: + mode.Set(Mle::DeviceMode::kModeRxOnWhenIdle | Mle::DeviceMode::kModeFullThreadDevice | + Mle::DeviceMode::kModeFullNetworkData); + break; + case kAsMed: + mode.Set(Mle::DeviceMode::kModeRxOnWhenIdle | Mle::DeviceMode::kModeFullNetworkData); + break; + case kAsSed: + mode.Set(Mle::DeviceMode::kModeFullNetworkData); + break; + } + + SuccessOrQuit(Get().SetDeviceMode(mode)); + + SuccessOrQuit(aNode.Get().Read(dataset)); + Get().SaveLocal(dataset); + + Get().Up(); + SuccessOrQuit(Get().Start()); +} + +void Node::AllowList(Node &aNode) +{ + SuccessOrQuit(Get().AddAddress(aNode.Get().GetExtAddress())); + Get().SetMode(Mac::Filter::kModeAllowlist); +} + +void Node::UnallowList(Node &aNode) { Get().RemoveAddress(aNode.Get().GetExtAddress()); } + +} // namespace Nexus +} // namespace ot diff --git a/tests/nexus/platform/nexus_node.hpp b/tests/nexus/platform/nexus_node.hpp new file mode 100644 index 00000000000..770332a3f63 --- /dev/null +++ b/tests/nexus/platform/nexus_node.hpp @@ -0,0 +1,92 @@ +/* + * 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. + */ + +#ifndef OT_NEXUS_NODE_HPP_ +#define OT_NEXUS_NODE_HPP_ + +#include "instance/instance.hpp" + +#include "nexus_alarm.hpp" +#include "nexus_core.hpp" +#include "nexus_radio.hpp" +#include "nexus_settings.hpp" +#include "nexus_utils.hpp" + +namespace ot { +namespace Nexus { + +class Node : public Heap::Allocatable, public LinkedListEntry, private Instance +{ + friend class Heap::Allocatable; + +public: + enum JoinMode : uint8_t + { + kAsFtd, + kAsFed, + kAsMed, + kAsSed, + }; + + void Form(void); + void Join(Node &aNode, JoinMode aJoinMode = kAsFtd); + void AllowList(Node &aNode); + void UnallowList(Node &aNode); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template Type &Get(void) + { + Core::Get().SetActiveNode(this); + return Instance::Get(); + } + + Instance &GetInstance(void) + { + Core::Get().SetActiveNode(this); + return *this; + } + + static Node &From(otInstance *aInstance) { return static_cast(*aInstance); } + + Node *mNext; + Radio mRadio; + Alarm mAlarm; + Settings mSettings; + bool mPendingTasklet; + +private: + Node(void) = default; +}; + +inline Node &AsNode(otInstance *aInstance) { return Node::From(aInstance); } + +} // namespace Nexus +} // namespace ot + +#endif // OT_NEXUS_NODE_HPP_ diff --git a/tests/nexus/platform/nexus_radio.cpp b/tests/nexus/platform/nexus_radio.cpp new file mode 100644 index 00000000000..5a94c771ba2 --- /dev/null +++ b/tests/nexus/platform/nexus_radio.cpp @@ -0,0 +1,341 @@ +/* + * 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. + */ + +#include + +#include "nexus_core.hpp" +#include "nexus_node.hpp" + +namespace ot { +namespace Nexus { + +//--------------------------------------------------------------------------------------------------------------------- +// otPlatRadio APIs + +extern "C" { + +otRadioCaps otPlatRadioGetCaps(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_RADIO_CAPS_NONE; +} + +int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return Radio::kRadioSensetivity; +} + +void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64) +{ + uint32_t nodeId = AsNode(aInstance).GetInstance().GetId(); + + memset(aIeeeEui64, 0, sizeof(Mac::ExtAddress)); + + aIeeeEui64[6] = (nodeId >> 8) & 0xff; + aIeeeEui64[7] = nodeId & 0xff; +} + +void otPlatRadioSetPanId(otInstance *aInstance, otPanId aPanId) { AsNode(aInstance).mRadio.mPanId = aPanId; } + +void otPlatRadioSetExtendedAddress(otInstance *aInstance, const otExtAddress *aExtAddress) +{ + AsNode(aInstance).mRadio.mExtAddress.Set(aExtAddress->m8, Mac::ExtAddress::kReverseByteOrder); +} + +void otPlatRadioSetShortAddress(otInstance *aInstance, otShortAddress aShortAddress) +{ + AsNode(aInstance).mRadio.mShortAddress = aShortAddress; +} + +bool otPlatRadioGetPromiscuous(otInstance *aInstance) { return AsNode(aInstance).mRadio.mPromiscuous; } + +void otPlatRadioSetPromiscuous(otInstance *aInstance, bool aEnable) { AsNode(aInstance).mRadio.mPromiscuous = aEnable; } + +otRadioState otPlatRadioGetState(otInstance *aInstance) { return AsNode(aInstance).mRadio.mState; } + +otError otPlatRadioEnable(otInstance *aInstance) +{ + Error error = kErrorNone; + Radio &radio = AsNode(aInstance).mRadio; + + VerifyOrExit(radio.mState == Radio::kStateDisabled, error = kErrorFailed); + radio.mState = Radio::kStateSleep; + +exit: + return error; +} + +otError otPlatRadioDisable(otInstance *aInstance) +{ + AsNode(aInstance).mRadio.mState = Radio::kStateDisabled; + return kErrorNone; +} + +bool otPlatRadioIsEnabled(otInstance *aInstance) { return AsNode(aInstance).mRadio.mState != Radio::kStateDisabled; } + +otError otPlatRadioSleep(otInstance *aInstance) +{ + Error error = kErrorNone; + Radio &radio = AsNode(aInstance).mRadio; + + VerifyOrExit(radio.mState != Radio::kStateDisabled, error = kErrorInvalidState); + VerifyOrExit(radio.mState != Radio::kStateTransmit, error = kErrorBusy); + radio.mState = Radio::kStateSleep; + +exit: + return error; +} + +otError otPlatRadioReceive(otInstance *aInstance, uint8_t aChannel) +{ + Error error = kErrorNone; + Radio &radio = AsNode(aInstance).mRadio; + + VerifyOrExit(radio.mState != Radio::kStateDisabled, error = kErrorInvalidState); + radio.mState = Radio::kStateReceive; + radio.mChannel = aChannel; + +exit: + return error; +} + +otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance) { return &AsNode(aInstance).mRadio.mTxFrame; } + +otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aFrame) +{ + Error error = kErrorNone; + Radio &radio = AsNode(aInstance).mRadio; + + VerifyOrExit(radio.mState == Radio::kStateReceive, error = kErrorInvalidState); + OT_ASSERT(aFrame == &AsNode(aInstance).mRadio.mTxFrame); + radio.mState = Radio::kStateTransmit; + + Core::Get().MarkPendingAction(); + +exit: + return error; +} + +int8_t otPlatRadioGetRssi(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return Radio::kRadioSensetivity; +} + +void otPlatRadioEnableSrcMatch(otInstance *aInstance, bool aEnable) +{ + AsNode(aInstance).mRadio.mSrcMatchEnabled = aEnable; +} + +otError otPlatRadioAddSrcMatchShortEntry(otInstance *aInstance, otShortAddress aShortAddress) +{ + Error error = kErrorNone; + Radio &radio = AsNode(aInstance).mRadio; + + VerifyOrExit(!radio.mSrcMatchShortEntries.Contains(aShortAddress)); + error = radio.mSrcMatchShortEntries.PushBack(aShortAddress); + +exit: + return error; +} + +otError otPlatRadioAddSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress) +{ + Error error = kErrorNone; + Radio &radio = AsNode(aInstance).mRadio; + Mac::ExtAddress extAddress; + + extAddress.Set(aExtAddress->m8, Mac::ExtAddress::kReverseByteOrder); + + VerifyOrExit(!radio.mSrcMatchExtEntries.Contains(extAddress)); + error = radio.mSrcMatchExtEntries.PushBack(extAddress); + +exit: + return error; +} + +otError otPlatRadioClearSrcMatchShortEntry(otInstance *aInstance, otShortAddress aShortAddress) +{ + Error error = kErrorNone; + Radio &radio = AsNode(aInstance).mRadio; + uint16_t *entry; + + entry = radio.mSrcMatchShortEntries.Find(aShortAddress); + VerifyOrExit(entry != nullptr, error = kErrorNoAddress); + + radio.mSrcMatchShortEntries.Remove(*entry); + +exit: + return error; +} + +otError otPlatRadioClearSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress) +{ + Error error = kErrorNone; + Radio &radio = AsNode(aInstance).mRadio; + Mac::ExtAddress extAddress; + Mac::ExtAddress *entry; + + extAddress.Set(aExtAddress->m8, Mac::ExtAddress::kReverseByteOrder); + + entry = radio.mSrcMatchExtEntries.Find(extAddress); + VerifyOrExit(entry != nullptr, error = kErrorNoAddress); + + radio.mSrcMatchExtEntries.Remove(*entry); + +exit: + return error; +} + +void otPlatRadioClearSrcMatchShortEntries(otInstance *aInstance) +{ + AsNode(aInstance).mRadio.mSrcMatchShortEntries.Clear(); +} + +void otPlatRadioClearSrcMatchExtEntries(otInstance *aInstance) { AsNode(aInstance).mRadio.mSrcMatchExtEntries.Clear(); } + +// Not supported + +otError otPlatRadioEnergyScan(otInstance *, uint8_t, uint16_t) { return kErrorNotImplemented; } + +otError otPlatRadioGetTransmitPower(otInstance *, int8_t *) { return kErrorNotImplemented; } +otError otPlatRadioSetTransmitPower(otInstance *, int8_t) { return kErrorNotImplemented; } +otError otPlatRadioGetCcaEnergyDetectThreshold(otInstance *, int8_t *) { return kErrorNotImplemented; } +otError otPlatRadioSetCcaEnergyDetectThreshold(otInstance *, int8_t) { return kErrorNotImplemented; } +otError otPlatRadioGetFemLnaGain(otInstance *, int8_t *) { return kErrorNotImplemented; } +otError otPlatRadioSetFemLnaGain(otInstance *, int8_t) { return kErrorNotImplemented; } +bool otPlatRadioIsCoexEnabled(otInstance *) { return false; } +otError otPlatRadioSetCoexEnabled(otInstance *, bool) { return kErrorNotImplemented; } +otError otPlatRadioGetCoexMetrics(otInstance *, otRadioCoexMetrics *) { return kErrorNotImplemented; } +otError otPlatRadioEnableCsl(otInstance *, uint32_t, otShortAddress, const otExtAddress *) { return kErrorNone; } +otError otPlatRadioResetCsl(otInstance *) { return kErrorNotImplemented; } +void otPlatRadioUpdateCslSampleTime(otInstance *, uint32_t) {} +uint8_t otPlatRadioGetCslAccuracy(otInstance *) { return 0; } +otError otPlatRadioSetChannelTargetPower(otInstance *, uint8_t, int16_t) { return kErrorNotImplemented; } +otError otPlatRadioClearCalibratedPowers(otInstance *) { return kErrorNotImplemented; } +otError otPlatRadioAddCalibratedPower(otInstance *, uint8_t, int16_t, const uint8_t *, uint16_t) +{ + return kErrorNotImplemented; +} + +} // extern "C" + +//--------------------------------------------------------------------------------------------------------------------- +// Radio + +bool Radio::CanReceiveOnChannel(uint8_t aChannel) const +{ + bool canRx = false; + + switch (mState) + { + case kStateReceive: + case kStateTransmit: + break; + default: + ExitNow(); + } + + VerifyOrExit(mChannel == aChannel); + canRx = true; + +exit: + return canRx; +} + +bool Radio::Matches(const Mac::Address &aAddress, Mac::PanId aPanId) const +{ + bool matches = false; + + if (aAddress.IsShort()) + { + VerifyOrExit(aAddress.IsBroadcast() || aAddress.GetShort() == mShortAddress); + } + else if (aAddress.IsExtended()) + { + VerifyOrExit(aAddress.GetExtended() == mExtAddress); + } + + if ((aPanId != Mac::kPanIdBroadcast) && (mPanId != Mac::kPanIdBroadcast)) + { + VerifyOrExit(mPanId == aPanId); + } + + matches = true; + +exit: + return matches; +} + +bool Radio::HasFramePendingFor(const Mac::Address &aAddress) const +{ + bool hasPending = false; + + if (!mSrcMatchEnabled) + { + // Always mark frame pending when `SrcMatch` is disabled. + hasPending = true; + ExitNow(); + } + + if (aAddress.IsShort()) + { + hasPending = mSrcMatchShortEntries.Contains(aAddress.GetShort()); + } + else if (aAddress.IsExtended()) + { + hasPending = mSrcMatchExtEntries.Contains(aAddress.GetExtended()); + } + +exit: + return hasPending; +} + +//--------------------------------------------------------------------------------------------------------------------- +// Radio::Frame + +Radio::Frame::Frame(void) +{ + ClearAllBytes(*this); + mPsdu = &mPsduBuffer[0]; +} + +Radio::Frame::Frame(const Frame &aFrame) +{ + ClearAllBytes(*this); + mPsdu = &mPsduBuffer[0]; + + mLength = aFrame.mLength; + mChannel = aFrame.mChannel; + mRadioType = aFrame.mRadioType; + memcpy(mPsdu, aFrame.mPsdu, mLength); +} + +} // namespace Nexus +} // namespace ot diff --git a/tests/nexus/platform/nexus_radio.hpp b/tests/nexus/platform/nexus_radio.hpp new file mode 100644 index 00000000000..d3857fbe25b --- /dev/null +++ b/tests/nexus/platform/nexus_radio.hpp @@ -0,0 +1,80 @@ +/* + * 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. + */ + +#ifndef OT_NEXUS_RADIO_HPP_ +#define OT_NEXUS_RADIO_HPP_ + +#include "instance/instance.hpp" + +namespace ot { +namespace Nexus { + +struct Radio +{ + static constexpr uint16_t kMaxFrameSize = OT_RADIO_FRAME_MAX_SIZE; + + static constexpr uint16_t kMaxSrcMaatchShort = 80; + static constexpr uint16_t kMaxSrcMatchExt = 10; + + static constexpr int8_t kRadioSensetivity = -100; + + using State = otRadioState; + + static constexpr State kStateDisabled = OT_RADIO_STATE_DISABLED; + static constexpr State kStateSleep = OT_RADIO_STATE_SLEEP; + static constexpr State kStateReceive = OT_RADIO_STATE_RECEIVE; + static constexpr State kStateTransmit = OT_RADIO_STATE_TRANSMIT; + + struct Frame : public Mac::Frame + { + Frame(void); + explicit Frame(const Frame &aFrame); + + uint8_t mPsduBuffer[kMaxFrameSize]; + }; + + bool CanReceiveOnChannel(uint8_t aChannel) const; + bool Matches(const Mac::Address &aAddress, Mac::PanId aPanId) const; + bool HasFramePendingFor(const Mac::Address &aAddress) const; + + State mState; + bool mPromiscuous : 1; + bool mSrcMatchEnabled : 1; + uint8_t mChannel; + Mac::PanId mPanId; + Mac::ShortAddress mShortAddress; + Mac::ExtAddress mExtAddress; + Frame mTxFrame; + Array mSrcMatchShortEntries; + Array mSrcMatchExtEntries; +}; + +} // namespace Nexus +} // namespace ot + +#endif // OT_NEXUS_RADIO_HPP_ diff --git a/tests/nexus/platform/nexus_settings.cpp b/tests/nexus/platform/nexus_settings.cpp new file mode 100644 index 00000000000..032ad185219 --- /dev/null +++ b/tests/nexus/platform/nexus_settings.cpp @@ -0,0 +1,160 @@ +/* + * 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. + */ + +#include + +#include "nexus_node.hpp" +#include "nexus_settings.hpp" + +namespace ot { +namespace Nexus { + +//--------------------------------------------------------------------------------------------------------------------- +// otPlatSettings APIs + +extern "C" { + +void otPlatSettingsInit(otInstance *, const uint16_t *, uint16_t) {} +void otPlatSettingsDeinit(otInstance *) {} + +otError otPlatSettingsGet(otInstance *aInstance, uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) +{ + return AsNode(aInstance).mSettings.Get(aKey, aIndex, aValue, aValueLength); +} + +otError otPlatSettingsSet(otInstance *aInstance, uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength) +{ + return AsNode(aInstance).mSettings.SetOrAdd(Settings::kSet, aKey, aValue, aValueLength); +} + +otError otPlatSettingsAdd(otInstance *aInstance, uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength) +{ + return AsNode(aInstance).mSettings.SetOrAdd(Settings::kAdd, aKey, aValue, aValueLength); +} + +otError otPlatSettingsDelete(otInstance *aInstance, uint16_t aKey, int aIndex) +{ + return AsNode(aInstance).mSettings.Delete(aKey, aIndex); +} + +void otPlatSettingsWipe(otInstance *aInstance) { AsNode(aInstance).mSettings.Wipe(); } + +} // extern "C" + +//--------------------------------------------------------------------------------------------------------------------- +// Settings + +Error Settings::Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const +{ + Error error = kErrorNone; + const Entry *entry; + const Entry::Value *value; + IndexMatcher IndexMatcher(aIndex); + + entry = mEntries.FindMatching(aKey); + VerifyOrExit(entry != nullptr, error = kErrorNotFound); + + value = entry->mValues.FindMatching(IndexMatcher); + VerifyOrExit(value != nullptr, error = kErrorNotFound); + + if (aValueLength != nullptr) + { + uint16_t size = *aValueLength; + uint16_t length = value->mData.GetLength(); + + *aValueLength = length; + + if (aValue != nullptr) + { + memcpy(aValue, value->mData.GetBytes(), Min(size, length)); + } + } + +exit: + return error; +} + +Error Settings::SetOrAdd(SetAddMode aMode, uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength) +{ + Entry *entry; + Entry::Value *value; + + entry = mEntries.FindMatching(aKey); + + if (entry == nullptr) + { + entry = Settings::Entry::Allocate(); + VerifyOrQuit(entry != nullptr); + entry->mKey = aKey; + mEntries.Push(*entry); + } + + value = Entry::Value::Allocate(); + VerifyOrQuit(value != nullptr); + SuccessOrQuit(value->mData.SetFrom(aValue, aValueLength)); + + if (aMode == kSet) + { + entry->mValues.Clear(); + } + + entry->mValues.Push(*value); + + return kErrorNone; +} + +Error Settings::Delete(uint16_t aKey, int aIndex) +{ + Error error = kErrorNone; + Entry *entry; + Entry::Value *preValue; + + entry = mEntries.FindMatching(aKey); + VerifyOrExit(entry != nullptr, error = kErrorNotFound); + + if (aIndex < 0) + { + mEntries.RemoveMatching(aKey); + } + else + { + IndexMatcher indexMatcher(aIndex); + OwnedPtr valuePtr; + + valuePtr = entry->mValues.RemoveMatching(indexMatcher); + VerifyOrExit(valuePtr != nullptr, error = kErrorNotFound); + } + +exit: + return error; +} + +void Settings::Wipe(void) { mEntries.Clear(); } + +} // namespace Nexus +} // namespace ot diff --git a/tests/nexus/platform/nexus_settings.hpp b/tests/nexus/platform/nexus_settings.hpp new file mode 100644 index 00000000000..1dfaa629472 --- /dev/null +++ b/tests/nexus/platform/nexus_settings.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. + */ + +#ifndef OT_NEXUS_SETTINGS_HPP_ +#define OT_NEXUS_SETTINGS_HPP_ + +#include "common/const_cast.hpp" +#include "common/owning_list.hpp" +#include "instance/instance.hpp" + +namespace ot { +namespace Nexus { + +struct Settings +{ + enum SetAddMode : uint8_t + { + kSet, + kAdd, + }; + + Error Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const; + Error SetOrAdd(SetAddMode aMode, uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength); + Error Delete(uint16_t aKey, int aIndex); + void Wipe(void); + + struct IndexMatcher + { + IndexMatcher(int aIndex) { mIndex = aIndex; } + + int mIndex; + }; + + struct Entry : public Heap::Allocatable, public LinkedListEntry + { + struct Value : public Heap::Allocatable, public LinkedListEntry + { + bool Matches(const IndexMatcher &aIndexMataher) const { return (AsNonConst(aIndexMataher).mIndex-- == 0); } + + Value *mNext; + Heap::Data mData; + }; + + bool Matches(uint16_t aKey) const { return mKey == aKey; } + + Entry *mNext; + uint16_t mKey; + OwningList mValues; + }; + + OwningList mEntries; +}; + +} // namespace Nexus +} // namespace ot + +#endif // OT_NEXUS_SETTINGS_HPP_ diff --git a/tests/nexus/platform/nexus_utils.hpp b/tests/nexus/platform/nexus_utils.hpp new file mode 100644 index 00000000000..8ec13d55d26 --- /dev/null +++ b/tests/nexus/platform/nexus_utils.hpp @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#ifndef OT_NEXUS_UTIL_HPP_ +#define OT_NEXUS_UTIL_HPP_ + +#include +#include + +#include "common/arg_macros.hpp" + +/** + * Verifies a given error status to be successful (compared against value zero (0)), otherwise, it emits a + * given error messages and exits the program. + * + * @param[in] aStatus A scalar error status to be evaluated against zero (0). + * @param[in] aMessage An optional message (constant C string) to print on failure. + */ +#define SuccessOrQuit(...) \ + do \ + { \ + if ((OT_FIRST_ARG(__VA_ARGS__)) != 0) \ + { \ + fprintf(stderr, "\nFAILED %s:%d - SuccessOrQuit(%s)" OT_SECOND_ARG(__VA_ARGS__) "\n", __FUNCTION__, \ + __LINE__, _Stringize(OT_FIRST_ARG(__VA_ARGS__))); \ + exit(-1); \ + } \ + } while (false) + +/** + * Verifies that a given boolean condition is true, otherwise, it emits a given error message and exits the + * program. + * + * @param[in] aCondition A Boolean expression to be evaluated. + * @param[in] aMessage An optional message (constant C string) to print on failure. + */ +#define VerifyOrQuit(...) \ + do \ + { \ + if (!(OT_FIRST_ARG(__VA_ARGS__))) \ + { \ + fprintf(stderr, "\nFAILED %s:%d - VerifyOrQuit(%s) " OT_SECOND_ARG(__VA_ARGS__) "\n", __FUNCTION__, \ + __LINE__, _Stringize(OT_FIRST_ARG(__VA_ARGS__))); \ + exit(-1); \ + } \ + } while (false) + +// Private macros to convert `aArg` to string +#define _Stringize(aArg) _Stringize2(aArg) +#define _Stringize2(aArg) #aArg + +#endif // OT_NEXUS_UTIL_HPP_ diff --git a/tests/nexus/test_form_join.cpp b/tests/nexus/test_form_join.cpp new file mode 100644 index 00000000000..6973afd2465 --- /dev/null +++ b/tests/nexus/test_form_join.cpp @@ -0,0 +1,124 @@ +/* + * 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. + */ + +#include +#include +#include + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +namespace ot { +namespace Nexus { + +void TestFormJoin(void) +{ + // Validate basic operations, forming a network and + // joining as router, FED, MED, and SED + + Core nexus; + + Node &leader = nexus.CreateNode(); + Node &fed = nexus.CreateNode(); + Node &sed = nexus.CreateNode(); + Node &med = nexus.CreateNode(); + Node &router1 = nexus.CreateNode(); + Node &router2 = nexus.CreateNode(); + + nexus.AdvanceTime(0); + + for (Node &node : nexus.GetNodes()) + { + node.GetInstance().SetLogLevel(kLogLevelInfo); + } + + Log("---------------------------------------------------------------------------------------"); + Log("Form network"); + + leader.Form(); + nexus.AdvanceTime(13 * 1000); + VerifyOrQuit(leader.Get().IsLeader()); + + Log("---------------------------------------------------------------------------------------"); + Log("Join an FED"); + + fed.Join(leader, Node::kAsFed); + nexus.AdvanceTime(2 * 1000); + VerifyOrQuit(fed.Get().IsChild()); + + Log("---------------------------------------------------------------------------------------"); + Log("Join an SED"); + + sed.Join(leader, Node::kAsSed); + nexus.AdvanceTime(2 * 1000); + VerifyOrQuit(sed.Get().IsChild()); + + Log("---------------------------------------------------------------------------------------"); + Log("Join an MED"); + + med.Join(leader, Node::kAsMed); + nexus.AdvanceTime(2 * 1000); + VerifyOrQuit(med.Get().IsChild()); + + Log("---------------------------------------------------------------------------------------"); + Log("Join two routers"); + + router1.Join(leader); + router2.Join(leader); + + Log("---------------------------------------------------------------------------------------"); + Log("Check all nodes roles and device modes"); + + nexus.AdvanceTime(300 * 1000); + + VerifyOrQuit(leader.Get().IsLeader()); + VerifyOrQuit(fed.Get().IsChild()); + VerifyOrQuit(sed.Get().IsChild()); + VerifyOrQuit(router1.Get().IsRouter()); + VerifyOrQuit(router2.Get().IsRouter()); + + VerifyOrQuit(fed.Get().IsRxOnWhenIdle()); + VerifyOrQuit(fed.Get().IsFullThreadDevice()); + + VerifyOrQuit(med.Get().IsRxOnWhenIdle()); + VerifyOrQuit(!med.Get().IsFullThreadDevice()); + VerifyOrQuit(med.Get().IsMinimalEndDevice()); + + VerifyOrQuit(!sed.Get().IsRxOnWhenIdle()); + VerifyOrQuit(!sed.Get().IsFullThreadDevice()); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestFormJoin(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/nexus/test_large_network.cpp b/tests/nexus/test_large_network.cpp new file mode 100644 index 00000000000..9e0d62ef1a3 --- /dev/null +++ b/tests/nexus/test_large_network.cpp @@ -0,0 +1,150 @@ +/* + * 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. + */ + +#include +#include +#include + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +namespace ot { +namespace Nexus { + +static constexpr uint16_t kNumberOfRoles = (Mle::kRoleLeader + 1); + +typedef uint16_t RoleStats[kNumberOfRoles]; + +static void CaculateRoleStats(Core &aNexus, RoleStats &aRoleStats) +{ + ClearAllBytes(aRoleStats); + + for (Node &node : aNexus.GetNodes()) + { + aRoleStats[node.Get().GetRole()]++; + } +} + +static bool CheckRoleStats(const RoleStats &aRoleStats) +{ + return (aRoleStats[Mle::kRoleLeader] == 1) && (aRoleStats[Mle::kRoleDetached] == 0) && + (aRoleStats[Mle::kRoleDisabled] == 0); +} + +void Test(void) +{ + static constexpr uint16_t kNumNodes = 200; + + // All times in msec + static constexpr uint32_t kMaxWaitTime = 20 * Time::kOneMinuteInMsec; + static constexpr uint32_t kStatCollectionInterval = 125; + static constexpr uint32_t kExtraSimulTimAfterPass = 5 * Time::kOneSecondInMsec; + + Core nexus; + Node *leader; + RoleStats roleStats; + + for (uint16_t i = 0; i < kNumNodes; i++) + { + nexus.CreateNode(); + } + + nexus.AdvanceTime(0); + + Log("Starting %u nodes simultaneously", kNumNodes); + + leader = nexus.GetNodes().GetHead(); + leader->Form(); + + for (Node &node : nexus.GetNodes()) + { + if (&node == leader) + { + continue; + } + + node.Join(*leader); + } + + for (uint32_t step = 0; step < kMaxWaitTime / kStatCollectionInterval; step++) + { + if ((step % 20) == 0) + { + Log("+----------+----------+----------+----------+----------+"); + Log("| Leader | Router | Child | Detached | Disabled |"); + Log("+----------+----------+----------+----------+----------+"); + } + + CaculateRoleStats(nexus, roleStats); + + Log("| %8u | %8u | %8u | %8u | %8u |", roleStats[Mle::kRoleLeader], roleStats[Mle::kRoleRouter], + roleStats[Mle::kRoleChild], roleStats[Mle::kRoleDetached], roleStats[Mle::kRoleDisabled]); + + nexus.AdvanceTime(kStatCollectionInterval); + + if (CheckRoleStats(roleStats)) + { + break; + } + } + + VerifyOrQuit(CheckRoleStats(roleStats)); + + Log("========================================================="); + Log("All nodes are now part of the same partition"); + Log("Network stabilized after %u sec", nexus.GetNow().GetValue() / Time::kOneSecondInMsec); + Log("Continue simulation for another %u sec", kExtraSimulTimAfterPass / Time::kOneSecondInMsec); + Log("========================================================="); + + for (uint32_t step = 0; step < kExtraSimulTimAfterPass / kStatCollectionInterval; step++) + { + if ((step % 20) == 0) + { + Log("+----------+----------+----------+----------+----------+"); + Log("| Leader | Router | Child | Detached | Disabled |"); + Log("+----------+----------+----------+----------+----------+"); + } + + CaculateRoleStats(nexus, roleStats); + + Log("| %8u | %8u | %8u | %8u | %8u |", roleStats[Mle::kRoleLeader], roleStats[Mle::kRoleRouter], + roleStats[Mle::kRoleChild], roleStats[Mle::kRoleDetached], roleStats[Mle::kRoleDisabled]); + + nexus.AdvanceTime(kStatCollectionInterval); + } +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::Test(); + printf("All tests passed\n"); + return 0; +}