From 176874028468a50846856f3c72af34e8b2df2077 Mon Sep 17 00:00:00 2001 From: zhhyu7 Date: Mon, 13 May 2024 21:02:06 +0800 Subject: [PATCH] Added compile support for NuttX system. (#31236) This is a basic version of compile support and only x86 simulator lightning-app is supported now The compilation method is shown below: ./scripts/build/build_examples.py --target nuttx-x64-light build and compiled binaries are in the out/nuttx-x64-light directory. Detailed introduction about NuttX system can refer to this link: https://github.com/apache/nuttx Signed-off-by: zhanghongyu --- .github/.wordlist.txt | 1 + .github/workflows/examples-nuttx.yaml | 55 + .github/workflows/lint.yml | 3 +- build/config/compiler/BUILD.gn | 5 + config/nuttx/chip-gn/.gn | 29 + config/nuttx/chip-gn/BUILD.gn | 57 + config/nuttx/chip-gn/args.gni | 32 + config/nuttx/chip-gn/toolchain/BUILD.gn | 33 + examples/lighting-app/linux/main.cpp | 2 +- scripts/build/build/targets.py | 18 + scripts/build/builders/nuttx.py | 98 + .../build/testdata/all_targets_linux_x64.txt | 1 + scripts/checkout_submodules.py | 1 + scripts/tools/check_includes_config.py | 1 + src/app/BUILD.gn | 1 + src/inet/BUILD.gn | 3 + src/lib/core/CHIPError.h | 8 +- src/platform/BUILD.gn | 9 + src/platform/NuttX/BLEManagerImpl.cpp | 897 ++++++++ src/platform/NuttX/BLEManagerImpl.h | 241 +++ src/platform/NuttX/BUILD.gn | 160 ++ src/platform/NuttX/BlePlatformConfig.h | 41 + src/platform/NuttX/CHIPDevicePlatformConfig.h | 73 + src/platform/NuttX/CHIPDevicePlatformEvent.h | 108 + src/platform/NuttX/CHIPLinuxStorage.cpp | 353 +++ src/platform/NuttX/CHIPLinuxStorage.h | 98 + src/platform/NuttX/CHIPLinuxStorageIni.cpp | 404 ++++ src/platform/NuttX/CHIPLinuxStorageIni.h | 63 + src/platform/NuttX/CHIPPlatformConfig.h | 71 + .../NuttX/ConfigurationManagerImpl.cpp | 385 ++++ src/platform/NuttX/ConfigurationManagerImpl.h | 95 + .../NuttX/ConnectivityManagerImpl.cpp | 1904 +++++++++++++++++ src/platform/NuttX/ConnectivityManagerImpl.h | 320 +++ src/platform/NuttX/ConnectivityUtils.cpp | 734 +++++++ src/platform/NuttX/ConnectivityUtils.h | 69 + .../NuttX/DeviceInstanceInfoProviderImpl.cpp | 37 + .../NuttX/DeviceInstanceInfoProviderImpl.h | 44 + .../NuttX/DiagnosticDataProviderImpl.cpp | 830 +++++++ .../NuttX/DiagnosticDataProviderImpl.h | 117 + src/platform/NuttX/DnssdImpl.cpp | 1061 +++++++++ src/platform/NuttX/DnssdImpl.h | 205 ++ src/platform/NuttX/InetPlatformConfig.h | 48 + .../NuttX/KeyValueStoreManagerImpl.cpp | 115 + src/platform/NuttX/KeyValueStoreManagerImpl.h | 79 + src/platform/NuttX/Logging.cpp | 89 + .../NuttX/NetworkCommissioningDriver.h | 241 +++ .../NetworkCommissioningEthernetDriver.cpp | 43 + .../NetworkCommissioningThreadDriver.cpp | 223 ++ .../NuttX/NetworkCommissioningWiFiDriver.cpp | 361 ++++ src/platform/NuttX/OTAImageProcessorImpl.cpp | 279 +++ src/platform/NuttX/OTAImageProcessorImpl.h | 75 + src/platform/NuttX/PlatformManagerImpl.cpp | 327 +++ src/platform/NuttX/PlatformManagerImpl.h | 159 ++ src/platform/NuttX/PosixConfig.cpp | 588 +++++ src/platform/NuttX/PosixConfig.h | 131 ++ src/platform/NuttX/README.md | 14 + src/platform/NuttX/SystemPlatformConfig.h | 44 + src/platform/NuttX/SystemTimeSupport.cpp | 105 + src/platform/NuttX/ThreadStackManagerImpl.cpp | 770 +++++++ src/platform/NuttX/ThreadStackManagerImpl.h | 179 ++ src/platform/NuttX/WirelessDefs.h | 186 ++ src/platform/NuttX/args.gni | 15 + src/platform/device.gni | 11 +- 63 files changed, 12740 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/examples-nuttx.yaml create mode 100644 config/nuttx/chip-gn/.gn create mode 100644 config/nuttx/chip-gn/BUILD.gn create mode 100644 config/nuttx/chip-gn/args.gni create mode 100644 config/nuttx/chip-gn/toolchain/BUILD.gn create mode 100644 scripts/build/builders/nuttx.py create mode 100644 src/platform/NuttX/BLEManagerImpl.cpp create mode 100644 src/platform/NuttX/BLEManagerImpl.h create mode 100644 src/platform/NuttX/BUILD.gn create mode 100644 src/platform/NuttX/BlePlatformConfig.h create mode 100644 src/platform/NuttX/CHIPDevicePlatformConfig.h create mode 100644 src/platform/NuttX/CHIPDevicePlatformEvent.h create mode 100644 src/platform/NuttX/CHIPLinuxStorage.cpp create mode 100644 src/platform/NuttX/CHIPLinuxStorage.h create mode 100644 src/platform/NuttX/CHIPLinuxStorageIni.cpp create mode 100644 src/platform/NuttX/CHIPLinuxStorageIni.h create mode 100644 src/platform/NuttX/CHIPPlatformConfig.h create mode 100644 src/platform/NuttX/ConfigurationManagerImpl.cpp create mode 100644 src/platform/NuttX/ConfigurationManagerImpl.h create mode 100644 src/platform/NuttX/ConnectivityManagerImpl.cpp create mode 100644 src/platform/NuttX/ConnectivityManagerImpl.h create mode 100644 src/platform/NuttX/ConnectivityUtils.cpp create mode 100644 src/platform/NuttX/ConnectivityUtils.h create mode 100644 src/platform/NuttX/DeviceInstanceInfoProviderImpl.cpp create mode 100644 src/platform/NuttX/DeviceInstanceInfoProviderImpl.h create mode 100644 src/platform/NuttX/DiagnosticDataProviderImpl.cpp create mode 100644 src/platform/NuttX/DiagnosticDataProviderImpl.h create mode 100644 src/platform/NuttX/DnssdImpl.cpp create mode 100644 src/platform/NuttX/DnssdImpl.h create mode 100644 src/platform/NuttX/InetPlatformConfig.h create mode 100644 src/platform/NuttX/KeyValueStoreManagerImpl.cpp create mode 100644 src/platform/NuttX/KeyValueStoreManagerImpl.h create mode 100644 src/platform/NuttX/Logging.cpp create mode 100644 src/platform/NuttX/NetworkCommissioningDriver.h create mode 100644 src/platform/NuttX/NetworkCommissioningEthernetDriver.cpp create mode 100644 src/platform/NuttX/NetworkCommissioningThreadDriver.cpp create mode 100644 src/platform/NuttX/NetworkCommissioningWiFiDriver.cpp create mode 100644 src/platform/NuttX/OTAImageProcessorImpl.cpp create mode 100644 src/platform/NuttX/OTAImageProcessorImpl.h create mode 100644 src/platform/NuttX/PlatformManagerImpl.cpp create mode 100644 src/platform/NuttX/PlatformManagerImpl.h create mode 100644 src/platform/NuttX/PosixConfig.cpp create mode 100644 src/platform/NuttX/PosixConfig.h create mode 100644 src/platform/NuttX/README.md create mode 100644 src/platform/NuttX/SystemPlatformConfig.h create mode 100644 src/platform/NuttX/SystemTimeSupport.cpp create mode 100644 src/platform/NuttX/ThreadStackManagerImpl.cpp create mode 100644 src/platform/NuttX/ThreadStackManagerImpl.h create mode 100644 src/platform/NuttX/WirelessDefs.h create mode 100644 src/platform/NuttX/args.gni diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 33889f519989be..25c9ef8daa6594 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -965,6 +965,7 @@ NTP nullable nullptr NUM +NuttX NVM NVS nwdiag diff --git a/.github/workflows/examples-nuttx.yaml b/.github/workflows/examples-nuttx.yaml new file mode 100644 index 00000000000000..bfe90b7363e4a6 --- /dev/null +++ b/.github/workflows/examples-nuttx.yaml @@ -0,0 +1,55 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Build example - NuttX + +on: + push: + pull_request: + merge_group: + workflow_dispatch: + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ (github.event_name == 'pull_request' && github.event.number) || (github.event_name == 'workflow_dispatch' && github.run_number) || github.sha }} + cancel-in-progress: true + +env: + CHIP_NO_LOG_TIMESTAMPS: true + +jobs: + nuttx: + name: NuttX + + runs-on: ubuntu-latest + if: github.actor != 'restyled-io[bot]' + + container: + image: ghcr.io/project-chip/chip-build-nuttx:51 + volumes: + - "/tmp/bloat_reports:/tmp/bloat_reports" + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Checkout submodules & Bootstrap + uses: ./.github/actions/checkout-submodules-and-bootstrap + with: + platform: nuttx + extra-submodule-parameters: " --recursive" + - name: Build example simulator NuttX Lighting App + run: | + ./scripts/run_in_build_env.sh \ + "./scripts/build/build_examples.py \ + --target nuttx-x64-light \ + build \ + " diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c810884ed98d30..7336213ba81a30 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -90,6 +90,7 @@ jobs: --skip-dir platform/webos \ --skip-dir platform/Zephyr \ --skip-dir test_driver \ + --skip-dir platform/NuttX \ --known-failure app/app-platform/ContentApp.cpp \ --known-failure app/app-platform/ContentApp.h \ --known-failure app/app-platform/ContentAppPlatform.cpp \ @@ -205,7 +206,7 @@ jobs: # TODO: TLVDebug should ideally not be excluded here. # TODO: protocol_decoder.cpp should ideally not be excluded here. # TODO: PersistentStorageMacros.h should ideally not be excluded here. - git grep -I -n "PRI.64" -- './*' ':(exclude).github/workflows/lint.yml' ':(exclude)examples/chip-tool' ':(exclude)examples/tv-casting-app' ':(exclude)src/app/MessageDef/MessageDefHelper.cpp' ':(exclude)src/app/tests/integration/chip_im_initiator.cpp' ':(exclude)src/lib/core/TLVDebug.cpp' ':(exclude)src/lib/dnssd/tests/TestTxtFields.cpp' ':(exclude)src/lib/format/protocol_decoder.cpp' ':(exclude)src/lib/support/PersistentStorageMacros.h' ':(exclude)src/messaging/tests/echo/echo_requester.cpp' ':(exclude)src/platform/Linux' ':(exclude)src/platform/Ameba' ':(exclude)src/platform/ESP32' ':(exclude)src/platform/Darwin' ':(exclude)src/darwin' ':(exclude)src/platform/webos' ':(exclude)zzz_generated/chip-tool' ':(exclude)src/tools/chip-cert/Cmd_PrintCert.cpp' && exit 1 || exit 0 + git grep -I -n "PRI.64" -- './*' ':(exclude).github/workflows/lint.yml' ':(exclude)examples/chip-tool' ':(exclude)examples/tv-casting-app' ':(exclude)src/app/MessageDef/MessageDefHelper.cpp' ':(exclude)src/app/tests/integration/chip_im_initiator.cpp' ':(exclude)src/lib/core/TLVDebug.cpp' ':(exclude)src/lib/dnssd/tests/TestTxtFields.cpp' ':(exclude)src/lib/format/protocol_decoder.cpp' ':(exclude)src/lib/support/PersistentStorageMacros.h' ':(exclude)src/messaging/tests/echo/echo_requester.cpp' ':(exclude)src/platform/Linux' ':(exclude)src/platform/Ameba' ':(exclude)src/platform/ESP32' ':(exclude)src/platform/Darwin' ':(exclude)src/darwin' ':(exclude)src/platform/webos' ':(exclude)zzz_generated/chip-tool' ':(exclude)src/tools/chip-cert/Cmd_PrintCert.cpp' ':(exclude)src/platform/NuttX' && exit 1 || exit 0 # git grep exits with 0 if it finds a match, but we want # to fail (exit nonzero) on match. And we want to exclude this file, diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn index df85153297cad7..63b5eef6003b1b 100644 --- a/build/config/compiler/BUILD.gn +++ b/build/config/compiler/BUILD.gn @@ -271,6 +271,11 @@ config("strict_warnings") { cflags_cc = [ "-Wnon-virtual-dtor" ] + if (current_os == "nuttx") { + cflags -= [ "-Wshadow" ] + cflags_cc -= [ "-Wnon-virtual-dtor" ] + } + configs = [] ldflags = [] diff --git a/config/nuttx/chip-gn/.gn b/config/nuttx/chip-gn/.gn new file mode 100644 index 00000000000000..b81b0bba35934b --- /dev/null +++ b/config/nuttx/chip-gn/.gn @@ -0,0 +1,29 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +# The location of the build configuration file. +buildconfig = "${build_root}/config/BUILDCONFIG.gn" + +# CHIP uses angle bracket includes. +check_system_includes = true + +default_args = { + target_cpu = "" + target_os = "nuttx" + + import("${chip_root}/config/nuttx/chip-gn/args.gni") +} diff --git a/config/nuttx/chip-gn/BUILD.gn b/config/nuttx/chip-gn/BUILD.gn new file mode 100644 index 00000000000000..fbefd303425475 --- /dev/null +++ b/config/nuttx/chip-gn/BUILD.gn @@ -0,0 +1,57 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +import("${chip_root}/build/chip/tests.gni") + +assert(current_os == "nuttx") + +declare_args() { + chip_build_example_providers = false + chip_example_lighting = false +} + +static_library("nuttx") { + output_name = "libchipnuttx" + + public_deps = [ + "${chip_root}/examples/platform/linux:app-main", + "${chip_root}/src/lib", + ] + + if (chip_build_tests) { + public_deps += [ "${chip_root}/src:tests" ] + } + + if (chip_build_example_providers) { + public_deps += [ "${chip_root}/examples/providers:device_info_provider" ] + } + + if (chip_example_lighting) { + public_deps += [ + "${chip_root}/examples/lighting-app/lighting-common", + "${chip_root}/examples/lighting-app/lighting-common:lighting-manager", + ] + } + + output_dir = "${root_out_dir}/lib" + + complete_static_lib = true +} + +group("default") { + deps = [ ":nuttx" ] +} diff --git a/config/nuttx/chip-gn/args.gni b/config/nuttx/chip-gn/args.gni new file mode 100644 index 00000000000000..1c114464aca2ec --- /dev/null +++ b/config/nuttx/chip-gn/args.gni @@ -0,0 +1,32 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") + +import("${chip_root}/src/crypto/crypto.gni") + +chip_device_platform = "nuttx" + +chip_build_tests = false + +chip_project_config_include = "" +chip_system_project_config_include = "" +chip_ble_project_config_include = "" + +chip_crypto = "mbedtls" +chip_external_mbedtls = true + +custom_toolchain = "${chip_root}/config/nuttx/chip-gn/toolchain:nuttx" + +pw_build_PIP_CONSTRAINTS = [ "${chip_root}/scripts/setup/constraints.txt" ] diff --git a/config/nuttx/chip-gn/toolchain/BUILD.gn b/config/nuttx/chip-gn/toolchain/BUILD.gn new file mode 100644 index 00000000000000..199979e0cd2c1d --- /dev/null +++ b/config/nuttx/chip-gn/toolchain/BUILD.gn @@ -0,0 +1,33 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/toolchain/gcc_toolchain.gni") +import("//build_overrides/build.gni") + +declare_args() { + nuttx_ar = "" + nuttx_cc = "" + nuttx_cxx = "" +} + +gcc_toolchain("nuttx") { + ar = nuttx_ar + cc = nuttx_cc + cxx = nuttx_cxx + + toolchain_args = { + current_os = "nuttx" + is_clang = false + } +} diff --git a/examples/lighting-app/linux/main.cpp b/examples/lighting-app/linux/main.cpp index 56cafc2e527509..8e586b5cf56d54 100644 --- a/examples/lighting-app/linux/main.cpp +++ b/examples/lighting-app/linux/main.cpp @@ -95,7 +95,7 @@ void ApplicationShutdown() } } -int main(int argc, char * argv[]) +extern "C" int main(int argc, char * argv[]) { if (ChipLinuxAppInit(argc, argv) != 0) { diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py index 1456c240e57719..33e37670040e2b 100755 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -27,6 +27,7 @@ from builders.mbed import MbedApp, MbedBoard, MbedBuilder, MbedProfile from builders.mw320 import MW320App, MW320Builder from builders.nrf import NrfApp, NrfBoard, NrfConnectBuilder +from builders.nuttx import NuttXApp, NuttXBoard, NuttXBuilder from builders.nxp import NxpApp, NxpBoard, NxpBuilder from builders.openiotsdk import OpenIotSdkApp, OpenIotSdkBuilder, OpenIotSdkCryptoBackend from builders.qpg import QpgApp, QpgBoard, QpgBuilder @@ -324,6 +325,22 @@ def BuildNrfTarget(): return target +def BuildNuttXTarget(): + target = BuildTarget('nuttx', NuttXBuilder) + + # Boards + target.AppendFixedTargets([ + TargetPart('x64', board=NuttXBoard.SIM), + ]) + + # Apps + target.AppendFixedTargets([ + TargetPart('light', app=NuttXApp.LIGHT), + ]) + + return target + + def BuildAndroidTarget(): target = BuildTarget('android', AndroidBuilder) @@ -795,6 +812,7 @@ def BuildOpenIotSdkTargets(): BuildMW320Target(), BuildNrfTarget(), BuildNrfNativeTarget(), + BuildNuttXTarget(), BuildQorvoTarget(), BuildStm32Target(), BuildTizenTarget(), diff --git a/scripts/build/builders/nuttx.py b/scripts/build/builders/nuttx.py new file mode 100644 index 00000000000000..ae198ca71f07ac --- /dev/null +++ b/scripts/build/builders/nuttx.py @@ -0,0 +1,98 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import os +from enum import Enum, auto + +from .gn import Builder + + +class NuttXApp(Enum): + LIGHT = auto() + + def ExampleName(self): + if self == NuttXApp.LIGHT: + return 'lighting-app' + else: + raise Exception('Unknown app type: %r' % self) + + def AppNamePrefix(self, chip_name): + if self == NuttXApp.LIGHT: + return ('chip-%s-lighting-example' % chip_name) + else: + raise Exception('Unknown app type: %r' % self) + + +class NuttXBoard(Enum): + SIM = auto() + + +def NuttXTarget(board, app): + if board == NuttXBoard.SIM: + if app == NuttXApp.LIGHT: + return 'sim:matter' + return 'none' + + +class NuttXBuilder(Builder): + + def __init__(self, + root, + runner, + app: NuttXApp = NuttXApp.LIGHT, + board: NuttXBoard = NuttXBoard.SIM, + ): + + nuttx_chip = 'nuttx' + + super(NuttXBuilder, self).__init__( + root=os.path.join(root, 'examples', + app.ExampleName(), nuttx_chip), + runner=runner + ) + + self.chip_name = nuttx_chip + self.app = app + self.board = board + + def generate(self): + self._Execute(['mkdir', '-p', self.output_dir], + title='Generating ' + self.identifier) + + def _build(self): + logging.info('Compiling NuttX %s at %s, ', + NuttXTarget(self.board, self.app), self.output_dir) + nuttx_dir = os.path.join(os.sep, 'opt', 'nuttx', 'nuttx') + + self._Execute(['cmake', '-S', nuttx_dir, '-B', self.output_dir, '-DCHIP_ROOT=' + os.getenv('PW_PROJECT_ROOT'), + '-DBOARD_CONFIG=' + NuttXTarget(self.board, self.app), + '-DCMAKE_C_COMPILER=/opt/nuttx/gcc-13/bin/gcc', + '-DCMAKE_CXX_COMPILER=/opt/nuttx/gcc-13/bin/g++', + '-GNinja'], + title='Building ' + self.identifier) + self._Execute(['cmake', '--build', self.output_dir]) + + def build_outputs(self): + logging.info('Compiling outputs NuttX at %s', self.output_dir) + items = { + '%s.out' % self.app.AppNamePrefix(self.chip_name): + os.path.join(self.output_dir, '%s.out' % + self.app.AppNamePrefix(self.chip_name)), + '%s.out.map' % self.app.AppNamePrefix(self.chip_name): + os.path.join(self.output_dir, + '%s.out.map' % self.app.AppNamePrefix(self.chip_name)), + } + + return items diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt index fcdf79baf41bc0..b2273d694acdc6 100644 --- a/scripts/build/testdata/all_targets_linux_x64.txt +++ b/scripts/build/testdata/all_targets_linux_x64.txt @@ -19,6 +19,7 @@ mbed-cy8cproto_062_4343w-{lock,light,all-clusters,all-clusters-minimal,pigweed,o mw320-all-clusters-app nrf-{nrf5340dk,nrf52840dk,nrf52840dongle}-{all-clusters,all-clusters-minimal,lock,light,light-switch,shell,pump,pump-controller,window-covering}[-rpc] nrf-native-posix-64-tests +nuttx-x64-light qpg-qpg6105-{lock,light,shell,persistent-storage,light-switch,thermostat}[-updateimage] stm32-stm32wb5mm-dk-light tizen-arm-{all-clusters,all-clusters-minimal,chip-tool,light,tests}[-no-ble][-no-thread][-no-wifi][-asan][-ubsan][-with-ui] diff --git a/scripts/checkout_submodules.py b/scripts/checkout_submodules.py index 81dafa1fb91a29..0290182b5b4bff 100755 --- a/scripts/checkout_submodules.py +++ b/scripts/checkout_submodules.py @@ -41,6 +41,7 @@ 'linux', 'mbed', 'nrfconnect', + 'nuttx', 'qpg', 'stm32', 'telink', diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py index 227d65a99877c6..20c853b9906df8 100644 --- a/scripts/tools/check_includes_config.py +++ b/scripts/tools/check_includes_config.py @@ -53,6 +53,7 @@ '/platform/webos/', '/platform/mt793x/', '/platform/ASR/', + '/platform/NuttX/', r'POSIX\.h$', } diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index e40847403cd01f..0eb7e1456e02e0 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -118,6 +118,7 @@ source_set("global-attributes") { # as a dependency public_deps = [ ":app_config", + "${chip_root}/src/app/common:ids", "${chip_root}/src/lib/support", ] } diff --git a/src/inet/BUILD.gn b/src/inet/BUILD.gn index 94d6c253660491..e30d417ffc0b39 100644 --- a/src/inet/BUILD.gn +++ b/src/inet/BUILD.gn @@ -185,4 +185,7 @@ static_library("inet") { } cflags = [ "-Wconversion" ] + if (current_os == "nuttx") { + cflags -= [ "-Wconversion" ] + } } diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h index 370a7d37d096ef..3a8ad9d5f24811 100644 --- a/src/lib/core/CHIPError.h +++ b/src/lib/core/CHIPError.h @@ -409,13 +409,13 @@ class ChipError * * This template ensures that the numeric value is constant and well-formed. */ - template + template struct SdkErrorConstant { static_assert(FitsInField(kSdkPartLength, to_underlying(PART)), "part is too large"); - static_assert(FitsInField(kSdkCodeLength, CODE), "code is too large"); - static_assert(MakeInteger(PART, CODE) != 0, "value is zero"); - static constexpr StorageType value = MakeInteger(PART, CODE); + static_assert(FitsInField(kSdkCodeLength, SCODE), "code is too large"); + static_assert(MakeInteger(PART, SCODE) != 0, "value is zero"); + static constexpr StorageType value = MakeInteger(PART, SCODE); }; }; diff --git a/src/platform/BUILD.gn b/src/platform/BUILD.gn index 16287f1afb16de..97365a2ee72e0f 100644 --- a/src/platform/BUILD.gn +++ b/src/platform/BUILD.gn @@ -308,6 +308,12 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { } else if (chip_device_platform == "stm32") { device_layer_target_define = "STM32" defines += [ "CHIP_DEVICE_LAYER_TARGET=stm32" ] + } else if (chip_device_platform == "nuttx") { + device_layer_target_define = "NUTTX" + defines += [ + "CHIP_DEVICE_LAYER_TARGET=NuttX", + "CHIP_DEVICE_CONFIG_ENABLE_WIFI=${chip_enable_wifi}", + ] } else { device_layer_target_define = "" } @@ -346,6 +352,7 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { "OPEN_IOT_SDK", "ASR", "STM32", + "NUTTX", ] foreach(possible_device_layer_target_define, possible_device_layer_target_defines) { @@ -576,6 +583,8 @@ if (chip_device_platform != "none") { _platform_target = "ASR" } else if (chip_device_platform == "stm32") { _platform_target = "stm32" + } else if (chip_device_platform == "nuttx") { + _platform_target = "NuttX" } else { assert(false, "Unknown chip_device_platform: ${chip_device_platform}") } diff --git a/src/platform/NuttX/BLEManagerImpl.cpp b/src/platform/NuttX/BLEManagerImpl.cpp new file mode 100644 index 00000000000000..e472e805a1154e --- /dev/null +++ b/src/platform/NuttX/BLEManagerImpl.cpp @@ -0,0 +1,897 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the BLEManager singleton object + * for Linux platforms. + */ + +/** + * Note: BLEManager requires ConnectivityManager to be defined beforehand, + * otherwise we will face circular dependency between them. */ +#include + +/** + * Note: Use public include for BLEManager which includes our local + * platform//BLEManagerImpl.h after defining interface class. */ +#include "platform/internal/BLEManager.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "bluez/BluezEndpoint.h" + +#if !CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION +#include +#endif + +using namespace ::nl; +using namespace ::chip::Ble; + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +namespace { + +static constexpr System::Clock::Timeout kNewConnectionScanTimeout = System::Clock::Seconds16(20); +static constexpr System::Clock::Timeout kConnectTimeout = System::Clock::Seconds16(20); +static constexpr System::Clock::Timeout kFastAdvertiseTimeout = + System::Clock::Milliseconds32(CHIP_DEVICE_CONFIG_BLE_ADVERTISING_INTERVAL_CHANGE_TIME); +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING +// The CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_CHANGE_TIME_MS specifies the transition time +// starting from advertisement commencement. Since the extended advertisement timer is started after +// the fast-to-slow transition, we have to subtract the time spent in fast advertising. +static constexpr System::Clock::Timeout kSlowAdvertiseTimeout = System::Clock::Milliseconds32( + CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_CHANGE_TIME_MS - CHIP_DEVICE_CONFIG_BLE_ADVERTISING_INTERVAL_CHANGE_TIME); +static_assert(CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_CHANGE_TIME_MS >= + CHIP_DEVICE_CONFIG_BLE_ADVERTISING_INTERVAL_CHANGE_TIME, + "The extended advertising interval change time must be greater than the fast advertising interval change time"); +#endif + +const ChipBleUUID ChipUUID_CHIPoBLEChar_RX = { { 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, 0x9F, + 0x9D, 0x11 } }; +const ChipBleUUID ChipUUID_CHIPoBLEChar_TX = { { 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, 0x9F, + 0x9D, 0x12 } }; + +void HandleConnectTimeout(chip::System::Layer *, void * apEndpoint) +{ + VerifyOrDie(apEndpoint != nullptr); + static_cast(apEndpoint)->CancelConnect(); + BLEManagerImpl::HandleConnectFailed(CHIP_ERROR_TIMEOUT); +} + +} // namespace + +BLEManagerImpl BLEManagerImpl::sInstance; + +void HandleIncomingBleConnection(BLEEndPoint * bleEP) +{ + ChipLogProgress(DeviceLayer, "CHIPoBluez con rcvd"); +} + +CHIP_ERROR BLEManagerImpl::_Init() +{ + CHIP_ERROR err; + + err = BleLayer::Init(this, this, this, &DeviceLayer::SystemLayer()); + SuccessOrExit(err); + + mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Enabled; + mFlags.ClearAll().Set(Flags::kAdvertisingEnabled, CHIP_DEVICE_CONFIG_CHIPOBLE_ENABLE_ADVERTISING_AUTOSTART && !mIsCentral); + mFlags.Set(Flags::kFastAdvertisingEnabled, true); + + memset(mDeviceName, 0, sizeof(mDeviceName)); + + OnChipBleConnectReceived = HandleIncomingBleConnection; + + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveBLEState(); }); + +exit: + return err; +} + +void BLEManagerImpl::_Shutdown() +{ + // Ensure scan resources are cleared (e.g. timeout timers). + mDeviceScanner.Shutdown(); + // Stop advertising and free resources. + mBLEAdvertisement.Shutdown(); + // Make sure that the endpoint is not used by the timer. + DeviceLayer::SystemLayer().CancelTimer(HandleConnectTimeout, &mEndpoint); + // Release BLE connection resources (unregister from BlueZ). + mEndpoint.Shutdown(); + mFlags.Clear(Flags::kBluezBLELayerInitialized); +} + +CHIP_ERROR BLEManagerImpl::_SetAdvertisingEnabled(bool val) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + if (mFlags.Has(Flags::kAdvertisingEnabled) != val) + { + mFlags.Set(Flags::kAdvertisingEnabled, val); + } + + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveBLEState(); }); + + return err; +} + +CHIP_ERROR BLEManagerImpl::_SetAdvertisingMode(BLEAdvertisingMode mode) +{ + switch (mode) + { + case BLEAdvertisingMode::kFastAdvertising: + mFlags.Set(Flags::kFastAdvertisingEnabled, true); + break; + case BLEAdvertisingMode::kSlowAdvertising: + mFlags.Set(Flags::kFastAdvertisingEnabled, false); + break; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + mFlags.Set(Flags::kAdvertisingRefreshNeeded); + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveBLEState(); }); + return CHIP_NO_ERROR; +} + +CHIP_ERROR BLEManagerImpl::_GetDeviceName(char * buf, size_t bufSize) +{ + if (strlen(mDeviceName) >= bufSize) + { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + strcpy(buf, mDeviceName); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR BLEManagerImpl::_SetDeviceName(const char * deviceName) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrExit(mServiceMode != ConnectivityManager::kCHIPoBLEServiceMode_NotSupported, err = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + if (deviceName != nullptr && deviceName[0] != 0) + { + VerifyOrExit(strlen(deviceName) < kMaxDeviceNameLength, err = CHIP_ERROR_INVALID_ARGUMENT); + strcpy(mDeviceName, deviceName); + mFlags.Set(Flags::kUseCustomDeviceName); + } + else + { + uint16_t discriminator; + SuccessOrExit(err = GetCommissionableDataProvider()->GetSetupDiscriminator(discriminator)); + snprintf(mDeviceName, sizeof(mDeviceName), "%s%04u", CHIP_DEVICE_CONFIG_BLE_DEVICE_NAME_PREFIX, discriminator); + mDeviceName[kMaxDeviceNameLength] = 0; + mFlags.Clear(Flags::kUseCustomDeviceName); + } + +exit: + return err; +} + +uint16_t BLEManagerImpl::_NumConnections() +{ + uint16_t numCons = 0; + return numCons; +} + +CHIP_ERROR BLEManagerImpl::ConfigureBle(uint32_t aAdapterId, bool aIsCentral) +{ + mAdapterId = aAdapterId; + mIsCentral = aIsCentral; + mpBLEAdvUUID = "0xFFF6"; + return CHIP_NO_ERROR; +} + +void BLEManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) +{ + switch (event->Type) + { + case DeviceEventType::kCHIPoBLESubscribe: + HandleSubscribeReceived(event->CHIPoBLESubscribe.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX); + { + ChipDeviceEvent connectionEvent; + connectionEvent.Type = DeviceEventType::kCHIPoBLEConnectionEstablished; + PlatformMgr().PostEventOrDie(&connectionEvent); + } + break; + + case DeviceEventType::kCHIPoBLEUnsubscribe: + HandleUnsubscribeReceived(event->CHIPoBLEUnsubscribe.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX); + break; + + case DeviceEventType::kCHIPoBLEWriteReceived: + HandleWriteReceived(event->CHIPoBLEWriteReceived.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_RX, + PacketBufferHandle::Adopt(event->CHIPoBLEWriteReceived.Data)); + break; + + case DeviceEventType::kCHIPoBLEIndicateConfirm: + HandleIndicationConfirmation(event->CHIPoBLEIndicateConfirm.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX); + break; + + case DeviceEventType::kCHIPoBLEConnectionError: + HandleConnectionError(event->CHIPoBLEConnectionError.ConId, event->CHIPoBLEConnectionError.Reason); + break; + case DeviceEventType::kServiceProvisioningChange: + // Force the advertising configuration to be refreshed to reflect new provisioning state. + mFlags.Clear(Flags::kAdvertisingConfigured); + + DriveBLEState(); + break; + default: + HandlePlatformSpecificBLEEvent(event); + break; + } +} + +void BLEManagerImpl::HandlePlatformSpecificBLEEvent(const ChipDeviceEvent * apEvent) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + bool controlOpComplete = false; + ChipLogDetail(DeviceLayer, "HandlePlatformSpecificBLEEvent %d", apEvent->Type); + switch (apEvent->Type) + { + case DeviceEventType::kPlatformLinuxBLECentralConnected: + if (mBLEScanConfig.mBleScanState == BleScanState::kConnecting) + { + BleConnectionDelegate::OnConnectionComplete(mBLEScanConfig.mAppState, + apEvent->Platform.BLECentralConnected.mConnection); + CleanScanConfig(); + } + break; + case DeviceEventType::kPlatformLinuxBLECentralConnectFailed: + if (mBLEScanConfig.mBleScanState == BleScanState::kConnecting) + { + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, apEvent->Platform.BLECentralConnectFailed.mError); + CleanScanConfig(); + } + break; + case DeviceEventType::kPlatformLinuxBLEWriteComplete: + HandleWriteConfirmation(apEvent->Platform.BLEWriteComplete.mConnection, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_RX); + break; + case DeviceEventType::kPlatformLinuxBLESubscribeOpComplete: + if (apEvent->Platform.BLESubscribeOpComplete.mIsSubscribed) + HandleSubscribeComplete(apEvent->Platform.BLESubscribeOpComplete.mConnection, &CHIP_BLE_SVC_ID, + &ChipUUID_CHIPoBLEChar_TX); + else + HandleUnsubscribeComplete(apEvent->Platform.BLESubscribeOpComplete.mConnection, &CHIP_BLE_SVC_ID, + &ChipUUID_CHIPoBLEChar_TX); + break; + case DeviceEventType::kPlatformLinuxBLEIndicationReceived: + HandleIndicationReceived(apEvent->Platform.BLEIndicationReceived.mConnection, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX, + PacketBufferHandle::Adopt(apEvent->Platform.BLEIndicationReceived.mData)); + break; + case DeviceEventType::kPlatformLinuxBLEPeripheralAdvStartComplete: + SuccessOrExit(err = apEvent->Platform.BLEPeripheralAdvStartComplete.mError); + sInstance.mFlags.Clear(Flags::kControlOpInProgress).Clear(Flags::kAdvertisingRefreshNeeded); + // Do not restart the timer if it is still active. This is to avoid the timer from being restarted + // if the advertising is stopped due to a premature release. + if (!DeviceLayer::SystemLayer().IsTimerActive(HandleAdvertisingTimer, this)) + { + // Start a timer to make sure that the fast advertising is stopped after specified timeout. + SuccessOrExit(err = DeviceLayer::SystemLayer().StartTimer(kFastAdvertiseTimeout, HandleAdvertisingTimer, this)); + } + sInstance.mFlags.Set(Flags::kAdvertising); + break; + case DeviceEventType::kPlatformLinuxBLEPeripheralAdvStopComplete: + SuccessOrExit(err = apEvent->Platform.BLEPeripheralAdvStopComplete.mError); + sInstance.mFlags.Clear(Flags::kControlOpInProgress).Clear(Flags::kAdvertisingRefreshNeeded); + DeviceLayer::SystemLayer().CancelTimer(HandleAdvertisingTimer, this); + + // Transition to the not Advertising state... + if (sInstance.mFlags.Has(Flags::kAdvertising)) + { + sInstance.mFlags.Clear(Flags::kAdvertising); + ChipLogProgress(DeviceLayer, "CHIPoBLE advertising stopped"); + } + break; + case DeviceEventType::kPlatformLinuxBLEPeripheralAdvReleased: + // If the advertising was stopped due to a premature release, check if it needs to be restarted. + sInstance.mFlags.Clear(Flags::kAdvertising); + DriveBLEState(); + break; + case DeviceEventType::kPlatformLinuxBLEPeripheralRegisterAppComplete: + SuccessOrExit(err = apEvent->Platform.BLEPeripheralRegisterAppComplete.mError); + mFlags.Set(Flags::kAppRegistered); + controlOpComplete = true; + break; + default: + break; + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Disabling CHIPoBLE service due to error: %s", ErrorStr(err)); + mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Disabled; + DeviceLayer::SystemLayer().CancelTimer(HandleAdvertisingTimer, this); + sInstance.mFlags.Clear(Flags::kControlOpInProgress); + } + + if (controlOpComplete) + { + mFlags.Clear(Flags::kControlOpInProgress); + DriveBLEState(); + } +} + +uint16_t BLEManagerImpl::GetMTU(BLE_CONNECTION_OBJECT conId) const +{ + uint16_t mtu = 0; + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + mtu = conId->GetMTU(); +exit: + return mtu; +} + +bool BLEManagerImpl::SubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId) +{ + bool result = false; + + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + VerifyOrExit(Ble::UUIDsMatch(svcId, &CHIP_BLE_SVC_ID), + ChipLogError(DeviceLayer, "SubscribeCharacteristic() called with invalid service ID")); + VerifyOrExit(Ble::UUIDsMatch(charId, &ChipUUID_CHIPoBLEChar_TX), + ChipLogError(DeviceLayer, "SubscribeCharacteristic() called with invalid characteristic ID")); + + VerifyOrExit(conId->SubscribeCharacteristic() == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "SubscribeCharacteristic() failed")); + result = true; + +exit: + return result; +} + +bool BLEManagerImpl::UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId) +{ + bool result = false; + + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + VerifyOrExit(Ble::UUIDsMatch(svcId, &CHIP_BLE_SVC_ID), + ChipLogError(DeviceLayer, "UnsubscribeCharacteristic() called with invalid service ID")); + VerifyOrExit(Ble::UUIDsMatch(charId, &ChipUUID_CHIPoBLEChar_TX), + ChipLogError(DeviceLayer, "UnsubscribeCharacteristic() called with invalid characteristic ID")); + + VerifyOrExit(conId->UnsubscribeCharacteristic() == CHIP_NO_ERROR, + ChipLogError(DeviceLayer, "UnsubscribeCharacteristic() failed")); + result = true; + +exit: + return result; +} + +bool BLEManagerImpl::CloseConnection(BLE_CONNECTION_OBJECT conId) +{ + bool result = false; + + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + ChipLogProgress(DeviceLayer, "Closing BLE GATT connection (con %p)", conId); + + VerifyOrExit(conId->CloseConnection() == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "CloseConnection() failed")); + result = true; + +exit: + return result; +} + +bool BLEManagerImpl::SendIndication(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + chip::System::PacketBufferHandle pBuf) +{ + bool result = false; + + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + VerifyOrExit(conId->SendIndication(std::move(pBuf)) == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "SendIndication() failed")); + result = true; + +exit: + return result; +} + +bool BLEManagerImpl::SendWriteRequest(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + chip::System::PacketBufferHandle pBuf) +{ + bool result = false; + + VerifyOrExit(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + VerifyOrExit(Ble::UUIDsMatch(svcId, &CHIP_BLE_SVC_ID), + ChipLogError(DeviceLayer, "SendWriteRequest() called with invalid service ID")); + VerifyOrExit(Ble::UUIDsMatch(charId, &ChipUUID_CHIPoBLEChar_RX), + ChipLogError(DeviceLayer, "SendWriteRequest() called with invalid characteristic ID")); + + VerifyOrExit(conId->SendWriteRequest(std::move(pBuf)) == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "SendWriteRequest() failed")); + result = true; + +exit: + return result; +} + +bool BLEManagerImpl::SendReadRequest(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + chip::System::PacketBufferHandle pBuf) +{ + ChipLogError(Ble, "SendReadRequest: Not implemented"); + return true; +} + +bool BLEManagerImpl::SendReadResponse(BLE_CONNECTION_OBJECT conId, BLE_READ_REQUEST_CONTEXT requestContext, + const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId) +{ + ChipLogError(Ble, "SendReadRBluezonse: Not implemented"); + return true; +} + +void BLEManagerImpl::HandleNewConnection(BLE_CONNECTION_OBJECT conId) +{ + if (sInstance.mIsCentral) + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLECentralConnected; + event.Platform.BLECentralConnected.mConnection = conId; + PlatformMgr().PostEventOrDie(&event); + } +} + +void BLEManagerImpl::HandleConnectFailed(CHIP_ERROR error) +{ + if (sInstance.mIsCentral) + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLECentralConnectFailed; + event.Platform.BLECentralConnectFailed.mError = error; + PlatformMgr().PostEventOrDie(&event); + } +} + +void BLEManagerImpl::HandleWriteComplete(BLE_CONNECTION_OBJECT conId) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEWriteComplete; + event.Platform.BLEWriteComplete.mConnection = conId; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::HandleSubscribeOpComplete(BLE_CONNECTION_OBJECT conId, bool subscribed) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLESubscribeOpComplete; + event.Platform.BLESubscribeOpComplete.mConnection = conId; + event.Platform.BLESubscribeOpComplete.mIsSubscribed = subscribed; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::HandleTXCharChanged(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferHandle buf = System::PacketBufferHandle::NewWithData(value, len); + + ChipLogDetail(DeviceLayer, "Indication received, conn = %p", conId); + + VerifyOrExit(!buf.IsNull(), err = CHIP_ERROR_NO_MEMORY); + + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEIndicationReceived; + event.Platform.BLEIndicationReceived.mConnection = conId; + event.Platform.BLEIndicationReceived.mData = std::move(buf).UnsafeRelease(); + PlatformMgr().PostEventOrDie(&event); + +exit: + if (err != CHIP_NO_ERROR) + ChipLogError(DeviceLayer, "HandleTXCharChanged() failed: %s", ErrorStr(err)); +} + +void BLEManagerImpl::HandleRXCharWrite(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferHandle buf; + + // Copy the data to a packet buffer. + buf = System::PacketBufferHandle::NewWithData(value, len); + VerifyOrExit(!buf.IsNull(), err = CHIP_ERROR_NO_MEMORY); + + // Post an event to the Chip queue to deliver the data into the Chip stack. + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kCHIPoBLEWriteReceived; + ChipLogProgress(Ble, "Write request received debug %p", conId); + event.CHIPoBLEWriteReceived.ConId = conId; + event.CHIPoBLEWriteReceived.Data = std::move(buf).UnsafeRelease(); + PlatformMgr().PostEventOrDie(&event); + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "HandleRXCharWrite() failed: %s", ErrorStr(err)); + } +} + +void BLEManagerImpl::CHIPoBluez_ConnectionClosed(BLE_CONNECTION_OBJECT conId) +{ + ChipLogProgress(DeviceLayer, "Bluez notify CHIPoBluez connection disconnected"); + + // If this was a CHIPoBLE connection, post an event to deliver a connection error to the CHIPoBLE layer. + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kCHIPoBLEConnectionError; + event.CHIPoBLEConnectionError.ConId = conId; + event.CHIPoBLEConnectionError.Reason = BLE_ERROR_REMOTE_DEVICE_DISCONNECTED; + PlatformMgr().PostEventOrDie(&event); + } +} + +void BLEManagerImpl::HandleTXCharCCCDWrite(BLE_CONNECTION_OBJECT conId) +{ + VerifyOrReturn(conId != BLE_CONNECTION_UNINITIALIZED, + ChipLogError(DeviceLayer, "BLE connection is not initialized in %s", __func__)); + + // Post an event to the Chip queue to process either a CHIPoBLE Subscribe or Unsubscribe based on + // whether the client is enabling or disabling indications. + ChipDeviceEvent event; + event.Type = conId->IsNotifyAcquired() ? DeviceEventType::kCHIPoBLESubscribe : DeviceEventType::kCHIPoBLEUnsubscribe; + event.CHIPoBLESubscribe.ConId = conId; + PlatformMgr().PostEventOrDie(&event); + + ChipLogProgress(DeviceLayer, "CHIPoBLE %s received", + (event.Type == DeviceEventType::kCHIPoBLESubscribe) ? "subscribe" : "unsubscribe"); +} + +void BLEManagerImpl::HandleTXComplete(BLE_CONNECTION_OBJECT conId) +{ + // Post an event to the Chip queue to process the indicate confirmation. + ChipDeviceEvent event; + event.Type = DeviceEventType::kCHIPoBLEIndicateConfirm; + event.CHIPoBLEIndicateConfirm.ConId = conId; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::DriveBLEState() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + // Perform any initialization actions that must occur after the Chip task is running. + if (!mFlags.Has(Flags::kAsyncInitCompleted)) + { + mFlags.Set(Flags::kAsyncInitCompleted); + ExitNow(); + } + + // If there's already a control operation in progress, wait until it completes. + VerifyOrExit(!mFlags.Has(Flags::kControlOpInProgress), /* */); + + // Initializes the Bluez BLE layer if needed. + if (mServiceMode == ConnectivityManager::kCHIPoBLEServiceMode_Enabled && !mFlags.Has(Flags::kBluezBLELayerInitialized)) + { + SuccessOrExit(err = mEndpoint.Init(mIsCentral, mAdapterId)); + mFlags.Set(Flags::kBluezBLELayerInitialized); + } + + // Register the CHIPoBLE application with the Bluez BLE layer if needed. + if (!mIsCentral && mServiceMode == ConnectivityManager::kCHIPoBLEServiceMode_Enabled && !mFlags.Has(Flags::kAppRegistered)) + { + SuccessOrExit(err = mEndpoint.RegisterGattApplication()); + mFlags.Set(Flags::kControlOpInProgress); + ExitNow(); + } + + // If the application has enabled CHIPoBLE and BLE advertising... + if (mServiceMode == ConnectivityManager::kCHIPoBLEServiceMode_Enabled && mFlags.Has(Flags::kAdvertisingEnabled)) + { + // Start/re-start advertising if not already advertising, or if the advertising state of the + // Bluez BLE layer needs to be refreshed. + if (!mFlags.Has(Flags::kAdvertising) || mFlags.Has(Flags::kAdvertisingRefreshNeeded)) + { + mFlags.Clear(Flags::kAdvertisingRefreshNeeded); + + // Configure advertising data if it hasn't been done yet. + if (!mFlags.Has(Flags::kAdvertisingConfigured)) + { + SuccessOrExit(err = mBLEAdvertisement.Init(mEndpoint, mpBLEAdvUUID, mDeviceName)); + mFlags.Set(Flags::kAdvertisingConfigured); + } + + // Setup service data for advertising. + auto serviceDataFlags = BluezAdvertisement::kServiceDataNone; +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + if (mFlags.Has(Flags::kExtAdvertisingEnabled)) + serviceDataFlags |= BluezAdvertisement::kServiceDataExtendedAnnouncement; +#endif + SuccessOrExit(err = mBLEAdvertisement.SetupServiceData(serviceDataFlags)); + + // Set or update the advertising intervals. + SuccessOrExit(err = mBLEAdvertisement.SetIntervals(GetAdvertisingIntervals())); + + if (!mFlags.Has(Flags::kAdvertising)) + { + // Start advertising. This is an asynchronous step. BLE manager will be notified of + // advertising start completion via a call to NotifyBLEPeripheralAdvStartComplete. + SuccessOrExit(err = mBLEAdvertisement.Start()); + mFlags.Set(Flags::kControlOpInProgress); + ExitNow(); + } + } + } + + // Otherwise stop advertising if needed... + else + { + if (mFlags.Has(Flags::kAdvertising)) + { + SuccessOrExit(err = mBLEAdvertisement.Stop()); + mFlags.Set(Flags::kControlOpInProgress); + + ExitNow(); + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Disabling CHIPoBLE service due to error: %s", ErrorStr(err)); + DeviceLayer::SystemLayer().CancelTimer(HandleAdvertisingTimer, this); + mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Disabled; + } +} + +void BLEManagerImpl::NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT conId) +{ + ChipLogProgress(Ble, "Got notification regarding chip connection closure"); +#if CHIP_DEVICE_CONFIG_ENABLE_WPA && !CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION + if (mState == kState_NotInitialized) + { + // Close BLE GATT connections to disconnect BlueZ + CloseConnection(conId); + // In Non-Concurrent mode start the Wi-Fi, as BLE has been stopped + DeviceLayer::ConnectivityMgrImpl().StartNonConcurrentWiFiManagement(); + } +#endif // CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION +} + +void BLEManagerImpl::CheckNonConcurrentBleClosing() +{ +#if CHIP_DEVICE_CONFIG_ENABLE_WPA && !CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION + if (mState == kState_Disconnecting) + { + DeviceLayer::DeviceControlServer::DeviceControlSvr().PostCloseAllBLEConnectionsToOperationalNetworkEvent(); + } +#endif +} + +BluezAdvertisement::AdvertisingIntervals BLEManagerImpl::GetAdvertisingIntervals() const +{ + if (mFlags.Has(Flags::kFastAdvertisingEnabled)) + return { CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_INTERVAL_MIN, CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_INTERVAL_MAX }; +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + if (mFlags.Has(Flags::kExtAdvertisingEnabled)) + return { CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_MIN, CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_MAX }; +#endif + return { CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MIN, CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MAX }; +} + +void BLEManagerImpl::HandleAdvertisingTimer(chip::System::Layer *, void * appState) +{ + auto * self = static_cast(appState); + + if (self->mFlags.Has(Flags::kFastAdvertisingEnabled)) + { + ChipLogDetail(DeviceLayer, "bleAdv Timeout : Start slow advertisement"); + self->_SetAdvertisingMode(BLEAdvertisingMode::kSlowAdvertising); +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + self->mFlags.Clear(Flags::kExtAdvertisingEnabled); + DeviceLayer::SystemLayer().StartTimer(kSlowAdvertiseTimeout, HandleAdvertisingTimer, self); + } + else + { + ChipLogDetail(DeviceLayer, "bleAdv Timeout : Start extended advertisement"); + self->mFlags.Set(Flags::kExtAdvertisingEnabled); + // This will trigger advertising intervals update in the DriveBLEState() function. + self->_SetAdvertisingMode(BLEAdvertisingMode::kSlowAdvertising); +#endif + } +} + +void BLEManagerImpl::InitiateScan(BleScanState scanType) +{ + DriveBLEState(); + + if (scanType == BleScanState::kNotScanning) + { + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INCORRECT_STATE); + ChipLogError(Ble, "Invalid scan type requested"); + return; + } + + if (!mFlags.Has(Flags::kBluezBLELayerInitialized)) + { + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INCORRECT_STATE); + ChipLogError(Ble, "BLE Layer is not yet initialized"); + return; + } + + if (mEndpoint.GetAdapter() == nullptr) + { + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INCORRECT_STATE); + ChipLogError(Ble, "No adapter available for new connection establishment"); + return; + } + + mBLEScanConfig.mBleScanState = scanType; + + CHIP_ERROR err = mDeviceScanner.Init(mEndpoint.GetAdapter(), this); + if (err != CHIP_NO_ERROR) + { + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INTERNAL); + ChipLogError(Ble, "Failed to create a BLE device scanner"); + return; + } + + err = mDeviceScanner.StartScan(kNewConnectionScanTimeout); + if (err != CHIP_NO_ERROR) + { + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; + ChipLogError(Ble, "Failed to start a BLE can: %s", chip::ErrorStr(err)); + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, err); + return; + } +} + +void BLEManagerImpl::CleanScanConfig() +{ + if (mBLEScanConfig.mBleScanState == BleScanState::kConnecting) + DeviceLayer::SystemLayer().CancelTimer(HandleConnectTimeout, &mEndpoint); + + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; +} + +void BLEManagerImpl::NewConnection(BleLayer * bleLayer, void * appState, const SetupDiscriminator & connDiscriminator) +{ + mBLEScanConfig.mDiscriminator = connDiscriminator; + mBLEScanConfig.mAppState = appState; + + // Scan initiation performed async, to ensure that the BLE subsystem is initialized. + DeviceLayer::SystemLayer().ScheduleLambda([this] { InitiateScan(BleScanState::kScanForDiscriminator); }); +} + +CHIP_ERROR BLEManagerImpl::CancelConnection() +{ + if (mBLEScanConfig.mBleScanState == BleScanState::kConnecting) + mEndpoint.CancelConnect(); + // If in discovery mode, stop scan. + else if (mBLEScanConfig.mBleScanState != BleScanState::kNotScanning) + mDeviceScanner.StopScan(); + return CHIP_NO_ERROR; +} + +void BLEManagerImpl::NotifyBLEPeripheralRegisterAppComplete(CHIP_ERROR error) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEPeripheralRegisterAppComplete; + event.Platform.BLEPeripheralRegisterAppComplete.mError = error; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::NotifyBLEPeripheralAdvStartComplete(CHIP_ERROR error) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEPeripheralAdvStartComplete; + event.Platform.BLEPeripheralAdvStartComplete.mError = error; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::NotifyBLEPeripheralAdvStopComplete(CHIP_ERROR error) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEPeripheralAdvStopComplete; + event.Platform.BLEPeripheralAdvStopComplete.mError = error; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::NotifyBLEPeripheralAdvReleased() +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformLinuxBLEPeripheralAdvReleased; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::OnDeviceScanned(BluezDevice1 & device, const chip::Ble::ChipBLEDeviceIdentificationInfo & info) +{ + const char * address = bluez_device1_get_address(&device); + ChipLogProgress(Ble, "New device scanned: %s", address); + + if (mBLEScanConfig.mBleScanState == BleScanState::kScanForDiscriminator) + { + auto isMatch = mBLEScanConfig.mDiscriminator.MatchesLongDiscriminator(info.GetDeviceDiscriminator()); + VerifyOrReturn( + isMatch, + ChipLogError(Ble, "Skip connection: Device discriminator does not match: %u != %u", info.GetDeviceDiscriminator(), + mBLEScanConfig.mDiscriminator.IsShortDiscriminator() ? mBLEScanConfig.mDiscriminator.GetShortValue() + : mBLEScanConfig.mDiscriminator.GetLongValue())); + ChipLogProgress(Ble, "Device discriminator match. Attempting to connect."); + } + else if (mBLEScanConfig.mBleScanState == BleScanState::kScanForAddress) + { + auto isMatch = strcmp(address, mBLEScanConfig.mAddress.c_str()) == 0; + VerifyOrReturn(isMatch, + ChipLogError(Ble, "Skip connection: Device address does not match: %s != %s", address, + mBLEScanConfig.mAddress.c_str())); + ChipLogProgress(Ble, "Device address match. Attempting to connect."); + } + else + { + // Internal consistency error + ChipLogError(Ble, "Unknown discovery type. Ignoring scanned device."); + return; + } + + mBLEScanConfig.mBleScanState = BleScanState::kConnecting; + + chip::DeviceLayer::PlatformMgr().LockChipStack(); + // We StartScan in the ChipStack thread. + // StopScan should also be performed in the ChipStack thread. + // At the same time, the scan timer also needs to be canceled in the ChipStack thread. + mDeviceScanner.StopScan(); + // Stop scanning and then start connecting timer + DeviceLayer::SystemLayer().StartTimer(kConnectTimeout, HandleConnectTimeout, &mEndpoint); + chip::DeviceLayer::PlatformMgr().UnlockChipStack(); + + CHIP_ERROR err = mEndpoint.ConnectDevice(device); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Ble, "Device connection failed: %" CHIP_ERROR_FORMAT, err.Format())); + + ChipLogProgress(Ble, "New device connected: %s", address); +} + +void BLEManagerImpl::OnScanComplete() +{ + switch (mBLEScanConfig.mBleScanState) + { + case BleScanState::kNotScanning: + ChipLogProgress(Ble, "Scan complete notification without an active scan."); + break; + case BleScanState::kScanForAddress: + case BleScanState::kScanForDiscriminator: + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; + ChipLogProgress(Ble, "Scan complete. No matching device found."); + break; + case BleScanState::kConnecting: + break; + } +} + +void BLEManagerImpl::OnScanError(CHIP_ERROR err) +{ + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, err); + ChipLogError(Ble, "BLE scan error: %" CHIP_ERROR_FORMAT, err.Format()); +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/BLEManagerImpl.h b/src/platform/NuttX/BLEManagerImpl.h new file mode 100644 index 00000000000000..790373c28b3830 --- /dev/null +++ b/src/platform/NuttX/BLEManagerImpl.h @@ -0,0 +1,241 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the BLEManager singleton object + * for the Linux platforms. + */ + +#pragma once + +#include +#include + +#include +#include + +#include "bluez/BluezAdvertisement.h" +#include "bluez/BluezEndpoint.h" +#include "bluez/ChipDeviceScanner.h" +#include "bluez/Types.h" + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +void HandleIncomingBleConnection(Ble::BLEEndPoint * bleEP); + +enum class BleScanState : uint8_t +{ + kNotScanning, + kScanForDiscriminator, + kScanForAddress, + kConnecting, +}; + +struct BLEScanConfig +{ + // If an active scan for connection is being performed + BleScanState mBleScanState = BleScanState::kNotScanning; + + // If scanning by discriminator, what are we scanning for + SetupDiscriminator mDiscriminator; + + // If scanning by address, what address are we searching for + std::string mAddress; + + // Optional argument to be passed to callback functions provided by the BLE scan/connect requestor + void * mAppState = nullptr; +}; + +/** + * Concrete implementation of the BLEManagerImpl singleton object for the Linux platforms. + */ +class BLEManagerImpl final : public BLEManager, + private Ble::BleLayer, + private Ble::BlePlatformDelegate, + private Ble::BleApplicationDelegate, + private Ble::BleConnectionDelegate, + private ChipDeviceScannerDelegate +{ + // Allow the BLEManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend BLEManager; + +public: + CHIP_ERROR ConfigureBle(uint32_t aAdapterId, bool aIsCentral); + void OnScanError(CHIP_ERROR error) override; + + // Driven by BlueZ IO + static void HandleNewConnection(BLE_CONNECTION_OBJECT conId); + static void HandleConnectFailed(CHIP_ERROR error); + static void HandleWriteComplete(BLE_CONNECTION_OBJECT conId); + static void HandleSubscribeOpComplete(BLE_CONNECTION_OBJECT conId, bool subscribed); + static void HandleTXCharChanged(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len); + static void HandleRXCharWrite(BLE_CONNECTION_OBJECT user_data, const uint8_t * value, size_t len); + static void CHIPoBluez_ConnectionClosed(BLE_CONNECTION_OBJECT user_data); + static void HandleTXCharCCCDWrite(BLE_CONNECTION_OBJECT user_data); + static void HandleTXComplete(BLE_CONNECTION_OBJECT user_data); + + static void NotifyBLEPeripheralRegisterAppComplete(CHIP_ERROR error); + static void NotifyBLEPeripheralAdvStartComplete(CHIP_ERROR error); + static void NotifyBLEPeripheralAdvStopComplete(CHIP_ERROR error); + static void NotifyBLEPeripheralAdvReleased(); + +private: + // ===== Members that implement the BLEManager internal interface. + + CHIP_ERROR _Init(); + void _Shutdown(); + bool _IsAdvertisingEnabled(); + CHIP_ERROR _SetAdvertisingEnabled(bool val); + bool _IsAdvertising(); + CHIP_ERROR _SetAdvertisingMode(BLEAdvertisingMode mode); + CHIP_ERROR _GetDeviceName(char * buf, size_t bufSize); + CHIP_ERROR _SetDeviceName(const char * deviceName); + uint16_t _NumConnections(); + + void _OnPlatformEvent(const ChipDeviceEvent * event); + void HandlePlatformSpecificBLEEvent(const ChipDeviceEvent * event); + BleLayer * _GetBleLayer(); + + // ===== Members that implement virtual methods on BlePlatformDelegate. + + bool SubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, + const Ble::ChipBleUUID * charId) override; + bool UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, + const Ble::ChipBleUUID * charId) override; + bool CloseConnection(BLE_CONNECTION_OBJECT conId) override; + uint16_t GetMTU(BLE_CONNECTION_OBJECT conId) const override; + bool SendIndication(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + System::PacketBufferHandle pBuf) override; + bool SendWriteRequest(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + System::PacketBufferHandle pBuf) override; + bool SendReadRequest(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, + System::PacketBufferHandle pBuf) override; + bool SendReadResponse(BLE_CONNECTION_OBJECT conId, BLE_READ_REQUEST_CONTEXT requestContext, const Ble::ChipBleUUID * svcId, + const Ble::ChipBleUUID * charId) override; + + // ===== Members that implement virtual methods on BleApplicationDelegate. + + void NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT conId) override; + void CheckNonConcurrentBleClosing() override; + + // ===== Members that implement virtual methods on BleConnectionDelegate. + + void NewConnection(BleLayer * bleLayer, void * appState, const SetupDiscriminator & connDiscriminator) override; + void NewConnection(BleLayer * bleLayer, void * appState, BLE_CONNECTION_OBJECT connObj) override{}; + CHIP_ERROR CancelConnection() override; + + // ===== Members that implement virtual methods on ChipDeviceScannerDelegate + + void OnDeviceScanned(BluezDevice1 & device, const chip::Ble::ChipBLEDeviceIdentificationInfo & info) override; + void OnScanComplete() override; + + // ===== Members for internal use by the following friends. + + friend BLEManager & BLEMgr(); + friend BLEManagerImpl & BLEMgrImpl(); + + static BLEManagerImpl sInstance; + + // ===== Private members reserved for use by this class only. + + enum class Flags : uint16_t + { + kAsyncInitCompleted = 0x0001, /**< One-time asynchronous initialization actions have been performed. */ + kBluezBLELayerInitialized = 0x0002, /**< The Bluez layer has been initialized. */ + kAppRegistered = 0x0004, /**< The CHIPoBLE application has been registered with the Bluez layer. */ + kAdvertisingConfigured = 0x0008, /**< CHIPoBLE advertising has been configured in the Bluez layer. */ + kAdvertising = 0x0010, /**< The system is currently CHIPoBLE advertising. */ + kControlOpInProgress = 0x0020, /**< An async control operation has been issued to the ESP BLE layer. */ + kAdvertisingEnabled = 0x0040, /**< The application has enabled CHIPoBLE advertising. */ + kFastAdvertisingEnabled = 0x0080, /**< The application has enabled fast advertising. */ + kUseCustomDeviceName = 0x0100, /**< The application has configured a custom BLE device name. */ + kAdvertisingRefreshNeeded = 0x0200, /**< The advertising configuration/state in BLE layer needs to be updated. */ + kExtAdvertisingEnabled = 0x0400, /**< The application has enabled CHIPoBLE extended advertising. */ + }; + + enum + { + kMaxConnections = 1, // TODO: right max connection + kMaxDeviceNameLength = 20, // TODO: right-size this + kMaxAdvertisementDataSetSize = 31 // TODO: verify this + }; + + void DriveBLEState(); + BluezAdvertisement::AdvertisingIntervals GetAdvertisingIntervals() const; + static void HandleAdvertisingTimer(chip::System::Layer *, void * appState); + void InitiateScan(BleScanState scanType); + void CleanScanConfig(); + + CHIPoBLEServiceMode mServiceMode; + BitFlags mFlags; + + uint32_t mAdapterId = 0; + char mDeviceName[kMaxDeviceNameLength + 1]; + bool mIsCentral = false; + BluezEndpoint mEndpoint; + + BluezAdvertisement mBLEAdvertisement; + const char * mpBLEAdvUUID = nullptr; + + ChipDeviceScanner mDeviceScanner; + BLEScanConfig mBLEScanConfig; +}; + +/** + * Returns a reference to the public interface of the BLEManager singleton object. + * + * Internal components should use this to access features of the BLEManager object + * that are common to all platforms. + */ +inline BLEManager & BLEMgr() +{ + return BLEManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the BLEManager singleton object. + * + * Internal components can use this to gain access to features of the BLEManager + * that are specific to the Linux platforms. + */ +inline BLEManagerImpl & BLEMgrImpl() +{ + return BLEManagerImpl::sInstance; +} + +inline Ble::BleLayer * BLEManagerImpl::_GetBleLayer() +{ + return this; +} + +inline bool BLEManagerImpl::_IsAdvertisingEnabled() +{ + return mFlags.Has(Flags::kAdvertisingEnabled); +} + +inline bool BLEManagerImpl::_IsAdvertising() +{ + return mFlags.Has(Flags::kAdvertising); +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/BUILD.gn b/src/platform/NuttX/BUILD.gn new file mode 100644 index 00000000000000..aa63339eaac526 --- /dev/null +++ b/src/platform/NuttX/BUILD.gn @@ -0,0 +1,160 @@ +# Copyright (c) 2021 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +import("${build_root}/config/linux/pkg_config.gni") + +import("${chip_root}/src/lib/core/core.gni") +import("${chip_root}/src/platform/device.gni") + +assert(chip_device_platform == "linux" || chip_device_platform == "nuttx") + +if (chip_use_pw_logging) { + import("//build_overrides/pigweed.gni") +} + +if (chip_enable_openthread) { + import("//build_overrides/openthread.gni") + import("//build_overrides/ot_br_posix.gni") +} + +if (chip_mdns == "platform") { + pkg_config("avahi_client_config") { + packages = [ "avahi-client" ] + } +} + +static_library("NuttX") { + sources = [ + "../DeviceSafeQueue.cpp", + "../DeviceSafeQueue.h", + "../GLibTypeDeleter.h", + "../SingletonConfigurationManager.cpp", + "CHIPDevicePlatformConfig.h", + "CHIPDevicePlatformEvent.h", + "CHIPLinuxStorage.cpp", + "CHIPLinuxStorage.h", + "CHIPLinuxStorageIni.cpp", + "CHIPLinuxStorageIni.h", + "CHIPPlatformConfig.h", + "ConfigurationManagerImpl.cpp", + "ConfigurationManagerImpl.h", + "ConnectivityManagerImpl.cpp", + "ConnectivityManagerImpl.h", + "ConnectivityUtils.cpp", + "ConnectivityUtils.h", + "DeviceInstanceInfoProviderImpl.cpp", + "DeviceInstanceInfoProviderImpl.h", + "DiagnosticDataProviderImpl.cpp", + "DiagnosticDataProviderImpl.h", + "InetPlatformConfig.h", + "KeyValueStoreManagerImpl.cpp", + "KeyValueStoreManagerImpl.h", + "NetworkCommissioningDriver.h", + "NetworkCommissioningEthernetDriver.cpp", + "PlatformManagerImpl.cpp", + "PlatformManagerImpl.h", + "PosixConfig.cpp", + "PosixConfig.h", + "SystemPlatformConfig.h", + "SystemTimeSupport.cpp", + ] + + deps = [ + "${chip_root}/src/app/icd/server:icd-server-config", + "${chip_root}/src/credentials:credentials_header", + "${chip_root}/src/setup_payload", + ] + + if (!chip_use_external_logging) { + sources += [ "Logging.cpp" ] + deps += [ "${chip_root}/src/platform/logging:headers" ] + } + + if (chip_enable_openthread) { + sources += [ "NetworkCommissioningThreadDriver.cpp" ] + } + + if (chip_enable_ble) { + sources += [ + "BLEManagerImpl.cpp", + "BLEManagerImpl.h", + "BlePlatformConfig.h", + "bluez/AdapterIterator.cpp", + "bluez/AdapterIterator.h", + "bluez/BluezAdvertisement.cpp", + "bluez/BluezAdvertisement.h", + "bluez/BluezConnection.cpp", + "bluez/BluezConnection.h", + "bluez/BluezEndpoint.cpp", + "bluez/BluezEndpoint.h", + "bluez/BluezObjectIterator.h", + "bluez/BluezObjectList.h", + "bluez/ChipDeviceScanner.cpp", + "bluez/ChipDeviceScanner.h", + "bluez/Types.h", + ] + } + + public_deps = [ + "${chip_root}/src/app/common:cluster-objects", + "${chip_root}/src/platform:platform_base", + "${chip_root}/third_party/inipp", + ] + + public_configs = [] + + if (chip_mdns == "platform") { + sources += [ + "DnssdImpl.cpp", + "DnssdImpl.h", + ] + + deps += [ "${chip_root}/src/lib/dnssd:platform_header" ] + + public_configs += [ ":avahi_client_config" ] + } + + if (chip_enable_ota_requestor) { + sources += [ + "OTAImageProcessorImpl.cpp", + "OTAImageProcessorImpl.h", + ] + } + + if (chip_enable_openthread) { + sources += [ + "ThreadStackManagerImpl.cpp", + "ThreadStackManagerImpl.h", + ] + + public_deps += [ "dbus/openthread" ] + } + + if (chip_use_pw_logging) { + deps += [ "$dir_pw_log" ] + } + + if (chip_enable_wifi) { + sources += [ "NetworkCommissioningWiFiDriver.cpp" ] + + public_deps += [ "dbus/wpa" ] + } + + if (chip_enable_ble) { + public_deps += [ "dbus/bluez" ] + } +} diff --git a/src/platform/NuttX/BlePlatformConfig.h b/src/platform/NuttX/BlePlatformConfig.h new file mode 100644 index 00000000000000..54640b707ab720 --- /dev/null +++ b/src/platform/NuttX/BlePlatformConfig.h @@ -0,0 +1,41 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific configuration overrides for the CHIP BLE + * Layer on Linux platforms. + * + */ + +#pragma once + +namespace chip { +namespace DeviceLayer { +namespace Internal { +class BluezConnection; +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip + +// ==================== Platform Adaptations ==================== +#define BLE_CONNECTION_OBJECT chip::DeviceLayer::Internal::BluezConnection * +#define BLE_CONNECTION_UNINITIALIZED nullptr + +// ========== Platform-specific Configuration Overrides ========= + +/* none so far */ diff --git a/src/platform/NuttX/CHIPDevicePlatformConfig.h b/src/platform/NuttX/CHIPDevicePlatformConfig.h new file mode 100644 index 00000000000000..530e2716308842 --- /dev/null +++ b/src/platform/NuttX/CHIPDevicePlatformConfig.h @@ -0,0 +1,73 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific configuration overrides for the chip Device Layer + * on Linux platforms. + */ + +#pragma once + +// ==================== Platform Adaptations ==================== + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION 1 +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_AP 0 +#else +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION 0 +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_AP 0 +#endif + +#ifndef CHIP_DEVICE_CONFIG_ENABLE_THREAD +#define CHIP_DEVICE_CONFIG_ENABLE_THREAD CHIP_ENABLE_OPENTHREAD +#endif + +#ifndef CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#define CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE 0 +#endif + +// Start GLib main event loop if BLE, Thread or WiFi is enabled. This is needed +// to handle D-Bus communication with BlueZ or wpa_supplicant. +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE || CHIP_DEVICE_CONFIG_ENABLE_THREAD || CHIP_DEVICE_CONFIG_ENABLE_WIFI +#define CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP 1 +#else +#define CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP 0 +#endif + +// ========== Platform-specific Configuration ========= + +// These are configuration options that are unique to Linux platforms. +// These can be overridden by the application as needed. + +// ========== Platform-specific Configuration Overrides ========= + +#ifndef CHIP_DEVICE_CONFIG_CHIP_TASK_STACK_SIZE +#define CHIP_DEVICE_CONFIG_CHIP_TASK_STACK_SIZE 8192 +#endif // CHIP_DEVICE_CONFIG_CHIP_TASK_STACK_SIZE + +#ifndef CHIP_DEVICE_CONFIG_THREAD_TASK_STACK_SIZE +#define CHIP_DEVICE_CONFIG_THREAD_TASK_STACK_SIZE 8192 +#endif // CHIP_DEVICE_CONFIG_THREAD_TASK_STACK_SIZE + +#ifndef CHIP_DEVICE_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS +#define CHIP_DEVICE_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS 1 +#endif // CHIP_DEVICE_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS + +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_TELEMETRY 0 +#define CHIP_DEVICE_CONFIG_ENABLE_THREAD_TELEMETRY 0 +#define CHIP_DEVICE_CONFIG_ENABLE_THREAD_TELEMETRY_FULL 0 diff --git a/src/platform/NuttX/CHIPDevicePlatformEvent.h b/src/platform/NuttX/CHIPDevicePlatformEvent.h new file mode 100644 index 00000000000000..019dcb31e3403a --- /dev/null +++ b/src/platform/NuttX/CHIPDevicePlatformEvent.h @@ -0,0 +1,108 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Defines platform-specific event types and data for the chip + * Device Layer on Linux platforms. + */ + +#pragma once + +#include +#include + +namespace chip { +namespace DeviceLayer { + +namespace DeviceEventType { + +/** + * Enumerates Linux platform-specific event types that are visible to the application. + */ +enum PublicPlatformSpecificEventTypes +{ + /* None currently defined */ +}; + +/** + * Enumerates Linux platform-specific event types that are internal to the chip Device Layer. + */ +enum InternalPlatformSpecificEventTypes +{ + kPlatformLinuxEvent = kRange_InternalPlatformSpecific, + kPlatformLinuxBLECentralConnected, + kPlatformLinuxBLECentralConnectFailed, + kPlatformLinuxBLEWriteComplete, + kPlatformLinuxBLESubscribeOpComplete, + kPlatformLinuxBLEIndicationReceived, + kPlatformLinuxBLEC1WriteEvent, + kPlatformLinuxBLEOutOfBuffersEvent, + kPlatformLinuxBLEPeripheralRegisterAppComplete, + kPlatformLinuxBLEPeripheralAdvStartComplete, + kPlatformLinuxBLEPeripheralAdvStopComplete, + kPlatformLinuxBLEPeripheralAdvReleased, +}; + +} // namespace DeviceEventType + +/** + * Represents platform-specific event information for Linux platforms. + */ +struct ChipDevicePlatformEvent +{ + union + { + struct + { + BLE_CONNECTION_OBJECT mConnection; + } BLECentralConnected; + struct + { + CHIP_ERROR mError; + } BLECentralConnectFailed; + struct + { + BLE_CONNECTION_OBJECT mConnection; + } BLEWriteComplete; + struct + { + BLE_CONNECTION_OBJECT mConnection; + bool mIsSubscribed; + } BLESubscribeOpComplete; + struct + { + BLE_CONNECTION_OBJECT mConnection; + chip::System::PacketBuffer * mData; + } BLEIndicationReceived; + struct + { + CHIP_ERROR mError; + } BLEPeripheralRegisterAppComplete; + struct + { + CHIP_ERROR mError; + } BLEPeripheralAdvStartComplete; + struct + { + CHIP_ERROR mError; + } BLEPeripheralAdvStopComplete; + }; +}; + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/CHIPLinuxStorage.cpp b/src/platform/NuttX/CHIPLinuxStorage.cpp new file mode 100644 index 00000000000000..866ea1bf675afb --- /dev/null +++ b/src/platform/NuttX/CHIPLinuxStorage.cpp @@ -0,0 +1,353 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file implements a class for managing client application + * user-editable settings on Linux platform. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +ChipLinuxStorage::ChipLinuxStorage() +{ + mDirty = false; +} + +ChipLinuxStorage::~ChipLinuxStorage() {} + +CHIP_ERROR ChipLinuxStorage::Init(const char * configFile) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + ChipLogDetail(DeviceLayer, "ChipLinuxStorage::Init: Using KVS config file: %s", StringOrNullMarker(configFile)); + if (mInitialized) + { + ChipLogError(DeviceLayer, "ChipLinuxStorage::Init: Attempt to re-initialize with KVS config file: %s", + StringOrNullMarker(configFile)); + return CHIP_NO_ERROR; + } + + mConfigPath.assign(configFile); + retval = ChipLinuxStorageIni::Init(); + + if (retval == CHIP_NO_ERROR) + { + std::ifstream ifs; + + ifs.open(configFile, std::ifstream::in); + + // Create default setting file if not exist. + if (!ifs.good()) + { + mDirty = true; + retval = Commit(); + mDirty = false; + } + } + + if (retval == CHIP_NO_ERROR) + { + retval = ChipLinuxStorageIni::AddConfig(mConfigPath); + } + + mInitialized = true; + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, bool & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + uint32_t result; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUIntValue(key, result); + val = (result != 0); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, uint16_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUInt16Value(key, val); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, uint32_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUIntValue(key, val); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, uint64_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUInt64Value(key, val); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValueStr(const char * key, char * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetStringValue(key, buf, bufSize, outLen); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValueBin(const char * key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetBinaryBlobValue(key, buf, bufSize, outLen); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, bool val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + if (val) + { + retval = WriteValue(key, static_cast(1)); + } + else + { + retval = WriteValue(key, static_cast(0)); + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, uint16_t val) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%u", val); + + return WriteValueStr(key, buf); +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, uint32_t val) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "%" PRIu32, val); + + return WriteValueStr(key, buf); +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, uint64_t val) +{ + char buf[64]; + + snprintf(buf, sizeof(buf), "%" PRIu64, val); + + return WriteValueStr(key, buf); +} + +CHIP_ERROR ChipLinuxStorage::WriteValueStr(const char * key, const char * val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::AddEntry(key, val); + + mDirty = true; + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::WriteValueBin(const char * key, const uint8_t * data, size_t dataLen) +{ + static const size_t kMaxBlobSize = 5 * 1024; + + CHIP_ERROR retval = CHIP_NO_ERROR; + chip::Platform::ScopedMemoryBuffer encodedData; + size_t encodedDataLen = 0; + size_t expectedEncodedLen = ((dataLen + 3) * 4) / 3; + + // We only support encoding blobs up to 5kb + if (dataLen > kMaxBlobSize) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + + // Compute our expectedEncodedLen + // Allocate just enough space for the encoded data, and the NULL terminator + if (retval == CHIP_NO_ERROR) + { + if (!encodedData.Alloc(expectedEncodedLen + 1)) + { + retval = CHIP_ERROR_NO_MEMORY; + } + } + + // Encode it + if (retval == CHIP_NO_ERROR) + { + // We tested above that dataLen is no more than kMaxBlobSize. + static_assert(kMaxBlobSize < UINT16_MAX, "dataLen won't fit"); + encodedDataLen = Base64Encode(data, static_cast(dataLen), encodedData.Get()); + encodedData[encodedDataLen] = 0; + } + + // Store it + if (retval == CHIP_NO_ERROR) + { + WriteValueStr(key, encodedData.Get()); + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ClearValue(const char * key) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::RemoveEntry(key); + + if (retval == CHIP_NO_ERROR) + { + mDirty = true; + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ClearAll() +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::RemoveAll(); + + mLock.unlock(); + + if (retval == CHIP_NO_ERROR) + { + mDirty = true; + retval = Commit(); + } + else + { + retval = CHIP_ERROR_WRITE_FAILED; + } + + return retval; +} + +bool ChipLinuxStorage::HasValue(const char * key) +{ + bool retval; + + mLock.lock(); + + retval = ChipLinuxStorageIni::HasValue(key); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::Commit() +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + if (mDirty && !mConfigPath.empty()) + { + mLock.lock(); + + retval = ChipLinuxStorageIni::CommitConfig(mConfigPath); + + mLock.unlock(); + } + else + { + retval = CHIP_ERROR_WRITE_FAILED; + } + + return retval; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/CHIPLinuxStorage.h b/src/platform/NuttX/CHIPLinuxStorage.h new file mode 100644 index 00000000000000..57ba0f054e4a89 --- /dev/null +++ b/src/platform/NuttX/CHIPLinuxStorage.h @@ -0,0 +1,98 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file defines a class for managing client application + * user-editable settings. CHIP settings are partitioned into two + * distinct areas: + * + * 1. immutable / durable: factory parameters (CHIP_DEFAULT_FACTORY_PATH) + * 2. mutable / ephemeral: user parameters (CHIP_DEFAULT_CONFIG_PATH/CHIP_DEFAULT_DATA_PATH) + * + * The ephemeral partition should be erased during factory reset. + * + * ChipLinuxStorage wraps the storage class ChipLinuxStorageIni with mutex. + * + */ + +#pragma once + +#include +#include + +#ifndef FATCONFDIR +#define FATCONFDIR "/tmp" +#endif + +#ifndef SYSCONFDIR +#define SYSCONFDIR "/tmp" +#endif + +#ifndef LOCALSTATEDIR +#define LOCALSTATEDIR "/tmp" +#endif + +#define CHIP_DEFAULT_FACTORY_PATH \ + FATCONFDIR "/" \ + "chip_factory.ini" +#define CHIP_DEFAULT_CONFIG_PATH \ + SYSCONFDIR "/" \ + "chip_config.ini" +#define CHIP_DEFAULT_DATA_PATH \ + LOCALSTATEDIR "/" \ + "chip_counters.ini" + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +class ChipLinuxStorage : private ChipLinuxStorageIni +{ +public: + ChipLinuxStorage(); + ~ChipLinuxStorage(); + + CHIP_ERROR Init(const char * configFile); + CHIP_ERROR ReadValue(const char * key, bool & val); + CHIP_ERROR ReadValue(const char * key, uint16_t & val); + CHIP_ERROR ReadValue(const char * key, uint32_t & val); + CHIP_ERROR ReadValue(const char * key, uint64_t & val); + CHIP_ERROR ReadValueStr(const char * key, char * buf, size_t bufSize, size_t & outLen); + CHIP_ERROR ReadValueBin(const char * key, uint8_t * buf, size_t bufSize, size_t & outLen); + CHIP_ERROR WriteValue(const char * key, bool val); + CHIP_ERROR WriteValue(const char * key, uint16_t val); + CHIP_ERROR WriteValue(const char * key, uint32_t val); + CHIP_ERROR WriteValue(const char * key, uint64_t val); + CHIP_ERROR WriteValueStr(const char * key, const char * val); + CHIP_ERROR WriteValueBin(const char * key, const uint8_t * data, size_t dataLen); + CHIP_ERROR ClearValue(const char * key); + CHIP_ERROR ClearAll(); + CHIP_ERROR Commit(); + bool HasValue(const char * key); + +private: + std::mutex mLock; + bool mDirty; + std::string mConfigPath; + bool mInitialized = false; +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/CHIPLinuxStorageIni.cpp b/src/platform/NuttX/CHIPLinuxStorageIni.cpp new file mode 100644 index 00000000000000..e9dbe107ffad9e --- /dev/null +++ b/src/platform/NuttX/CHIPLinuxStorageIni.cpp @@ -0,0 +1,404 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the Configuration key-value store object + * using IniPP on Linux platform. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace chip::IniEscaping; + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +CHIP_ERROR ChipLinuxStorageIni::Init() +{ + return RemoveAll(); +} + +CHIP_ERROR ChipLinuxStorageIni::GetDefaultSection(std::map & section) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + auto it = mConfigStore.sections.find("DEFAULT"); + + if (it != mConfigStore.sections.end()) + { + section = mConfigStore.sections["DEFAULT"]; + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::AddConfig(const std::string & configFile) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::ifstream ifs; + + ifs.open(configFile, std::ifstream::in); + + if (ifs.is_open()) + { + mConfigStore.parse(ifs); + ifs.close(); + } + else + { + ChipLogError(DeviceLayer, "Failed to open config file: %s", configFile.c_str()); + retval = CHIP_ERROR_OPEN_FAILED; + } + + return retval; +} + +// Updating a file atomically and durably on Linux requires: +// 1. Writing to a temporary file +// 2. Sync'ing the temp file to commit updated data +// 3. Using rename() to overwrite the existing file +CHIP_ERROR ChipLinuxStorageIni::CommitConfig(const std::string & configFile) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::string tmpPath = configFile + "-XXXXXX"; + + int fd = mkstemp(&tmpPath[0]); + if (fd != -1) + { + std::ofstream ofs; + + ChipLogProgress(DeviceLayer, "writing settings to file (%s)", tmpPath.c_str()); + + ofs.open(tmpPath, std::ofstream::out | std::ofstream::trunc); + mConfigStore.generate(ofs); + + close(fd); + + if (rename(tmpPath.c_str(), configFile.c_str()) == 0) + { + ChipLogProgress(DeviceLayer, "renamed tmp file to file (%s)", configFile.c_str()); + } + else + { + ChipLogError(DeviceLayer, "failed to rename (%s), %s (%d)", tmpPath.c_str(), strerror(errno), errno); + retval = CHIP_ERROR_WRITE_FAILED; + } + } + else + { + ChipLogError(DeviceLayer, "failed to open file (%s) for writing", tmpPath.c_str()); + retval = CHIP_ERROR_OPEN_FAILED; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetUInt16Value(const char * key, uint16_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + if (it != section.end()) + { + if (!inipp::extract(section[escapedKey], val)) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetUIntValue(const char * key, uint32_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + if (it != section.end()) + { + if (!inipp::extract(section[escapedKey], val)) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetUInt64Value(const char * key, uint64_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + if (it != section.end()) + { + if (!inipp::extract(section[escapedKey], val)) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetStringValue(const char * key, char * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + if (it != section.end()) + { + std::string value; + if (inipp::extract(section[escapedKey], value)) + { + size_t len = value.size(); + + if (len > bufSize - 1) + { + outLen = len; + retval = CHIP_ERROR_BUFFER_TOO_SMALL; + } + else + { + outLen = value.copy(buf, len); + buf[outLen] = '\0'; + } + } + else + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetBinaryBlobDataAndLengths(const char * key, + chip::Platform::ScopedMemoryBuffer & encodedData, + size_t & encodedDataLen, size_t & decodedDataLen) +{ + size_t encodedDataPaddingLen = 0; + std::map section; + CHIP_ERROR err = GetDefaultSection(section); + if (err != CHIP_NO_ERROR) + { + return err; + } + + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + if (it == section.end()) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + std::string value; + + // Compute the expectedDecodedLen + if (!inipp::extract(section[escapedKey], value)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + size_t len = value.size(); + if (!encodedData.Alloc(len + 1)) + { + return CHIP_ERROR_NO_MEMORY; + } + encodedDataLen = value.copy(encodedData.Get(), len); + encodedData[encodedDataLen] = '\0'; + + // Check if encoded data was padded. Only "=" or "==" padding combinations are allowed. + if ((encodedDataLen > 0) && (encodedData[encodedDataLen - 1] == '=')) + { + encodedDataPaddingLen++; + if ((encodedDataLen > 1) && (encodedData[encodedDataLen - 2] == '=')) + encodedDataPaddingLen++; + } + + decodedDataLen = ((encodedDataLen - encodedDataPaddingLen) * 3) / 4; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ChipLinuxStorageIni::GetBinaryBlobValue(const char * key, uint8_t * decodedData, size_t bufSize, size_t & decodedDataLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + chip::Platform::ScopedMemoryBuffer encodedData; + size_t encodedDataLen; + size_t expectedDecodedLen = 0; + + retval = GetBinaryBlobDataAndLengths(key, encodedData, encodedDataLen, expectedDecodedLen); + + // Check the size + if (retval != CHIP_NO_ERROR) + { + return retval; + } + + if (expectedDecodedLen > bufSize) + { + decodedDataLen = expectedDecodedLen; + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + + if (encodedDataLen > UINT16_MAX) + { + // We can't even pass this length into Base64Decode. + return CHIP_ERROR_DECODE_FAILED; + } + + // Decode it + // Cast is safe because we checked encodedDataLen above. + decodedDataLen = Base64Decode(encodedData.Get(), static_cast(encodedDataLen), decodedData); + if (decodedDataLen == UINT16_MAX || decodedDataLen > expectedDecodedLen) + { + return CHIP_ERROR_DECODE_FAILED; + } + + return CHIP_NO_ERROR; +} + +bool ChipLinuxStorageIni::HasValue(const char * key) +{ + std::map section; + + if (GetDefaultSection(section) != CHIP_NO_ERROR) + return false; + + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + return it != section.end(); +} + +CHIP_ERROR ChipLinuxStorageIni::AddEntry(const char * key, const char * value) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + if ((key != nullptr) && (value != nullptr)) + { + std::string escapedKey = EscapeKey(key); + std::map & section = mConfigStore.sections["DEFAULT"]; + section[escapedKey] = std::string(value); + } + else + { + ChipLogError(DeviceLayer, "Invalid input argument, failed to add entry"); + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::RemoveEntry(const char * key) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + std::map & section = mConfigStore.sections["DEFAULT"]; + + std::string escapedKey = EscapeKey(key); + auto it = section.find(escapedKey); + + if (it != section.end()) + { + section.erase(it); + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::RemoveAll() +{ + mConfigStore.clear(); + + return CHIP_NO_ERROR; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/CHIPLinuxStorageIni.h b/src/platform/NuttX/CHIPLinuxStorageIni.h new file mode 100644 index 00000000000000..1c6e564c12bf35 --- /dev/null +++ b/src/platform/NuttX/CHIPLinuxStorageIni.h @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the Configuration key-value store interface + * using IniPP. + * + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +class ChipLinuxStorageIni +{ +public: + CHIP_ERROR Init(); + CHIP_ERROR AddConfig(const std::string & configFile); + CHIP_ERROR CommitConfig(const std::string & configFile); + CHIP_ERROR GetUInt16Value(const char * key, uint16_t & val); + CHIP_ERROR GetUIntValue(const char * key, uint32_t & val); + CHIP_ERROR GetUInt64Value(const char * key, uint64_t & val); + CHIP_ERROR GetStringValue(const char * key, char * buf, size_t bufSize, size_t & outLen); + CHIP_ERROR GetBinaryBlobValue(const char * key, uint8_t * decodedData, size_t bufSize, size_t & decodedDataLen); + bool HasValue(const char * key); + +protected: + CHIP_ERROR AddEntry(const char * key, const char * value); + CHIP_ERROR RemoveEntry(const char * key); + CHIP_ERROR RemoveAll(); + +private: + CHIP_ERROR GetDefaultSection(std::map & section); + CHIP_ERROR GetBinaryBlobDataAndLengths(const char * key, chip::Platform::ScopedMemoryBuffer & encodedData, + size_t & encodedDataLen, size_t & decodedDataLen); + inipp::Ini mConfigStore; +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/CHIPPlatformConfig.h b/src/platform/NuttX/CHIPPlatformConfig.h new file mode 100644 index 00000000000000..9e2832307f27b2 --- /dev/null +++ b/src/platform/NuttX/CHIPPlatformConfig.h @@ -0,0 +1,71 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific configuration overrides for CHIP on + * Linux platforms. + */ + +#pragma once + +// ==================== General Platform Adaptations ==================== + +#define CHIP_CONFIG_ABORT() abort() + +using CHIP_CONFIG_PERSISTED_STORAGE_KEY_TYPE = const char *; +#define CHIP_CONFIG_PERSISTED_STORAGE_MAX_KEY_LENGTH 16 + +#define CHIP_CONFIG_LIFETIIME_PERSISTED_COUNTER_KEY "life-count" + +#define CHIP_CONFIG_ERROR_FORMAT_AS_STRING 1 +#define CHIP_CONFIG_ERROR_SOURCE 1 + +#define CHIP_CONFIG_VERBOSE_VERIFY_OR_DIE 1 + +#define CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT 1 + +// ==================== Security Adaptations ==================== + +// If unspecified, assume crypto is fast on Linux +#ifndef CHIP_CONFIG_SLOW_CRYPTO +#define CHIP_CONFIG_SLOW_CRYPTO 0 +#endif // CHIP_CONFIG_SLOW_CRYPTO + +// ==================== General Configuration Overrides ==================== + +#ifndef CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS +#define CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS 8 +#endif // CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS + +#ifndef CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS +#define CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS 8 +#endif // CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS + +#ifndef CHIP_LOG_FILTERING +#define CHIP_LOG_FILTERING 1 +#endif // CHIP_LOG_FILTERING + +#ifndef CHIP_CONFIG_BDX_MAX_NUM_TRANSFERS +#define CHIP_CONFIG_BDX_MAX_NUM_TRANSFERS 1 +#endif // CHIP_CONFIG_BDX_MAX_NUM_TRANSFERS + +// ==================== Security Configuration Overrides ==================== + +#ifndef CHIP_CONFIG_KVS_PATH +#define CHIP_CONFIG_KVS_PATH "/tmp/chip_kvs" +#endif // CHIP_CONFIG_KVS_PATH diff --git a/src/platform/NuttX/ConfigurationManagerImpl.cpp b/src/platform/NuttX/ConfigurationManagerImpl.cpp new file mode 100644 index 00000000000000..e451832433f2c2 --- /dev/null +++ b/src/platform/NuttX/ConfigurationManagerImpl.cpp @@ -0,0 +1,385 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides the implementation of the Device Layer ConfigurationManager object + * for Linux platforms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { + +using namespace ::chip::DeviceLayer::Internal; + +ConfigurationManagerImpl & ConfigurationManagerImpl::GetDefaultInstance() +{ + static ConfigurationManagerImpl sInstance; + return sInstance; +} + +CHIP_ERROR ConfigurationManagerImpl::Init() +{ + CHIP_ERROR err; + uint32_t rebootCount; + + // Force initialization of NVS namespaces if they doesn't already exist. + err = PosixConfig::EnsureNamespace(PosixConfig::kConfigNamespace_ChipFactory); + SuccessOrExit(err); + err = PosixConfig::EnsureNamespace(PosixConfig::kConfigNamespace_ChipConfig); + SuccessOrExit(err); + err = PosixConfig::EnsureNamespace(PosixConfig::kConfigNamespace_ChipCounters); + SuccessOrExit(err); + + // Initialize the generic implementation base class. + err = Internal::GenericConfigurationManagerImpl::Init(); + SuccessOrExit(err); + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_VendorId)) + { + err = StoreVendorId(CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_ProductId)) + { + err = StoreProductId(CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID); + SuccessOrExit(err); + } + + if (PosixConfig::ConfigValueExists(PosixConfig::kCounterKey_RebootCount)) + { + err = GetRebootCount(rebootCount); + SuccessOrExit(err); + + err = StoreRebootCount(rebootCount + 1); + SuccessOrExit(err); + } + else + { + // The first boot after factory reset of the Node. + err = StoreRebootCount(1); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kCounterKey_TotalOperationalHours)) + { + err = StoreTotalOperationalHours(0); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kCounterKey_BootReason)) + { + err = StoreBootReason(to_underlying(BootReasonType::kUnspecified)); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_RegulatoryLocation)) + { + uint32_t location = to_underlying(chip::app::Clusters::GeneralCommissioning::RegulatoryLocationTypeEnum::kIndoor); + err = WriteConfigValue(PosixConfig::kConfigKey_RegulatoryLocation, location); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_LocationCapability)) + { + uint32_t location = to_underlying(chip::app::Clusters::GeneralCommissioning::RegulatoryLocationTypeEnum::kIndoorOutdoor); + err = WriteConfigValue(PosixConfig::kConfigKey_LocationCapability, location); + SuccessOrExit(err); + } + + err = CHIP_NO_ERROR; + +exit: + return err; +} + +CHIP_ERROR ConfigurationManagerImpl::GetPrimaryWiFiMACAddress(uint8_t * buf) +{ + struct ifaddrs * addresses = nullptr; + struct sockaddr_ll * mac = nullptr; + CHIP_ERROR error = CHIP_NO_ERROR; + + // TODO: ideally the buffer size should have been passed as a span, however + // for now use the size that is validated in GenericConfigurationManagerImpl.ipp + constexpr size_t kExpectedBufMinSize = ConfigurationManager::kPrimaryMACAddressLength; + memset(buf, 0, kExpectedBufMinSize); + + // Prioritize address for interface matching the WiFi interface name + // specified in the config headers. Otherwise, use the address for the + // first non-loopback interface. + VerifyOrExit(getifaddrs(&addresses) == 0, error = CHIP_ERROR_INTERNAL); + for (auto addr = addresses; addr != nullptr; addr = addr->ifa_next) + { + if ((addr->ifa_addr) && (addr->ifa_addr->sa_family == AF_PACKET)) + { + if (strncmp(addr->ifa_name, CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME, IFNAMSIZ) == 0) + { + mac = (struct sockaddr_ll *) addr->ifa_addr; + break; + } + + if (strncmp(addr->ifa_name, "lo", IFNAMSIZ) != 0 && !mac) + { + mac = (struct sockaddr_ll *) addr->ifa_addr; + } + } + } + + if (mac) + { + memcpy(buf, mac->sll_addr, std::min(mac->sll_halen, kExpectedBufMinSize)); + } + else + { + error = CHIP_ERROR_NO_ENDPOINT; + } + + freeifaddrs(addresses); + +exit: + return error; +} + +bool ConfigurationManagerImpl::CanFactoryReset() +{ + // TODO(#742): query the application to determine if factory reset is allowed. + return true; +} + +void ConfigurationManagerImpl::InitiateFactoryReset() +{ + PlatformMgr().ScheduleWork(DoFactoryReset); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadPersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t & value) +{ + PosixConfig::Key configKey{ PosixConfig::kConfigNamespace_ChipCounters, key }; + + CHIP_ERROR err = ReadConfigValue(configKey, value); + if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND) + { + err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } + return err; +} + +CHIP_ERROR ConfigurationManagerImpl::WritePersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t value) +{ + PosixConfig::Key configKey{ PosixConfig::kConfigNamespace_ChipCounters, key }; + return WriteConfigValue(configKey, value); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, bool & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, uint16_t & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, uint32_t & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, uint64_t & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) +{ + return PosixConfig::ReadConfigValueStr(key, buf, bufSize, outLen); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + return PosixConfig::ReadConfigValueBin(key, buf, bufSize, outLen); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, bool val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, uint16_t val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, uint32_t val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, uint64_t val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueStr(Key key, const char * str) +{ + return PosixConfig::WriteConfigValueStr(key, str); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueStr(Key key, const char * str, size_t strLen) +{ + return PosixConfig::WriteConfigValueStr(key, str, strLen); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) +{ + return PosixConfig::WriteConfigValueBin(key, data, dataLen); +} + +void ConfigurationManagerImpl::RunConfigUnitTest() +{ + PosixConfig::RunConfigUnitTest(); +} + +void ConfigurationManagerImpl::DoFactoryReset(intptr_t arg) +{ + CHIP_ERROR err; + + ChipLogProgress(DeviceLayer, "Performing factory reset"); + + err = PosixConfig::FactoryResetConfig(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to factory reset configurations: %s", ErrorStr(err)); + } + + err = PosixConfig::FactoryResetCounters(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to factory reset counters: %s", ErrorStr(err)); + } + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + + ChipLogProgress(DeviceLayer, "Clearing Thread provision"); + ThreadStackMgr().ErasePersistentInfo(); + +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD + + // Restart the system. + ChipLogProgress(DeviceLayer, "System restarting (not implemented)"); + // TODO(#742): restart CHIP exe +} + +CHIP_ERROR ConfigurationManagerImpl::StoreVendorId(uint16_t vendorId) +{ + return WriteConfigValue(PosixConfig::kConfigKey_VendorId, vendorId); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreProductId(uint16_t productId) +{ + return WriteConfigValue(PosixConfig::kConfigKey_ProductId, productId); +} + +CHIP_ERROR ConfigurationManagerImpl::GetRebootCount(uint32_t & rebootCount) +{ + return ReadConfigValue(PosixConfig::kCounterKey_RebootCount, rebootCount); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreRebootCount(uint32_t rebootCount) +{ + return WriteConfigValue(PosixConfig::kCounterKey_RebootCount, rebootCount); +} + +CHIP_ERROR ConfigurationManagerImpl::GetTotalOperationalHours(uint32_t & totalOperationalHours) +{ + return ReadConfigValue(PosixConfig::kCounterKey_TotalOperationalHours, totalOperationalHours); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreTotalOperationalHours(uint32_t totalOperationalHours) +{ + return WriteConfigValue(PosixConfig::kCounterKey_TotalOperationalHours, totalOperationalHours); +} + +CHIP_ERROR ConfigurationManagerImpl::GetBootReason(uint32_t & bootReason) +{ + return ReadConfigValue(PosixConfig::kCounterKey_BootReason, bootReason); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreBootReason(uint32_t bootReason) +{ + return WriteConfigValue(PosixConfig::kCounterKey_BootReason, bootReason); +} + +CHIP_ERROR ConfigurationManagerImpl::GetRegulatoryLocation(uint8_t & location) +{ + uint32_t value; + + if (CHIP_NO_ERROR != ReadConfigValue(PosixConfig::kConfigKey_RegulatoryLocation, value)) + { + ReturnErrorOnFailure(GetLocationCapability(location)); + + if (CHIP_NO_ERROR != StoreRegulatoryLocation(location)) + { + ChipLogError(DeviceLayer, "Failed to store RegulatoryLocation"); + } + } + else + { + location = static_cast(value); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConfigurationManagerImpl::GetLocationCapability(uint8_t & location) +{ + uint32_t value = 0; + + CHIP_ERROR err = ReadConfigValue(PosixConfig::kConfigKey_LocationCapability, value); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(value <= UINT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + location = static_cast(value); + } + + return err; +} + +ConfigurationManager & ConfigurationMgrImpl() +{ + return ConfigurationManagerImpl::GetDefaultInstance(); +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ConfigurationManagerImpl.h b/src/platform/NuttX/ConfigurationManagerImpl.h new file mode 100644 index 00000000000000..f995307c1bc937 --- /dev/null +++ b/src/platform/NuttX/ConfigurationManagerImpl.h @@ -0,0 +1,95 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the ConfigurationManager object + * for Linux platforms. + */ + +#pragma once + +#include "platform/internal/DeviceNetworkInfo.h" +#include + +#include + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the ConfigurationManager singleton object for the Linux platform. + */ +class ConfigurationManagerImpl : public Internal::GenericConfigurationManagerImpl +{ +public: + CHIP_ERROR StoreVendorId(uint16_t vendorId); + CHIP_ERROR StoreProductId(uint16_t productId); + + CHIP_ERROR GetRebootCount(uint32_t & rebootCount) override; + CHIP_ERROR StoreRebootCount(uint32_t rebootCount) override; + CHIP_ERROR GetTotalOperationalHours(uint32_t & totalOperationalHours) override; + CHIP_ERROR StoreTotalOperationalHours(uint32_t totalOperationalHours) override; + CHIP_ERROR GetBootReason(uint32_t & bootReason) override; + CHIP_ERROR StoreBootReason(uint32_t bootReason) override; + CHIP_ERROR GetRegulatoryLocation(uint8_t & location) override; + CHIP_ERROR GetLocationCapability(uint8_t & location) override; + static ConfigurationManagerImpl & GetDefaultInstance(); + +private: + // ===== Members that implement the ConfigurationManager public interface. + + CHIP_ERROR Init() override; + CHIP_ERROR GetPrimaryWiFiMACAddress(uint8_t * buf) override; + bool CanFactoryReset() override; + void InitiateFactoryReset() override; + CHIP_ERROR ReadPersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t & value) override; + CHIP_ERROR WritePersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t value) override; + + // NOTE: Other public interface methods are implemented by GenericConfigurationManagerImpl<>. + CHIP_ERROR WriteConfigValue(Key key, uint16_t val); + CHIP_ERROR ReadConfigValue(Key key, uint16_t & val); + + // ===== Members that implement the GenericConfigurationManagerImpl protected interface. + CHIP_ERROR ReadConfigValue(Key key, bool & val) override; + CHIP_ERROR ReadConfigValue(Key key, uint32_t & val) override; + CHIP_ERROR ReadConfigValue(Key key, uint64_t & val) override; + CHIP_ERROR ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) override; + CHIP_ERROR ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) override; + CHIP_ERROR WriteConfigValue(Key key, bool val) override; + CHIP_ERROR WriteConfigValue(Key key, uint32_t val) override; + CHIP_ERROR WriteConfigValue(Key key, uint64_t val) override; + CHIP_ERROR WriteConfigValueStr(Key key, const char * str) override; + CHIP_ERROR WriteConfigValueStr(Key key, const char * str, size_t strLen) override; + CHIP_ERROR WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) override; + void RunConfigUnitTest(void) override; + + // ===== Private members reserved for use by this class only. + + static void DoFactoryReset(intptr_t arg); +}; + +/** + * Returns the platform-specific implementation of the ConfigurationManager object. + * + * Applications can use this to gain access to features of the ConfigurationManager + * that are specific to the selected platform. + */ +ConfigurationManager & ConfigurationMgrImpl(); + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ConnectivityManagerImpl.cpp b/src/platform/NuttX/ConnectivityManagerImpl.cpp new file mode 100644 index 00000000000000..ea4ca105bbcb45 --- /dev/null +++ b/src/platform/NuttX/ConnectivityManagerImpl.cpp @@ -0,0 +1,1904 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2019 Nest Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#if INET_CONFIG_ENABLE_TCP_ENDPOINT +#include +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#include +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +#include +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +#include +#include +#include +#endif + +using namespace ::chip; +using namespace ::chip::TLV; +using namespace ::chip::Credentials; +using namespace ::chip::DeviceLayer; +using namespace ::chip::DeviceLayer::Internal; +using namespace ::chip::app::Clusters::GeneralDiagnostics; +using namespace ::chip::app::Clusters::WiFiNetworkDiagnostics; + +using namespace ::chip::DeviceLayer::NetworkCommissioning; + +namespace chip { + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + +template <> +struct GAutoPtrDeleter +{ + using deleter = GObjectDeleter; +}; + +template <> +struct GAutoPtrDeleter +{ + using deleter = GObjectDeleter; +}; + +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +namespace DeviceLayer { + +ConnectivityManagerImpl ConnectivityManagerImpl::sInstance; + +CHIP_ERROR ConnectivityManagerImpl::_Init() +{ +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + mWiFiStationMode = kWiFiStationMode_Disabled; + mWiFiStationReconnectInterval = System::Clock::Milliseconds32(CHIP_DEVICE_CONFIG_WIFI_STATION_RECONNECT_INTERVAL); +#endif + mpConnectCallback = nullptr; + mpScanCallback = nullptr; + + if (ConnectivityUtils::GetEthInterfaceName(mEthIfName, IFNAMSIZ) == CHIP_NO_ERROR) + { + ChipLogProgress(DeviceLayer, "Got Ethernet interface: %s", mEthIfName); + } + else + { + ChipLogError(DeviceLayer, "Failed to get Ethernet interface"); + mEthIfName[0] = '\0'; + } + + if (GetDiagnosticDataProvider().ResetEthNetworkDiagnosticsCounts() != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to reset Ethernet statistic counts"); + } + + // Initialize the generic base classes that require it. +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + GenericConnectivityManagerImpl_Thread::_Init(); +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + if (ConnectivityUtils::GetWiFiInterfaceName(sWiFiIfName, IFNAMSIZ) == CHIP_NO_ERROR) + { + ChipLogProgress(DeviceLayer, "Got WiFi interface: %s", sWiFiIfName); + } + else + { + ChipLogError(DeviceLayer, "Failed to get WiFi interface"); + sWiFiIfName[0] = '\0'; + } + + if (GetDiagnosticDataProvider().ResetWiFiNetworkDiagnosticsCounts() != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to reset WiFi statistic counts"); + } +#endif + + return CHIP_NO_ERROR; +} + +void ConnectivityManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) +{ + // Forward the event to the generic base classes as needed. +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + GenericConnectivityManagerImpl_Thread::_OnPlatformEvent(event); +#endif +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + +ConnectivityManager::WiFiStationMode ConnectivityManagerImpl::_GetWiFiStationMode() +{ + if (mWiFiStationMode != kWiFiStationMode_ApplicationControlled) + { + std::lock_guard lock(mWpaSupplicantMutex); + mWiFiStationMode = (mWpaSupplicant.iface != nullptr) ? kWiFiStationMode_Enabled : kWiFiStationMode_Disabled; + } + + return mWiFiStationMode; +} + +CHIP_ERROR ConnectivityManagerImpl::_SetWiFiStationMode(ConnectivityManager::WiFiStationMode val) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrExit(val != ConnectivityManager::kWiFiStationMode_NotSupported, err = CHIP_ERROR_INVALID_ARGUMENT); + + if (mWiFiStationMode != val) + { + ChipLogProgress(DeviceLayer, "WiFi station mode change: %s -> %s", WiFiStationModeToStr(mWiFiStationMode), + WiFiStationModeToStr(val)); + } + + mWiFiStationMode = val; +exit: + return err; +} + +System::Clock::Timeout ConnectivityManagerImpl::_GetWiFiStationReconnectInterval() +{ + return mWiFiStationReconnectInterval; +} + +CHIP_ERROR ConnectivityManagerImpl::_SetWiFiStationReconnectInterval(System::Clock::Timeout val) +{ + mWiFiStationReconnectInterval = val; + + return CHIP_NO_ERROR; +} + +bool ConnectivityManagerImpl::_IsWiFiStationEnabled() +{ + return GetWiFiStationMode() == kWiFiStationMode_Enabled; +} + +bool ConnectivityManagerImpl::_IsWiFiStationConnected() +{ + bool ret = false; + const gchar * state = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: _IsWiFiStationConnected: interface not connected"); + return false; + } + + state = wpa_fi_w1_wpa_supplicant1_interface_get_state(mWpaSupplicant.iface); + if (g_strcmp0(state, "completed") == 0) + { + mConnectivityFlag.Set(ConnectivityFlags::kHaveIPv4InternetConnectivity) + .Set(ConnectivityFlags::kHaveIPv6InternetConnectivity); + ret = true; + } + + return ret; +} + +bool ConnectivityManagerImpl::_IsWiFiStationApplicationControlled() +{ + return mWiFiStationMode == ConnectivityManager::kWiFiStationMode_ApplicationControlled; +} + +bool ConnectivityManagerImpl::_IsWiFiStationProvisioned() +{ + bool ret = false; + const gchar * bss = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: _IsWiFiStationProvisioned: interface not connected"); + return false; + } + + bss = wpa_fi_w1_wpa_supplicant1_interface_get_current_bss(mWpaSupplicant.iface); + if (g_str_match_string("BSSs", bss, true)) + { + ret = true; + } + + return ret; +} + +void ConnectivityManagerImpl::_ClearWiFiStationProvision() +{ + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: _ClearWiFiStationProvision: interface not connected"); + return; + } + + if (mWiFiStationMode != kWiFiStationMode_ApplicationControlled) + { + GAutoPtr err; + wpa_fi_w1_wpa_supplicant1_interface_call_remove_all_networks_sync(mWpaSupplicant.iface, nullptr, &err.GetReceiver()); + + if (err != nullptr) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to remove all networks with error: %s", + err ? err->message : "unknown error"); + } + } +} + +bool ConnectivityManagerImpl::_CanStartWiFiScan() +{ + std::lock_guard lock(mWpaSupplicantMutex); + + bool ret = mWpaSupplicant.state == GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED && + mWpaSupplicant.scanState == GDBusWpaSupplicant::WIFI_SCANNING_IDLE; + + return ret; +} + +CHIP_ERROR ConnectivityManagerImpl::_SetWiFiAPMode(WiFiAPMode val) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrExit(val != kWiFiAPMode_NotSupported, err = CHIP_ERROR_INVALID_ARGUMENT); + + if (mWiFiAPMode != val) + { + ChipLogProgress(DeviceLayer, "WiFi AP mode change: %s -> %s", WiFiAPModeToStr(mWiFiAPMode), WiFiAPModeToStr(val)); + mWiFiAPMode = val; + + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveAPState(); }); + } + +exit: + return err; +} + +void ConnectivityManagerImpl::_DemandStartWiFiAP() +{ + if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand start WiFi AP"); + mLastAPDemandTime = System::SystemClock().GetMonotonicTimestamp(); + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveAPState(); }); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand start WiFi AP ignored, mode: %s", WiFiAPModeToStr(mWiFiAPMode)); + } +} + +void ConnectivityManagerImpl::_StopOnDemandWiFiAP() +{ + if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand stop WiFi AP"); + mLastAPDemandTime = System::Clock::kZero; + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveAPState(); }); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand stop WiFi AP ignored, mode: %s", WiFiAPModeToStr(mWiFiAPMode)); + } +} + +void ConnectivityManagerImpl::_MaintainOnDemandWiFiAP() +{ + if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + if (mWiFiAPState == kWiFiAPState_Active) + { + mLastAPDemandTime = System::SystemClock().GetMonotonicTimestamp(); + } + } +} + +void ConnectivityManagerImpl::_SetWiFiAPIdleTimeout(System::Clock::Timeout val) +{ + mWiFiAPIdleTimeout = val; + DeviceLayer::SystemLayer().ScheduleLambda([this] { DriveAPState(); }); +} + +void ConnectivityManagerImpl::UpdateNetworkStatus() +{ + Network configuredNetwork; + + VerifyOrReturn(IsWiFiStationEnabled() && mpStatusChangeCallback != nullptr); + + CHIP_ERROR err = GetConfiguredNetwork(configuredNetwork); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get configured network when updating network status: %s", err.AsString()); + return; + } + + // If we have already connected to the WiFi AP, then return null to indicate a success state. + if (IsWiFiStationConnected()) + { + mpStatusChangeCallback->OnNetworkingStatusChange( + Status::kSuccess, MakeOptional(ByteSpan(configuredNetwork.networkID, configuredNetwork.networkIDLen)), NullOptional); + return; + } + + mpStatusChangeCallback->OnNetworkingStatusChange( + Status::kUnknownError, MakeOptional(ByteSpan(configuredNetwork.networkID, configuredNetwork.networkIDLen)), + MakeOptional(GetDisconnectReason())); +} + +void ConnectivityManagerImpl::_OnWpaPropertiesChanged(WpaFiW1Wpa_supplicant1Interface * proxy, GVariant * changedProperties) +{ + std::lock_guard lock(mWpaSupplicantMutex); + + if (g_variant_n_children(changedProperties) > 0) + { + GAutoPtr iter; + const gchar * key; + GVariant * value; + + WiFiDiagnosticsDelegate * delegate = GetDiagnosticDataProvider().GetWiFiDiagnosticsDelegate(); + + g_variant_get(changedProperties, "a{sv}", &iter.GetReceiver()); + + while (g_variant_iter_loop(iter.get(), "{&sv}", &key, &value)) + { + GAutoPtr value_str(g_variant_print(value, TRUE)); + ChipLogProgress(DeviceLayer, "wpa_supplicant:PropertiesChanged:key:%s -> %s", StringOrNullMarker(key), + StringOrNullMarker(value_str.get())); + + if (g_strcmp0(key, "State") == 0) + { + if (g_strcmp0(value_str.get(), "\'associating\'") == 0) + { + mAssociationStarted = true; + } + else if (g_strcmp0(value_str.get(), "\'disconnected\'") == 0) + { + gint reason = wpa_fi_w1_wpa_supplicant1_interface_get_disconnect_reason(mWpaSupplicant.iface); + + if (delegate) + { + chip::DeviceLayer::StackLock stackLock; + delegate->OnDisconnectionDetected(reason); + delegate->OnConnectionStatusChanged(static_cast(ConnectionStatusEnum::kConnected)); + } + + if (mAssociationStarted) + { + uint8_t associationFailureCause = static_cast(AssociationFailureCauseEnum::kUnknown); + uint16_t status = WLAN_STATUS_UNSPECIFIED_FAILURE; + + switch (abs(reason)) + { + case WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY: + case WLAN_REASON_DISASSOC_AP_BUSY: + case WLAN_REASON_DISASSOC_STA_HAS_LEFT: + case WLAN_REASON_DISASSOC_LOW_ACK: + case WLAN_REASON_BSS_TRANSITION_DISASSOC: + associationFailureCause = static_cast(AssociationFailureCauseEnum::kAssociationFailed); + status = wpa_fi_w1_wpa_supplicant1_interface_get_assoc_status_code(mWpaSupplicant.iface); + break; + case WLAN_REASON_PREV_AUTH_NOT_VALID: + case WLAN_REASON_DEAUTH_LEAVING: + case WLAN_REASON_IEEE_802_1X_AUTH_FAILED: + associationFailureCause = static_cast(AssociationFailureCauseEnum::kAuthenticationFailed); + status = wpa_fi_w1_wpa_supplicant1_interface_get_auth_status_code(mWpaSupplicant.iface); + break; + default: + break; + } + + DeviceLayer::SystemLayer().ScheduleLambda([this, reason]() { + if (mpConnectCallback != nullptr) + { + mpConnectCallback->OnResult(NetworkCommissioning::Status::kUnknownError, CharSpan(), reason); + mpConnectCallback = nullptr; + } + }); + + if (delegate) + { + chip::DeviceLayer::StackLock stackLock; + delegate->OnAssociationFailureDetected(associationFailureCause, status); + } + } + + DeviceLayer::SystemLayer().ScheduleLambda([]() { ConnectivityMgrImpl().UpdateNetworkStatus(); }); + + mAssociationStarted = false; + } + else if (g_strcmp0(value_str.get(), "\'associated\'") == 0) + { + if (delegate) + { + chip::DeviceLayer::StackLock stackLock; + delegate->OnConnectionStatusChanged(static_cast(ConnectionStatusEnum::kNotConnected)); + } + + DeviceLayer::SystemLayer().ScheduleLambda([]() { ConnectivityMgrImpl().UpdateNetworkStatus(); }); + } + else if (g_strcmp0(value_str.get(), "\'completed\'") == 0) + { + if (mAssociationStarted) + { + DeviceLayer::SystemLayer().ScheduleLambda([this]() { + if (mpConnectCallback != nullptr) + { + mpConnectCallback->OnResult(NetworkCommissioning::Status::kSuccess, CharSpan(), 0); + mpConnectCallback = nullptr; + } + ConnectivityMgrImpl().PostNetworkConnect(); + }); + } + mAssociationStarted = false; + } + } + } + } +} + +void ConnectivityManagerImpl::_OnWpaInterfaceProxyReady(GObject * sourceObject, GAsyncResult * res) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + WpaFiW1Wpa_supplicant1Interface * iface = wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus_finish(res, &err.GetReceiver()); + + if (mWpaSupplicant.iface) + { + g_object_unref(mWpaSupplicant.iface); + mWpaSupplicant.iface = nullptr; + } + + if (iface != nullptr && err == nullptr) + { + mWpaSupplicant.iface = iface; + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED; + ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to wpa_supplicant interface proxy"); + + g_signal_connect( + mWpaSupplicant.iface, "properties-changed", + G_CALLBACK(+[](WpaFiW1Wpa_supplicant1Interface * proxy, GVariant * properties, ConnectivityManagerImpl * self) { + return self->_OnWpaPropertiesChanged(proxy, properties); + }), + this); + g_signal_connect(mWpaSupplicant.iface, "scan-done", + G_CALLBACK(+[](WpaFiW1Wpa_supplicant1Interface * proxy, gboolean success, ConnectivityManagerImpl * self) { + return self->_OnWpaInterfaceScanDone(proxy, success); + }), + this); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create wpa_supplicant interface proxy %s: %s", + mWpaSupplicant.interfacePath, err ? err->message : "unknown error"); + + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NOT_CONNECTED; + } + + // We need to stop auto scan or it will block our network scan. + DeviceLayer::SystemLayer().ScheduleLambda([this]() { + CHIP_ERROR errInner = StopAutoScan(); + if (errInner != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "wpa_supplicant: Failed to stop auto scan: %s", ErrorStr(errInner)); + } + }); +} + +void ConnectivityManagerImpl::_OnWpaBssProxyReady(GObject * sourceObject, GAsyncResult * res) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + WpaFiW1Wpa_supplicant1BSS * bss = wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus_finish(res, &err.GetReceiver()); + + if (mWpaSupplicant.bss) + { + g_object_unref(mWpaSupplicant.bss); + mWpaSupplicant.bss = nullptr; + } + + if (bss != nullptr && err.get() == nullptr) + { + mWpaSupplicant.bss = bss; + ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to wpa_supplicant bss proxy"); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create wpa_supplicant bss proxy %s: %s", + mWpaSupplicant.interfacePath, err ? err->message : "unknown error"); + } +} + +void ConnectivityManagerImpl::_OnWpaInterfaceReady(GObject * sourceObject, GAsyncResult * res) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + gboolean result = wpa_fi_w1_wpa_supplicant1_call_get_interface_finish(mWpaSupplicant.proxy, &mWpaSupplicant.interfacePath, res, + &err.GetReceiver()); + if (result) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_GOT_INTERFACE_PATH; + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface: %s", mWpaSupplicant.interfacePath); + + wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaInterfaceProxyReady(sourceObject_, res_); + }), + this); + + wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaBssProxyReady(sourceObject_, res_); + }), + this); + } + else + { + GAutoPtr error; + GVariant * args = nullptr; + GVariantBuilder builder; + + ChipLogProgress(DeviceLayer, "wpa_supplicant: can't find interface %s: %s", sWiFiIfName, + err ? err->message : "unknown error"); + + ChipLogProgress(DeviceLayer, "wpa_supplicant: try to create interface %s", CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "Ifname", g_variant_new_string(CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME)); + args = g_variant_builder_end(&builder); + + result = wpa_fi_w1_wpa_supplicant1_call_create_interface_sync(mWpaSupplicant.proxy, args, &mWpaSupplicant.interfacePath, + nullptr, &error.GetReceiver()); + + if (result) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_GOT_INTERFACE_PATH; + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface: %s", mWpaSupplicant.interfacePath); + + Platform::CopyString(sWiFiIfName, CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME); + + wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaInterfaceProxyReady(sourceObject_, res_); + }), + this); + + wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaBssProxyReady(sourceObject_, res_); + }), + this); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create interface %s: %s", + CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME, error ? error->message : "unknown error"); + + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NO_INTERFACE_PATH; + + if (mWpaSupplicant.interfacePath) + { + g_free(mWpaSupplicant.interfacePath); + mWpaSupplicant.interfacePath = nullptr; + } + } + } +} + +void ConnectivityManagerImpl::_OnWpaInterfaceAdded(WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.interfacePath) + { + return; + } + + mWpaSupplicant.interfacePath = const_cast(path); + if (mWpaSupplicant.interfacePath) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_GOT_INTERFACE_PATH; + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface added: %s", mWpaSupplicant.interfacePath); + + wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaInterfaceProxyReady(sourceObject_, res_); + }), + this); + + wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaBssProxyReady(sourceObject_, res_); + }), + this); + } +} + +void ConnectivityManagerImpl::_OnWpaInterfaceRemoved(WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties) +{ + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.interfacePath == nullptr) + { + return; + } + + if (g_strcmp0(mWpaSupplicant.interfacePath, path) == 0) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface removed: %s", StringOrNullMarker(path)); + + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NO_INTERFACE_PATH; + + if (mWpaSupplicant.interfacePath) + { + g_free(mWpaSupplicant.interfacePath); + mWpaSupplicant.interfacePath = nullptr; + } + + if (mWpaSupplicant.iface) + { + g_object_unref(mWpaSupplicant.iface); + mWpaSupplicant.iface = nullptr; + } + + if (mWpaSupplicant.bss) + { + g_object_unref(mWpaSupplicant.bss); + mWpaSupplicant.bss = nullptr; + } + + mWpaSupplicant.scanState = GDBusWpaSupplicant::WIFI_SCANNING_IDLE; + } +} + +void ConnectivityManagerImpl::_OnWpaProxyReady(GObject * sourceObject, GAsyncResult * res) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + mWpaSupplicant.proxy = wpa_fi_w1_wpa_supplicant1_proxy_new_for_bus_finish(res, &err.GetReceiver()); + if (mWpaSupplicant.proxy != nullptr && err.get() == nullptr) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_CONNECTED; + ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to wpa_supplicant proxy"); + + g_signal_connect( + mWpaSupplicant.proxy, "interface-added", + G_CALLBACK(+[](WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties, + ConnectivityManagerImpl * self) { return self->_OnWpaInterfaceAdded(proxy, path, properties); }), + this); + g_signal_connect( + mWpaSupplicant.proxy, "interface-removed", + G_CALLBACK(+[](WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties, + ConnectivityManagerImpl * self) { return self->_OnWpaInterfaceRemoved(proxy, path, properties); }), + this); + + wpa_fi_w1_wpa_supplicant1_call_get_interface( + mWpaSupplicant.proxy, sWiFiIfName, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaInterfaceReady(sourceObject_, res_); + }), + this); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create wpa_supplicant proxy %s", + err ? err->message : "unknown error"); + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NOT_CONNECTED; + } +} + +void ConnectivityManagerImpl::StartWiFiManagement() +{ + std::lock_guard lock(mWpaSupplicantMutex); + + mConnectivityFlag.ClearAll(); + mWpaSupplicant = GDBusWpaSupplicant{}; + + CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync( + +[](ConnectivityManagerImpl * self) { return self->_StartWiFiManagement(); }, this); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "Failed to start WiFi management")); +} + +bool ConnectivityManagerImpl::IsWiFiManagementStarted() +{ + std::lock_guard lock(mWpaSupplicantMutex); + + bool ret = mWpaSupplicant.state == GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED; + + return ret; +} + +void ConnectivityManagerImpl::StartNonConcurrentWiFiManagement() +{ + StartWiFiManagement(); + + for (int cnt = 0; cnt < WIFI_START_CHECK_ATTEMPTS; cnt++) + { + if (IsWiFiManagementStarted()) + { + DeviceControlServer::DeviceControlSvr().PostOperationalNetworkStartedEvent(); + ChipLogProgress(DeviceLayer, "Non-concurrent mode Wi-Fi Management Started."); + return; + } + usleep(WIFI_START_CHECK_TIME_USEC); + } + ChipLogError(Ble, "Non-concurrent mode Wi-Fi Management taking too long to start."); +} + +void ConnectivityManagerImpl::DriveAPState() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + WiFiAPState targetState; + + std::lock_guard lock(mWpaSupplicantMutex); + + // If the AP interface is not under application control... + if (mWiFiAPMode != kWiFiAPMode_ApplicationControlled) + { + // Determine the target (desired) state for AP interface... + + // The target state is 'NotActive' if the application has expressly disabled the AP interface. + if (mWiFiAPMode == kWiFiAPMode_Disabled) + { + targetState = kWiFiAPState_NotActive; + } + + // The target state is 'Active' if the application has expressly enabled the AP interface. + else if (mWiFiAPMode == kWiFiAPMode_Enabled) + { + targetState = kWiFiAPState_Active; + } + + // The target state is 'Active' if the AP mode is 'On demand, when no station is available' + // and the station interface is not provisioned or the application has disabled the station + // interface. + else if (mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision && + (!IsWiFiStationProvisioned() || GetWiFiStationMode() == kWiFiStationMode_Disabled)) + { + targetState = kWiFiAPState_Active; + } + + // The target state is 'Active' if the AP mode is one of the 'On demand' modes and there + // has been demand for the AP within the idle timeout period. + else if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + System::Clock::Timestamp now = System::SystemClock().GetMonotonicTimestamp(); + + if (mLastAPDemandTime != System::Clock::kZero && now < (mLastAPDemandTime + mWiFiAPIdleTimeout)) + { + targetState = kWiFiAPState_Active; + + // Compute the amount of idle time before the AP should be deactivated and + // arm a timer to fire at that time. + System::Clock::Timeout apTimeout = (mLastAPDemandTime + mWiFiAPIdleTimeout) - now; + err = DeviceLayer::SystemLayer().StartTimer(apTimeout, DriveAPState, this); + SuccessOrExit(err); + ChipLogProgress(DeviceLayer, "Next WiFi AP timeout in %" PRIu32 " s", + std::chrono::duration_cast(apTimeout).count()); + } + else + { + targetState = kWiFiAPState_NotActive; + } + } + + // Otherwise the target state is 'NotActive'. + else + { + targetState = kWiFiAPState_NotActive; + } + + // If the current AP state does not match the target state... + if (mWiFiAPState != targetState) + { + if (targetState == kWiFiAPState_Active) + { + err = ConfigureWiFiAP(); + SuccessOrExit(err); + + ChangeWiFiAPState(kWiFiAPState_Active); + } + else + { + if (mWpaSupplicant.networkPath) + { + GAutoPtr error(nullptr); + + gboolean result = wpa_fi_w1_wpa_supplicant1_interface_call_remove_network_sync( + mWpaSupplicant.iface, mWpaSupplicant.networkPath, nullptr, &error.GetReceiver()); + + if (result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: removed network: %s", mWpaSupplicant.networkPath); + g_free(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + ChangeWiFiAPState(kWiFiAPState_NotActive); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to stop AP mode with error: %s", + error ? error->message : "unknown error"); + err = CHIP_ERROR_INTERNAL; + } + } + } + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + SetWiFiAPMode(kWiFiAPMode_Disabled); + ChipLogError(DeviceLayer, "Drive AP state failed: %s", ErrorStr(err)); + } +} + +CHIP_ERROR ConnectivityManagerImpl::ConfigureWiFiAP() +{ + CHIP_ERROR ret = CHIP_NO_ERROR; + GAutoPtr err; + GVariant * args = nullptr; + GVariantBuilder builder; + + uint16_t channel = 1; + uint16_t discriminator = 0; + char ssid[32]; + + std::lock_guard lock(mWpaSupplicantMutex); + + channel = ConnectivityUtils::MapChannelToFrequency(kWiFi_BAND_2_4_GHZ, CHIP_DEVICE_CONFIG_WIFI_AP_CHANNEL); + + if (GetCommissionableDataProvider()->GetSetupDiscriminator(discriminator) != CHIP_NO_ERROR) + discriminator = 0; + + snprintf(ssid, 32, "%s%04u", CHIP_DEVICE_CONFIG_WIFI_AP_SSID_PREFIX, discriminator); + + ChipLogProgress(DeviceLayer, "wpa_supplicant: ConfigureWiFiAP, ssid: %s, channel: %d", ssid, channel); + + // Clean up current network if exists + if (mWpaSupplicant.networkPath) + { + g_free(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + } + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "ssid", g_variant_new_string(ssid)); + g_variant_builder_add(&builder, "{sv}", "key_mgmt", g_variant_new_string("NONE")); + g_variant_builder_add(&builder, "{sv}", "mode", g_variant_new_int32(2)); + g_variant_builder_add(&builder, "{sv}", "frequency", g_variant_new_int32(channel)); + args = g_variant_builder_end(&builder); + + gboolean result = wpa_fi_w1_wpa_supplicant1_interface_call_add_network_sync( + mWpaSupplicant.iface, args, &mWpaSupplicant.networkPath, nullptr, &err.GetReceiver()); + + if (result) + { + GAutoPtr error; + + ChipLogProgress(DeviceLayer, "wpa_supplicant: added network: SSID: %s: %s", ssid, mWpaSupplicant.networkPath); + + result = wpa_fi_w1_wpa_supplicant1_interface_call_select_network_sync(mWpaSupplicant.iface, mWpaSupplicant.networkPath, + nullptr, &error.GetReceiver()); + if (result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: succeeded to start softAP: SSID: %s", ssid); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to start softAP: SSID: %s: %s", ssid, + error ? error->message : "unknown error"); + + ret = CHIP_ERROR_INTERNAL; + } + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to add network: %s: %s", ssid, err ? err->message : "unknown error"); + + if (mWpaSupplicant.networkPath) + { + g_free(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + } + + ret = CHIP_ERROR_INTERNAL; + } + + return ret; +} + +void ConnectivityManagerImpl::ChangeWiFiAPState(WiFiAPState newState) +{ + if (mWiFiAPState != newState) + { + ChipLogProgress(DeviceLayer, "WiFi AP state change: %s -> %s", WiFiAPStateToStr(mWiFiAPState), WiFiAPStateToStr(newState)); + mWiFiAPState = newState; + } +} + +void ConnectivityManagerImpl::DriveAPState(::chip::System::Layer * aLayer, void * aAppState) +{ + reinterpret_cast(aAppState)->DriveAPState(); +} + +CHIP_ERROR +ConnectivityManagerImpl::_ConnectWiFiNetworkAsync(GVariant * args, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * apCallback) +{ + GAutoPtr argsDeleter(g_variant_ref_sink(args)); // args may be floating, ensure we don't leak it + + CHIP_ERROR ret = CHIP_NO_ERROR; + GAutoPtr err; + gboolean result; + + const gchar * networkPath = wpa_fi_w1_wpa_supplicant1_interface_get_current_network(mWpaSupplicant.iface); + + // wpa_supplicant DBus API: if network path of current network is not "/", means we have already selected some network. + if (strcmp(networkPath, "/") != 0) + { + GAutoPtr error; + + result = wpa_fi_w1_wpa_supplicant1_interface_call_remove_network_sync(mWpaSupplicant.iface, networkPath, nullptr, + &error.GetReceiver()); + + if (result) + { + if (mWpaSupplicant.networkPath != nullptr) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: removed network: %s", mWpaSupplicant.networkPath); + g_free(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + } + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to stop AP mode with error: %s", + error ? error->message : "unknown error"); + ret = CHIP_ERROR_INTERNAL; + } + + SuccessOrExit(ret); + } + + result = wpa_fi_w1_wpa_supplicant1_interface_call_add_network_sync(mWpaSupplicant.iface, args, &mWpaSupplicant.networkPath, + nullptr, &err.GetReceiver()); + + if (result) + { + // Note: wpa_supplicant will return immediately if the network is already connected, but it will still try reconnect in the + // background. The client still need to wait for a few seconds for this reconnect operation. So we always disconnect from + // the network we are connected and ignore any errors. + wpa_fi_w1_wpa_supplicant1_interface_call_disconnect_sync(mWpaSupplicant.iface, nullptr, nullptr); + ChipLogProgress(DeviceLayer, "wpa_supplicant: added network: %s", mWpaSupplicant.networkPath); + + wpa_fi_w1_wpa_supplicant1_interface_call_select_network( + mWpaSupplicant.iface, mWpaSupplicant.networkPath, nullptr, + reinterpret_cast( + +[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_ConnectWiFiNetworkAsyncCallback(sourceObject_, res_); + }), + this); + + mpConnectCallback = apCallback; + } + else + { + ChipLogError(DeviceLayer, "wpa_supplicant: failed to add network: %s", err ? err->message : "unknown error"); + + if (mWpaSupplicant.networkPath) + { + g_free(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + } + + ret = CHIP_ERROR_INTERNAL; + } + +exit: + return ret; +} + +CHIP_ERROR +ConnectivityManagerImpl::ConnectWiFiNetworkAsync(ByteSpan ssid, ByteSpan credentials, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback) +{ + char ssidStr[kMaxWiFiSSIDLength + 1] = { 0 }; + char keyStr[kMaxWiFiKeyLength + 1] = { 0 }; + + VerifyOrReturnError(ssid.size() <= kMaxWiFiSSIDLength, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(credentials.size() <= kMaxWiFiKeyLength, CHIP_ERROR_INVALID_ARGUMENT); + + std::lock_guard lock(mWpaSupplicantMutex); + VerifyOrReturnError(mWpaSupplicant.iface != nullptr, CHIP_ERROR_INCORRECT_STATE); + + // There is another ongoing connect request, reject the new one. + VerifyOrReturnError(mpConnectCallback == nullptr, CHIP_ERROR_INCORRECT_STATE); + + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + memcpy(ssidStr, ssid.data(), ssid.size()); + memcpy(keyStr, credentials.data(), credentials.size()); + g_variant_builder_add(&builder, "{sv}", "ssid", g_variant_new_string(ssidStr)); + g_variant_builder_add(&builder, "{sv}", "psk", g_variant_new_string(keyStr)); + g_variant_builder_add(&builder, "{sv}", "key_mgmt", g_variant_new_string("SAE WPA-PSK")); + GVariant * args = g_variant_builder_end(&builder); + return _ConnectWiFiNetworkAsync(args, connectCallback); +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC +static CHIP_ERROR AddOrReplaceBlob(WpaFiW1Wpa_supplicant1Interface * iface, const char * nameOrRef, ByteSpan data) +{ + // Strip the blob:// prefix off the name (if present), so we don't need as many string constants. + constexpr auto refPrefix = "blob://"_span; + const char * name = (strncmp(nameOrRef, refPrefix.data(), refPrefix.size()) == 0) ? nameOrRef + refPrefix.size() : nameOrRef; + + GAutoPtr err; + if (!wpa_fi_w1_wpa_supplicant1_interface_call_remove_blob_sync(iface, name, nullptr, &err.GetReceiver())) + { + GAutoPtr remoteError(g_dbus_error_get_remote_error(err.get())); + if (!(remoteError && strcmp(remoteError.get(), kWpaSupplicantBlobUnknown) == 0)) + { + ChipLogError(DeviceLayer, "wpa_supplicant: failed to remove blob: %s", err ? err->message : "unknown error"); + return CHIP_ERROR_INTERNAL; + } + err.reset(); + } + if (!wpa_fi_w1_wpa_supplicant1_interface_call_add_blob_sync( + iface, name, g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, data.data(), data.size(), 1), nullptr, &err.GetReceiver())) + { + ChipLogError(DeviceLayer, "wpa_supplicant: failed to add blob: %s", err ? err->message : "unknown error"); + return CHIP_ERROR_INTERNAL; + } + return CHIP_NO_ERROR; +} + +// Note: Static blob names assume we're only supporting a single network configuration. +static constexpr char kNetworkIdentityBlobRef[] = "blob://pdc-ni"; +static constexpr char kClientIdentityBlobRef[] = "blob://pdc-ci"; +static constexpr char kClientIdentityKeyBlobRef[] = "blob://pdc-cik"; + +CHIP_ERROR ConnectivityManagerImpl::ConnectWiFiNetworkWithPDCAsync( + ByteSpan ssid, ByteSpan networkIdentity, ByteSpan clientIdentity, const Crypto::P256Keypair & clientIdentityKeypair, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback) +{ + VerifyOrReturnError(ssid.size() <= kMaxWiFiSSIDLength, CHIP_ERROR_INVALID_ARGUMENT); + + std::lock_guard lock(mWpaSupplicantMutex); + VerifyOrReturnError(mWpaSupplicant.iface != nullptr, CHIP_ERROR_INCORRECT_STATE); + + // There is another ongoing connect request, reject the new one. + VerifyOrReturnError(mpConnectCallback == nullptr, CHIP_ERROR_INCORRECT_STATE); + + // Convert identities and our key pair to DER and add them to wpa_supplicant as blobs + { + constexpr size_t bufferSize = std::max(kMaxDERCertLength, kP256ECPrivateKeyDERLength); + Platform::ScopedMemoryBuffer buffer; + VerifyOrReturnError(buffer.Alloc(bufferSize), CHIP_ERROR_NO_MEMORY); + + MutableByteSpan networkIdentityDER(buffer.Get(), bufferSize); + ReturnErrorOnFailure(ConvertChipCertToX509Cert(networkIdentity, networkIdentityDER)); + ReturnErrorOnFailure(AddOrReplaceBlob(mWpaSupplicant.iface, kNetworkIdentityBlobRef, networkIdentityDER)); + + MutableByteSpan clientIdentityDER(buffer.Get(), bufferSize); + ReturnErrorOnFailure(ConvertChipCertToX509Cert(clientIdentity, clientIdentityDER)); + ReturnErrorOnFailure(AddOrReplaceBlob(mWpaSupplicant.iface, kClientIdentityBlobRef, clientIdentityDER)); + + Crypto::P256SerializedKeypair serializedKeypair; + MutableByteSpan clientIdentityKeypairDER(buffer.Get(), bufferSize); + ReturnErrorOnFailure(clientIdentityKeypair.Serialize(serializedKeypair)); + ReturnErrorOnFailure(ConvertECDSAKeypairRawToDER(serializedKeypair, clientIdentityKeypairDER)); + ReturnErrorOnFailure(AddOrReplaceBlob(mWpaSupplicant.iface, kClientIdentityKeyBlobRef, clientIdentityKeypairDER)); + } + + // Build the network configuration + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + { + char ssidStr[kMaxWiFiSSIDLength + 1] = { 0 }; + memcpy(ssidStr, ssid.data(), ssid.size()); + g_variant_builder_add(&builder, "{sv}", "ssid", g_variant_new_string(ssidStr)); + } + + { + CertificateKeyIdStorage keyId; + ReturnErrorOnFailure(ExtractIdentifierFromChipNetworkIdentity(networkIdentity, keyId)); + + static constexpr char kNAIDomain[] = ".pdc.csa-iot.org"; + static constexpr auto keyIdHexSize = keyId.size() * 2; + char identityStr[1 + keyIdHexSize + sizeof(kNAIDomain)]; // sizeof(kNAIDomain) includes null terminator + + identityStr[0] = '@'; + ReturnErrorOnFailure(Encoding::BytesToUppercaseHexBuffer(keyId.data(), keyId.size(), &identityStr[1], keyIdHexSize)); + strcpy(&identityStr[1 + keyIdHexSize], kNAIDomain); + g_variant_builder_add(&builder, "{sv}", "identity", g_variant_new_string(identityStr)); + } + + // The configuration will become simpler once we add explicit Matter support to wpa_supplicant + g_variant_builder_add(&builder, "{sv}", "key_mgmt", g_variant_new_string("WPA-EAP-SHA256")); + g_variant_builder_add(&builder, "{sv}", "fallback_key_mgmt", g_variant_new_string("WPA-EAP-SHA256")); + g_variant_builder_add(&builder, "{sv}", "pairwise", g_variant_new_string("CCMP")); + g_variant_builder_add(&builder, "{sv}", "group", g_variant_new_string("CCMP")); + g_variant_builder_add(&builder, "{sv}", "ieee80211w", g_variant_new_int32(2)); + g_variant_builder_add(&builder, "{sv}", "eap", g_variant_new_string("TLS")); + g_variant_builder_add(&builder, "{sv}", "eap_workaround", g_variant_new_int32(0)); + + g_variant_builder_add( + &builder, "{sv}", "phase1", + g_variant_new_string("tls_disable_tlsv1_0=1,tls_disable_tlsv1_1=1,tls_disable_tlsv1_2=1,tls_disable_tlsv1_3=0")); + g_variant_builder_add(&builder, "{sv}", "openssl_ciphers", g_variant_new_string("TLS_AES_128_CCM_SHA256")); + g_variant_builder_add(&builder, "{sv}", "openssl_ecdh_curves", g_variant_new_string("P-256")); + + g_variant_builder_add(&builder, "{sv}", "ca_cert", g_variant_new_string(kNetworkIdentityBlobRef)); + g_variant_builder_add(&builder, "{sv}", "client_cert", g_variant_new_string(kClientIdentityBlobRef)); + g_variant_builder_add(&builder, "{sv}", "private_key", g_variant_new_string(kClientIdentityKeyBlobRef)); + GVariant * args = g_variant_builder_end(&builder); + return _ConnectWiFiNetworkAsync(args, connectCallback); +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + +void ConnectivityManagerImpl::_ConnectWiFiNetworkAsyncCallback(GObject * sourceObject, GAsyncResult * res) +{ + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + { + gboolean result = + wpa_fi_w1_wpa_supplicant1_interface_call_select_network_finish(mWpaSupplicant.iface, res, &err.GetReceiver()); + if (!result) + { + ChipLogError(DeviceLayer, "Failed to perform connect network: %s", err == nullptr ? "unknown error" : err->message); + DeviceLayer::SystemLayer().ScheduleLambda([this]() { + if (mpConnectCallback != nullptr) + { + // TODO(#14175): Replace this with actual thread attach result. + mpConnectCallback->OnResult(NetworkCommissioning::Status::kUnknownError, CharSpan(), 0); + mpConnectCallback = nullptr; + } + mpConnectCallback = nullptr; + }); + + return; + } + + result = wpa_fi_w1_wpa_supplicant1_interface_call_save_config_sync(mWpaSupplicant.iface, nullptr, &err.GetReceiver()); + if (result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: save config succeeded!"); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to save config: %s", err ? err->message : "unknown error"); + } + } +} + +void ConnectivityManagerImpl::PostNetworkConnect() +{ + // Iterate on the network interface to see if we already have beed assigned addresses. + // The temporary hack for getting IP address change on linux for network provisioning in the rendezvous session. + // This should be removed or find a better place once we depercate the rendezvous session. + for (chip::Inet::InterfaceAddressIterator it; it.HasCurrent(); it.Next()) + { + char ifName[chip::Inet::InterfaceId::kMaxIfNameLength]; + if (it.IsUp() && CHIP_NO_ERROR == it.GetInterfaceName(ifName, sizeof(ifName)) && + strncmp(ifName, sWiFiIfName, sizeof(ifName)) == 0) + { + chip::Inet::IPAddress addr; + if ((it.GetAddress(addr) == CHIP_NO_ERROR) && addr.IsIPv4()) + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kInternetConnectivityChange; + event.InternetConnectivityChange.IPv4 = kConnectivity_Established; + event.InternetConnectivityChange.IPv6 = kConnectivity_NoChange; + event.InternetConnectivityChange.ipAddress = addr; + + char ipStrBuf[chip::Inet::IPAddress::kMaxStringLength] = { 0 }; + addr.ToString(ipStrBuf); + + ChipLogDetail(DeviceLayer, "Got IP address on interface: %s IP: %s", ifName, ipStrBuf); + + PlatformMgr().PostEventOrDie(&event); + } + } + } + +#if defined(CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD) + // CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD can be defined to a command pattern + // to run once the network has been connected, with a %s placeholder for the + // interface name. E.g. "dhclient -nw %s" + // Run dhclient for IP on WiFi. + // TODO: The wifi can be managed by networkmanager on linux so we don't have to care about this. + char cmdBuffer[128]; + sprintf(cmdBuffer, CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD, sWiFiIfName); + int dhclientSystemRet = system(cmdBuffer); + if (dhclientSystemRet != 0) + { + ChipLogError(DeviceLayer, "Failed to run dhclient, system() returns %d", dhclientSystemRet); + } + else + { + ChipLogProgress(DeviceLayer, "dhclient is running on the %s interface.", sWiFiIfName); + } +#endif // defined(CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD) +} + +CHIP_ERROR ConnectivityManagerImpl::CommitConfig() +{ + gboolean result; + GAutoPtr err; + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogError(DeviceLayer, "wpa_supplicant: CommitConfig: interface proxy not connected"); + return CHIP_ERROR_INCORRECT_STATE; + } + + ChipLogProgress(DeviceLayer, "wpa_supplicant: save config"); + + result = wpa_fi_w1_wpa_supplicant1_interface_call_save_config_sync(mWpaSupplicant.iface, nullptr, &err.GetReceiver()); + + if (!result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to save config: %s", err ? err->message : "unknown error"); + return CHIP_ERROR_INTERNAL; + } + + ChipLogProgress(DeviceLayer, "wpa_supplicant: save config succeeded!"); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConnectivityManagerImpl::GetWiFiBssId(MutableByteSpan & value) +{ + constexpr size_t bssIdSize = 6; + static_assert(kMaxHardwareAddrSize >= bssIdSize, "We are assuming we can fit a BSSID in a buffer of size kMaxHardwareAddrSize"); + VerifyOrReturnError(value.size() >= bssIdSize, CHIP_ERROR_BUFFER_TOO_SMALL); + + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + // On Linux simulation, we don't have the DBus API to get the BSSID of connected AP. Use mac address + // of local WiFi network card instead. + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + // Walk through linked list, maintaining head pointer so we can free list later. + for (struct ifaddrs * ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kWiFi) + { + if (ConnectivityUtils::GetInterfaceHardwareAddrs(ifa->ifa_name, value.data(), kMaxHardwareAddrSize) != + CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get WiFi network hardware address"); + } + else + { + // Set 48-bit IEEE MAC Address + value.reduce_size(bssIdSize); + err = CHIP_NO_ERROR; + break; + } + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityManagerImpl::GetWiFiSecurityType(SecurityTypeEnum & securityType) +{ + const gchar * mode = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogError(DeviceLayer, "wpa_supplicant: GetWiFiSecurityType: interface proxy not connected"); + return CHIP_ERROR_INCORRECT_STATE; + } + + mode = wpa_fi_w1_wpa_supplicant1_interface_get_current_auth_mode(mWpaSupplicant.iface); + ChipLogProgress(DeviceLayer, "wpa_supplicant: current Wi-Fi security type: %s", StringOrNullMarker(mode)); + + if (strncmp(mode, "WPA-PSK", 7) == 0) + { + securityType = SecurityTypeEnum::kWpa; + } + else if (strncmp(mode, "WPA2-PSK", 8) == 0) + { + securityType = SecurityTypeEnum::kWpa2; + } + else if (strncmp(mode, "WPA2-EAP", 8) == 0) + { + securityType = SecurityTypeEnum::kWpa2; + } + else if (strncmp(mode, "WPA3-PSK", 8) == 0) + { + securityType = SecurityTypeEnum::kWpa3; + } + else if (strncmp(mode, "WEP", 3) == 0) + { + securityType = SecurityTypeEnum::kWep; + } + else if (strncmp(mode, "NONE", 4) == 0) + { + securityType = SecurityTypeEnum::kNone; + } + else if (strncmp(mode, "WPA-NONE", 8) == 0) + { + securityType = SecurityTypeEnum::kNone; + } + else + { + securityType = SecurityTypeEnum::kUnspecified; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConnectivityManagerImpl::GetWiFiVersion(WiFiVersionEnum & wiFiVersion) +{ + // We don't have direct API to get the WiFi version yet, return 802.11n on Linux simulation. + wiFiVersion = WiFiVersionEnum::kN; + + return CHIP_NO_ERROR; +} + +int32_t ConnectivityManagerImpl::GetDisconnectReason() +{ + std::lock_guard lock(mWpaSupplicantMutex); + GAutoPtr err; + + gint errorValue = wpa_fi_w1_wpa_supplicant1_interface_get_disconnect_reason(mWpaSupplicant.iface); + // wpa_supplicant DBus API: DisconnectReason: The most recent IEEE 802.11 reason code for disconnect. Negative value + // indicates locally generated disconnection. + return errorValue; +} + +CHIP_ERROR ConnectivityManagerImpl::GetConfiguredNetwork(NetworkCommissioning::Network & network) +{ + // This function can be called without g_main_context_get_thread_default() being set. + // The network proxy object is created in a synchronous manner, so the D-Bus call will + // be completed before this function returns. Also, no external callbacks are registered + // with the proxy object. + + std::lock_guard lock(mWpaSupplicantMutex); + GAutoPtr err; + + if (mWpaSupplicant.iface == nullptr) + { + ChipLogDetail(DeviceLayer, "Wifi network not currently connected"); + return CHIP_ERROR_INCORRECT_STATE; + } + + const gchar * networkPath = wpa_fi_w1_wpa_supplicant1_interface_get_current_network(mWpaSupplicant.iface); + + // wpa_supplicant DBus API: if network path of current network is "/", means no networks is currently selected. + if (strcmp(networkPath, "/") == 0) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + GAutoPtr networkInfo(wpa_fi_w1_wpa_supplicant1_network_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, networkPath, nullptr, &err.GetReceiver())); + if (networkInfo == nullptr) + { + return CHIP_ERROR_INTERNAL; + } + + network.connected = wpa_fi_w1_wpa_supplicant1_network_get_enabled(networkInfo.get()); + GVariant * properties = wpa_fi_w1_wpa_supplicant1_network_get_properties(networkInfo.get()); + GAutoPtr ssid(g_variant_lookup_value(properties, "ssid", nullptr)); + gsize length; + const gchar * ssidStr = g_variant_get_string(ssid.get(), &length); + // TODO: wpa_supplicant will return ssid with quotes! We should have a better way to get the actual ssid in bytes. + gsize length_actual = length - 2; + VerifyOrReturnError(length_actual <= sizeof(network.networkID), CHIP_ERROR_INTERNAL); + ChipLogDetail(DeviceLayer, "Current connected network: %s", StringOrNullMarker(ssidStr)); + memcpy(network.networkID, ssidStr + 1, length_actual); + network.networkIDLen = length_actual; + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConnectivityManagerImpl::StopAutoScan() +{ + std::lock_guard lock(mWpaSupplicantMutex); + VerifyOrReturnError(mWpaSupplicant.iface != nullptr, CHIP_ERROR_INCORRECT_STATE); + + GAutoPtr err; + gboolean result; + + ChipLogDetail(DeviceLayer, "wpa_supplicant: disabling auto scan"); + + result = wpa_fi_w1_wpa_supplicant1_interface_call_auto_scan_sync( + mWpaSupplicant.iface, "" /* empty string means disabling auto scan */, nullptr, &err.GetReceiver()); + if (!result) + { + ChipLogError(DeviceLayer, "wpa_supplicant: Failed to stop auto network scan: %s", err ? err->message : "unknown"); + return CHIP_ERROR_INTERNAL; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConnectivityManagerImpl::StartWiFiScan(ByteSpan ssid, WiFiDriver::ScanCallback * callback) +{ + std::lock_guard lock(mWpaSupplicantMutex); + VerifyOrReturnError(mWpaSupplicant.iface != nullptr, CHIP_ERROR_INCORRECT_STATE); + // There is another ongoing scan request, reject the new one. + VerifyOrReturnError(mpScanCallback == nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(ssid.size() <= sizeof(sInterestedSSID), CHIP_ERROR_INVALID_ARGUMENT); + + CHIP_ERROR ret = CHIP_NO_ERROR; + GAutoPtr err; + GVariant * args = nullptr; + GVariantBuilder builder; + gboolean result; + + memcpy(sInterestedSSID, ssid.data(), ssid.size()); + sInterestedSSIDLen = ssid.size(); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "Type", g_variant_new_string("active")); + args = g_variant_builder_end(&builder); + + result = wpa_fi_w1_wpa_supplicant1_interface_call_scan_sync(mWpaSupplicant.iface, args, nullptr, &err.GetReceiver()); + + if (result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: initialized network scan."); + mpScanCallback = callback; + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to start network scan: %s", err ? err->message : "unknown error"); + ret = CHIP_ERROR_INTERNAL; + } + + return ret; +} + +namespace { + +// wpa_supplicant's scan results don't contains the channel infomation, so we need this lookup table for resolving the band and +// channel infomation. +std::pair GetBandAndChannelFromFrequency(uint32_t freq) +{ + std::pair ret = std::make_pair(WiFiBand::k2g4, 0); + if (freq <= 931) + { + ret.first = WiFiBand::k1g; + if (freq >= 916) + { + ret.second = ((freq - 916) * 2) - 1; + } + else if (freq >= 902) + { + ret.second = (freq - 902) * 2; + } + else if (freq >= 863) + { + ret.second = (freq - 863) * 2; + } + else + { + ret.second = 1; + } + } + else if (freq <= 2472) + { + ret.second = static_cast((freq - 2412) / 5 + 1); + } + else if (freq == 2484) + { + ret.second = 14; + } + else if (freq >= 3600 && freq <= 3700) + { + // Note: There are not many devices supports this band, and this band contains rational frequency in MHz, need to figure out + // the behavior of wpa_supplicant in this case. + ret.first = WiFiBand::k3g65; + } + else if (freq >= 5035 && freq <= 5945) + { + ret.first = WiFiBand::k5g; + ret.second = static_cast((freq - 5000) / 5); + } + else if (freq == 5960 || freq == 5980) + { + ret.first = WiFiBand::k5g; + ret.second = static_cast((freq - 5000) / 5); + } + else if (freq >= 5955) + { + ret.first = WiFiBand::k6g; + ret.second = static_cast((freq - 5950) / 5); + } + else if (freq >= 58000) + { + ret.first = WiFiBand::k60g; + // Note: Some channel has the same center frequency but different bandwidth. Should figure out wpa_supplicant's behavior in + // this case. Also, wpa_supplicant's frequency property is uint16 infact. + switch (freq) + { + case 58'320: + ret.second = 1; + break; + case 60'480: + ret.second = 2; + break; + case 62'640: + ret.second = 3; + break; + case 64'800: + ret.second = 4; + break; + case 66'960: + ret.second = 5; + break; + case 69'120: + ret.second = 6; + break; + case 59'400: + ret.second = 9; + break; + case 61'560: + ret.second = 10; + break; + case 63'720: + ret.second = 11; + break; + case 65'880: + ret.second = 12; + break; + case 68'040: + ret.second = 13; + break; + } + } + return ret; +} + +} // namespace + +bool ConnectivityManagerImpl::_GetBssInfo(const gchar * bssPath, NetworkCommissioning::WiFiScanResponse & result) +{ + // This function can be called without g_main_context_get_thread_default() being set. + // The BSS proxy object is created in a synchronous manner, so the D-Bus call will be + // completed before this function returns. Also, no external callbacks are registered + // with the proxy object. + + GAutoPtr err; + GAutoPtr bss(wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, bssPath, nullptr, &err.GetReceiver())); + + if (bss == nullptr) + { + return false; + } + + WpaFiW1Wpa_supplicant1BSSProxy * bssProxy = WPA_FI_W1_WPA_SUPPLICANT1_BSS_PROXY(bss.get()); + + GAutoPtr ssid(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(bssProxy), "SSID")); + GAutoPtr bssid(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(bssProxy), "BSSID")); + + // Network scan is performed in the background, so the BSS + // may be gone when we try to get the properties. + if (ssid == nullptr || bssid == nullptr) + { + ChipLogDetail(DeviceLayer, "wpa_supplicant: BSS not found: %s", StringOrNullMarker(bssPath)); + return false; + } + + const guchar * ssidStr = nullptr; + const guchar * bssidBuf = nullptr; + char bssidStr[2 * 6 + 5 + 1] = { 0 }; + gsize ssidLen = 0; + gsize bssidLen = 0; + gint16 signal = wpa_fi_w1_wpa_supplicant1_bss_get_signal(bss.get()); + guint16 frequency = wpa_fi_w1_wpa_supplicant1_bss_get_frequency(bss.get()); + + ssidStr = reinterpret_cast(g_variant_get_fixed_array(ssid.get(), &ssidLen, sizeof(guchar))); + bssidBuf = reinterpret_cast(g_variant_get_fixed_array(bssid.get(), &bssidLen, sizeof(guchar))); + + if (bssidLen == 6) + { + snprintf(bssidStr, sizeof(bssidStr), "%02x:%02x:%02x:%02x:%02x:%02x", bssidBuf[0], bssidBuf[1], bssidBuf[2], bssidBuf[3], + bssidBuf[4], bssidBuf[5]); + } + else + { + bssidLen = 0; + ChipLogError(DeviceLayer, "Got a network with bssid not equals to 6"); + } + ChipLogDetail(DeviceLayer, "Network Found: %.*s (%s) Signal:%d", int(ssidLen), StringOrNullMarker((const gchar *) ssidStr), + bssidStr, signal); + + // A flag for enterprise encryption option to avoid returning open for these networks by mistake + // TODO: The following code will mistakenly recognize WEP encryption as OPEN network, this should be fixed by reading + // IEs (information elements) field instead of reading cooked data. + + static constexpr uint8_t kEAP = (1 << 7); + + auto IsNetworkWPAPSK = [](GVariant * wpa) -> uint8_t { + if (wpa == nullptr) + { + return 0; + } + + GAutoPtr keyMgmt(g_variant_lookup_value(wpa, "KeyMgmt", nullptr)); + if (keyMgmt == nullptr) + { + return 0; + } + GAutoPtr keyMgmts(g_variant_get_strv(keyMgmt.get(), nullptr)); + const gchar ** keyMgmtsHendle = keyMgmts.get(); + uint8_t res = 0; + + VerifyOrReturnError(keyMgmtsHendle != nullptr, res); + + for (auto keyMgmtVal = *keyMgmtsHendle; keyMgmtVal != nullptr; keyMgmtVal = *(++keyMgmtsHendle)) + { + if (g_strcasecmp(keyMgmtVal, "wpa-psk") == 0 || g_strcasecmp(keyMgmtVal, "wpa-none") == 0) + { + res |= (1 << 2); // SecurityType::WPA_PERSONAL + } + else if (g_strcasecmp(keyMgmtVal, "wpa-eap")) + { + res |= (kEAP); + } + } + + return res; + }; + auto IsNetworkWPA2PSK = [](GVariant * rsn) -> uint8_t { + if (rsn == nullptr) + { + return 0; + } + GAutoPtr keyMgmt(g_variant_lookup_value(rsn, "KeyMgmt", nullptr)); + if (keyMgmt == nullptr) + { + return 0; + } + GAutoPtr keyMgmts(g_variant_get_strv(keyMgmt.get(), nullptr)); + const gchar ** keyMgmtsHendle = keyMgmts.get(); + uint8_t res = 0; + + VerifyOrReturnError(keyMgmtsHendle != nullptr, res); + + for (auto keyMgmtVal = *keyMgmtsHendle; keyMgmtVal != nullptr; keyMgmtVal = *(++keyMgmtsHendle)) + { + if (g_strcasecmp(keyMgmtVal, "wpa-psk") == 0 || g_strcasecmp(keyMgmtVal, "wpa-psk-sha256") == 0 || + g_strcasecmp(keyMgmtVal, "wpa-ft-psk") == 0) + { + res |= (1 << 3); // SecurityType::WPA2_PERSONAL + } + else if (g_strcasecmp(keyMgmtVal, "wpa-eap") == 0 || g_strcasecmp(keyMgmtVal, "wpa-eap-sha256") == 0 || + g_strcasecmp(keyMgmtVal, "wpa-ft-eap") == 0) + { + res |= kEAP; + } + else if (g_strcasecmp(keyMgmtVal, "sae") == 0) + { + // wpa_supplicant will include "sae" in KeyMgmt field for WPA3 WiFi, this is not included in the wpa_supplicant + // document. + res |= (1 << 4); // SecurityType::WPA3_PERSONAL + } + } + + return res; + }; + auto GetNetworkSecurityType = [IsNetworkWPAPSK, IsNetworkWPA2PSK](WpaFiW1Wpa_supplicant1BSSProxy * proxy) -> uint8_t { + GAutoPtr wpa(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(proxy), "WPA")); + GAutoPtr rsn(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(proxy), "RSN")); + + uint8_t res = IsNetworkWPAPSK(wpa.get()) | IsNetworkWPA2PSK(rsn.get()); + if (res == 0) + { + res = 1; // Open + } + return res & (0x7F); + }; + + // Drop the network if its SSID or BSSID is illegal. + VerifyOrReturnError(ssidLen <= kMaxWiFiSSIDLength, false); + VerifyOrReturnError(bssidLen == kWiFiBSSIDLength, false); + memcpy(result.ssid, ssidStr, ssidLen); + memcpy(result.bssid, bssidBuf, bssidLen); + result.ssidLen = ssidLen; + if (signal < INT8_MIN) + { + result.rssi = INT8_MIN; + } + else if (signal > INT8_MAX) + { + result.rssi = INT8_MAX; + } + else + { + result.rssi = static_cast(signal); + } + + auto bandInfo = GetBandAndChannelFromFrequency(frequency); + result.wiFiBand = bandInfo.first; + result.channel = bandInfo.second; + result.security.SetRaw(GetNetworkSecurityType(bssProxy)); + + return true; +} + +void ConnectivityManagerImpl::_OnWpaInterfaceScanDone(WpaFiW1Wpa_supplicant1Interface * proxy, gboolean success) +{ + std::lock_guard lock(mWpaSupplicantMutex); + + ChipLogProgress(DeviceLayer, "wpa_supplicant: network scan done"); + gchar ** bsss = wpa_fi_w1_wpa_supplicant1_interface_dup_bsss(mWpaSupplicant.iface); + gchar ** oldBsss = bsss; + if (bsss == nullptr) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: no network found"); + DeviceLayer::SystemLayer().ScheduleLambda([this]() { + if (mpScanCallback != nullptr) + { + mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), nullptr); + mpScanCallback = nullptr; + } + }); + return; + } + + std::vector * networkScanned = new std::vector(); + for (const gchar * bssPath = (bsss != nullptr ? *bsss : nullptr); bssPath != nullptr; bssPath = *(++bsss)) + { + WiFiScanResponse network; + if (_GetBssInfo(bssPath, network)) + { + if (sInterestedSSIDLen == 0) + { + networkScanned->push_back(network); + } + else if (network.ssidLen == sInterestedSSIDLen && memcmp(network.ssid, sInterestedSSID, sInterestedSSIDLen) == 0) + { + networkScanned->push_back(network); + } + } + } + + DeviceLayer::SystemLayer().ScheduleLambda([this, networkScanned]() { + // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copiable. This results in the use of + // const_cast but should be fine for almost all cases, since we actually handled the ownership of this element to this + // lambda. + if (mpScanCallback != nullptr) + { + LinuxScanResponseIterator iter(const_cast *>(networkScanned)); + mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter); + mpScanCallback = nullptr; + } + + delete const_cast *>(networkScanned); + }); + + g_strfreev(oldBsss); +} + +CHIP_ERROR ConnectivityManagerImpl::_StartWiFiManagement() +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + ChipLogProgress(DeviceLayer, "wpa_supplicant: Start WiFi management"); + wpa_fi_w1_wpa_supplicant1_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, kWpaSupplicantObjectPath, nullptr, + reinterpret_cast(+[](GObject * sourceObject_, GAsyncResult * res_, ConnectivityManagerImpl * self) { + return self->_OnWpaProxyReady(sourceObject_, res_); + }), + this); + + return CHIP_NO_ERROR; +} + +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ConnectivityManagerImpl.h b/src/platform/NuttX/ConnectivityManagerImpl.h new file mode 100644 index 00000000000000..0d96adcac6e6d8 --- /dev/null +++ b/src/platform/NuttX/ConnectivityManagerImpl.h @@ -0,0 +1,320 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#if INET_CONFIG_ENABLE_TCP_ENDPOINT +#include +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#include +#else +#include +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +#include +#else +#include +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +#include +#else +#include +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +#include +#include +#include +#include +#include + +#include +#endif + +#include +#include +#include + +namespace chip { +namespace Inet { +class IPAddress; +} // namespace Inet +} // namespace chip + +namespace chip { +namespace DeviceLayer { + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +struct GDBusWpaSupplicant +{ + enum WpaState + { + INIT, + WPA_CONNECTING, + WPA_CONNECTED, + WPA_NOT_CONNECTED, + WPA_NO_INTERFACE_PATH, + WPA_GOT_INTERFACE_PATH, + WPA_INTERFACE_CONNECTED, + }; + + enum WpaScanning + { + WIFI_SCANNING_IDLE, + WIFI_SCANNING, + }; + + WpaState state = INIT; + WpaScanning scanState = WIFI_SCANNING_IDLE; + WpaFiW1Wpa_supplicant1 * proxy = nullptr; + WpaFiW1Wpa_supplicant1Interface * iface = nullptr; + WpaFiW1Wpa_supplicant1BSS * bss = nullptr; + gchar * interfacePath = nullptr; + gchar * networkPath = nullptr; +}; +#endif + +/** + * Concrete implementation of the ConnectivityManager singleton object for Linux platforms. + */ +class ConnectivityManagerImpl final : public ConnectivityManager, +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE + public Internal::GenericConnectivityManagerImpl_BLE, +#else + public Internal::GenericConnectivityManagerImpl_NoBLE, +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + public Internal::GenericConnectivityManagerImpl_Thread, +#else + public Internal::GenericConnectivityManagerImpl_NoThread, +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + public Internal::GenericConnectivityManagerImpl_WiFi, +#else + public Internal::GenericConnectivityManagerImpl_NoWiFi, +#endif + public Internal::GenericConnectivityManagerImpl_UDP, +#if INET_CONFIG_ENABLE_TCP_ENDPOINT + public Internal::GenericConnectivityManagerImpl_TCP, +#endif + public Internal::GenericConnectivityManagerImpl +{ + // Allow the ConnectivityManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend class ConnectivityManager; + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +public: + void + SetNetworkStatusChangeCallback(NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * statusChangeCallback) + { + mpStatusChangeCallback = statusChangeCallback; + } + + CHIP_ERROR ConnectWiFiNetworkAsync(ByteSpan ssid, ByteSpan credentials, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback); +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + CHIP_ERROR ConnectWiFiNetworkWithPDCAsync(ByteSpan ssid, ByteSpan networkIdentity, ByteSpan clientIdentity, + const Crypto::P256Keypair & clientIdentityKeypair, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback); +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + + void PostNetworkConnect(); + CHIP_ERROR CommitConfig(); + + void StartWiFiManagement(); + bool IsWiFiManagementStarted(); + void StartNonConcurrentWiFiManagement(); + int32_t GetDisconnectReason(); + CHIP_ERROR GetWiFiBssId(MutableByteSpan & value); + CHIP_ERROR GetWiFiSecurityType(app::Clusters::WiFiNetworkDiagnostics::SecurityTypeEnum & securityType); + CHIP_ERROR GetWiFiVersion(app::Clusters::WiFiNetworkDiagnostics::WiFiVersionEnum & wiFiVersion); + CHIP_ERROR GetConfiguredNetwork(NetworkCommissioning::Network & network); + CHIP_ERROR StartWiFiScan(ByteSpan ssid, NetworkCommissioning::WiFiDriver::ScanCallback * callback); + +private: + CHIP_ERROR _ConnectWiFiNetworkAsync(GVariant * networkArgs, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback) + CHIP_REQUIRES(mWpaSupplicantMutex); + void _ConnectWiFiNetworkAsyncCallback(GObject * sourceObject, GAsyncResult * res); +#endif + +public: + const char * GetEthernetIfName() { return (mEthIfName[0] == '\0') ? nullptr : mEthIfName; } + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + const char * GetWiFiIfName() { return (sWiFiIfName[0] == '\0') ? nullptr : sWiFiIfName; } +#endif + +private: + // ===== Members that implement the ConnectivityManager abstract interface. + + struct WiFiNetworkScanned + { + // The fields matches WiFiInterfaceScanResult::Type. + uint8_t ssid[Internal::kMaxWiFiSSIDLength]; + uint8_t ssidLen; + uint8_t bssid[6]; + int8_t rssi; + uint16_t frequencyBand; + uint8_t channel; + uint8_t security; + }; + + CHIP_ERROR _Init(); + void _OnPlatformEvent(const ChipDeviceEvent * event); + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + WiFiStationMode _GetWiFiStationMode(); + CHIP_ERROR _SetWiFiStationMode(ConnectivityManager::WiFiStationMode val); + System::Clock::Timeout _GetWiFiStationReconnectInterval(); + CHIP_ERROR _SetWiFiStationReconnectInterval(System::Clock::Timeout val); + bool _IsWiFiStationEnabled(); + bool _IsWiFiStationConnected(); + bool _IsWiFiStationApplicationControlled(); + bool _IsWiFiStationProvisioned(); + void _ClearWiFiStationProvision(); + bool _CanStartWiFiScan(); + + WiFiAPMode _GetWiFiAPMode(); + CHIP_ERROR _SetWiFiAPMode(WiFiAPMode val); + bool _IsWiFiAPActive(); + bool _IsWiFiAPApplicationControlled(); + void _DemandStartWiFiAP(); + void _StopOnDemandWiFiAP(); + void _MaintainOnDemandWiFiAP(); + System::Clock::Timeout _GetWiFiAPIdleTimeout(); + void _SetWiFiAPIdleTimeout(System::Clock::Timeout val); + void UpdateNetworkStatus(); + CHIP_ERROR StopAutoScan(); + + void _OnWpaProxyReady(GObject * sourceObject, GAsyncResult * res); + void _OnWpaInterfaceRemoved(WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties); + void _OnWpaInterfaceAdded(WpaFiW1Wpa_supplicant1 * proxy, const char * path, GVariant * properties); + void _OnWpaPropertiesChanged(WpaFiW1Wpa_supplicant1Interface * proxy, GVariant * properties); + void _OnWpaInterfaceScanDone(WpaFiW1Wpa_supplicant1Interface * proxy, gboolean success); + void _OnWpaInterfaceReady(GObject * sourceObject, GAsyncResult * res); + void _OnWpaInterfaceProxyReady(GObject * sourceObject, GAsyncResult * res); + void _OnWpaBssProxyReady(GObject * sourceObject, GAsyncResult * res); + + bool _GetBssInfo(const gchar * bssPath, NetworkCommissioning::WiFiScanResponse & result); + + CHIP_ERROR _StartWiFiManagement(); + + bool mAssociationStarted = false; + BitFlags mConnectivityFlag; + GDBusWpaSupplicant mWpaSupplicant CHIP_GUARDED_BY(mWpaSupplicantMutex); + // Access to mWpaSupplicant has to be protected by a mutex because it is accessed from + // the CHIP event loop thread and dedicated D-Bus thread started by platform manager. + std::mutex mWpaSupplicantMutex; + + NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * mpStatusChangeCallback = nullptr; +#endif + + // ==================== ConnectivityManager Private Methods ==================== + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + void DriveAPState(); + CHIP_ERROR ConfigureWiFiAP(); + void ChangeWiFiAPState(WiFiAPState newState); + static void DriveAPState(::chip::System::Layer * aLayer, void * aAppState); +#endif + + // ===== Members for internal use by the following friends. + + friend ConnectivityManager & ConnectivityMgr(); + friend ConnectivityManagerImpl & ConnectivityMgrImpl(); + + static ConnectivityManagerImpl sInstance; + + // ===== Private members reserved for use by this class only. + + char mEthIfName[IFNAMSIZ]; + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + ConnectivityManager::WiFiStationMode mWiFiStationMode; + ConnectivityManager::WiFiAPMode mWiFiAPMode; + WiFiAPState mWiFiAPState; + System::Clock::Timestamp mLastAPDemandTime; + System::Clock::Timeout mWiFiStationReconnectInterval; + System::Clock::Timeout mWiFiAPIdleTimeout; +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + char sWiFiIfName[IFNAMSIZ]; +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + uint8_t sInterestedSSID[Internal::kMaxWiFiSSIDLength]; + uint8_t sInterestedSSIDLen; +#endif + NetworkCommissioning::WiFiDriver::ScanCallback * mpScanCallback; + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * mpConnectCallback; +}; + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +inline ConnectivityManager::WiFiAPMode ConnectivityManagerImpl::_GetWiFiAPMode() +{ + return mWiFiAPMode; +} + +inline bool ConnectivityManagerImpl::_IsWiFiAPActive() +{ + return mWiFiAPState == kWiFiAPState_Active; +} + +inline bool ConnectivityManagerImpl::_IsWiFiAPApplicationControlled() +{ + return mWiFiAPMode == kWiFiAPMode_ApplicationControlled; +} + +inline System::Clock::Timeout ConnectivityManagerImpl::_GetWiFiAPIdleTimeout() +{ + return mWiFiAPIdleTimeout; +} + +#endif + +/** + * Returns the public interface of the ConnectivityManager singleton object. + * + * chip applications should use this to access features of the ConnectivityManager object + * that are common to all platforms. + */ +inline ConnectivityManager & ConnectivityMgr() +{ + return ConnectivityManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the ConnectivityManager singleton object. + * + * chip applications can use this to gain access to features of the ConnectivityManager + * that are specific to the ESP32 platform. + */ +inline ConnectivityManagerImpl & ConnectivityMgrImpl() +{ + return ConnectivityManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ConnectivityUtils.cpp b/src/platform/NuttX/ConnectivityUtils.cpp new file mode 100644 index 00000000000000..47f6253b2b1b0e --- /dev/null +++ b/src/platform/NuttX/ConnectivityUtils.cpp @@ -0,0 +1,734 @@ +/* + * + * Copyright (c) 2021-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Utilities for accessing parameters of the network interface and the wireless + * statistics(extracted from /proc/net/wireless) on Linux platforms. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace ::chip::app::Clusters::GeneralDiagnostics; + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +uint16_t ConnectivityUtils::Map2400MHz(const uint8_t inChannel) +{ + uint16_t frequency = 0; + + if (inChannel >= 1 && inChannel <= 13) + { + frequency = static_cast(2412 + ((inChannel - 1) * 5)); + } + else if (inChannel == 14) + { + frequency = 2484; + } + + return frequency; +} + +uint16_t ConnectivityUtils::Map5000MHz(const uint8_t inChannel) +{ + uint16_t frequency = 0; + + switch (inChannel) + { + case 183: + frequency = 4915; + break; + case 184: + frequency = 4920; + break; + case 185: + frequency = 4925; + break; + case 187: + frequency = 4935; + break; + case 188: + frequency = 4940; + break; + case 189: + frequency = 4945; + break; + case 192: + frequency = 4960; + break; + case 196: + frequency = 4980; + break; + case 7: + frequency = 5035; + break; + case 8: + frequency = 5040; + break; + case 9: + frequency = 5045; + break; + case 11: + frequency = 5055; + break; + case 12: + frequency = 5060; + break; + case 16: + frequency = 5080; + break; + case 34: + frequency = 5170; + break; + case 36: + frequency = 5180; + break; + case 38: + frequency = 5190; + break; + case 40: + frequency = 5200; + break; + case 42: + frequency = 5210; + break; + case 44: + frequency = 5220; + break; + case 46: + frequency = 5230; + break; + case 48: + frequency = 5240; + break; + case 52: + frequency = 5260; + break; + case 56: + frequency = 5280; + break; + case 60: + frequency = 5300; + break; + case 64: + frequency = 5320; + break; + case 100: + frequency = 5500; + break; + case 104: + frequency = 5520; + break; + case 108: + frequency = 5540; + break; + case 112: + frequency = 5560; + break; + case 116: + frequency = 5580; + break; + case 120: + frequency = 5600; + break; + case 124: + frequency = 5620; + break; + case 128: + frequency = 5640; + break; + case 132: + frequency = 5660; + break; + case 136: + frequency = 5680; + break; + case 140: + frequency = 5700; + break; + case 149: + frequency = 5745; + break; + case 153: + frequency = 5765; + break; + case 157: + frequency = 5785; + break; + case 161: + frequency = 5805; + break; + case 165: + frequency = 5825; + break; + } + + return frequency; +} + +uint16_t ConnectivityUtils::MapChannelToFrequency(const uint16_t inBand, const uint8_t inChannel) +{ + uint16_t frequency = 0; + + if (inBand == kWiFi_BAND_2_4_GHZ) + { + frequency = Map2400MHz(inChannel); + } + else if (inBand == kWiFi_BAND_5_0_GHZ) + { + frequency = Map5000MHz(inChannel); + } + + return frequency; +} + +uint8_t ConnectivityUtils::MapFrequencyToChannel(const uint16_t frequency) +{ + if (frequency < 2412) + return 0; + + if (frequency < 2484) + return (frequency - 2407) / 5; + + if (frequency == 2484) + return 14; + + return frequency / 5 - 1000; +} + +double ConnectivityUtils::ConvertFrequenceToFloat(const iw_freq * in) +{ + double result = (double) in->m; + + for (int i = 0; i < in->e; i++) + result *= 10; + + return result; +} + +InterfaceTypeEnum ConnectivityUtils::GetInterfaceConnectionType(const char * ifname) +{ + InterfaceTypeEnum ret = InterfaceTypeEnum::kUnspecified; + int sock = -1; + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + ChipLogError(DeviceLayer, "Failed to open socket"); + return InterfaceTypeEnum::kUnspecified; + } + + // Test wireless extensions for CONNECTION_WIFI + struct iwreq pwrq = {}; + Platform::CopyString(pwrq.ifr_name, ifname); + + if (ioctl(sock, SIOCGIWNAME, &pwrq) != -1) + { + ret = InterfaceTypeEnum::kWiFi; + } + else if ((strncmp(ifname, "en", 2) == 0) || (strncmp(ifname, "eth", 3) == 0)) + { + struct ethtool_cmd ecmd = {}; + ecmd.cmd = ETHTOOL_GSET; + struct ifreq ifr = {}; + ifr.ifr_data = reinterpret_cast(&ecmd); + Platform::CopyString(ifr.ifr_name, ifname); + + if (ioctl(sock, SIOCETHTOOL, &ifr) != -1) + ret = InterfaceTypeEnum::kEthernet; + } + + close(sock); + + return ret; +} + +CHIP_ERROR ConnectivityUtils::GetInterfaceHardwareAddrs(const char * ifname, uint8_t * buf, size_t bufSize) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (ifname[0] != '\0') + { + struct ifreq req; + + strcpy(req.ifr_name, ifname); + if (ioctl(skfd, SIOCGIFHWADDR, &req) != -1) + { + // Copy 48-bit IEEE MAC Address + VerifyOrReturnError(bufSize >= 6, CHIP_ERROR_BUFFER_TOO_SMALL); + + memset(buf, 0, bufSize); + memcpy(buf, req.ifr_ifru.ifru_hwaddr.sa_data, 6); + err = CHIP_NO_ERROR; + } + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetInterfaceIPv4Addrs(const char * ifname, uint8_t & size, NetworkInterface * ifp) +{ + CHIP_ERROR err; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + err = CHIP_ERROR_READ_FAILED; + } + else + { + uint8_t index = 0; + + for (struct ifaddrs * ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) + { + if (strcmp(ifname, ifa->ifa_name) == 0) + { + void * addPtr = &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr; + + memcpy(ifp->Ipv4AddressesBuffer[index], addPtr, kMaxIPv4AddrSize); + ifp->Ipv4AddressSpans[index] = ByteSpan(ifp->Ipv4AddressesBuffer[index], kMaxIPv4AddrSize); + index++; + + if (index >= kMaxIPv4AddrCount) + { + break; + } + } + } + } + + if (index > 0) + { + err = CHIP_NO_ERROR; + size = index; + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetInterfaceIPv6Addrs(const char * ifname, uint8_t & size, NetworkInterface * ifp) +{ + CHIP_ERROR err; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + err = CHIP_ERROR_READ_FAILED; + } + else + { + uint8_t index = 0; + + for (struct ifaddrs * ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET6) + { + if (strcmp(ifname, ifa->ifa_name) == 0) + { + void * addPtr = &((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr; + + memcpy(ifp->Ipv6AddressesBuffer[index], addPtr, kMaxIPv6AddrSize); + ifp->Ipv6AddressSpans[index] = ByteSpan(ifp->Ipv6AddressesBuffer[index], kMaxIPv6AddrSize); + index++; + + if (index >= kMaxIPv6AddrCount) + { + break; + } + } + } + } + + if (index > 0) + { + err = CHIP_NO_ERROR; + size = index; + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiInterfaceName(char * ifname, size_t bufSize) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + /* Walk through linked list, maintaining head pointer so we + can free list later */ + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kWiFi) + { + Platform::CopyString(ifname, bufSize, ifa->ifa_name); + err = CHIP_NO_ERROR; + break; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiParameter(int skfd, /* Socket to the kernel */ + const char * ifname, /* Device name */ + int request, /* WE ID */ + struct iwreq * pwrq) /* Fixed part of the request */ +{ + /* Set device name */ + Platform::CopyString(pwrq->ifr_name, ifname); + + /* Do the request */ + if (ioctl(skfd, request, pwrq) < 0) + { + return CHIP_ERROR_BAD_REQUEST; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiStats(int skfd, const char * ifname, struct iw_statistics * stats) +{ + struct iwreq wrq; + + wrq.u.data.pointer = (caddr_t) stats; + wrq.u.data.length = sizeof(struct iw_statistics); + wrq.u.data.flags = 1; /*Clear updated flag */ + Platform::CopyString(wrq.ifr_name, ifname); + + return GetWiFiParameter(skfd, ifname, SIOCGIWSTATS, &wrq); +} + +CHIP_ERROR ConnectivityUtils::GetWiFiChannelNumber(const char * ifname, uint16_t & channelNumber) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + + struct iwreq wrq; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiParameter(skfd, ifname, SIOCGIWFREQ, &wrq) == CHIP_NO_ERROR) + { + double freq = ConvertFrequenceToFloat(&(wrq.u.freq)); + VerifyOrReturnError((freq / 1000000) <= UINT16_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + channelNumber = MapFrequencyToChannel(static_cast(freq / 1000000)); + + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiRssi(const char * ifname, int8_t & rssi) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct iw_statistics stats; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiStats(skfd, ifname, &stats) == CHIP_NO_ERROR) + { + struct iw_quality * qual = &stats.qual; + + /* Check if the statistics are in RCPI (IEEE 802.11k) */ + if (qual->updated & IW_QUAL_RCPI) + { /* Deal with signal level in RCPI */ + /* RCPI = int{(Power in dBm +110)*2} for 0dbm > Power > -110dBm */ + if (!(qual->updated & IW_QUAL_LEVEL_INVALID)) + { + double rcpilevel = (qual->level / 2.0) - 110.0; + VerifyOrReturnError(rcpilevel <= INT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rssi = static_cast(rcpilevel); + err = CHIP_NO_ERROR; + } + } + else + { /* Check if the statistics are in dBm */ + if (qual->updated & IW_QUAL_DBM) + { /* Deal with signal level in dBm (absolute power measurement) */ + if (!(qual->updated & IW_QUAL_LEVEL_INVALID)) + { + int dblevel = qual->level; + /* Implement a range for dBm[-192; 63] */ + if (qual->level >= 64) + dblevel -= 0x100; + + VerifyOrReturnError(dblevel <= INT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rssi = static_cast(dblevel); + err = CHIP_NO_ERROR; + } + } + else + { /* Deal with signal level as relative value (0 -> max) */ + if (!(qual->updated & IW_QUAL_LEVEL_INVALID)) + { + VerifyOrReturnError(qual->level <= INT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rssi = static_cast(qual->level); + err = CHIP_NO_ERROR; + } + } + } + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiBeaconLostCount(const char * ifname, uint32_t & beaconLostCount) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct iw_statistics stats; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiStats(skfd, ifname, &stats) == CHIP_NO_ERROR) + { + beaconLostCount = stats.miss.beacon; + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiCurrentMaxRate(const char * ifname, uint64_t & currentMaxRate) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct iwreq wrq; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiParameter(skfd, ifname, SIOCGIWRATE, &wrq) == CHIP_NO_ERROR) + { + currentMaxRate = wrq.u.bitrate.value; + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetEthInterfaceName(char * ifname, size_t bufSize) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + /* Walk through linked list, maintaining head pointer so we + can free list later */ + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kEthernet) + { + Platform::CopyString(ifname, bufSize, ifa->ifa_name); + err = CHIP_NO_ERROR; + break; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetEthPHYRate(const char * ifname, app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum & pHYRate) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + int skfd; + uint32_t speed = 0; + struct ethtool_cmd ecmd = {}; + ecmd.cmd = ETHTOOL_GSET; + struct ifreq ifr = {}; + + ifr.ifr_data = reinterpret_cast(&ecmd); + Platform::CopyString(ifr.ifr_name, ifname); + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1) + { + ChipLogError(DeviceLayer, "Cannot get device settings"); + close(skfd); + return CHIP_ERROR_READ_FAILED; + } + + speed = (ecmd.speed_hi << 16) | ecmd.speed; + switch (speed) + { + case 10: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate10M; + break; + case 100: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate100M; + break; + case 1000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate1G; + break; + case 25000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate25g; + break; + case 5000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate5G; + break; + case 10000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate10G; + break; + case 40000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate40G; + break; + case 100000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate100G; + break; + case 200000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate200G; + break; + case 400000: + pHYRate = app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum::kRate400G; + break; + default: + ChipLogError(DeviceLayer, "Undefined speed! (%" PRIu32 ")\n", speed); + err = CHIP_ERROR_READ_FAILED; + break; + }; + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetEthFullDuplex(const char * ifname, bool & fullDuplex) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + + int skfd; + struct ethtool_cmd ecmd = {}; + ecmd.cmd = ETHTOOL_GSET; + struct ifreq ifr = {}; + + ifr.ifr_data = reinterpret_cast(&ecmd); + Platform::CopyString(ifr.ifr_name, ifname); + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1) + { + ChipLogError(DeviceLayer, "Cannot get device settings"); + err = CHIP_ERROR_READ_FAILED; + } + else + { + fullDuplex = ecmd.duplex == DUPLEX_FULL; + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ConnectivityUtils.h b/src/platform/NuttX/ConnectivityUtils.h new file mode 100644 index 00000000000000..0cfabeca000477 --- /dev/null +++ b/src/platform/NuttX/ConnectivityUtils.h @@ -0,0 +1,69 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Utilities for accessing parameters of the network interface and the wireless + * statistics(extracted from /proc/net/wireless) on Linux platforms. + */ + +#pragma once + +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +static constexpr uint16_t kWiFi_BAND_2_4_GHZ = 2400; +static constexpr uint16_t kWiFi_BAND_5_0_GHZ = 5000; +static constexpr char kWpaSupplicantServiceName[] = "fi.w1.wpa_supplicant1"; +static constexpr char kWpaSupplicantObjectPath[] = "/fi/w1/wpa_supplicant1"; +static constexpr char kWpaSupplicantBlobUnknown[] = "fi.w1.wpa_supplicant1.BlobUnknown"; + +class ConnectivityUtils +{ +public: + static uint16_t MapChannelToFrequency(const uint16_t inBand, const uint8_t inChannel); + static uint8_t MapFrequencyToChannel(const uint16_t frequency); + static app::Clusters::GeneralDiagnostics::InterfaceTypeEnum GetInterfaceConnectionType(const char * ifname); + static CHIP_ERROR GetInterfaceHardwareAddrs(const char * ifname, uint8_t * buf, size_t bufSize); + static CHIP_ERROR GetInterfaceIPv4Addrs(const char * ifname, uint8_t & size, NetworkInterface * ifp); + static CHIP_ERROR GetInterfaceIPv6Addrs(const char * ifname, uint8_t & size, NetworkInterface * ifp); + static CHIP_ERROR GetWiFiInterfaceName(char * ifname, size_t bufSize); + static CHIP_ERROR GetWiFiChannelNumber(const char * ifname, uint16_t & channelNumber); + static CHIP_ERROR GetWiFiRssi(const char * ifname, int8_t & rssi); + static CHIP_ERROR GetWiFiBeaconLostCount(const char * ifname, uint32_t & beaconLostCount); + static CHIP_ERROR GetWiFiCurrentMaxRate(const char * ifname, uint64_t & currentMaxRate); + static CHIP_ERROR GetEthInterfaceName(char * ifname, size_t bufSize); + static CHIP_ERROR GetEthPHYRate(const char * ifname, app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum & pHYRate); + static CHIP_ERROR GetEthFullDuplex(const char * ifname, bool & fullDuplex); + +private: + static uint16_t Map2400MHz(const uint8_t inChannel); + static uint16_t Map5000MHz(const uint8_t inChannel); + static double ConvertFrequenceToFloat(const iw_freq * in); + static CHIP_ERROR GetWiFiParameter(int skfd, const char * ifname, int request, struct iwreq * pwrq); + static CHIP_ERROR GetWiFiStats(int skfd, const char * ifname, struct iw_statistics * stats); +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/DeviceInstanceInfoProviderImpl.cpp b/src/platform/NuttX/DeviceInstanceInfoProviderImpl.cpp new file mode 100644 index 00000000000000..9e148e77b4484d --- /dev/null +++ b/src/platform/NuttX/DeviceInstanceInfoProviderImpl.cpp @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DeviceInstanceInfoProviderImpl.h" + +#include + +namespace chip { +namespace DeviceLayer { + +CHIP_ERROR DeviceInstanceInfoProviderImpl::GetVendorId(uint16_t & vendorId) +{ + return Internal::PosixConfig::ReadConfigValue(Internal::PosixConfig::kConfigKey_VendorId, vendorId); +} + +CHIP_ERROR DeviceInstanceInfoProviderImpl::GetProductId(uint16_t & productId) +{ + return Internal::PosixConfig::ReadConfigValue(Internal::PosixConfig::kConfigKey_ProductId, productId); +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/DeviceInstanceInfoProviderImpl.h b/src/platform/NuttX/DeviceInstanceInfoProviderImpl.h new file mode 100644 index 00000000000000..72679be226b619 --- /dev/null +++ b/src/platform/NuttX/DeviceInstanceInfoProviderImpl.h @@ -0,0 +1,44 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace chip { +namespace DeviceLayer { + +class DeviceInstanceInfoProviderImpl : public Internal::GenericDeviceInstanceInfoProvider +{ +public: + CHIP_ERROR GetVendorId(uint16_t & vendorId) override; + CHIP_ERROR GetProductId(uint16_t & productId) override; + + DeviceInstanceInfoProviderImpl(ConfigurationManagerImpl & configManager) : + Internal::GenericDeviceInstanceInfoProvider(configManager) + {} +}; + +inline DeviceInstanceInfoProviderImpl & DeviceInstanceInfoProviderMgrImpl() +{ + static DeviceInstanceInfoProviderImpl sInstance(ConfigurationManagerImpl::GetDefaultInstance()); + return sInstance; +} +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/DiagnosticDataProviderImpl.cpp b/src/platform/NuttX/DiagnosticDataProviderImpl.cpp new file mode 100644 index 00000000000000..b47e5c431e41b4 --- /dev/null +++ b/src/platform/NuttX/DiagnosticDataProviderImpl.cpp @@ -0,0 +1,830 @@ +/* + * + * Copyright (c) 2021-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the DiagnosticDataProvider object + * for Linux platform. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::chip; +using namespace ::chip::app; +using namespace ::chip::TLV; +using namespace ::chip::DeviceLayer; +using namespace ::chip::DeviceLayer::Internal; +using namespace ::chip::app::Clusters::GeneralDiagnostics; + +namespace { + +enum class EthernetStatsCountType +{ + kEthPacketRxCount, + kEthPacketTxCount, + kEthTxErrCount, + kEthCollisionCount, + kEthOverrunCount +}; + +enum class WiFiStatsCountType +{ + kWiFiUnicastPacketRxCount, + kWiFiUnicastPacketTxCount, + kWiFiMulticastPacketRxCount, + kWiFiMulticastPacketTxCount, + kWiFiOverrunCount +}; + +CHIP_ERROR GetEthernetStatsCount(EthernetStatsCountType type, uint64_t & count) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kEthernet) + { + ChipLogProgress(DeviceLayer, "Found the primary Ethernet interface:%s", StringOrNullMarker(ifa->ifa_name)); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + switch (type) + { + case EthernetStatsCountType::kEthPacketRxCount: + count = stats->rx_packets; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthPacketTxCount: + count = stats->tx_packets; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthTxErrCount: + count = stats->tx_errors; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthCollisionCount: + count = stats->collisions; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthOverrunCount: + count = stats->rx_over_errors; + err = CHIP_NO_ERROR; + break; + default: + ChipLogError(DeviceLayer, "Unknown Ethernet statistic metric type"); + break; + } + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI +CHIP_ERROR GetWiFiStatsCount(WiFiStatsCountType type, uint64_t & count) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kWiFi) + { + ChipLogProgress(DeviceLayer, "Found the primary WiFi interface:%s", StringOrNullMarker(ifa->ifa_name)); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + // The usecase of this function is embedded devices,on which we can interact with the WiFi + // driver to get the accurate number of muticast and unicast packets accurately. + // On Linux simulation, we can only get the total packets received, the total bytes transmitted, + // the multicast packets received and receiver ring buff overflow. + + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + switch (type) + { + case WiFiStatsCountType::kWiFiUnicastPacketRxCount: + count = stats->rx_packets; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiUnicastPacketTxCount: + count = stats->tx_packets; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiMulticastPacketRxCount: + count = stats->multicast; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiMulticastPacketTxCount: + count = 0; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiOverrunCount: + count = stats->rx_over_errors; + err = CHIP_NO_ERROR; + break; + default: + ChipLogError(DeviceLayer, "Unknown WiFi statistic metric type"); + break; + } + } + } + + freeifaddrs(ifaddr); + } + + return err; +} +#endif // #if CHIP_DEVICE_CONFIG_ENABLE_WIFI + +} // namespace + +namespace chip { +namespace DeviceLayer { + +DiagnosticDataProviderImpl & DiagnosticDataProviderImpl::GetDefaultInstance() +{ + static DiagnosticDataProviderImpl sInstance; + return sInstance; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapFree(uint64_t & currentHeapFree) +{ +#ifndef __GLIBC__ + return CHIP_ERROR_NOT_IMPLEMENTED; +#else + struct mallinfo mallocInfo = mallinfo(); + + // Get the current amount of heap memory, in bytes, that are not being utilized + // by the current running program. + currentHeapFree = mallocInfo.fordblks; + + return CHIP_NO_ERROR; +#endif +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapUsed(uint64_t & currentHeapUsed) +{ +#ifndef __GLIBC__ + return CHIP_ERROR_NOT_IMPLEMENTED; +#else + struct mallinfo mallocInfo = mallinfo(); + + // Get the current amount of heap memory, in bytes, that are being used by + // the current running program. + currentHeapUsed = mallocInfo.uordblks; + + return CHIP_NO_ERROR; +#endif +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapHighWatermark(uint64_t & currentHeapHighWatermark) +{ +#ifndef __GLIBC__ + return CHIP_ERROR_NOT_IMPLEMENTED; +#else + struct mallinfo mallocInfo = mallinfo(); + + // The usecase of this function is embedded devices,on which we would need to intercept + // malloc/calloc/free and then record the maximum amount of heap memory,in bytes, that + // has been used by the Node. + // On Linux, since it uses virtual memory, whereby a page of memory could be copied to + // the hard disk, called swap space, and free up that page of memory. So it is impossible + // to know accurately peak physical memory it use. We just return the current heap memory + // being used by the current running program. + currentHeapHighWatermark = mallocInfo.uordblks; + + return CHIP_NO_ERROR; +#endif +} + +CHIP_ERROR DiagnosticDataProviderImpl::ResetWatermarks() +{ + // If implemented, the server SHALL set the value of the CurrentHeapHighWatermark attribute to the + // value of the CurrentHeapUsed. + + // On Linux, the write operation is non-op since we always rely on the mallinfo system + // function to get the current heap memory. + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetThreadMetrics(ThreadMetrics ** threadMetricsOut) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + DIR * proc_dir = opendir("/proc/self/task"); + + if (proc_dir == nullptr) + { + ChipLogError(DeviceLayer, "Failed to open current process task directory"); + } + else + { + ThreadMetrics * head = nullptr; + struct dirent * entry; + + /* proc available, iterate through tasks... */ + while ((entry = readdir(proc_dir)) != nullptr) + { + if (entry->d_name[0] == '.') + continue; + + ThreadMetrics * thread = new ThreadMetrics(); + + Platform::CopyString(thread->NameBuf, entry->d_name); + thread->name.Emplace(CharSpan::fromCharString(thread->NameBuf)); + thread->id = atoi(entry->d_name); + + // TODO: Get stack info of each thread: thread->stackFreeCurrent, + // thread->stackFreeMinimum, thread->stackSize. + + thread->Next = head; + head = thread; + } + + closedir(proc_dir); + + *threadMetricsOut = head; + err = CHIP_NO_ERROR; + } + + return err; +} + +void DiagnosticDataProviderImpl::ReleaseThreadMetrics(ThreadMetrics * threadMetrics) +{ + while (threadMetrics) + { + ThreadMetrics * del = threadMetrics; + threadMetrics = threadMetrics->Next; + delete del; + } +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetRebootCount(uint16_t & rebootCount) +{ + uint32_t count = 0; + + CHIP_ERROR err = ConfigurationMgr().GetRebootCount(count); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(count <= UINT16_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rebootCount = static_cast(count); + } + + return err; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetUpTime(uint64_t & upTime) +{ + System::Clock::Timestamp currentTime = System::SystemClock().GetMonotonicTimestamp(); + System::Clock::Timestamp startTime = PlatformMgrImpl().GetStartTime(); + + if (currentTime >= startTime) + { + upTime = std::chrono::duration_cast(currentTime - startTime).count(); + return CHIP_NO_ERROR; + } + + return CHIP_ERROR_INVALID_TIME; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetTotalOperationalHours(uint32_t & totalOperationalHours) +{ + uint64_t upTime = 0; + + if (GetUpTime(upTime) == CHIP_NO_ERROR) + { + uint32_t totalHours = 0; + if (ConfigurationMgr().GetTotalOperationalHours(totalHours) == CHIP_NO_ERROR) + { + VerifyOrReturnError(upTime / 3600 <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + totalOperationalHours = totalHours + static_cast(upTime / 3600); + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_INVALID_TIME; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetBootReason(BootReasonType & bootReason) +{ + uint32_t reason = 0; + + CHIP_ERROR err = ConfigurationMgr().GetBootReason(reason); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(reason <= UINT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + bootReason = static_cast(reason); + } + + return err; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetActiveHardwareFaults(GeneralFaults & hardwareFaults) +{ +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following hardware faults statically. + ReturnErrorOnFailure(hardwareFaults.add(to_underlying(HardwareFaultEnum::kRadio))); + ReturnErrorOnFailure(hardwareFaults.add(to_underlying(HardwareFaultEnum::kSensor))); + ReturnErrorOnFailure(hardwareFaults.add(to_underlying(HardwareFaultEnum::kPowerSource))); + ReturnErrorOnFailure(hardwareFaults.add(to_underlying(HardwareFaultEnum::kUserInterfaceFault))); +#endif + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetActiveRadioFaults(GeneralFaults & radioFaults) +{ +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following radio faults statically. + ReturnErrorOnFailure(radioFaults.add(to_underlying(RadioFaultEnum::kWiFiFault))); + ReturnErrorOnFailure(radioFaults.add(to_underlying(RadioFaultEnum::kCellularFault))); + ReturnErrorOnFailure(radioFaults.add(to_underlying(RadioFaultEnum::kThreadFault))); + ReturnErrorOnFailure(radioFaults.add(to_underlying(RadioFaultEnum::kNFCFault))); +#endif + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetActiveNetworkFaults(GeneralFaults & networkFaults) +{ +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following radio faults statically. + ReturnErrorOnFailure(networkFaults.add(to_underlying(NetworkFaultEnum::kHardwareFailure))); + ReturnErrorOnFailure(networkFaults.add(to_underlying(NetworkFaultEnum::kNetworkJammed))); + ReturnErrorOnFailure(networkFaults.add(to_underlying(NetworkFaultEnum::kConnectionFailed))); +#endif + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetNetworkInterfaces(NetworkInterface ** netifpp) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + NetworkInterface * head = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (struct ifaddrs * ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_PACKET) + { + uint8_t size = 0; + NetworkInterface * ifp = new NetworkInterface(); + + Platform::CopyString(ifp->Name, ifa->ifa_name); + + ifp->name = CharSpan::fromCharString(ifp->Name); + ifp->isOperational = ifa->ifa_flags & IFF_RUNNING; + ifp->type = ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name); + ifp->offPremiseServicesReachableIPv4.SetNull(); + ifp->offPremiseServicesReachableIPv6.SetNull(); + + if (ConnectivityUtils::GetInterfaceIPv4Addrs(ifa->ifa_name, size, ifp) == CHIP_NO_ERROR) + { + if (size > 0) + { + ifp->IPv4Addresses = DataModel::List(ifp->Ipv4AddressSpans, size); + } + } + + if (ConnectivityUtils::GetInterfaceIPv6Addrs(ifa->ifa_name, size, ifp) == CHIP_NO_ERROR) + { + if (size > 0) + { + ifp->IPv6Addresses = DataModel::List(ifp->Ipv6AddressSpans, size); + } + } + + if (ConnectivityUtils::GetInterfaceHardwareAddrs(ifa->ifa_name, ifp->MacAddress, kMaxHardwareAddrSize) != + CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get network hardware address"); + } + else + { + // Set 48-bit IEEE MAC Address + ifp->hardwareAddress = ByteSpan(ifp->MacAddress, 6); + } + + ifp->Next = head; + head = ifp; + } + } + + *netifpp = head; + err = CHIP_NO_ERROR; + + freeifaddrs(ifaddr); + } + + return err; +} + +void DiagnosticDataProviderImpl::ReleaseNetworkInterfaces(NetworkInterface * netifp) +{ + while (netifp) + { + NetworkInterface * del = netifp; + netifp = netifp->Next; + delete del; + } +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthPHYRate(app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum & pHYRate) +{ + if (ConnectivityMgrImpl().GetEthernetIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetEthPHYRate(ConnectivityMgrImpl().GetEthernetIfName(), pHYRate); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthFullDuplex(bool & fullDuplex) +{ + if (ConnectivityMgrImpl().GetEthernetIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetEthFullDuplex(ConnectivityMgrImpl().GetEthernetIfName(), fullDuplex); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthTimeSinceReset(uint64_t & timeSinceReset) +{ + return GetDiagnosticDataProvider().GetUpTime(timeSinceReset); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthPacketRxCount(uint64_t & packetRxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthPacketRxCount, count)); + VerifyOrReturnError(count >= mEthPacketRxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetRxCount = count - mEthPacketRxCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthPacketTxCount(uint64_t & packetTxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthPacketTxCount, count)); + VerifyOrReturnError(count >= mEthPacketTxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetTxCount = count - mEthPacketTxCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthTxErrCount(uint64_t & txErrCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthTxErrCount, count)); + VerifyOrReturnError(count >= mEthTxErrCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + txErrCount = count - mEthTxErrCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthCollisionCount(uint64_t & collisionCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthCollisionCount, count)); + VerifyOrReturnError(count >= mEthCollisionCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + collisionCount = count - mEthCollisionCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthOverrunCount(uint64_t & overrunCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthOverrunCount, count)); + VerifyOrReturnError(count >= mEthOverrunCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + overrunCount = count - mEthOverrunCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::ResetEthNetworkDiagnosticsCounts() +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kEthernet) + { + ChipLogProgress(DeviceLayer, "Found the primary Ethernet interface:%s", StringOrNullMarker(ifa->ifa_name)); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + + mEthPacketRxCount = stats->rx_packets; + mEthPacketTxCount = stats->tx_packets; + mEthTxErrCount = stats->tx_errors; + mEthCollisionCount = stats->collisions; + mEthOverrunCount = stats->rx_over_errors; + err = CHIP_NO_ERROR; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiChannelNumber(uint16_t & channelNumber) +{ + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetWiFiChannelNumber(ConnectivityMgrImpl().GetWiFiIfName(), channelNumber); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiRssi(int8_t & rssi) +{ + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetWiFiRssi(ConnectivityMgrImpl().GetWiFiIfName(), rssi); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiBeaconLostCount(uint32_t & beaconLostCount) +{ + uint32_t count; + + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + ReturnErrorOnFailure(ConnectivityUtils::GetWiFiBeaconLostCount(ConnectivityMgrImpl().GetWiFiIfName(), count)); + VerifyOrReturnError(count >= mBeaconLostCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + beaconLostCount = count - mBeaconLostCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiCurrentMaxRate(uint64_t & currentMaxRate) +{ + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetWiFiCurrentMaxRate(ConnectivityMgrImpl().GetWiFiIfName(), currentMaxRate); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketMulticastRxCount(uint32_t & packetMulticastRxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiMulticastPacketRxCount, count)); + VerifyOrReturnError(count >= mPacketMulticastRxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketMulticastRxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetMulticastRxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketMulticastTxCount(uint32_t & packetMulticastTxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiMulticastPacketTxCount, count)); + VerifyOrReturnError(count >= mPacketMulticastTxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketMulticastTxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetMulticastTxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketUnicastRxCount(uint32_t & packetUnicastRxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiUnicastPacketRxCount, count)); + VerifyOrReturnError(count >= mPacketUnicastRxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketUnicastRxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetUnicastRxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketUnicastTxCount(uint32_t & packetUnicastTxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiUnicastPacketTxCount, count)); + VerifyOrReturnError(count >= mPacketUnicastTxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketUnicastTxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetUnicastTxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiOverrunCount(uint64_t & overrunCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiOverrunCount, count)); + VerifyOrReturnError(count >= mOverrunCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + overrunCount = count - mOverrunCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::ResetWiFiNetworkDiagnosticsCounts() +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + ReturnErrorOnFailure(GetWiFiBeaconLostCount(mBeaconLostCount)); + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceTypeEnum::kWiFi) + { + ChipLogProgress(DeviceLayer, "Found the primary WiFi interface:%s", StringOrNullMarker(ifa->ifa_name)); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + + mPacketMulticastRxCount = stats->multicast; + mPacketMulticastTxCount = 0; + mPacketUnicastRxCount = stats->rx_packets; + mPacketUnicastTxCount = stats->tx_packets; + mOverrunCount = stats->rx_over_errors; + + err = CHIP_NO_ERROR; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiVersion(app::Clusters::WiFiNetworkDiagnostics::WiFiVersionEnum & wiFiVersion) +{ + return ConnectivityMgrImpl().GetWiFiVersion(wiFiVersion); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiBssId(MutableByteSpan & value) +{ + return ConnectivityMgrImpl().GetWiFiBssId(value); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiSecurityType(app::Clusters::WiFiNetworkDiagnostics::SecurityTypeEnum & securityType) +{ + return ConnectivityMgrImpl().GetWiFiSecurityType(securityType); +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +DiagnosticDataProvider & GetDiagnosticDataProviderImpl() +{ + return DiagnosticDataProviderImpl::GetDefaultInstance(); +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/DiagnosticDataProviderImpl.h b/src/platform/NuttX/DiagnosticDataProviderImpl.h new file mode 100644 index 00000000000000..427a1d1c8d8b3d --- /dev/null +++ b/src/platform/NuttX/DiagnosticDataProviderImpl.h @@ -0,0 +1,117 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the DiagnosticDataProvider object. + */ + +#pragma once + +#include + +#include + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the DiagnosticDataProvider singleton object for Linux platforms. + */ +class DiagnosticDataProviderImpl : public DiagnosticDataProvider +{ +public: + static DiagnosticDataProviderImpl & GetDefaultInstance(); + + // ===== Methods that implement the DiagnosticDataProvider abstract interface. + + bool SupportsWatermarks() override { return true; } + CHIP_ERROR GetCurrentHeapFree(uint64_t & currentHeapFree) override; + CHIP_ERROR GetCurrentHeapUsed(uint64_t & currentHeapUsed) override; + CHIP_ERROR GetCurrentHeapHighWatermark(uint64_t & currentHeapHighWatermark) override; + CHIP_ERROR GetThreadMetrics(ThreadMetrics ** threadMetricsOut) override; + CHIP_ERROR ResetWatermarks() override; + void ReleaseThreadMetrics(ThreadMetrics * threadMetrics) override; + + CHIP_ERROR GetRebootCount(uint16_t & rebootCount) override; + CHIP_ERROR GetUpTime(uint64_t & upTime) override; + CHIP_ERROR GetTotalOperationalHours(uint32_t & totalOperationalHours) override; + CHIP_ERROR GetBootReason(BootReasonType & bootReason) override; + + CHIP_ERROR GetActiveHardwareFaults(GeneralFaults & hardwareFaults) override; + CHIP_ERROR GetActiveRadioFaults(GeneralFaults & radioFaults) override; + CHIP_ERROR GetActiveNetworkFaults(GeneralFaults & networkFaults) override; + + CHIP_ERROR GetNetworkInterfaces(NetworkInterface ** netifpp) override; + void ReleaseNetworkInterfaces(NetworkInterface * netifp) override; + + CHIP_ERROR GetEthPHYRate(app::Clusters::EthernetNetworkDiagnostics::PHYRateEnum & pHYRate) override; + CHIP_ERROR GetEthFullDuplex(bool & fullDuplex) override; + CHIP_ERROR GetEthTimeSinceReset(uint64_t & timeSinceReset) override; + CHIP_ERROR GetEthPacketRxCount(uint64_t & packetRxCount) override; + CHIP_ERROR GetEthPacketTxCount(uint64_t & packetTxCount) override; + CHIP_ERROR GetEthTxErrCount(uint64_t & txErrCount) override; + CHIP_ERROR GetEthCollisionCount(uint64_t & collisionCount) override; + CHIP_ERROR GetEthOverrunCount(uint64_t & overrunCount) override; + CHIP_ERROR ResetEthNetworkDiagnosticsCounts() override; + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + CHIP_ERROR GetWiFiChannelNumber(uint16_t & channelNumber) override; + CHIP_ERROR GetWiFiRssi(int8_t & rssi) override; + CHIP_ERROR GetWiFiBeaconLostCount(uint32_t & beaconLostCount) override; + CHIP_ERROR GetWiFiPacketMulticastRxCount(uint32_t & packetMulticastRxCount) override; + CHIP_ERROR GetWiFiPacketMulticastTxCount(uint32_t & packetMulticastTxCount) override; + CHIP_ERROR GetWiFiPacketUnicastRxCount(uint32_t & packetUnicastRxCount) override; + CHIP_ERROR GetWiFiPacketUnicastTxCount(uint32_t & packetUnicastTxCount) override; + CHIP_ERROR GetWiFiCurrentMaxRate(uint64_t & currentMaxRate) override; + CHIP_ERROR GetWiFiOverrunCount(uint64_t & overrunCount) override; + CHIP_ERROR ResetWiFiNetworkDiagnosticsCounts() override; +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + CHIP_ERROR GetWiFiVersion(app::Clusters::WiFiNetworkDiagnostics::WiFiVersionEnum & wiFiVersion) override; + CHIP_ERROR GetWiFiBssId(MutableByteSpan & value) override; + CHIP_ERROR GetWiFiSecurityType(app::Clusters::WiFiNetworkDiagnostics::SecurityTypeEnum & securityType) override; +#endif + +private: + uint64_t mEthPacketRxCount = 0; + uint64_t mEthPacketTxCount = 0; + uint64_t mEthTxErrCount = 0; + uint64_t mEthCollisionCount = 0; + uint64_t mEthOverrunCount = 0; + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + uint32_t mBeaconLostCount = 0; + uint32_t mPacketMulticastRxCount = 0; + uint32_t mPacketMulticastTxCount = 0; + uint32_t mPacketUnicastRxCount = 0; + uint32_t mPacketUnicastTxCount = 0; + uint64_t mOverrunCount = 0; +#endif +}; + +/** + * Returns the platform-specific implementation of the DiagnosticDataProvider singleton object. + * + * Applications can use this to gain access to features of the DiagnosticDataProvider + * that are specific to the selected platform. + */ +DiagnosticDataProvider & GetDiagnosticDataProviderImpl(); + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/DnssdImpl.cpp b/src/platform/NuttX/DnssdImpl.cpp new file mode 100644 index 00000000000000..2a1873072fbb9e --- /dev/null +++ b/src/platform/NuttX/DnssdImpl.cpp @@ -0,0 +1,1061 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DnssdImpl.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using chip::Dnssd::DnssdServiceProtocol; +using chip::Dnssd::kDnssdTypeMaxSize; +using chip::Dnssd::TextEntry; +using chip::System::SocketEvents; +using std::chrono::duration_cast; +using std::chrono::microseconds; +using std::chrono::seconds; +using std::chrono::steady_clock; + +namespace { + +AvahiProtocol ToAvahiProtocol(chip::Inet::IPAddressType addressType) +{ +#if INET_CONFIG_ENABLE_IPV4 + AvahiProtocol protocol; + + switch (addressType) + { + case chip::Inet::IPAddressType::kIPv4: + protocol = AVAHI_PROTO_INET; + break; + case chip::Inet::IPAddressType::kIPv6: + protocol = AVAHI_PROTO_INET6; + break; + default: + protocol = AVAHI_PROTO_UNSPEC; + break; + } + + return protocol; +#else + // We only support IPV6, never tell AVAHI about INET4 or UNSPEC because + // UNSPEC may actually return IPv4 data. + return AVAHI_PROTO_INET6; +#endif +} + +chip::Inet::IPAddressType ToAddressType(AvahiProtocol protocol) +{ + chip::Inet::IPAddressType type; + + switch (protocol) + { +#if INET_CONFIG_ENABLE_IPV4 + case AVAHI_PROTO_INET: + type = chip::Inet::IPAddressType::kIPv4; + break; +#endif + case AVAHI_PROTO_INET6: + type = chip::Inet::IPAddressType::kIPv6; + break; + default: + type = chip::Inet::IPAddressType::kUnknown; + break; + } + + return type; +} + +AvahiWatchEvent ToAvahiWatchEvent(SocketEvents events) +{ + return static_cast((events.Has(chip::System::SocketEventFlags::kRead) ? AVAHI_WATCH_IN : 0) | + (events.Has(chip::System::SocketEventFlags::kWrite) ? AVAHI_WATCH_OUT : 0) | + (events.Has(chip::System::SocketEventFlags::kError) ? AVAHI_WATCH_ERR : 0)); +} + +void AvahiWatchCallbackTrampoline(chip::System::SocketEvents events, intptr_t data) +{ + AvahiWatch * const watch = reinterpret_cast(data); + watch->mPendingIO = ToAvahiWatchEvent(events); + watch->mCallback(watch, watch->mSocket, watch->mPendingIO, watch->mContext); +} + +CHIP_ERROR MakeAvahiStringListFromTextEntries(TextEntry * entries, size_t size, AvahiStringList ** strListOut) +{ + *strListOut = avahi_string_list_new(nullptr, nullptr); + + for (size_t i = 0; i < size; i++) + { + uint8_t buf[chip::Dnssd::kDnssdTextMaxSize]; + size_t offset = static_cast(snprintf(reinterpret_cast(buf), sizeof(buf), "%s=", entries[i].mKey)); + + if (offset + entries[i].mDataSize > sizeof(buf)) + { + avahi_string_list_free(*strListOut); + *strListOut = nullptr; + return CHIP_ERROR_INVALID_ARGUMENT; + } + + memcpy(&buf[offset], entries[i].mData, entries[i].mDataSize); + *strListOut = avahi_string_list_add_arbitrary(*strListOut, buf, offset + entries[i].mDataSize); + } + return CHIP_NO_ERROR; +} + +const char * GetProtocolString(DnssdServiceProtocol protocol) +{ + return protocol == DnssdServiceProtocol::kDnssdProtocolUdp ? "_udp" : "_tcp"; +} + +std::string GetFullType(const char * type, DnssdServiceProtocol protocol) +{ + std::ostringstream typeBuilder; + typeBuilder << type << "." << GetProtocolString(protocol); + return typeBuilder.str(); +} + +} // namespace + +namespace chip { +namespace Dnssd { + +MdnsAvahi MdnsAvahi::sInstance; + +Poller::Poller() +{ + mAvahiPoller.userdata = this; + mAvahiPoller.watch_new = WatchNew; + mAvahiPoller.watch_update = WatchUpdate; + mAvahiPoller.watch_get_events = WatchGetEvents; + mAvahiPoller.watch_free = WatchFree; + + mAvahiPoller.timeout_new = TimeoutNew; + mAvahiPoller.timeout_update = TimeoutUpdate; + mAvahiPoller.timeout_free = TimeoutFree; + + mEarliestTimeout = std::chrono::steady_clock::time_point(); +} + +AvahiWatch * Poller::WatchNew(const struct AvahiPoll * poller, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, + void * context) +{ + return reinterpret_cast(poller->userdata)->WatchNew(fd, event, callback, context); +} + +AvahiWatch * Poller::WatchNew(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void * context) +{ + VerifyOrDie(callback != nullptr && fd >= 0); + + auto watch = std::make_unique(); + watch->mSocket = fd; + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().StartWatchingSocket(fd, &watch->mSocketWatch)); + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().SetCallback(watch->mSocketWatch, AvahiWatchCallbackTrampoline, + reinterpret_cast(watch.get()))); + WatchUpdate(watch.get(), event); + watch->mCallback = callback; + watch->mContext = context; + watch->mPoller = this; + mWatches.emplace_back(std::move(watch)); + + return mWatches.back().get(); +} + +void Poller::WatchUpdate(AvahiWatch * watch, AvahiWatchEvent event) +{ + if (event & AVAHI_WATCH_IN) + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().RequestCallbackOnPendingRead(watch->mSocketWatch)); + } + else + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().ClearCallbackOnPendingRead(watch->mSocketWatch)); + } + if (event & AVAHI_WATCH_OUT) + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().RequestCallbackOnPendingWrite(watch->mSocketWatch)); + } + else + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().ClearCallbackOnPendingWrite(watch->mSocketWatch)); + } +} + +AvahiWatchEvent Poller::WatchGetEvents(AvahiWatch * watch) +{ + return watch->mPendingIO; +} + +void Poller::WatchFree(AvahiWatch * watch) +{ + reinterpret_cast(watch->mPoller)->WatchFree(*watch); +} + +void Poller::WatchFree(AvahiWatch & watch) +{ + DeviceLayer::SystemLayerSockets().StopWatchingSocket(&watch.mSocketWatch); + mWatches.erase(std::remove_if(mWatches.begin(), mWatches.end(), + [&watch](const std::unique_ptr & aValue) { return aValue.get() == &watch; }), + mWatches.end()); +} + +AvahiTimeout * Poller::TimeoutNew(const AvahiPoll * poller, const struct timeval * timeout, AvahiTimeoutCallback callback, + void * context) +{ + VerifyOrDie(poller != nullptr && callback != nullptr); + + return static_cast(poller->userdata)->TimeoutNew(timeout, callback, context); +} + +steady_clock::time_point GetAbsTimeout(const struct timeval * timeout) +{ + steady_clock::time_point now = steady_clock::now(); + steady_clock::time_point absTimeout = now; + + if (timeout != nullptr) + { + absTimeout += seconds(timeout->tv_sec); + absTimeout += microseconds(timeout->tv_usec); + } + + return absTimeout; +} + +AvahiTimeout * Poller::TimeoutNew(const struct timeval * timeout, AvahiTimeoutCallback callback, void * context) +{ + mTimers.emplace_back(new AvahiTimeout{ GetAbsTimeout(timeout), callback, timeout != nullptr, context, this }); + AvahiTimeout * timer = mTimers.back().get(); + SystemTimerUpdate(timer); + return timer; +} + +void Poller::TimeoutUpdate(AvahiTimeout * timer, const struct timeval * timeout) +{ + if (timeout) + { + timer->mAbsTimeout = GetAbsTimeout(timeout); + timer->mEnabled = true; + static_cast(timer->mPoller)->SystemTimerUpdate(timer); + } + else + { + timer->mEnabled = false; + } +} + +void Poller::TimeoutFree(AvahiTimeout * timer) +{ + static_cast(timer->mPoller)->TimeoutFree(*timer); +} + +void Poller::TimeoutFree(AvahiTimeout & timer) +{ + mTimers.erase(std::remove_if(mTimers.begin(), mTimers.end(), + [&timer](const std::unique_ptr & aValue) { return aValue.get() == &timer; }), + mTimers.end()); +} + +void Poller::SystemTimerCallback(System::Layer * layer, void * data) +{ + static_cast(data)->HandleTimeout(); +} + +void Poller::HandleTimeout() +{ + mEarliestTimeout = std::chrono::steady_clock::time_point(); + steady_clock::time_point now = steady_clock::now(); + + AvahiTimeout * earliest = nullptr; + for (auto && timer : mTimers) + { + if (!timer->mEnabled) + { + continue; + } + if (timer->mAbsTimeout <= now) + { + timer->mCallback(timer.get(), timer->mContext); + } + else + { + if ((earliest == nullptr) || (timer->mAbsTimeout < earliest->mAbsTimeout)) + { + earliest = timer.get(); + } + } + } + if (earliest) + { + SystemTimerUpdate(earliest); + } +} + +void Poller::SystemTimerUpdate(AvahiTimeout * timer) +{ + if ((mEarliestTimeout == std::chrono::steady_clock::time_point()) || (timer->mAbsTimeout < mEarliestTimeout)) + { + mEarliestTimeout = timer->mAbsTimeout; + auto delay = std::chrono::duration_cast(steady_clock::now() - mEarliestTimeout); + DeviceLayer::SystemLayer().StartTimer(delay, SystemTimerCallback, this); + } +} + +CHIP_ERROR MdnsAvahi::Init(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + int avahiError = 0; + + Shutdown(); + + VerifyOrExit(initCallback != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(errorCallback != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(mClient == nullptr && mPublishedGroups.empty(), error = CHIP_ERROR_INCORRECT_STATE); + mInitCallback = initCallback; + mErrorCallback = errorCallback; + mAsyncReturnContext = context; + mClient = avahi_client_new(mPoller.GetAvahiPoll(), AVAHI_CLIENT_NO_FAIL, HandleClientState, this, &avahiError); + VerifyOrExit(mClient != nullptr, error = CHIP_ERROR_OPEN_FAILED); + VerifyOrExit(avahiError == 0, error = CHIP_ERROR_OPEN_FAILED); + +exit: + return error; +} + +void MdnsAvahi::Shutdown() +{ + StopPublish(); + if (mClient) + { + avahi_client_free(mClient); + mClient = nullptr; + } +} + +CHIP_ERROR MdnsAvahi::SetHostname(const char * hostname) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + + VerifyOrExit(mClient != nullptr, error = CHIP_ERROR_INCORRECT_STATE); + // Note: we do no longer set the primary hostname here, as other services + // on the platform might not be happy with the matter mandated hostname. + // Instead, we'll establish our own hostname when needed (see PublishService()) +exit: + return error; +} + +void MdnsAvahi::HandleClientState(AvahiClient * client, AvahiClientState state, void * context) +{ + static_cast(context)->HandleClientState(client, state); +} + +void MdnsAvahi::HandleClientState(AvahiClient * client, AvahiClientState state) +{ + switch (state) + { + case AVAHI_CLIENT_S_RUNNING: + ChipLogProgress(DeviceLayer, "Avahi client registered"); + mClient = client; + // no longer create groups here, but on a by-service basis in PublishService() + mInitCallback(mAsyncReturnContext, CHIP_NO_ERROR); + break; + case AVAHI_CLIENT_FAILURE: + ChipLogError(DeviceLayer, "Avahi client failure"); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_INTERNAL); + break; + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_REGISTERING: + ChipLogProgress(DeviceLayer, "Avahi re-register required"); + StopPublish(); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_FORCED_RESET); + break; + case AVAHI_CLIENT_CONNECTING: + ChipLogProgress(DeviceLayer, "Avahi connecting"); + break; + } +} + +void MdnsAvahi::HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state, void * context) +{ + static_cast(context)->HandleGroupState(group, state); +} + +void MdnsAvahi::HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state) +{ + switch (state) + { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + ChipLogProgress(DeviceLayer, "Avahi group established"); + break; + case AVAHI_ENTRY_GROUP_COLLISION: + ChipLogError(DeviceLayer, "Avahi group collision"); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_MDNS_COLLISION); + break; + case AVAHI_ENTRY_GROUP_FAILURE: + ChipLogError(DeviceLayer, "Avahi group internal failure %s", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(group)))); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_INTERNAL); + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } +} + +CHIP_ERROR MdnsAvahi::PublishService(const DnssdService & service, DnssdPublishCallback callback, void * context) +{ + std::ostringstream keyBuilder; + std::string key; + std::string type = GetFullType(service.mType, service.mProtocol); + std::string matterHostname; + CHIP_ERROR error = CHIP_NO_ERROR; + AvahiStringList * text = nullptr; + AvahiEntryGroup * group = nullptr; + const char * mainHostname = nullptr; + AvahiIfIndex interface = + service.mInterface.IsPresent() ? static_cast(service.mInterface.GetPlatformInterface()) : AVAHI_IF_UNSPEC; + AvahiProtocol protocol = ToAvahiProtocol(service.mAddressType); + + keyBuilder << service.mName << "." << type << service.mPort << "." << interface; + key = keyBuilder.str(); + ChipLogProgress(DeviceLayer, "PublishService %s", key.c_str()); + auto publishedgroups_it = mPublishedGroups.find(key); + if (publishedgroups_it != mPublishedGroups.end()) + { + // same service was already published, we need to de-publish it first + int avahiRet = avahi_entry_group_free(publishedgroups_it->second); + if (avahiRet != AVAHI_OK) + { + ChipLogError(DeviceLayer, "Cannot remove avahi group: %s", avahi_strerror(avahiRet)); + ExitNow(error = CHIP_ERROR_INTERNAL); + } + mPublishedGroups.erase(publishedgroups_it); + } + + // create fresh group + group = avahi_entry_group_new(mClient, HandleGroupState, this); + VerifyOrExit(group != nullptr, error = CHIP_ERROR_INTERNAL); + + // establish the host name (separately from avahi's default host name that the platform might have, + // unless it matches the matter hostname) + mainHostname = avahi_client_get_host_name(mClient); + if (strcmp(mainHostname, service.mHostName) == 0) + { + // main host name is correct, we can use it + matterHostname = std::string(mainHostname) + ".local"; + } + else + { + // we need to establish a matter hostname separately from the platform's default hostname + char b[chip::Inet::IPAddress::kMaxStringLength]; + SuccessOrExit(error = service.mInterface.GetInterfaceName(b, chip::Inet::IPAddress::kMaxStringLength)); + ChipLogDetail(DeviceLayer, "Using addresses from interface id=%d name=%s", service.mInterface.GetPlatformInterface(), b); + matterHostname = std::string(service.mHostName) + ".local"; + // find addresses to publish + for (chip::Inet::InterfaceAddressIterator addr_it; addr_it.HasCurrent(); addr_it.Next()) + { + // only specific interface? + if (service.mInterface.IsPresent() && addr_it.GetInterfaceId() != service.mInterface) + { + continue; + } + if (addr_it.IsUp()) + { + if (addr_it.IsLoopback()) + { + // do not advertise loopback interface addresses + continue; + } + chip::Inet::IPAddress addr; + if ((addr_it.GetAddress(addr) == CHIP_NO_ERROR) && + ((service.mAddressType == chip::Inet::IPAddressType::kAny) || + (addr.IsIPv6() && service.mAddressType == chip::Inet::IPAddressType::kIPv6) +#if INET_CONFIG_ENABLE_IPV4 + || (addr.IsIPv4() && service.mAddressType == chip::Inet::IPAddressType::kIPv4) +#endif + )) + { + VerifyOrExit(addr.ToString(b) != nullptr, error = CHIP_ERROR_INTERNAL); + AvahiAddress a; + VerifyOrExit(avahi_address_parse(b, AVAHI_PROTO_UNSPEC, &a) != nullptr, error = CHIP_ERROR_INTERNAL); + AvahiIfIndex thisinterface = static_cast(addr_it.GetInterfaceId().GetPlatformInterface()); + // Note: NO_REVERSE publish flag is needed because otherwise we can't have more than one hostname + // for reverse resolving IP addresses back to hostnames + VerifyOrExit(avahi_entry_group_add_address(group, // group + thisinterface, // interface + ToAvahiProtocol(addr.Type()), // protocol + AVAHI_PUBLISH_NO_REVERSE, // publish flags + matterHostname.c_str(), // hostname + &a // address + ) == 0, + error = CHIP_ERROR_INTERNAL); + } + } + } + } + + // create the service + SuccessOrExit(error = MakeAvahiStringListFromTextEntries(service.mTextEntries, service.mTextEntrySize, &text)); + + VerifyOrExit(avahi_entry_group_add_service_strlst(group, interface, protocol, // group, interface, protocol + static_cast(0), // publish flags + service.mName, // service name + type.c_str(), // type + nullptr, // domain + matterHostname.c_str(), // host + service.mPort, // port + text) == 0, // TXT records StringList + error = CHIP_ERROR_INTERNAL); + + // add the subtypes + for (size_t i = 0; i < service.mSubTypeSize; i++) + { + std::ostringstream sstream; + + sstream << service.mSubTypes[i] << "._sub." << type; + + VerifyOrExit(avahi_entry_group_add_service_subtype(group, interface, protocol, static_cast(0), + service.mName, type.c_str(), nullptr, sstream.str().c_str()) == 0, + error = CHIP_ERROR_INTERNAL); + } + VerifyOrExit(avahi_entry_group_commit(group) == 0, error = CHIP_ERROR_INTERNAL); + + // group is now published, pass it to the service map + mPublishedGroups[key] = group; + group = nullptr; + +exit: + if (group != nullptr) + { + avahi_entry_group_free(group); + } + + if (text != nullptr) + { + avahi_string_list_free(text); + } + + // Ideally the callback would be called from `HandleGroupState` when the `AVAHI_ENTRY_GROUP_ESTABLISHED` state + // is received. But the current code use the `userdata` field to pass a pointer to the current MdnsAvahi instance + // and this is all comes from MdnsAvahi::Init that does not have any clue about the `type` that *will* be published. + // The code needs to be updated to support that callback properly. + if (CHIP_NO_ERROR == error) + { + callback(context, type.c_str(), service.mName, CHIP_NO_ERROR); + } + else + { + ChipLogError(DeviceLayer, "PublishService failed: %s", + mClient ? avahi_strerror(avahi_client_errno(mClient)) : "no mClient"); + callback(context, nullptr, nullptr, error); + } + + return error; +} + +CHIP_ERROR MdnsAvahi::StopPublish() +{ + CHIP_ERROR error = CHIP_NO_ERROR; + for (const auto & group : mPublishedGroups) + { + if (group.second) + { + int avahiRet = avahi_entry_group_free(group.second); + if (avahiRet != AVAHI_OK) + { + ChipLogError(DeviceLayer, "Error freeing avahi group: %s", avahi_strerror(avahiRet)); + error = CHIP_ERROR_INTERNAL; + } + } + } + mPublishedGroups.clear(); + return error; +} + +CHIP_ERROR MdnsAvahi::Browse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context, + intptr_t * browseIdentifier) +{ + AvahiServiceBrowser * browser; + BrowseContext * browseContext = chip::Platform::New(); + AvahiIfIndex avahiInterface = static_cast(interface.GetPlatformInterface()); + + browseContext->mInstance = this; + browseContext->mContext = context; + browseContext->mCallback = callback; + browseContext->mAddressType = addressType; + if (!interface.IsPresent()) + { + avahiInterface = AVAHI_IF_UNSPEC; + } + browseContext->mInterface = avahiInterface; + browseContext->mProtocol = GetFullType(type, protocol); + browseContext->mBrowseRetries = 0; + browseContext->mStopped.store(false); + + browser = avahi_service_browser_new(mClient, avahiInterface, AVAHI_PROTO_UNSPEC, browseContext->mProtocol.c_str(), nullptr, + static_cast(0), HandleBrowse, browseContext); + // Otherwise the browser will be freed in the callback + if (browser == nullptr) + { + chip::Platform::Delete(browseContext); + *browseIdentifier = reinterpret_cast(nullptr); + } + else + { + *browseIdentifier = reinterpret_cast(browseContext); + } + + return browser == nullptr ? CHIP_ERROR_INTERNAL : CHIP_NO_ERROR; +} + +CHIP_ERROR MdnsAvahi::StopBrowse(intptr_t browseIdentifier) +{ + BrowseContext * browseContext = reinterpret_cast(browseIdentifier); + if (browseContext == nullptr) + { + return CHIP_ERROR_NOT_FOUND; + } + // Any running timers here will check mStopped before rescheduling. Leave the timer running + // so we don't race on deletion of the browse context. + browseContext->mStopped.store(true); + return CHIP_NO_ERROR; +} + +DnssdServiceProtocol GetProtocolInType(const char * type) +{ + const char * deliminator = strrchr(type, '.'); + + if (deliminator == nullptr) + { + ChipLogError(Discovery, "Failed to find protocol in type: %s", StringOrNullMarker(type)); + return DnssdServiceProtocol::kDnssdProtocolUnknown; + } + + if (strcmp("._tcp", deliminator) == 0) + { + return DnssdServiceProtocol::kDnssdProtocolTcp; + } + if (strcmp("._udp", deliminator) == 0) + { + return DnssdServiceProtocol::kDnssdProtocolUdp; + } + + ChipLogError(Discovery, "Unknown protocol in type: %s", StringOrNullMarker(type)); + return DnssdServiceProtocol::kDnssdProtocolUnknown; +} + +/// Copies the type from a string containing both type and protocol +/// +/// e.g. if input is "foo.bar", output is "foo", input is 'a.b._tcp", output is "a.b" +template +void CopyTypeWithoutProtocol(char (&dest)[N], const char * typeAndProtocol) +{ + const char * dotPos = strrchr(typeAndProtocol, '.'); + size_t lengthWithoutProtocol = (dotPos != nullptr) ? static_cast(dotPos - typeAndProtocol) : N; + + Platform::CopyString(dest, typeAndProtocol); + + /// above copied everything including the protocol. Truncate the protocol away. + if (lengthWithoutProtocol < N) + { + dest[lengthWithoutProtocol] = 0; + } +} + +void MdnsAvahi::BrowseRetryCallback(chip::System::Layer * aLayer, void * appState) +{ + BrowseContext * context = static_cast(appState); + // Don't schedule anything new if we've stopped. + if (context->mStopped.load()) + { + chip::Platform::Delete(context); + return; + } + AvahiServiceBrowser * newBrowser = + avahi_service_browser_new(context->mInstance->mClient, context->mInterface, AVAHI_PROTO_UNSPEC, context->mProtocol.c_str(), + nullptr, static_cast(0), HandleBrowse, context); + if (newBrowser == nullptr) + { + // If we failed to create the browser, this browse context is effectively done. We need to call the final callback and + // delete the context. + context->mCallback(context->mContext, context->mServices.data(), context->mServices.size(), true, CHIP_NO_ERROR); + chip::Platform::Delete(context); + } +} + +void MdnsAvahi::HandleBrowse(AvahiServiceBrowser * browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char * name, const char * type, const char * domain, AvahiLookupResultFlags /*flags*/, + void * userdata) +{ + BrowseContext * context = static_cast(userdata); + + switch (event) + { + case AVAHI_BROWSER_FAILURE: + context->mCallback(context->mContext, nullptr, 0, true, CHIP_ERROR_INTERNAL); + avahi_service_browser_free(browser); + chip::Platform::Delete(context); + break; + case AVAHI_BROWSER_NEW: + ChipLogProgress(DeviceLayer, "Avahi browse: cache new"); + if (strcmp("local", domain) == 0) + { + DnssdService service = {}; + + Platform::CopyString(service.mName, name); + CopyTypeWithoutProtocol(service.mType, type); + service.mProtocol = GetProtocolInType(type); + service.mAddressType = context->mAddressType; + service.mTransportType = ToAddressType(protocol); + service.mInterface = Inet::InterfaceId::Null(); + if (interface != AVAHI_IF_UNSPEC) + { + service.mInterface = static_cast(interface); + } + service.mType[kDnssdTypeMaxSize] = 0; + context->mServices.push_back(service); + } + break; + case AVAHI_BROWSER_ALL_FOR_NOW: { + ChipLogProgress(DeviceLayer, "Avahi browse: all for now"); + bool needRetries = context->mBrowseRetries++ < kMaxBrowseRetries && !context->mStopped.load(); + // If we were already asked to stop, no need to send a callback - no one is listening. + if (!context->mStopped.load()) + { + context->mCallback(context->mContext, context->mServices.data(), context->mServices.size(), !needRetries, + CHIP_NO_ERROR); + } + avahi_service_browser_free(browser); + if (needRetries) + { + context->mNextRetryDelay *= 2; + // Hand the ownership of the context over to the timer. It will either schedule a new browse on the context, + // triggering this function, or it will delete and not reschedule (if stopped). + DeviceLayer::SystemLayer().StartTimer(context->mNextRetryDelay / 2, BrowseRetryCallback, context); + } + else + { + // We didn't schedule a timer, so we're responsible for deleting the context + chip::Platform::Delete(context); + } + break; + } + case AVAHI_BROWSER_REMOVE: + ChipLogProgress(DeviceLayer, "Avahi browse: remove"); + if (strcmp("local", domain) == 0) + { + context->mServices.erase( + std::remove_if(context->mServices.begin(), context->mServices.end(), [name, type](const DnssdService & service) { + return strcmp(name, service.mName) == 0 && type == GetFullType(service.mType, service.mProtocol); + })); + } + break; + case AVAHI_BROWSER_CACHE_EXHAUSTED: + ChipLogProgress(DeviceLayer, "Avahi browse: cache exhausted"); + break; + } +} + +MdnsAvahi::ResolveContext * MdnsAvahi::AllocateResolveContext() +{ + ResolveContext * context = chip::Platform::New(); + if (context == nullptr) + { + return nullptr; + } + + context->mNumber = mResolveCount++; + mAllocatedResolves.push_back(context); + + return context; +} + +MdnsAvahi::ResolveContext * MdnsAvahi::ResolveContextForHandle(size_t handle) +{ + for (auto it : mAllocatedResolves) + { + if (it->mNumber == handle) + { + return it; + } + } + return nullptr; +} + +void MdnsAvahi::FreeResolveContext(size_t handle) +{ + for (auto it = mAllocatedResolves.begin(); it != mAllocatedResolves.end(); it++) + { + if ((*it)->mNumber == handle) + { + chip::Platform::Delete(*it); + mAllocatedResolves.erase(it); + return; + } + } +} + +void MdnsAvahi::StopResolve(const char * name) +{ + auto truncate_end = std::remove_if(mAllocatedResolves.begin(), mAllocatedResolves.end(), + [name](ResolveContext * ctx) { return strcmp(ctx->mName, name) == 0; }); + + for (auto it = truncate_end; it != mAllocatedResolves.end(); it++) + { + (*it)->mCallback((*it)->mContext, nullptr, Span(), CHIP_ERROR_CANCELLED); + chip::Platform::Delete(*it); + } + + mAllocatedResolves.erase(truncate_end, mAllocatedResolves.end()); +} + +CHIP_ERROR MdnsAvahi::Resolve(const char * name, const char * type, DnssdServiceProtocol protocol, Inet::IPAddressType addressType, + Inet::IPAddressType transportType, Inet::InterfaceId interface, DnssdResolveCallback callback, + void * context) +{ + AvahiIfIndex avahiInterface = static_cast(interface.GetPlatformInterface()); + ResolveContext * resolveContext = AllocateResolveContext(); + CHIP_ERROR error = CHIP_NO_ERROR; + resolveContext->mInstance = this; + resolveContext->mCallback = callback; + resolveContext->mContext = context; + + if (!interface.IsPresent()) + { + avahiInterface = AVAHI_IF_UNSPEC; + } + + Platform::CopyString(resolveContext->mName, name); + resolveContext->mInterface = avahiInterface; + resolveContext->mTransport = ToAvahiProtocol(transportType); + resolveContext->mAddressType = ToAvahiProtocol(addressType); + resolveContext->mFullType = GetFullType(type, protocol); + + AvahiServiceResolver * resolver = + avahi_service_resolver_new(mClient, avahiInterface, resolveContext->mTransport, name, resolveContext->mFullType.c_str(), + nullptr, resolveContext->mAddressType, static_cast(0), HandleResolve, + reinterpret_cast(resolveContext->mNumber)); + // Otherwise the resolver will be freed in the callback + if (resolver == nullptr) + { + error = CHIP_ERROR_INTERNAL; + chip::Platform::Delete(resolveContext); + } + resolveContext->mResolver = resolver; + + return error; +} + +void MdnsAvahi::HandleResolve(AvahiServiceResolver * resolver, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, const char * name, const char * type, const char * /*domain*/, + const char * host_name, const AvahiAddress * address, uint16_t port, AvahiStringList * txt, + AvahiLookupResultFlags flags, void * userdata) +{ + size_t handle = reinterpret_cast(userdata); + ResolveContext * context = sInstance.ResolveContextForHandle(handle); + std::vector textEntries; + + if (context == nullptr) + { + ChipLogError(Discovery, "Invalid context for handling resolves: %ld", static_cast(handle)); + return; + } + + switch (event) + { + case AVAHI_RESOLVER_FAILURE: + if (context->mAttempts++ < 3) + { + ChipLogProgress(DeviceLayer, "Re-trying resolve"); + avahi_service_resolver_free(resolver); + context->mResolver = avahi_service_resolver_new( + context->mInstance->mClient, context->mInterface, context->mTransport, context->mName, context->mFullType.c_str(), + nullptr, context->mAddressType, static_cast(0), HandleResolve, userdata); + if (context->mResolver == nullptr) + { + ChipLogError(DeviceLayer, "Avahi resolve failed on retry"); + context->mCallback(context->mContext, nullptr, Span(), CHIP_ERROR_INTERNAL); + sInstance.FreeResolveContext(handle); + } + return; + } + ChipLogError(DeviceLayer, "Avahi resolve failed"); + context->mCallback(context->mContext, nullptr, Span(), CHIP_ERROR_INTERNAL); + break; + case AVAHI_RESOLVER_FOUND: + DnssdService result = {}; + + ChipLogProgress(DeviceLayer, "Avahi resolve found"); + + Platform::CopyString(result.mName, name); + CopyTypeWithoutProtocol(result.mType, type); + result.mProtocol = GetProtocolInType(type); + result.mPort = port; + result.mAddressType = ToAddressType(protocol); + result.mInterface = Inet::InterfaceId::Null(); + // It's not clear if we can get the actual value from avahi, so just assume default. + result.mTtlSeconds = AVAHI_DEFAULT_TTL_HOST_NAME; + if (interface != AVAHI_IF_UNSPEC) + { + result.mInterface = static_cast(interface); + } + Platform::CopyString(result.mHostName, host_name); + // Returned value is full QName, want only host part. + char * dot = strchr(result.mHostName, '.'); + if (dot != nullptr) + { + *dot = '\0'; + } + + CHIP_ERROR result_err = CHIP_ERROR_INVALID_ADDRESS; + chip::Inet::IPAddress ipAddress; // Will be set of result_err is set to CHIP_NO_ERROR + if (address) + { + switch (address->proto) + { + case AVAHI_PROTO_INET: +#if INET_CONFIG_ENABLE_IPV4 + struct in_addr addr4; + + memcpy(&addr4, &(address->data.ipv4), sizeof(addr4)); + ipAddress = chip::Inet::IPAddress(addr4); + result_err = CHIP_NO_ERROR; +#else + ChipLogError(Discovery, "Ignoring IPv4 mDNS address."); +#endif + break; + case AVAHI_PROTO_INET6: + struct in6_addr addr6; + + memcpy(&addr6, &(address->data.ipv6), sizeof(addr6)); + ipAddress = chip::Inet::IPAddress(addr6); + result_err = CHIP_NO_ERROR; + break; + default: + break; + } + } + + while (txt != nullptr) + { + for (size_t i = 0; i < txt->size; i++) + { + if (txt->text[i] == '=') + { + txt->text[i] = '\0'; + textEntries.push_back(TextEntry{ reinterpret_cast(txt->text), &txt->text[i + 1], txt->size - i - 1 }); + break; + } + } + txt = txt->next; + } + + if (!textEntries.empty()) + { + result.mTextEntries = textEntries.data(); + } + result.mTextEntrySize = textEntries.size(); + + if (result_err == CHIP_NO_ERROR) + { + context->mCallback(context->mContext, &result, Span(&ipAddress, 1), CHIP_NO_ERROR); + } + else + { + context->mCallback(context->mContext, nullptr, Span(), result_err); + } + break; + } + + sInstance.FreeResolveContext(handle); +} + +CHIP_ERROR ChipDnssdInit(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context) +{ + return MdnsAvahi::GetInstance().Init(initCallback, errorCallback, context); +} + +void ChipDnssdShutdown() +{ + MdnsAvahi::GetInstance().Shutdown(); +} + +CHIP_ERROR ChipDnssdPublishService(const DnssdService * service, DnssdPublishCallback callback, void * context) +{ + VerifyOrReturnError(service != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(callback != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + if (strcmp(service->mHostName, "") != 0) + { + ReturnErrorOnFailure(MdnsAvahi::GetInstance().SetHostname(service->mHostName)); + } + + return MdnsAvahi::GetInstance().PublishService(*service, callback, context); +} + +CHIP_ERROR ChipDnssdRemoveServices() +{ + return MdnsAvahi::GetInstance().StopPublish(); +} + +CHIP_ERROR ChipDnssdFinalizeServiceUpdate() +{ + return CHIP_NO_ERROR; +} + +CHIP_ERROR ChipDnssdBrowse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context, + intptr_t * browseIdentifier) +{ + return MdnsAvahi::GetInstance().Browse(type, protocol, addressType, interface, callback, context, browseIdentifier); +} + +CHIP_ERROR ChipDnssdStopBrowse(intptr_t browseIdentifier) +{ + return MdnsAvahi::GetInstance().StopBrowse(browseIdentifier); +} + +CHIP_ERROR ChipDnssdResolve(DnssdService * browseResult, chip::Inet::InterfaceId interface, DnssdResolveCallback callback, + void * context) + +{ + VerifyOrReturnError(browseResult != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + return MdnsAvahi::GetInstance().Resolve(browseResult->mName, browseResult->mType, browseResult->mProtocol, + browseResult->mAddressType, Inet::IPAddressType::kAny, interface, callback, context); +} + +void ChipDnssdResolveNoLongerNeeded(const char * instanceName) +{ + MdnsAvahi::GetInstance().StopResolve(instanceName); +} + +CHIP_ERROR ChipDnssdReconfirmRecord(const char * hostname, chip::Inet::IPAddress address, chip::Inet::InterfaceId interface) +{ + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +} // namespace Dnssd +} // namespace chip diff --git a/src/platform/NuttX/DnssdImpl.h b/src/platform/NuttX/DnssdImpl.h new file mode 100644 index 00000000000000..c66a8c23f700b6 --- /dev/null +++ b/src/platform/NuttX/DnssdImpl.h @@ -0,0 +1,205 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct AvahiWatch +{ + int mSocket; + chip::System::SocketWatchToken mSocketWatch; + AvahiWatchCallback mCallback; ///< The function to be called when interested events happened on mFd. + AvahiWatchEvent mPendingIO; ///< The pending events from the currently active or most recent callback. + void * mContext; ///< A pointer to application-specific context. + void * mPoller; ///< The poller created this watch. +}; + +struct AvahiTimeout +{ + std::chrono::steady_clock::time_point mAbsTimeout; ///< Absolute time when this timer timeout. + AvahiTimeoutCallback mCallback; ///< The function to be called when timeout. + bool mEnabled; ///< Whether the timeout is enabled. + void * mContext; ///< The pointer to application-specific context. + void * mPoller; ///< The poller created this timer. +}; + +namespace chip { +namespace Dnssd { + +class Poller +{ +public: + Poller(void); + + void HandleTimeout(); + + const AvahiPoll * GetAvahiPoll(void) const { return &mAvahiPoller; } + +private: + static AvahiWatch * WatchNew(const struct AvahiPoll * poller, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, + void * context); + AvahiWatch * WatchNew(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void * context); + + static void WatchUpdate(AvahiWatch * watch, AvahiWatchEvent event); + + static AvahiWatchEvent WatchGetEvents(AvahiWatch * watch); + + static void WatchFree(AvahiWatch * watch); + void WatchFree(AvahiWatch & watch); + + static AvahiTimeout * TimeoutNew(const AvahiPoll * poller, const struct timeval * timeout, AvahiTimeoutCallback callback, + void * context); + AvahiTimeout * TimeoutNew(const struct timeval * timeout, AvahiTimeoutCallback callback, void * context); + + static void TimeoutUpdate(AvahiTimeout * timer, const struct timeval * timeout); + + static void TimeoutFree(AvahiTimeout * timer); + void TimeoutFree(AvahiTimeout & timer); + + void SystemTimerUpdate(AvahiTimeout * timer); + static void SystemTimerCallback(System::Layer * layer, void * data); + + std::vector> mWatches; + std::vector> mTimers; + std::chrono::steady_clock::time_point mEarliestTimeout; + + AvahiPoll mAvahiPoller; +}; + +class MdnsAvahi +{ +public: + MdnsAvahi(const MdnsAvahi &) = delete; + MdnsAvahi & operator=(const MdnsAvahi &) = delete; + + CHIP_ERROR Init(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context); + void Shutdown(); + CHIP_ERROR SetHostname(const char * hostname); + CHIP_ERROR PublishService(const DnssdService & service, DnssdPublishCallback callback, void * context); + CHIP_ERROR StopPublish(); + CHIP_ERROR Browse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context, intptr_t * browseIdentifier); + CHIP_ERROR StopBrowse(intptr_t browseIdentifier); + CHIP_ERROR Resolve(const char * name, const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::IPAddressType transportType, chip::Inet::InterfaceId interface, DnssdResolveCallback callback, + void * context); + void StopResolve(const char * name); + + Poller & GetPoller() { return mPoller; } + + static MdnsAvahi & GetInstance() { return sInstance; } + +private: + struct BrowseContext + { + MdnsAvahi * mInstance; + DnssdBrowseCallback mCallback; + void * mContext; + Inet::IPAddressType mAddressType; + std::vector mServices; + size_t mBrowseRetries; + AvahiIfIndex mInterface; + std::string mProtocol; + chip::System::Clock::Timeout mNextRetryDelay = chip::System::Clock::Seconds16(1); + std::atomic_bool mStopped{ false }; + }; + + struct ResolveContext + { + size_t mNumber; // unique number for this context + MdnsAvahi * mInstance; + DnssdResolveCallback mCallback; + void * mContext; + char mName[Common::kInstanceNameMaxLength + 1]; + AvahiIfIndex mInterface; + AvahiProtocol mTransport; + AvahiProtocol mAddressType; + std::string mFullType; + uint8_t mAttempts = 0; + AvahiServiceResolver * mResolver = nullptr; + + ~ResolveContext() + { + if (mResolver != nullptr) + { + avahi_service_resolver_free(mResolver); + mResolver = nullptr; + } + } + }; + + MdnsAvahi() : mClient(nullptr) {} + static MdnsAvahi sInstance; + + /// Allocates a new resolve context with a unique `mNumber` + ResolveContext * AllocateResolveContext(); + + ResolveContext * ResolveContextForHandle(size_t handle); + void FreeResolveContext(size_t handle); + void FreeResolveContext(const char * name); + + static void HandleClientState(AvahiClient * client, AvahiClientState state, void * context); + void HandleClientState(AvahiClient * client, AvahiClientState state); + + static void HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state, void * context); + void HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state); + + static void HandleBrowse(AvahiServiceBrowser * broswer, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char * name, const char * type, const char * domain, AvahiLookupResultFlags flags, + void * userdata); + static void BrowseRetryCallback(chip::System::Layer * aLayer, void * appState); + static void HandleResolve(AvahiServiceResolver * resolver, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, const char * name, const char * type, const char * domain, + const char * host_name, const AvahiAddress * address, uint16_t port, AvahiStringList * txt, + AvahiLookupResultFlags flags, void * userdata); + + DnssdAsyncReturnCallback mInitCallback; + DnssdAsyncReturnCallback mErrorCallback; + void * mAsyncReturnContext; + + AvahiClient * mClient; + std::map mPublishedGroups; + Poller mPoller; + static constexpr size_t kMaxBrowseRetries = 4; + + // Handling of allocated resolves + size_t mResolveCount = 0; + std::list mAllocatedResolves; +}; + +} // namespace Dnssd +} // namespace chip diff --git a/src/platform/NuttX/InetPlatformConfig.h b/src/platform/NuttX/InetPlatformConfig.h new file mode 100644 index 00000000000000..3aab9a7b9b9cd9 --- /dev/null +++ b/src/platform/NuttX/InetPlatformConfig.h @@ -0,0 +1,48 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific configuration overrides for the CHIP Inet + * Layer on Linux platforms. + * + */ + +#pragma once + +// ==================== Platform Adaptations ==================== + +#ifndef INET_CONFIG_ENABLE_IPV4 +#error Inet IPv4 configuration should be configured at build generation time +#endif + +// ========== Platform-specific Configuration Overrides ========= + +#ifndef INET_CONFIG_NUM_TCP_ENDPOINTS +#define INET_CONFIG_NUM_TCP_ENDPOINTS 32 +#endif // INET_CONFIG_NUM_TCP_ENDPOINTS + +#ifndef IPV6_MULTICAST_IMPLEMENTED +#define IPV6_MULTICAST_IMPLEMENTED +#endif + +#ifndef INET_CONFIG_NUM_UDP_ENDPOINTS +#define INET_CONFIG_NUM_UDP_ENDPOINTS 32 +#endif // INET_CONFIG_NUM_UDP_ENDPOINTS + +// On linux platform, we have sys/socket.h, so HAVE_SO_BINDTODEVICE should be set to 1 +#define HAVE_SO_BINDTODEVICE 1 diff --git a/src/platform/NuttX/KeyValueStoreManagerImpl.cpp b/src/platform/NuttX/KeyValueStoreManagerImpl.cpp new file mode 100644 index 00000000000000..2f4e2f19663c2c --- /dev/null +++ b/src/platform/NuttX/KeyValueStoreManagerImpl.cpp @@ -0,0 +1,115 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific implementatiuon of KVS for linux. + */ + +#include + +#include +#include + +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace PersistedStorage { + +KeyValueStoreManagerImpl KeyValueStoreManagerImpl::sInstance; + +CHIP_ERROR KeyValueStoreManagerImpl::_Get(const char * key, void * value, size_t value_size, size_t * read_bytes_size, + size_t offset_bytes) +{ + size_t read_size; + + // Copy data into value buffer + VerifyOrReturnError(value != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + // On linux read first without a buffer which returns the size, and then + // use a local buffer to read the entire object, which allows partial and + // offset reads. + CHIP_ERROR err = mStorage.ReadValueBin(key, nullptr, 0, read_size); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } + if ((err != CHIP_NO_ERROR) && (err != CHIP_ERROR_BUFFER_TOO_SMALL)) + { + return err; + } + if (offset_bytes > read_size) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + Platform::ScopedMemoryBuffer buf; + VerifyOrReturnError(buf.Alloc(read_size), CHIP_ERROR_NO_MEMORY); + ReturnErrorOnFailure(mStorage.ReadValueBin(key, buf.Get(), read_size, read_size)); + + size_t total_size_to_read = read_size - offset_bytes; + size_t copy_size = std::min(value_size, total_size_to_read); + if (read_bytes_size != nullptr) + { + *read_bytes_size = copy_size; + } + ::memcpy(value, buf.Get() + offset_bytes, copy_size); + + return (value_size < total_size_to_read) ? CHIP_ERROR_BUFFER_TOO_SMALL : CHIP_NO_ERROR; +} + +CHIP_ERROR KeyValueStoreManagerImpl::_Put(const char * key, const void * value, size_t value_size) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + err = mStorage.WriteValueBin(key, reinterpret_cast(value), value_size); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = mStorage.Commit(); + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR KeyValueStoreManagerImpl::_Delete(const char * key) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + err = mStorage.ClearValue(key); + + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + ExitNow(err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + } + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = mStorage.Commit(); + SuccessOrExit(err); + +exit: + return err; +} + +} // namespace PersistedStorage +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/KeyValueStoreManagerImpl.h b/src/platform/NuttX/KeyValueStoreManagerImpl.h new file mode 100644 index 00000000000000..206a3dc1276128 --- /dev/null +++ b/src/platform/NuttX/KeyValueStoreManagerImpl.h @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific implementation of KVS for linux. + */ + +#pragma once + +#include + +namespace chip { +namespace DeviceLayer { +namespace PersistedStorage { + +class KeyValueStoreManagerImpl : public KeyValueStoreManager +{ +public: + /** + * @brief + * Initalize the KVS, must be called before using. + */ + CHIP_ERROR Init(const char * file) { return mStorage.Init(file); } + + CHIP_ERROR _Get(const char * key, void * value, size_t value_size, size_t * read_bytes_size = nullptr, size_t offset = 0); + CHIP_ERROR _Delete(const char * key); + CHIP_ERROR _Put(const char * key, const void * value, size_t value_size); + +private: + DeviceLayer::Internal::ChipLinuxStorage mStorage; + + // ===== Members for internal use by the following friends. + friend KeyValueStoreManager & KeyValueStoreMgr(); + friend KeyValueStoreManagerImpl & KeyValueStoreMgrImpl(); + + static KeyValueStoreManagerImpl sInstance; +}; + +/** + * Returns the public interface of the KeyValueStoreManager singleton object. + * + * Chip applications should use this to access features of the KeyValueStoreManager object + * that are common to all platforms. + */ +inline KeyValueStoreManager & KeyValueStoreMgr(void) +{ + return KeyValueStoreManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the KeyValueStoreManager singleton object. + * + * Chip applications can use this to gain access to features of the KeyValueStoreManager + * that are specific to the ESP32 platform. + */ +inline KeyValueStoreManagerImpl & KeyValueStoreMgrImpl(void) +{ + return KeyValueStoreManagerImpl::sInstance; +} + +} // namespace PersistedStorage +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/Logging.cpp b/src/platform/NuttX/Logging.cpp new file mode 100644 index 00000000000000..e10ec76d0a9c12 --- /dev/null +++ b/src/platform/NuttX/Logging.cpp @@ -0,0 +1,89 @@ +/* See Project CHIP LICENSE file for licensing information. */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#if CHIP_USE_PW_LOGGING +#include +#endif // CHIP_USE_PW_LOGGING + +namespace chip { +namespace DeviceLayer { + +/** + * Called whenever a log message is emitted by chip or LwIP. + * + * This function is intended be overridden by the application to, e.g., + * schedule output of queued log entries. + */ +void __attribute__((weak)) OnLogOutput() {} + +} // namespace DeviceLayer + +namespace Logging { +namespace Platform { + +/** + * CHIP log output functions. + */ +void ENFORCE_FORMAT(3, 0) LogV(const char * module, uint8_t category, const char * msg, va_list v) +{ + struct timeval tv; + + // Should not fail per man page of gettimeofday(), but failed to get time is not a fatal error in log. The bad time value will + // indicate the error occurred during getting time. + gettimeofday(&tv, nullptr); + +#if !CHIP_USE_PW_LOGGING + // Lock standard output, so a single log line will not be corrupted in case + // where multiple threads are using logging subsystem at the same time. + flockfile(stdout); + + printf("[%" PRIu64 ".%06" PRIu64 "][%lld:%lld] CHIP:%s: ", static_cast(tv.tv_sec), static_cast(tv.tv_usec), + static_cast(getpid()), static_cast(gettid()), module); + vprintf(msg, v); + printf("\n"); + fflush(stdout); + + funlockfile(stdout); +#else // !CHIP_USE_PW_LOGGING + char formattedMsg[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE]; + snprintf(formattedMsg, sizeof(formattedMsg), + "[%" PRIu64 ".%06" PRIu64 "][%lld:%lld] CHIP:%s: ", static_cast(tv.tv_sec), + static_cast(tv.tv_usec), static_cast(syscall(SYS_getpid)), + static_cast(syscall(SYS_gettid)), module); + size_t len = strnlen(formattedMsg, sizeof(formattedMsg)); + vsnprintf(formattedMsg + len, sizeof(formattedMsg) - len, msg, v); + + switch (static_cast(category)) + { + case kLogCategory_Error: + PW_LOG_ERROR("%s", formattedMsg); + break; + case kLogCategory_Progress: + PW_LOG_INFO("%s", formattedMsg); + break; + case kLogCategory_Detail: + case kLogCategory_None: + case kLogCategory_Automation: + PW_LOG_DEBUG("%s", formattedMsg); + break; + } +#endif // !CHIP_USE_PW_LOGGING + + // Let the application know that a log message has been emitted. + DeviceLayer::OnLogOutput(); +} + +} // namespace Platform +} // namespace Logging +} // namespace chip diff --git a/src/platform/NuttX/NetworkCommissioningDriver.h b/src/platform/NuttX/NetworkCommissioningDriver.h new file mode 100644 index 00000000000000..96a647c2d22cbc --- /dev/null +++ b/src/platform/NuttX/NetworkCommissioningDriver.h @@ -0,0 +1,241 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { +namespace NetworkCommissioning { + +template +class LinuxScanResponseIterator : public Iterator +{ +public: + LinuxScanResponseIterator(std::vector * apScanResponse) : mpScanResponse(apScanResponse) {} + size_t Count() override { return mpScanResponse != nullptr ? mpScanResponse->size() : 0; } + bool Next(T & item) override + { + if (mpScanResponse == nullptr || currentIterating >= mpScanResponse->size()) + { + return false; + } + item = (*mpScanResponse)[currentIterating]; + currentIterating++; + return true; + } + void Release() override + { /* nothing to do, we don't hold the ownership of the vector, and users is not expected to hold the ownership in OnFinished for + scan. */ + } + +private: + size_t currentIterating = 0; + // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copyable. + std::vector * mpScanResponse; +}; + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +class LinuxWiFiDriver final : public WiFiDriver +{ +public: + class WiFiNetworkIterator final : public NetworkIterator + { + public: + WiFiNetworkIterator(LinuxWiFiDriver * aDriver) : driver(aDriver) {} + size_t Count() override; + bool Next(Network & item) override; + void Release() override { delete this; } + ~WiFiNetworkIterator() override = default; + + private: + LinuxWiFiDriver * driver; + bool exhausted = false; + }; + + void Set5gSupport(bool is5gSupported) { mIs5gSupported = is5gSupported; } + + // BaseDriver + NetworkIterator * GetNetworks() override { return new WiFiNetworkIterator(this); } + CHIP_ERROR Init(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) override; + void Shutdown() override; + + // WirelessDriver + uint8_t GetMaxNetworks() override { return 1; } + uint8_t GetScanNetworkTimeoutSeconds() override { return 10; } + uint8_t GetConnectNetworkTimeoutSeconds() override { return 20; } + + CHIP_ERROR CommitConfiguration() override; + CHIP_ERROR RevertConfiguration() override; + + Status RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) override; + Status ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) override; + void ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) override; + + // WiFiDriver + Status AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials, MutableCharSpan & outDebugText, + uint8_t & outNetworkIndex) override; + void ScanNetworks(ByteSpan ssid, ScanCallback * callback) override; + + uint32_t GetSupportedWiFiBandsMask() const override + { + uint32_t supportedBands = static_cast(1UL << chip::to_underlying(WiFiBandEnum::k2g4)); + if (mIs5gSupported) + { + supportedBands |= static_cast(1UL << chip::to_underlying(WiFiBandEnum::k5g)); + } + return supportedBands; + } + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + bool SupportsPerDeviceCredentials() override { return true; }; + CHIP_ERROR AddOrUpdateNetworkWithPDC(ByteSpan ssid, ByteSpan networkIdentity, Optional clientIdentityNetworkIndex, + Status & outStatus, MutableCharSpan & outDebugText, MutableByteSpan & outClientIdentity, + uint8_t & outNetworkIndex) override; + CHIP_ERROR GetNetworkIdentity(uint8_t networkIndex, MutableByteSpan & outNetworkIdentity) override; + CHIP_ERROR GetClientIdentity(uint8_t networkIndex, MutableByteSpan & outClientIdentity) override; + CHIP_ERROR SignWithClientIdentity(uint8_t networkIndex, const ByteSpan & message, + Crypto::P256ECDSASignature & outSignature) override; +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + +private: + struct WiFiNetwork + { + bool Empty() const { return ssidLen == 0; } + bool Matches(ByteSpan aSsid) const { return !Empty() && ByteSpan(ssid, ssidLen).data_equal(aSsid); } + + uint8_t ssid[DeviceLayer::Internal::kMaxWiFiSSIDLength]; + uint8_t ssidLen = 0; + static_assert(std::numeric_limits::max() >= sizeof(ssid)); + + uint8_t credentials[DeviceLayer::Internal::kMaxWiFiKeyLength]; + uint8_t credentialsLen = 0; + static_assert(std::numeric_limits::max() >= sizeof(credentials)); + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + bool UsingPDC() const { return networkIdentityLen != 0; } + + uint8_t networkIdentity[Credentials::kMaxCHIPCompactNetworkIdentityLength]; + uint8_t networkIdentityLen = 0; + static_assert(std::numeric_limits::max() >= sizeof(networkIdentity)); + + uint8_t clientIdentity[Credentials::kMaxCHIPCompactNetworkIdentityLength]; + uint8_t clientIdentityLen = 0; + static_assert(std::numeric_limits::max() >= sizeof(clientIdentity)); + + Platform::SharedPtr clientIdentityKeypair; +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + }; + + WiFiNetwork mSavedNetwork; + WiFiNetwork mStagingNetwork; + // Whether 5GHz band is supported, as claimed by callers (`Set5gSupport()`) rather than syscalls. + bool mIs5gSupported = false; +}; +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +class LinuxThreadDriver final : public ThreadDriver +{ +public: + class ThreadNetworkIterator final : public NetworkIterator + { + public: + ThreadNetworkIterator(LinuxThreadDriver * aDriver) : driver(aDriver) {} + size_t Count() override; + bool Next(Network & item) override; + void Release() override { delete this; } + ~ThreadNetworkIterator() override = default; + + private: + LinuxThreadDriver * driver; + bool exhausted = false; + }; + + // BaseDriver + NetworkIterator * GetNetworks() override { return new ThreadNetworkIterator(this); } + CHIP_ERROR Init(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) override; + void Shutdown() override; + + // WirelessDriver + uint8_t GetMaxNetworks() override { return 1; } + uint8_t GetScanNetworkTimeoutSeconds() override { return 10; } + uint8_t GetConnectNetworkTimeoutSeconds() override { return 20; } + + CHIP_ERROR CommitConfiguration() override; + CHIP_ERROR RevertConfiguration() override; + + Status RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) override; + Status ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) override; + void ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) override; + + // ThreadDriver + Status AddOrUpdateNetwork(ByteSpan operationalDataset, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) override; + void ScanNetworks(ThreadDriver::ScanCallback * callback) override; + ThreadCapabilities GetSupportedThreadFeatures() override; + uint16_t GetThreadVersion() override; + +private: + ThreadNetworkIterator mThreadIterator = ThreadNetworkIterator(this); + Thread::OperationalDataset mSavedNetwork; + Thread::OperationalDataset mStagingNetwork; +}; + +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD + +class LinuxEthernetDriver final : public EthernetDriver +{ +public: + struct EthernetNetworkIterator final : public NetworkIterator + { + EthernetNetworkIterator() = default; + size_t Count() override { return interfaceNameLen > 0 ? 1 : 0; } + bool Next(Network & item) override + { + if (exhausted) + { + return false; + } + exhausted = true; + memcpy(item.networkID, interfaceName, interfaceNameLen); + item.networkIDLen = interfaceNameLen; + item.connected = true; + return true; + } + void Release() override { delete this; } + ~EthernetNetworkIterator() override = default; + + // Public, but cannot be accessed via NetworkIterator interface. + uint8_t interfaceName[kMaxNetworkIDLen]; + uint8_t interfaceNameLen = 0; + bool exhausted = false; + }; + + uint8_t GetMaxNetworks() override { return 1; }; + NetworkIterator * GetNetworks() override; +}; + +} // namespace NetworkCommissioning +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/NetworkCommissioningEthernetDriver.cpp b/src/platform/NuttX/NetworkCommissioningEthernetDriver.cpp new file mode 100644 index 00000000000000..c1792a78fe437c --- /dev/null +++ b/src/platform/NuttX/NetworkCommissioningEthernetDriver.cpp @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include +#include + +using namespace chip::DeviceLayer::Internal; + +namespace chip { +namespace DeviceLayer { +namespace NetworkCommissioning { + +NetworkIterator * LinuxEthernetDriver::GetNetworks() +{ + auto ret = new EthernetNetworkIterator(); + ConnectivityUtils::GetEthInterfaceName(SafePointerCast(ret->interfaceName), sizeof(ret->interfaceName)); + ret->interfaceNameLen = static_cast(strnlen(SafePointerCast(ret->interfaceName), sizeof(ret->interfaceName))); + return ret; +} + +} // namespace NetworkCommissioning +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/NetworkCommissioningThreadDriver.cpp b/src/platform/NuttX/NetworkCommissioningThreadDriver.cpp new file mode 100644 index 00000000000000..79da48d49c0419 --- /dev/null +++ b/src/platform/NuttX/NetworkCommissioningThreadDriver.cpp @@ -0,0 +1,223 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace chip; +using namespace chip::Thread; + +namespace chip { +namespace DeviceLayer { +namespace NetworkCommissioning { + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + +// NOTE: For ThreadDriver, we uses two network configs, one is mSavedNetwork, and another is mStagingNetwork, during init, it will +// load the network config from otbr-agent, and loads it into both mSavedNetwork and mStagingNetwork. When updating the networks, +// all changed are made on the staging network. +// TODO: The otbr-posix does not actually maintains its own networking states, it will always persist the last network connected. +// This should not be an issue for most cases, but we should implement the code for maintaining the states by ourselves. + +CHIP_ERROR LinuxThreadDriver::Init(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) +{ + VerifyOrReturnError(ConnectivityMgrImpl().IsThreadAttached(), CHIP_NO_ERROR); + VerifyOrReturnError(ThreadStackMgrImpl().GetThreadProvision(mStagingNetwork) == CHIP_NO_ERROR, CHIP_NO_ERROR); + + mSavedNetwork.Init(mStagingNetwork.AsByteSpan()); + + ThreadStackMgrImpl().SetNetworkStatusChangeCallback(networkStatusChangeCallback); + + return CHIP_NO_ERROR; +} + +void LinuxThreadDriver::Shutdown() +{ + ThreadStackMgrImpl().SetNetworkStatusChangeCallback(nullptr); +} + +CHIP_ERROR LinuxThreadDriver::CommitConfiguration() +{ + // Note: otbr-agent will persist the networks by their own, we don't have much to do for saving the networks (see Init() above, + // we just loads the saved dataset from otbr-agent.) + mSavedNetwork = mStagingNetwork; + return CHIP_NO_ERROR; +} + +CHIP_ERROR LinuxThreadDriver::RevertConfiguration() +{ + mStagingNetwork = mSavedNetwork; + return CHIP_NO_ERROR; +} + +Status LinuxThreadDriver::AddOrUpdateNetwork(ByteSpan operationalDataset, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) +{ + uint8_t extpanid[kSizeExtendedPanId]; + uint8_t newExtpanid[kSizeExtendedPanId]; + Thread::OperationalDataset newDataset; + outDebugText.reduce_size(0); + outNetworkIndex = 0; + newDataset.Init(operationalDataset); + VerifyOrReturnError(newDataset.IsCommissioned(), Status::kOutOfRange); + + VerifyOrReturnError(!mStagingNetwork.IsCommissioned() || memcmp(extpanid, newExtpanid, kSizeExtendedPanId) == 0, + Status::kBoundsExceeded); + + mStagingNetwork = newDataset; + return Status::kSuccess; +} + +Status LinuxThreadDriver::RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) +{ + outDebugText.reduce_size(0); + outNetworkIndex = 0; + uint8_t extpanid[kSizeExtendedPanId]; + if (!mStagingNetwork.IsCommissioned()) + { + return Status::kNetworkNotFound; + } + if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR) + { + return Status::kUnknownError; + } + + VerifyOrReturnError(networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0, + Status::kNetworkNotFound); + mStagingNetwork.Clear(); + return Status::kSuccess; +} + +Status LinuxThreadDriver::ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) +{ + outDebugText.reduce_size(0); + uint8_t extpanid[kSizeExtendedPanId]; + if (!mStagingNetwork.IsCommissioned()) + { + return Status::kNetworkNotFound; + } + if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR) + { + return Status::kUnknownError; + } + + VerifyOrReturnError(networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0, + Status::kNetworkNotFound); + + return Status::kSuccess; +} + +void LinuxThreadDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) +{ + NetworkCommissioning::Status status = Status::kSuccess; + uint8_t extpanid[kSizeExtendedPanId]; + if (!mStagingNetwork.IsCommissioned()) + { + ExitNow(status = Status::kNetworkNotFound); + } + else if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR) + { + ExitNow(status = Status::kUnknownError); + } + + VerifyOrExit((networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0), + status = Status::kNetworkNotFound); + + VerifyOrExit(DeviceLayer::ThreadStackMgrImpl().AttachToThreadNetwork(mStagingNetwork, callback) == CHIP_NO_ERROR, + status = Status::kUnknownError); + +exit: + if (status != Status::kSuccess) + { + callback->OnResult(status, CharSpan(), 0); + } +} + +void LinuxThreadDriver::ScanNetworks(ThreadDriver::ScanCallback * callback) +{ + CHIP_ERROR err = DeviceLayer::ThreadStackMgrImpl().StartThreadScan(callback); + // The ThreadScan callback will always be invoked in CHIP mainloop, which is strictly after this function + if (err != CHIP_NO_ERROR) + { + callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr); + } +} + +size_t LinuxThreadDriver::ThreadNetworkIterator::Count() +{ + return driver->mStagingNetwork.IsCommissioned() ? 1 : 0; +} + +bool LinuxThreadDriver::ThreadNetworkIterator::Next(Network & item) +{ + if (exhausted || !driver->mStagingNetwork.IsCommissioned()) + { + return false; + } + uint8_t extpanid[kSizeExtendedPanId]; + VerifyOrReturnError(driver->mStagingNetwork.GetExtendedPanId(extpanid) == CHIP_NO_ERROR, false); + memcpy(item.networkID, extpanid, kSizeExtendedPanId); + item.networkIDLen = kSizeExtendedPanId; + item.connected = false; + exhausted = true; + + Thread::OperationalDataset currentDataset; + uint8_t enabledExtPanId[Thread::kSizeExtendedPanId]; + + // The Thread network is not actually enabled. + VerifyOrReturnError(ConnectivityMgrImpl().IsThreadAttached(), true); + VerifyOrReturnError(ThreadStackMgrImpl().GetThreadProvision(currentDataset) == CHIP_NO_ERROR, true); + // The Thread network is not enabled, but has a different extended pan id. + VerifyOrReturnError(currentDataset.GetExtendedPanId(enabledExtPanId) == CHIP_NO_ERROR, true); + VerifyOrReturnError(memcmp(extpanid, enabledExtPanId, kSizeExtendedPanId) == 0, true); + // The Thread network is enabled and has the same extended pan id as the one in our record. + item.connected = true; + + return true; +} + +ThreadCapabilities LinuxThreadDriver::GetSupportedThreadFeatures() +{ + BitMask capabilites = 0; + capabilites.SetField(ThreadCapabilities::kIsBorderRouterCapable, CHIP_DEVICE_CONFIG_THREAD_BORDER_ROUTER); + capabilites.SetField(ThreadCapabilities::kIsRouterCapable, CHIP_DEVICE_CONFIG_THREAD_FTD); + capabilites.SetField(ThreadCapabilities::kIsSleepyEndDeviceCapable, !CHIP_DEVICE_CONFIG_THREAD_FTD); + capabilites.SetField(ThreadCapabilities::kIsFullThreadDevice, CHIP_DEVICE_CONFIG_THREAD_FTD); + capabilites.SetField(ThreadCapabilities::kIsSynchronizedSleepyEndDeviceCapable, + (!CHIP_DEVICE_CONFIG_THREAD_FTD && CHIP_DEVICE_CONFIG_THREAD_SSED)); + return capabilites; +} + +uint16_t LinuxThreadDriver::GetThreadVersion() +{ + // TODO https://github.com/project-chip/connectedhomeip/issues/30602 + // Needs to be implemented with DBUS io.openthread.BorderRouter Thread API + return 0; +} + +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD + +} // namespace NetworkCommissioning +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/NetworkCommissioningWiFiDriver.cpp b/src/platform/NuttX/NetworkCommissioningWiFiDriver.cpp new file mode 100644 index 00000000000000..e2de245cbb418e --- /dev/null +++ b/src/platform/NuttX/NetworkCommissioningWiFiDriver.cpp @@ -0,0 +1,361 @@ +/* + * + * Copyright (c) 2021-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace chip; +using namespace chip::Crypto; +using namespace chip::Credentials; + +namespace chip { +namespace DeviceLayer { +namespace NetworkCommissioning { + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +// TODO(#14172): Here, most interfaces are just calling ConnectivityManager interfaces, this is because the ConnectivityProvides +// some bootstrap code for the wpa_supplicant. However, we can wrap the wpa_supplicant dbus api directly (and remove the related +// code in ConnectivityManagerImpl). +namespace { +constexpr char kWiFiSSIDKeyName[] = "wifi-ssid"; +constexpr char kWiFiCredentialsKeyName[] = "wifi-pass"; +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC +constexpr char kWifiNetworkIdentityKeyName[] = "wifi-ni"; +constexpr char kWifiClientIdentityKeyName[] = "wifi-ci"; +constexpr char kWifiClientIdentityKeypairKeyName[] = "wifi-cik"; +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + +inline CHIP_ERROR IgnoreNotFound(CHIP_ERROR err) +{ + return (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) ? CHIP_NO_ERROR : err; +} +} // namespace + +// NOTE: For WiFiDriver, we uses two network configs, one is mSavedNetwork, and another is mStagingNetwork, during init, it will +// load the network config from k-v storage, and loads it into both mSavedNetwork and mStagingNetwork. When updating the networks, +// all changed are made on the staging network, and when the network is committed, it will update the mSavedNetwork to +// mStagingNetwork and persist the changes. + +// NOTE: LinuxWiFiDriver uses network config with empty ssid (ssidLen = 0) for empty network config. + +// NOTE: For now, the LinuxWiFiDriver only supports one network, this can be fixed by using the wpa_supplicant API directly (then +// wpa_supplicant will manage the networks for us.) + +CHIP_ERROR LinuxWiFiDriver::Init(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) +{ + CHIP_ERROR err; + WiFiNetwork network; + size_t valueLen = 0; + + auto & kvs = PersistedStorage::KeyValueStoreMgr(); + + SuccessOrExit(err = IgnoreNotFound(kvs.Get(kWiFiSSIDKeyName, network.ssid, sizeof(network.ssid), &valueLen))); + if (valueLen != 0) + { + network.ssidLen = valueLen; + + err = kvs.Get(kWiFiCredentialsKeyName, network.credentials, sizeof(network.credentials), &valueLen); +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + SuccessOrExit( + err = kvs.Get(kWifiNetworkIdentityKeyName, network.networkIdentity, sizeof(network.networkIdentity), &valueLen)); + VerifyOrExit(valueLen > 0, err = CHIP_ERROR_INTEGRITY_CHECK_FAILED); + network.networkIdentityLen = valueLen; + + SuccessOrExit( + err = kvs.Get(kWifiClientIdentityKeyName, network.clientIdentity, sizeof(network.clientIdentity), &valueLen)); + VerifyOrExit(valueLen > 0, err = CHIP_ERROR_INTEGRITY_CHECK_FAILED); + network.clientIdentityLen = valueLen; + + P256SerializedKeypair serializedKeypair; + SuccessOrExit(err = kvs.Get(kWifiClientIdentityKeypairKeyName, serializedKeypair.Bytes(), serializedKeypair.Capacity(), + &valueLen)); + serializedKeypair.SetLength(valueLen); + network.clientIdentityKeypair = Platform::MakeShared(); + SuccessOrExit(err = network.clientIdentityKeypair->Deserialize(serializedKeypair)); + } + else +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + { + SuccessOrExit(err); + network.credentialsLen = valueLen; + } + + mStagingNetwork = mSavedNetwork = network; + } + + ConnectivityMgrImpl().SetNetworkStatusChangeCallback(networkStatusChangeCallback); + return CHIP_NO_ERROR; + +exit: + ChipLogProgress(NetworkProvisioning, "LinuxWiFiDriver: Failed to load network configuration: %" CHIP_ERROR_FORMAT, + err.Format()); + return err; +} + +void LinuxWiFiDriver::Shutdown() +{ + ConnectivityMgrImpl().SetNetworkStatusChangeCallback(nullptr); +} + +CHIP_ERROR LinuxWiFiDriver::CommitConfiguration() +{ + auto & kvs = PersistedStorage::KeyValueStoreMgr(); + ReturnErrorOnFailure(kvs.Put(kWiFiSSIDKeyName, mStagingNetwork.ssid, mStagingNetwork.ssidLen)); +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + if (mStagingNetwork.UsingPDC()) + { + ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWiFiCredentialsKeyName))); + ReturnErrorOnFailure( + kvs.Put(kWifiNetworkIdentityKeyName, mStagingNetwork.networkIdentity, mStagingNetwork.networkIdentityLen)); + ReturnErrorOnFailure( + kvs.Put(kWifiClientIdentityKeyName, mStagingNetwork.clientIdentity, mStagingNetwork.clientIdentityLen)); + + P256SerializedKeypair serializedKeypair; + ReturnErrorOnFailure(mStagingNetwork.clientIdentityKeypair->Serialize(serializedKeypair)); + ReturnErrorOnFailure( + kvs.Put(kWifiClientIdentityKeypairKeyName, serializedKeypair.ConstBytes(), serializedKeypair.Length())); + } + else + { + ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWifiNetworkIdentityKeyName))); + ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWifiClientIdentityKeyName))); + ReturnErrorOnFailure(IgnoreNotFound(kvs.Delete(kWifiClientIdentityKeypairKeyName))); +#else // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + { +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + ReturnErrorOnFailure(kvs.Put(kWiFiCredentialsKeyName, mStagingNetwork.credentials, mStagingNetwork.credentialsLen)); + } + + ReturnErrorOnFailure(ConnectivityMgrImpl().CommitConfig()); + mSavedNetwork = mStagingNetwork; + return CHIP_NO_ERROR; +} + +CHIP_ERROR LinuxWiFiDriver::RevertConfiguration() +{ + mStagingNetwork = mSavedNetwork; + return CHIP_NO_ERROR; +} + +Status LinuxWiFiDriver::AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials, MutableCharSpan & outDebugText, + uint8_t & outNetworkIndex) +{ + outDebugText.reduce_size(0); + outNetworkIndex = 0; + VerifyOrReturnError(mStagingNetwork.Empty() || mStagingNetwork.Matches(ssid), Status::kBoundsExceeded); + + // Do the check before setting the values, so the data is not updated on error. + VerifyOrReturnError(credentials.size() <= sizeof(mStagingNetwork.credentials), Status::kOutOfRange); + VerifyOrReturnError(!ssid.empty() && ssid.size() <= sizeof(mStagingNetwork.ssid), Status::kOutOfRange); + + memcpy(mStagingNetwork.credentials, credentials.data(), credentials.size()); + mStagingNetwork.credentialsLen = static_cast(credentials.size()); + + memcpy(mStagingNetwork.ssid, ssid.data(), ssid.size()); + mStagingNetwork.ssidLen = static_cast(ssid.size()); + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + mStagingNetwork.networkIdentityLen = 0; + mStagingNetwork.clientIdentityLen = 0; + mStagingNetwork.clientIdentityKeypair.reset(); +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + + return Status::kSuccess; +} + +Status LinuxWiFiDriver::RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) +{ + outDebugText.reduce_size(0); + outNetworkIndex = 0; + VerifyOrReturnError(mStagingNetwork.Matches(networkId), Status::kNetworkIDNotFound); + + // Use empty ssid for representing invalid network + mStagingNetwork.ssidLen = 0; + return Status::kSuccess; +} + +Status LinuxWiFiDriver::ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) +{ + outDebugText.reduce_size(0); + VerifyOrReturnError(mStagingNetwork.Matches(networkId), Status::kNetworkIDNotFound); + VerifyOrReturnError(index == 0, Status::kOutOfRange); + // We only support one network, so reorder is actually no-op. + return Status::kSuccess; +} + +void LinuxWiFiDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + Status networkingStatus = Status::kSuccess; + + const auto & network = mStagingNetwork; + VerifyOrExit(network.Matches(networkId), networkingStatus = Status::kNetworkIDNotFound); + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + if (network.UsingPDC()) + { + ChipLogProgress(NetworkProvisioning, "LinuxWiFiDriver: ConnectNetwork (PDC) '%.*s'", network.ssidLen, network.ssid); + err = ConnectivityMgrImpl().ConnectWiFiNetworkWithPDCAsync( + ByteSpan(network.ssid, network.ssidLen), ByteSpan(network.networkIdentity, network.networkIdentityLen), + ByteSpan(network.clientIdentity, network.clientIdentityLen), *network.clientIdentityKeypair, callback); + } + else +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + { + ChipLogProgress(NetworkProvisioning, "LinuxWiFiDriver: ConnectNetwork '%.*s'", network.ssidLen, network.ssid); + + err = ConnectivityMgrImpl().ConnectWiFiNetworkAsync(ByteSpan(network.ssid, network.ssidLen), + ByteSpan(network.credentials, network.credentialsLen), callback); + } + +exit: + if (err != CHIP_NO_ERROR) + { + networkingStatus = Status::kUnknownError; + } + + if (networkingStatus != Status::kSuccess) + { + ChipLogError(NetworkProvisioning, "Failed to connect to WiFi network: %" CHIP_ERROR_FORMAT, err.Format()); + callback->OnResult(networkingStatus, CharSpan(), 0); + } +} + +void LinuxWiFiDriver::ScanNetworks(ByteSpan ssid, WiFiDriver::ScanCallback * callback) +{ + CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().StartWiFiScan(ssid, callback); + if (err != CHIP_NO_ERROR) + { + callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr); + } +} + +size_t LinuxWiFiDriver::WiFiNetworkIterator::Count() +{ + return driver->mStagingNetwork.Empty() ? 0 : 1; +} + +bool LinuxWiFiDriver::WiFiNetworkIterator::Next(Network & item) +{ + if (exhausted || driver->mStagingNetwork.Empty()) + { + return false; + } + memcpy(item.networkID, driver->mStagingNetwork.ssid, driver->mStagingNetwork.ssidLen); + item.networkIDLen = driver->mStagingNetwork.ssidLen; + item.connected = false; + exhausted = true; + + Network configuredNetwork; + CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().GetConfiguredNetwork(configuredNetwork); + if (err == CHIP_NO_ERROR) + { + if (DeviceLayer::ConnectivityMgrImpl().IsWiFiStationConnected() && configuredNetwork.networkIDLen == item.networkIDLen && + memcmp(configuredNetwork.networkID, item.networkID, item.networkIDLen) == 0) + { + item.connected = true; + } + } + + return true; +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC +CHIP_ERROR LinuxWiFiDriver::AddOrUpdateNetworkWithPDC(ByteSpan ssid, ByteSpan networkIdentity, + Optional clientIdentityNetworkIndex, Status & outStatus, + MutableCharSpan & outDebugText, MutableByteSpan & outClientIdentity, + uint8_t & outNetworkIndex) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + outStatus = Status::kUnknownError; + VerifyOrExit(mStagingNetwork.Empty() || mStagingNetwork.Matches(ssid), outStatus = Status::kBoundsExceeded); + + VerifyOrExit(!ssid.empty() && ssid.size() <= sizeof(WiFiNetwork::ssid), outStatus = Status::kOutOfRange); + VerifyOrExit(!networkIdentity.empty() && networkIdentity.size() <= sizeof(WiFiNetwork::networkIdentity), + outStatus = Status::kOutOfRange); + VerifyOrExit(!clientIdentityNetworkIndex.HasValue() || (clientIdentityNetworkIndex.Value() == 0 && mStagingNetwork.UsingPDC()), + outStatus = Status::kOutOfRange); + + { + WiFiNetwork network = mStagingNetwork; // update a copy first in case of errors + + memcpy(network.ssid, ssid.data(), network.ssidLen = ssid.size()); + memcpy(network.networkIdentity, networkIdentity.data(), network.networkIdentityLen = networkIdentity.size()); + + // If an existing client identity is being reused, we would need to copy it here, + // but since we're only supporting a single network we simply don't overwrite it. + if (!clientIdentityNetworkIndex.HasValue()) + { + network.clientIdentityKeypair = Platform::MakeShared(); + SuccessOrExit(err = network.clientIdentityKeypair->Initialize(ECPKeyTarget::ECDSA)); + + MutableByteSpan clientIdentity(network.clientIdentity); + SuccessOrExit(err = NewChipNetworkIdentity(*network.clientIdentityKeypair, clientIdentity)); + network.clientIdentityLen = clientIdentity.size(); + } + + network.credentialsLen = 0; + + SuccessOrExit(err = CopySpanToMutableSpan(ByteSpan(network.clientIdentity, network.clientIdentityLen), outClientIdentity)); + + mStagingNetwork = network; + outNetworkIndex = 0; + outStatus = Status::kSuccess; + } + +exit: + outDebugText.reduce_size(0); + return err; +} + +CHIP_ERROR LinuxWiFiDriver::GetNetworkIdentity(uint8_t networkIndex, MutableByteSpan & outNetworkIdentity) +{ + VerifyOrReturnError(!mStagingNetwork.Empty() && networkIndex == 0, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mStagingNetwork.UsingPDC(), CHIP_ERROR_INVALID_ARGUMENT); + return CopySpanToMutableSpan(ByteSpan(mStagingNetwork.networkIdentity, mStagingNetwork.networkIdentityLen), outNetworkIdentity); +} + +CHIP_ERROR LinuxWiFiDriver::GetClientIdentity(uint8_t networkIndex, MutableByteSpan & outClientIdentity) +{ + VerifyOrReturnError(!mStagingNetwork.Empty() && networkIndex == 0, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mStagingNetwork.UsingPDC(), CHIP_ERROR_INVALID_ARGUMENT); + return CopySpanToMutableSpan(ByteSpan(mStagingNetwork.clientIdentity, mStagingNetwork.clientIdentityLen), outClientIdentity); +} + +CHIP_ERROR LinuxWiFiDriver::SignWithClientIdentity(uint8_t networkIndex, const ByteSpan & message, + P256ECDSASignature & outSignature) +{ + VerifyOrReturnError(!mStagingNetwork.Empty() && networkIndex == 0, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mStagingNetwork.UsingPDC(), CHIP_ERROR_INVALID_ARGUMENT); + return mStagingNetwork.clientIdentityKeypair->ECDSA_sign_msg(message.data(), message.size(), outSignature); +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_PDC + +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +} // namespace NetworkCommissioning +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/OTAImageProcessorImpl.cpp b/src/platform/NuttX/OTAImageProcessorImpl.cpp new file mode 100644 index 00000000000000..b6fe393e01e19d --- /dev/null +++ b/src/platform/NuttX/OTAImageProcessorImpl.cpp @@ -0,0 +1,279 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "OTAImageProcessorImpl.h" + +#include + +namespace chip { + +CHIP_ERROR OTAImageProcessorImpl::PrepareDownload() +{ + if (mImageFile == nullptr) + { + ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); + return CHIP_ERROR_INTERNAL; + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandlePrepareDownload, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Finalize() +{ + DeviceLayer::PlatformMgr().ScheduleWork(HandleFinalize, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Apply() +{ + DeviceLayer::PlatformMgr().ScheduleWork(HandleApply, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Abort() +{ + if (mImageFile == nullptr) + { + ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); + return CHIP_ERROR_INTERNAL; + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandleAbort, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::ProcessBlock(ByteSpan & block) +{ + if (!mOfs.is_open() || !mOfs.good()) + { + return CHIP_ERROR_INTERNAL; + } + + // Store block data for HandleProcessBlock to access + CHIP_ERROR err = SetBlock(block); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot set block data: %" CHIP_ERROR_FORMAT, err.Format()); + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandleProcessBlock, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +bool OTAImageProcessorImpl::IsFirstImageRun() +{ + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + if (requestor == nullptr) + { + return false; + } + + return requestor->GetCurrentUpdateState() == OTARequestorInterface::OTAUpdateStateEnum::kApplying; +} + +CHIP_ERROR OTAImageProcessorImpl::ConfirmCurrentImage() +{ + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + if (requestor == nullptr) + { + return CHIP_ERROR_INTERNAL; + } + + uint32_t currentVersion; + uint32_t targetVersion = requestor->GetTargetVersion(); + ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(currentVersion)); + if (currentVersion != targetVersion) + { + ChipLogError(SoftwareUpdate, "Current software version = %" PRIu32 ", expected software version = %" PRIu32, currentVersion, + targetVersion); + return CHIP_ERROR_INCORRECT_STATE; + } + + return CHIP_NO_ERROR; +} + +void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); + return; + } + else if (imageProcessor->mDownloader == nullptr) + { + ChipLogError(SoftwareUpdate, "mDownloader is null"); + return; + } + + unlink(imageProcessor->mImageFile); + + imageProcessor->mParams.downloadedBytes = 0; + imageProcessor->mParams.totalFileBytes = 0; + imageProcessor->mHeaderParser.Init(); + imageProcessor->mOfs.open(imageProcessor->mImageFile, std::ofstream::out | std::ofstream::ate | std::ofstream::app); + if (!imageProcessor->mOfs.good()) + { + imageProcessor->mDownloader->OnPreparedForDownload(CHIP_ERROR_OPEN_FAILED); + return; + } + + imageProcessor->mDownloader->OnPreparedForDownload(CHIP_NO_ERROR); +} + +void OTAImageProcessorImpl::HandleFinalize(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + return; + } + + imageProcessor->mOfs.close(); + imageProcessor->ReleaseBlock(); + + ChipLogProgress(SoftwareUpdate, "OTA image downloaded to %s", imageProcessor->mImageFile); +} + +void OTAImageProcessorImpl::HandleApply(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + VerifyOrReturn(imageProcessor != nullptr); + + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + VerifyOrReturn(requestor != nullptr); + + // Move the downloaded image to the location where the new image is to be executed from + unlink(kImageExecPath); + rename(imageProcessor->mImageFile, kImageExecPath); + chmod(kImageExecPath, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + + // Shutdown the stack and expect to boot into the new image once the event loop is stopped + DeviceLayer::PlatformMgr().ScheduleWork([](intptr_t) { DeviceLayer::PlatformMgr().HandleServerShuttingDown(); }); + DeviceLayer::PlatformMgr().ScheduleWork([](intptr_t) { DeviceLayer::PlatformMgr().StopEventLoopTask(); }); +} + +void OTAImageProcessorImpl::HandleAbort(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + return; + } + + imageProcessor->mOfs.close(); + unlink(imageProcessor->mImageFile); + imageProcessor->ReleaseBlock(); +} + +void OTAImageProcessorImpl::HandleProcessBlock(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); + return; + } + else if (imageProcessor->mDownloader == nullptr) + { + ChipLogError(SoftwareUpdate, "mDownloader is null"); + return; + } + + ByteSpan block = imageProcessor->mBlock; + CHIP_ERROR error = imageProcessor->ProcessHeader(block); + if (error != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Image does not contain a valid header"); + imageProcessor->mDownloader->EndDownload(CHIP_ERROR_INVALID_FILE_IDENTIFIER); + return; + } + + if (!imageProcessor->mOfs.write(reinterpret_cast(block.data()), static_cast(block.size()))) + { + imageProcessor->mDownloader->EndDownload(CHIP_ERROR_WRITE_FAILED); + return; + } + + imageProcessor->mParams.downloadedBytes += block.size(); + imageProcessor->mDownloader->FetchNextData(); +} + +CHIP_ERROR OTAImageProcessorImpl::ProcessHeader(ByteSpan & block) +{ + if (mHeaderParser.IsInitialized()) + { + OTAImageHeader header; + CHIP_ERROR error = mHeaderParser.AccumulateAndDecode(block, header); + + // Needs more data to decode the header + ReturnErrorCodeIf(error == CHIP_ERROR_BUFFER_TOO_SMALL, CHIP_NO_ERROR); + ReturnErrorOnFailure(error); + + mParams.totalFileBytes = header.mPayloadSize; + mHeaderParser.Clear(); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::SetBlock(ByteSpan & block) +{ + if (block.empty()) + { + ReleaseBlock(); + return CHIP_NO_ERROR; + } + if (mBlock.size() < block.size()) + { + if (!mBlock.empty()) + { + ReleaseBlock(); + } + uint8_t * mBlock_ptr = static_cast(chip::Platform::MemoryAlloc(block.size())); + if (mBlock_ptr == nullptr) + { + return CHIP_ERROR_NO_MEMORY; + } + mBlock = MutableByteSpan(mBlock_ptr, block.size()); + } + CHIP_ERROR err = CopySpanToMutableSpan(block, mBlock); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot copy block data: %" CHIP_ERROR_FORMAT, err.Format()); + return err; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::ReleaseBlock() +{ + if (mBlock.data() != nullptr) + { + chip::Platform::MemoryFree(mBlock.data()); + } + + mBlock = MutableByteSpan(); + return CHIP_NO_ERROR; +} + +} // namespace chip diff --git a/src/platform/NuttX/OTAImageProcessorImpl.h b/src/platform/NuttX/OTAImageProcessorImpl.h new file mode 100644 index 00000000000000..a8939512e113dd --- /dev/null +++ b/src/platform/NuttX/OTAImageProcessorImpl.h @@ -0,0 +1,75 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace chip { + +// Full file path to where the new image will be executed from post-download +static char kImageExecPath[] = "/tmp/ota.update"; + +class OTAImageProcessorImpl : public OTAImageProcessorInterface +{ +public: + //////////// OTAImageProcessorInterface Implementation /////////////// + CHIP_ERROR PrepareDownload() override; + CHIP_ERROR Finalize() override; + CHIP_ERROR Apply() override; + CHIP_ERROR Abort() override; + CHIP_ERROR ProcessBlock(ByteSpan & block) override; + bool IsFirstImageRun() override; + CHIP_ERROR ConfirmCurrentImage() override; + + void SetOTADownloader(OTADownloader * downloader) { mDownloader = downloader; } + void SetOTAImageFile(const char * imageFile) { mImageFile = imageFile; } + +private: + //////////// Actual handlers for the OTAImageProcessorInterface /////////////// + static void HandlePrepareDownload(intptr_t context); + static void HandleFinalize(intptr_t context); + static void HandleApply(intptr_t context); + static void HandleAbort(intptr_t context); + static void HandleProcessBlock(intptr_t context); + + CHIP_ERROR ProcessHeader(ByteSpan & block); + + /** + * Called to allocate memory for mBlock if necessary and set it to block + */ + CHIP_ERROR SetBlock(ByteSpan & block); + + /** + * Called to release allocated memory for mBlock + */ + CHIP_ERROR ReleaseBlock(); + + std::ofstream mOfs; + MutableByteSpan mBlock; + OTADownloader * mDownloader; + OTAImageHeaderParser mHeaderParser; + const char * mImageFile = nullptr; +}; + +} // namespace chip diff --git a/src/platform/NuttX/PlatformManagerImpl.cpp b/src/platform/NuttX/PlatformManagerImpl.cpp new file mode 100644 index 00000000000000..d590f58c343232 --- /dev/null +++ b/src/platform/NuttX/PlatformManagerImpl.cpp @@ -0,0 +1,327 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the PlatformManager object + * for Linux platforms. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::chip::app::Clusters; + +namespace chip { +namespace DeviceLayer { + +PlatformManagerImpl PlatformManagerImpl::sInstance; + +namespace { + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP +void * GLibMainLoopThread(void * userData) +{ + GMainLoop * loop = static_cast(userData); + GMainContext * context = g_main_loop_get_context(loop); + + g_main_context_push_thread_default(context); + g_main_loop_run(loop); + + return nullptr; +} +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + +gboolean WiFiIPChangeListener(GIOChannel * ch, GIOCondition /* condition */, void * /* userData */) +{ + + char buffer[4096]; + auto * header = reinterpret_cast(buffer); + ssize_t len; + + if ((len = recv(g_io_channel_unix_get_fd(ch), buffer, sizeof(buffer), 0)) == -1) + { + if (errno == EINTR || errno == EAGAIN) + return G_SOURCE_CONTINUE; + ChipLogError(DeviceLayer, "Error reading from netlink socket: %d", errno); + return G_SOURCE_CONTINUE; + } + + if (len > 0) + { + for (struct nlmsghdr * messageHeader = header; + (NLMSG_OK(messageHeader, static_cast(len))) && (messageHeader->nlmsg_type != NLMSG_DONE); + messageHeader = NLMSG_NEXT(messageHeader, len)) + { + if (header->nlmsg_type == RTM_NEWADDR) + { + struct ifaddrmsg * addressMessage = (struct ifaddrmsg *) NLMSG_DATA(header); + struct rtattr * routeInfo = IFA_RTA(addressMessage); + size_t rtl = IFA_PAYLOAD(header); + + for (; rtl && RTA_OK(routeInfo, rtl); routeInfo = RTA_NEXT(routeInfo, rtl)) + { + if (routeInfo->rta_type == IFA_LOCAL) + { + char name[IFNAMSIZ]; + if (if_indextoname(addressMessage->ifa_index, name) == nullptr) + { + ChipLogError(DeviceLayer, "Error %d when getting the interface name at index: %d", errno, + addressMessage->ifa_index); + continue; + } + + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + ChipLogDetail(DeviceLayer, "No wifi interface name. Ignoring IP update event."); + continue; + } + + if (strcmp(name, ConnectivityMgrImpl().GetWiFiIfName()) != 0) + { + continue; + } + + char ipStrBuf[chip::Inet::IPAddress::kMaxStringLength] = { 0 }; + inet_ntop(AF_INET, RTA_DATA(routeInfo), ipStrBuf, sizeof(ipStrBuf)); + ChipLogDetail(DeviceLayer, "Got IP address on interface: %s IP: %s", name, ipStrBuf); + + ChipDeviceEvent event; + event.Type = DeviceEventType::kInternetConnectivityChange; + event.InternetConnectivityChange.IPv4 = kConnectivity_Established; + event.InternetConnectivityChange.IPv6 = kConnectivity_NoChange; + + if (!chip::Inet::IPAddress::FromString(ipStrBuf, event.InternetConnectivityChange.ipAddress)) + { + ChipLogDetail(DeviceLayer, "Failed to report IP address - ip address parsing failed"); + continue; + } + + CHIP_ERROR status = PlatformMgr().PostEvent(&event); + if (status != CHIP_NO_ERROR) + { + ChipLogDetail(DeviceLayer, "Failed to report IP address: %" CHIP_ERROR_FORMAT, status.Format()); + } + } + } + } + } + } + else + { + ChipLogError(DeviceLayer, "EOF on netlink socket"); + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +// The temporary hack for getting IP address change on linux for network provisioning in the rendezvous session. +// This should be removed or find a better place once we deprecate the rendezvous session. +CHIP_ERROR RunWiFiIPChangeListener() +{ + int sock; + if ((sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) + { + ChipLogError(DeviceLayer, "Failed to init netlink socket for IP addresses: %d", errno); + return CHIP_ERROR_INTERNAL; + } + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = RTMGRP_IPV4_IFADDR; + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) + { + ChipLogError(DeviceLayer, "Failed to bind netlink socket for IP addresses: %d", errno); + close(sock); + return CHIP_ERROR_INTERNAL; + } + + GIOChannel * ch = g_io_channel_unix_new(sock); + GSource * watchSource = g_io_create_watch(ch, G_IO_IN); + g_source_set_callback(watchSource, G_SOURCE_FUNC(WiFiIPChangeListener), nullptr, nullptr); + g_io_channel_set_close_on_unref(ch, TRUE); + g_io_channel_set_encoding(ch, nullptr, nullptr); + + PlatformMgrImpl().GLibMatterContextAttachSource(watchSource); + + g_source_unref(watchSource); + g_io_channel_unref(ch); + + return CHIP_NO_ERROR; +} + +#endif // #if CHIP_DEVICE_CONFIG_ENABLE_WIFI + +} // namespace + +CHIP_ERROR PlatformManagerImpl::_InitChipStack() +{ +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP + + auto * context = g_main_context_new(); + mGLibMainLoop = g_main_loop_new(context, FALSE); + mGLibMainLoopThread = g_thread_new("gmain-matter", GLibMainLoopThread, mGLibMainLoop); + g_main_context_unref(context); + + { + // Wait for the GLib main loop to start. It is required that the context used + // by the main loop is acquired before any other GLib functions are called. Otherwise, + // the GLibMatterContextInvokeSync() might run functions on the wrong thread. + + std::unique_lock lock(mGLibMainLoopCallbackIndirectionMutex); + GLibMatterContextInvokeData invokeData{}; + + auto * idleSource = g_idle_source_new(); + g_source_set_callback( + idleSource, + [](void * userData_) { + auto * data = reinterpret_cast(userData_); + std::unique_lock lock_(PlatformMgrImpl().mGLibMainLoopCallbackIndirectionMutex); + data->mDone = true; + data->mDoneCond.notify_one(); + return G_SOURCE_REMOVE; + }, + &invokeData, nullptr); + GLibMatterContextAttachSource(idleSource); + g_source_unref(idleSource); + + invokeData.mDoneCond.wait(lock, [&invokeData]() { return invokeData.mDone; }); + } + +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + ReturnErrorOnFailure(RunWiFiIPChangeListener()); +#endif + + // Initialize the configuration system. + ReturnErrorOnFailure(Internal::PosixConfig::Init()); + + // Call _InitChipStack() on the generic implementation base class + // to finish the initialization process. + ReturnErrorOnFailure(Internal::GenericPlatformManagerImpl_POSIX::_InitChipStack()); + + // Now set up our device instance info provider. We couldn't do that + // earlier, because the generic implementation sets a generic one. + SetDeviceInstanceInfoProvider(&DeviceInstanceInfoProviderMgrImpl()); + + mStartTime = System::SystemClock().GetMonotonicTimestamp(); + + return CHIP_NO_ERROR; +} + +void PlatformManagerImpl::_Shutdown() +{ + uint64_t upTime = 0; + + if (GetDiagnosticDataProvider().GetUpTime(upTime) == CHIP_NO_ERROR) + { + uint32_t totalOperationalHours = 0; + + if (ConfigurationMgr().GetTotalOperationalHours(totalOperationalHours) == CHIP_NO_ERROR) + { + ConfigurationMgr().StoreTotalOperationalHours(totalOperationalHours + static_cast(upTime / 3600)); + } + else + { + ChipLogError(DeviceLayer, "Failed to get total operational hours of the Node"); + } + } + else + { + ChipLogError(DeviceLayer, "Failed to get current uptime since the Node’s last reboot"); + } + + Internal::GenericPlatformManagerImpl_POSIX::_Shutdown(); + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP + g_main_loop_quit(mGLibMainLoop); + g_thread_join(mGLibMainLoopThread); + g_main_loop_unref(mGLibMainLoop); +#endif +} + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP +CHIP_ERROR PlatformManagerImpl::_GLibMatterContextInvokeSync(CHIP_ERROR (*func)(void *), void * userData) +{ + // Because of TSAN false positives, we need to use a mutex to synchronize access to all members of + // the GLibMatterContextInvokeData object (including constructor and destructor). This is a temporary + // workaround until TSAN-enabled GLib will be used in our CI. + std::unique_lock lock(mGLibMainLoopCallbackIndirectionMutex); + + GLibMatterContextInvokeData invokeData{ func, userData }; + + lock.unlock(); + + g_main_context_invoke_full( + g_main_loop_get_context(mGLibMainLoop), G_PRIORITY_HIGH_IDLE, + [](void * userData_) { + auto * data = reinterpret_cast(userData_); + + // XXX: Temporary workaround for TSAN false positives. + std::unique_lock lock_(PlatformMgrImpl().mGLibMainLoopCallbackIndirectionMutex); + + auto mFunc = data->mFunc; + auto mUserData = data->mFuncUserData; + + lock_.unlock(); + auto result = mFunc(mUserData); + lock_.lock(); + + data->mDone = true; + data->mFuncResult = result; + data->mDoneCond.notify_one(); + + return G_SOURCE_REMOVE; + }, + &invokeData, nullptr); + + lock.lock(); + + invokeData.mDoneCond.wait(lock, [&invokeData]() { return invokeData.mDone; }); + + return invokeData.mFuncResult; +} +#endif // CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/PlatformManagerImpl.h b/src/platform/NuttX/PlatformManagerImpl.h new file mode 100644 index 00000000000000..5669ce5dd28d62 --- /dev/null +++ b/src/platform/NuttX/PlatformManagerImpl.h @@ -0,0 +1,159 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the PlatformManager object. + */ + +#pragma once + +#include +#include + +#include +#include + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP +#include +#endif + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the PlatformManager singleton object for Linux platforms. + */ +class PlatformManagerImpl final : public PlatformManager, public Internal::GenericPlatformManagerImpl_POSIX +{ + // Allow the PlatformManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend PlatformManager; + + // Allow the generic implementation base class to call helper methods on + // this class. +#ifndef DOXYGEN_SHOULD_SKIP_THIS + friend Internal::GenericPlatformManagerImpl_POSIX; +#endif + +public: + // ===== Platform-specific members that may be accessed directly by the application. + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP + + /** + * @brief Invoke a function on the Matter GLib context. + * + * If execution of the function will have to be scheduled on other thread, + * this call will block the current thread until the function is executed. + * + * @param[in] function The function to call. + * @param[in] userData User data to pass to the function. + * @returns The result of the function. + */ + template + CHIP_ERROR GLibMatterContextInvokeSync(CHIP_ERROR (*func)(T *), T * userData) + { + return _GLibMatterContextInvokeSync((CHIP_ERROR(*)(void *)) func, (void *) userData); + } + + unsigned int GLibMatterContextAttachSource(GSource * source) + { + VerifyOrDie(mGLibMainLoop != nullptr); + return g_source_attach(source, g_main_loop_get_context(mGLibMainLoop)); + } + +#endif + + System::Clock::Timestamp GetStartTime() { return mStartTime; } + +private: + // ===== Methods that implement the PlatformManager abstract interface. + + CHIP_ERROR _InitChipStack(); + void _Shutdown(); + + // ===== Members for internal use by the following friends. + + friend PlatformManager & PlatformMgr(); + friend PlatformManagerImpl & PlatformMgrImpl(); + friend class Internal::BLEManagerImpl; + + System::Clock::Timestamp mStartTime = System::Clock::kZero; + + static PlatformManagerImpl sInstance; + +#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP + + struct GLibMatterContextInvokeData + { + CHIP_ERROR (*mFunc)(void *); + void * mFuncUserData; + CHIP_ERROR mFuncResult; + // Sync primitives to wait for the function to be executed + std::condition_variable mDoneCond; + bool mDone = false; + }; + + /** + * @brief Invoke a function on the Matter GLib context. + * + * @note This function does not provide type safety for the user data. Please, + * use the GLibMatterContextInvokeSync() template function instead. + */ + CHIP_ERROR _GLibMatterContextInvokeSync(CHIP_ERROR (*func)(void *), void * userData); + + // XXX: Mutex for guarding access to glib main event loop callback indirection + // synchronization primitives. This is a workaround to suppress TSAN warnings. + // TSAN does not know that from the thread synchronization perspective the + // g_source_attach() function should be treated as pthread_create(). Memory + // access to shared data before the call to g_source_attach() without mutex + // is not a race condition - the callback will not be executed on glib main + // event loop thread before the call to g_source_attach(). + std::mutex mGLibMainLoopCallbackIndirectionMutex; + + GMainLoop * mGLibMainLoop = nullptr; + GThread * mGLibMainLoopThread = nullptr; + +#endif // CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP +}; + +/** + * Returns the public interface of the PlatformManager singleton object. + * + * chip applications should use this to access features of the PlatformManager object + * that are common to all platforms. + */ +inline PlatformManager & PlatformMgr() +{ + return PlatformManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the PlatformManager singleton object. + * + * chip applications can use this to gain access to features of the PlatformManager + * that are specific to the platform. + */ +inline PlatformManagerImpl & PlatformMgrImpl() +{ + return PlatformManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/PosixConfig.cpp b/src/platform/NuttX/PosixConfig.cpp new file mode 100644 index 00000000000000..8e8feda20de202 --- /dev/null +++ b/src/platform/NuttX/PosixConfig.cpp @@ -0,0 +1,588 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2019-2020 Google LLC. + * Copyright (c) 2018 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Utilities for interacting with multiple file partitions and maps + * key-value config calls to the correct partition. + */ + +#include +#include + +#include +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +static ChipLinuxStorage gChipLinuxFactoryStorage; +static ChipLinuxStorage gChipLinuxConfigStorage; +static ChipLinuxStorage gChipLinuxCountersStorage; + +// *** CAUTION ***: Changing the names or namespaces of these values will *break* existing devices. + +// NVS namespaces used to store device configuration information. +const char PosixConfig::kConfigNamespace_ChipFactory[] = "chip-factory"; +const char PosixConfig::kConfigNamespace_ChipConfig[] = "chip-config"; +const char PosixConfig::kConfigNamespace_ChipCounters[] = "chip-counters"; + +// Keys stored in the Chip-factory namespace +const PosixConfig::Key PosixConfig::kConfigKey_SerialNum = { kConfigNamespace_ChipFactory, "serial-num" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDeviceId = { kConfigNamespace_ChipFactory, "device-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDeviceCert = { kConfigNamespace_ChipFactory, "device-cert" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDeviceICACerts = { kConfigNamespace_ChipFactory, "device-ca-certs" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDevicePrivateKey = { kConfigNamespace_ChipFactory, "device-key" }; +const PosixConfig::Key PosixConfig::kConfigKey_HardwareVersion = { kConfigNamespace_ChipFactory, "hardware-ver" }; +const PosixConfig::Key PosixConfig::kConfigKey_ManufacturingDate = { kConfigNamespace_ChipFactory, "mfg-date" }; +const PosixConfig::Key PosixConfig::kConfigKey_SetupPinCode = { kConfigNamespace_ChipFactory, "pin-code" }; +const PosixConfig::Key PosixConfig::kConfigKey_SetupDiscriminator = { kConfigNamespace_ChipFactory, "discriminator" }; +const PosixConfig::Key PosixConfig::kConfigKey_Spake2pIterationCount = { kConfigNamespace_ChipFactory, "iteration-count" }; +const PosixConfig::Key PosixConfig::kConfigKey_Spake2pSalt = { kConfigNamespace_ChipFactory, "salt" }; +const PosixConfig::Key PosixConfig::kConfigKey_Spake2pVerifier = { kConfigNamespace_ChipFactory, "verifier" }; +const PosixConfig::Key PosixConfig::kConfigKey_VendorId = { kConfigNamespace_ChipFactory, "vendor-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_ProductId = { kConfigNamespace_ChipFactory, "product-id" }; + +// Keys stored in the Chip-config namespace +const PosixConfig::Key PosixConfig::kConfigKey_ServiceConfig = { kConfigNamespace_ChipConfig, "service-config" }; +const PosixConfig::Key PosixConfig::kConfigKey_PairedAccountId = { kConfigNamespace_ChipConfig, "account-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_ServiceId = { kConfigNamespace_ChipConfig, "service-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_LastUsedEpochKeyId = { kConfigNamespace_ChipConfig, "last-ek-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_FailSafeArmed = { kConfigNamespace_ChipConfig, "fail-safe-armed" }; +const PosixConfig::Key PosixConfig::kConfigKey_RegulatoryLocation = { kConfigNamespace_ChipConfig, "regulatory-location" }; +const PosixConfig::Key PosixConfig::kConfigKey_CountryCode = { kConfigNamespace_ChipConfig, "country-code" }; +const PosixConfig::Key PosixConfig::kConfigKey_LocationCapability = { kConfigNamespace_ChipConfig, "location-capability" }; +const PosixConfig::Key PosixConfig::kConfigKey_UniqueId = { kConfigNamespace_ChipFactory, "unique-id" }; + +// Keys stored in the Chip-counters namespace +const PosixConfig::Key PosixConfig::kCounterKey_RebootCount = { kConfigNamespace_ChipCounters, "reboot-count" }; +const PosixConfig::Key PosixConfig::kCounterKey_UpTime = { kConfigNamespace_ChipCounters, "up-time" }; +const PosixConfig::Key PosixConfig::kCounterKey_TotalOperationalHours = { kConfigNamespace_ChipCounters, + "total-operational-hours" }; +const PosixConfig::Key PosixConfig::kCounterKey_BootReason = { kConfigNamespace_ChipCounters, "boot-reason" }; + +ChipLinuxStorage * PosixConfig::GetStorageForNamespace(Key key) +{ + if (strcmp(key.Namespace, kConfigNamespace_ChipFactory) == 0) + return &gChipLinuxFactoryStorage; + + if (strcmp(key.Namespace, kConfigNamespace_ChipConfig) == 0) + return &gChipLinuxConfigStorage; + + if (strcmp(key.Namespace, kConfigNamespace_ChipCounters) == 0) + return &gChipLinuxCountersStorage; + + return nullptr; +} + +CHIP_ERROR PosixConfig::Init() +{ + return PersistedStorage::KeyValueStoreMgrImpl().Init(CHIP_CONFIG_KVS_PATH); +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, bool & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + uint32_t intVal; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValue(key.Name, intVal); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + + val = (intVal != 0); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, uint16_t & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValue(key.Name, val); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, uint32_t & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValue(key.Name, val); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, uint64_t & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + // Special case the MfrDeviceId value, optionally allowing it to be read as a blob containing + // a 64-bit big-endian integer, instead of a u64 value. + if (key == kConfigKey_MfrDeviceId) + { + uint8_t deviceIdBytes[sizeof(uint64_t)]; + size_t deviceIdLen = sizeof(deviceIdBytes); + size_t deviceIdOutLen; + err = storage->ReadValueBin(key.Name, deviceIdBytes, deviceIdLen, deviceIdOutLen); + if (err == CHIP_NO_ERROR) + { + VerifyOrExit(deviceIdOutLen == sizeof(deviceIdBytes), err = CHIP_ERROR_INCORRECT_STATE); + val = Encoding::BigEndian::Get64(deviceIdBytes); + ExitNow(); + } + } + + err = storage->ReadValue(key.Name, val); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValueStr(key.Name, buf, bufSize, outLen); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + outLen = 0; + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + else if (err == CHIP_ERROR_BUFFER_TOO_SMALL) + { + err = (buf == nullptr) ? CHIP_NO_ERROR : CHIP_ERROR_BUFFER_TOO_SMALL; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValueBin(key.Name, buf, bufSize, outLen); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + outLen = 0; + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + else if (err == CHIP_ERROR_BUFFER_TOO_SMALL) + { + err = (buf == nullptr) ? CHIP_NO_ERROR : CHIP_ERROR_BUFFER_TOO_SMALL; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, bool val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %s", StringOrNullMarker(key.Namespace), StringOrNullMarker(key.Name), + val ? "true" : "false"); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, uint16_t val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %u (0x%X)", StringOrNullMarker(key.Namespace), StringOrNullMarker(key.Name), val, + val); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, uint32_t val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %" PRIu32 " (0x%" PRIX32 ")", StringOrNullMarker(key.Namespace), + StringOrNullMarker(key.Name), val, val); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, uint64_t val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %" PRIu64 " (0x%" PRIX64 ")", StringOrNullMarker(key.Namespace), + StringOrNullMarker(key.Name), val, val); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValueStr(Key key, const char * str) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + if (str != nullptr) + { + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValueStr(key.Name, str); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = \"%s\"", StringOrNullMarker(key.Namespace), StringOrNullMarker(key.Name), + str); + } + + else + { + err = ClearConfigValue(key); + SuccessOrExit(err); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValueStr(Key key, const char * str, size_t strLen) +{ +#if CHIP_CONFIG_MEMORY_MGMT_MALLOC + CHIP_ERROR err; + char * strCopy = nullptr; + + if (str != nullptr) + { + strCopy = strndup(str, strLen); + VerifyOrExit(strCopy != nullptr, err = CHIP_ERROR_NO_MEMORY); + } + + err = PosixConfig::WriteConfigValueStr(key, strCopy); + +exit: + if (strCopy != nullptr) + { + free(strCopy); + } + return err; +#else +#error "Unsupported CHIP_CONFIG_MEMORY_MGMT configuration" +#endif +} + +CHIP_ERROR PosixConfig::WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + if (data != nullptr) + { + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValueBin(key.Name, data, dataLen); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = (blob length %u)", StringOrNullMarker(key.Namespace), + StringOrNullMarker(key.Name), static_cast(dataLen)); + } + else + { + err = ClearConfigValue(key); + SuccessOrExit(err); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ClearConfigValue(Key key) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ClearValue(key.Name); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + ExitNow(err = CHIP_NO_ERROR); + } + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS erase: %s/%s", StringOrNullMarker(key.Namespace), StringOrNullMarker(key.Name)); + +exit: + return err; +} + +bool PosixConfig::ConfigValueExists(Key key) +{ + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + if (storage == nullptr) + return false; + + return storage->HasValue(key.Name); +} + +CHIP_ERROR PosixConfig::EnsureNamespace(const char * ns) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage = nullptr; + + if (strcmp(ns, kConfigNamespace_ChipFactory) == 0) + { + storage = &gChipLinuxFactoryStorage; + err = storage->Init(CHIP_DEFAULT_FACTORY_PATH); + } + else if (strcmp(ns, kConfigNamespace_ChipConfig) == 0) + { + storage = &gChipLinuxConfigStorage; + err = storage->Init(CHIP_DEFAULT_CONFIG_PATH); + } + else if (strcmp(ns, kConfigNamespace_ChipCounters) == 0) + { + storage = &gChipLinuxCountersStorage; + err = storage->Init(CHIP_DEFAULT_DATA_PATH); + } + + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ClearNamespace(const char * ns) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage = nullptr; + + if (strcmp(ns, kConfigNamespace_ChipConfig) == 0) + { + storage = &gChipLinuxConfigStorage; + } + else if (strcmp(ns, kConfigNamespace_ChipCounters) == 0) + { + storage = &gChipLinuxCountersStorage; + } + + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ClearAll(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage ClearAll failed: %s", ErrorStr(err)); + } + SuccessOrExit(err); + + err = storage->Commit(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage Commit failed: %s", ErrorStr(err)); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::FactoryResetConfig() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage; + + ChipLogProgress(DeviceLayer, "Performing factory reset configuration"); + + storage = &gChipLinuxConfigStorage; + if (storage == nullptr) + { + ChipLogError(DeviceLayer, "Storage get failed"); + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + + err = storage->ClearAll(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage ClearAll failed: %s", ErrorStr(err)); + } + SuccessOrExit(err); + + err = storage->Commit(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage Commit failed: %s", ErrorStr(err)); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::FactoryResetCounters() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage; + + ChipLogProgress(DeviceLayer, "Performing factory reset counters"); + + storage = &gChipLinuxCountersStorage; + if (storage == nullptr) + { + ChipLogError(DeviceLayer, "Storage get failed"); + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + + err = storage->ClearAll(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage ClearAll failed: %s", ErrorStr(err)); + } + SuccessOrExit(err); + + err = storage->Commit(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage Commit failed: %s", ErrorStr(err)); + } + +exit: + return err; +} + +void PosixConfig::RunConfigUnitTest() +{ + // Run common unit test. + ::chip::DeviceLayer::Internal::RunConfigUnitTest(); +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/PosixConfig.h b/src/platform/NuttX/PosixConfig.h new file mode 100644 index 00000000000000..c04d4a9be1093f --- /dev/null +++ b/src/platform/NuttX/PosixConfig.h @@ -0,0 +1,131 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Utilities for accessing persisted device configuration on + * Linux platforms. + */ + +#pragma once + +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +class ChipLinuxStorage; + +/** + * Provides functions and definitions for accessing device configuration information on the Posix. + * + * This class is designed to be mixed-in to concrete implementation classes as a means to + * provide access to configuration information to generic base classes. + */ +class PosixConfig +{ +public: + struct Key; + + // Maximum length of an NVS key name. + static constexpr size_t kMaxConfigKeyNameLength = 15; + + // NVS namespaces used to store device configuration information. + static const char kConfigNamespace_ChipFactory[]; + static const char kConfigNamespace_ChipConfig[]; + static const char kConfigNamespace_ChipCounters[]; + + // Key definitions for well-known keys. + static const Key kConfigKey_SerialNum; + static const Key kConfigKey_UniqueId; + static const Key kConfigKey_MfrDeviceId; + static const Key kConfigKey_MfrDeviceCert; + static const Key kConfigKey_MfrDeviceICACerts; + static const Key kConfigKey_MfrDevicePrivateKey; + static const Key kConfigKey_HardwareVersion; + static const Key kConfigKey_ManufacturingDate; + static const Key kConfigKey_SetupPinCode; + static const Key kConfigKey_ServiceConfig; + static const Key kConfigKey_PairedAccountId; + static const Key kConfigKey_ServiceId; + static const Key kConfigKey_LastUsedEpochKeyId; + static const Key kConfigKey_FailSafeArmed; + static const Key kConfigKey_SetupDiscriminator; + static const Key kConfigKey_RegulatoryLocation; + static const Key kConfigKey_CountryCode; + static const Key kConfigKey_LocationCapability; + static const Key kConfigKey_Spake2pIterationCount; + static const Key kConfigKey_Spake2pSalt; + static const Key kConfigKey_Spake2pVerifier; + static const Key kConfigKey_VendorId; + static const Key kConfigKey_ProductId; + + static const Key kCounterKey_RebootCount; + static const Key kCounterKey_UpTime; + static const Key kCounterKey_TotalOperationalHours; + static const Key kCounterKey_BootReason; + + static CHIP_ERROR Init(); + + // Config value accessors. + static CHIP_ERROR ReadConfigValue(Key key, bool & val); + static CHIP_ERROR ReadConfigValue(Key key, uint16_t & val); + static CHIP_ERROR ReadConfigValue(Key key, uint32_t & val); + static CHIP_ERROR ReadConfigValue(Key key, uint64_t & val); + static CHIP_ERROR ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen); + static CHIP_ERROR ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen); + static CHIP_ERROR WriteConfigValue(Key key, bool val); + static CHIP_ERROR WriteConfigValue(Key key, uint16_t val); + static CHIP_ERROR WriteConfigValue(Key key, uint32_t val); + static CHIP_ERROR WriteConfigValue(Key key, uint64_t val); + static CHIP_ERROR WriteConfigValueStr(Key key, const char * str); + static CHIP_ERROR WriteConfigValueStr(Key key, const char * str, size_t strLen); + static CHIP_ERROR WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen); + static CHIP_ERROR ClearConfigValue(Key key); + static bool ConfigValueExists(Key key); + static CHIP_ERROR FactoryResetConfig(); + static CHIP_ERROR FactoryResetCounters(); + static void RunConfigUnitTest(); + + // NVS Namespace helper functions. + static CHIP_ERROR EnsureNamespace(const char * ns); + static CHIP_ERROR ClearNamespace(const char * ns); + +private: + static ChipLinuxStorage * GetStorageForNamespace(Key key); +}; + +struct PosixConfig::Key +{ + const char * Namespace; + const char * Name; + + bool operator==(const Key & other) const; +}; + +inline bool PosixConfig::Key::operator==(const Key & other) const +{ + return strcmp(Namespace, other.Namespace) == 0 && strcmp(Name, other.Name) == 0; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/README.md b/src/platform/NuttX/README.md new file mode 100644 index 00000000000000..6cd083f929024c --- /dev/null +++ b/src/platform/NuttX/README.md @@ -0,0 +1,14 @@ +# Overview of CHIP NuttX Adaption + +This platform is based on Linux adaptation, The code introduction can be seen in +Linux's README.md:`src/platform/Linux/README.md` + +To avoid integration errors caused by CI build breaks on the NuttX platform when +new features are added to the Linux code, the current Linux code was copied as +the base for NuttX to avoid a strong dependency between the two platforms. + +The reason for adapting based on Linux is that NuttX is also a POSIX-compliant +operating system, and the code can be almost completely reused while keeping the +definitions and comments with the Linux prefix, which makes it easier to +cherry-pick modifications from the Linux platform to the NuttX platform in the +future. diff --git a/src/platform/NuttX/SystemPlatformConfig.h b/src/platform/NuttX/SystemPlatformConfig.h new file mode 100644 index 00000000000000..21495f93a38b82 --- /dev/null +++ b/src/platform/NuttX/SystemPlatformConfig.h @@ -0,0 +1,44 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific configuration overrides for the CHIP System + * Layer on Linux platforms. + * + */ + +#pragma once + +#include + +namespace chip { +namespace DeviceLayer { +struct ChipDeviceEvent; +} // namespace DeviceLayer +} // namespace chip + +// ==================== Platform Adaptations ==================== + +#define CHIP_SYSTEM_CONFIG_POSIX_LOCKING 1 +#define CHIP_SYSTEM_CONFIG_FREERTOS_LOCKING 0 +#define CHIP_SYSTEM_CONFIG_NO_LOCKING 0 +#define CHIP_SYSTEM_CONFIG_PLATFORM_PROVIDES_TIME 1 +#define CHIP_SYSTEM_CONFIG_POOL_USE_HEAP 1 + +// ========== Platform-specific Configuration Overrides ========= +#define CHIP_CONFIG_MDNS_RESOLVE_LOOKUP_RESULTS 5 diff --git a/src/platform/NuttX/SystemTimeSupport.cpp b/src/platform/NuttX/SystemTimeSupport.cpp new file mode 100644 index 00000000000000..7eed058256ac3e --- /dev/null +++ b/src/platform/NuttX/SystemTimeSupport.cpp @@ -0,0 +1,105 @@ +/* + * + * Copyright (c) 2018 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides implementations of the CHIP System Layer platform + * time/clock functions that are suitable for use on the Posix platform. + */ + +#include + +#include +#include + +#include +#include +#include +#include + +namespace chip { +namespace System { +namespace Clock { + +namespace Internal { +ClockImpl gClockImpl; +} // namespace Internal + +Microseconds64 ClockImpl::GetMonotonicMicroseconds64() +{ + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); +} + +Milliseconds64 ClockImpl::GetMonotonicMilliseconds64() +{ + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); +} + +CHIP_ERROR ClockImpl::GetClock_RealTime(Microseconds64 & aCurTime) +{ + struct timeval tv; + if (gettimeofday(&tv, nullptr) != 0) + { + return CHIP_ERROR_POSIX(errno); + } + if (tv.tv_sec < CHIP_SYSTEM_CONFIG_VALID_REAL_TIME_THRESHOLD) + { + return CHIP_ERROR_REAL_TIME_NOT_SYNCED; + } + if (tv.tv_usec < 0) + { + return CHIP_ERROR_REAL_TIME_NOT_SYNCED; + } + static_assert(CHIP_SYSTEM_CONFIG_VALID_REAL_TIME_THRESHOLD >= 0, "We might be letting through negative tv_sec values!"); + aCurTime = Microseconds64((static_cast(tv.tv_sec) * UINT64_C(1000000)) + static_cast(tv.tv_usec)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ClockImpl::GetClock_RealTimeMS(Milliseconds64 & aCurTime) +{ + Microseconds64 curTimeUs; + auto err = GetClock_RealTime(curTimeUs); + aCurTime = std::chrono::duration_cast(curTimeUs); + return err; +} + +CHIP_ERROR ClockImpl::SetClock_RealTime(Microseconds64 aNewCurTime) +{ + struct timeval tv; + tv.tv_sec = static_cast(aNewCurTime.count() / UINT64_C(1000000)); + tv.tv_usec = static_cast(aNewCurTime.count() % UINT64_C(1000000)); + if (settimeofday(&tv, nullptr) != 0) + { + return (errno == EPERM) ? CHIP_ERROR_ACCESS_DENIED : CHIP_ERROR_POSIX(errno); + } +#if CHIP_PROGRESS_LOGGING + { + const time_t timep = tv.tv_sec; + struct tm calendar; + localtime_r(&timep, &calendar); + ChipLogProgress(DeviceLayer, "Real time clock set to %lld (%04d/%02d/%02d %02d:%02d:%02d UTC)", + static_cast(tv.tv_sec), calendar.tm_year, calendar.tm_mon, calendar.tm_mday, calendar.tm_hour, + calendar.tm_min, calendar.tm_sec); + } +#endif // CHIP_PROGRESS_LOGGING + return CHIP_NO_ERROR; +} + +} // namespace Clock +} // namespace System +} // namespace chip diff --git a/src/platform/NuttX/ThreadStackManagerImpl.cpp b/src/platform/NuttX/ThreadStackManagerImpl.cpp new file mode 100644 index 00000000000000..cb717324fc989d --- /dev/null +++ b/src/platform/NuttX/ThreadStackManagerImpl.cpp @@ -0,0 +1,770 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace ::chip::app; +using namespace ::chip::app::Clusters; +using namespace chip::DeviceLayer::NetworkCommissioning; + +namespace chip { +namespace DeviceLayer { + +ThreadStackManagerImpl ThreadStackManagerImpl::sInstance; + +constexpr char ThreadStackManagerImpl::kDBusOpenThreadService[]; +constexpr char ThreadStackManagerImpl::kDBusOpenThreadObjectPath[]; + +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleDisabled[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleDetached[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleChild[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleRouter[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleLeader[]; + +constexpr char ThreadStackManagerImpl::kPropertyDeviceRole[]; + +namespace { + +struct SetActiveDatasetContext +{ + OpenthreadIoOpenthreadBorderRouter * proxy; + ByteSpan netInfo; +}; + +CHIP_ERROR GLibMatterContextSetActiveDataset(SetActiveDatasetContext * context) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr bytes(g_bytes_new(context->netInfo.data(), context->netInfo.size())); + if (!bytes) + return CHIP_ERROR_NO_MEMORY; + GAutoPtr value(g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes.release(), true)); + if (!value) + return CHIP_ERROR_NO_MEMORY; + openthread_io_openthread_border_router_set_active_dataset_tlvs(context->proxy, value.release()); + return CHIP_NO_ERROR; +} + +} // namespace + +ThreadStackManagerImpl::ThreadStackManagerImpl() : mAttached(false) {} + +CHIP_ERROR ThreadStackManagerImpl::GLibMatterContextInitThreadStack(ThreadStackManagerImpl * self) +{ + // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, + // all D-Bus signals will be delivered to the GLib global default main context. + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + + GAutoPtr err; + self->mProxy.reset(openthread_io_openthread_border_router_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kDBusOpenThreadService, kDBusOpenThreadObjectPath, nullptr, + &err.GetReceiver())); + VerifyOrReturnError( + self->mProxy != nullptr, CHIP_ERROR_INTERNAL, + ChipLogError(DeviceLayer, "openthread: failed to create openthread dbus proxy %s", err ? err->message : "unknown error")); + + g_signal_connect(self->mProxy.get(), "g-properties-changed", G_CALLBACK(OnDbusPropertiesChanged), self); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ThreadStackManagerImpl::_InitThreadStack() +{ + CHIP_ERROR err; + + err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextInitThreadStack, this); + VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to init dbus proxy")); + + // If get property is called inside dbus thread (we are going to make it so), XXX_get_XXX can be used instead of XXX_dup_XXX + // which is a little bit faster and the returned object doesn't need to be freed. Same for all following get properties. + GAutoPtr role(openthread_io_openthread_border_router_dup_device_role(mProxy.get())); + if (role) + { + ThreadDeviceRoleChangedHandler(role.get()); + } + + return CHIP_NO_ERROR; +} + +void ThreadStackManagerImpl::OnDbusPropertiesChanged(OpenthreadIoOpenthreadBorderRouter * proxy, GVariant * changed_properties, + const gchar * const * invalidated_properties, gpointer user_data) +{ + ThreadStackManagerImpl * me = reinterpret_cast(user_data); + if (g_variant_n_children(changed_properties) > 0) + { + const gchar * key; + GVariant * value; + + GAutoPtr iter; + g_variant_get(changed_properties, "a{sv}", &iter.GetReceiver()); + if (!iter) + return; + while (g_variant_iter_loop(iter.get(), "{&sv}", &key, &value)) + { + if (key == nullptr || value == nullptr) + continue; + // ownership of key and value is still holding by the iter + DeviceLayer::SystemLayer().ScheduleLambda([me]() { me->_UpdateNetworkStatus(); }); + + if (strcmp(key, kPropertyDeviceRole) == 0) + { + const gchar * value_str = g_variant_get_string(value, nullptr); + if (value_str == nullptr) + continue; + ChipLogProgress(DeviceLayer, "Thread role changed to: %s", StringOrNullMarker(value_str)); + me->ThreadDeviceRoleChangedHandler(value_str); + } + } + } +} + +void ThreadStackManagerImpl::ThreadDeviceRoleChangedHandler(const gchar * role) +{ + bool attached = strcmp(role, kOpenthreadDeviceRoleDetached) != 0 && strcmp(role, kOpenthreadDeviceRoleDisabled) != 0; + + ChipDeviceEvent event = ChipDeviceEvent{}; + + if (attached != mAttached) + { + event.Type = DeviceEventType::kThreadConnectivityChange; + event.ThreadConnectivityChange.Result = + attached ? ConnectivityChange::kConnectivity_Established : ConnectivityChange::kConnectivity_Lost; + CHIP_ERROR status = PlatformMgr().PostEvent(&event); + if (status != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to post thread connectivity change: %" CHIP_ERROR_FORMAT, status.Format()); + } + } + mAttached = attached; + + event.Type = DeviceEventType::kThreadStateChange; + event.ThreadStateChange.RoleChanged = true; + CHIP_ERROR status = PlatformMgr().PostEvent(&event); + if (status != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to post thread state change: %" CHIP_ERROR_FORMAT, status.Format()); + } +} + +void ThreadStackManagerImpl::_ProcessThreadActivity() {} + +bool ThreadStackManagerImpl::_HaveRouteToAddress(const Inet::IPAddress & destAddr) +{ + if (!mProxy || !_IsThreadAttached()) + { + return false; + } + if (destAddr.IsIPv6LinkLocal()) + { + return true; + } + + GAutoPtr routes(openthread_io_openthread_border_router_dup_external_routes(mProxy.get())); + if (!routes) + return false; + + if (g_variant_n_children(routes.get()) > 0) + { + GAutoPtr iter; + g_variant_get(routes.get(), "av", &iter.GetReceiver()); + if (!iter) + return false; + + GVariant * route; + while (g_variant_iter_loop(iter.get(), "&v", &route)) + { + if (route == nullptr) + continue; + GAutoPtr prefix; + guint16 rloc16; + guchar preference; + gboolean stable; + gboolean nextHopIsThisDevice; + g_variant_get(route, "(&vqybb)", &prefix.GetReceiver(), &rloc16, &preference, &stable, &nextHopIsThisDevice); + if (!prefix) + continue; + + GAutoPtr address; + guchar prefixLength; + g_variant_get(prefix.get(), "(&vy)", &address.GetReceiver(), &prefixLength); + if (!address) + continue; + + GBytes * bytes = g_variant_get_data_as_bytes(address.get()); // the ownership still hold by address + if (bytes == nullptr) + continue; + gsize size; + gconstpointer data = g_bytes_get_data(bytes, &size); + if (data == nullptr) + continue; + if (size != sizeof(struct in6_addr)) + continue; + + Inet::IPPrefix p; + p.IPAddr = Inet::IPAddress(*reinterpret_cast(data)); + p.Length = prefixLength; + + if (p.MatchAddress(destAddr)) + { + return true; + } + } + } + + return false; +} + +void ThreadStackManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) +{ + (void) event; + // The otbr-agent processes the Thread state handling by itself so there + // isn't much to do in the Chip stack. +} + +CHIP_ERROR ThreadStackManagerImpl::_SetThreadProvision(ByteSpan netInfo) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(Thread::OperationalDataset::IsValid(netInfo), CHIP_ERROR_INVALID_ARGUMENT); + + SetActiveDatasetContext context = { mProxy.get(), netInfo }; + CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextSetActiveDataset, &context); + VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to set active dataset")); + + // post an event alerting other subsystems about change in provisioning state + ChipDeviceEvent event; + event.Type = DeviceEventType::kServiceProvisioningChange; + event.ServiceProvisioningChange.IsServiceProvisioned = true; + return PlatformMgr().PostEvent(&event); +} + +CHIP_ERROR ThreadStackManagerImpl::_GetThreadProvision(Thread::OperationalDataset & dataset) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + + { + GAutoPtr err; + GAutoPtr response(g_dbus_proxy_call_sync(G_DBUS_PROXY(mProxy.get()), "org.freedesktop.DBus.Properties.Get", + g_variant_new("(ss)", "io.openthread.BorderRouter", "ActiveDatasetTlvs"), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err.GetReceiver())); + + if (err) + { + ChipLogError(DeviceLayer, "openthread: failed to read ActiveDatasetTlvs property: %s", err->message); + return CHIP_ERROR_INTERNAL; + } + + // Note: The actual value is wrapped by a GVariant container, wrapped in another GVariant with tuple type. + + if (response == nullptr) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + GAutoPtr tupleContent(g_variant_get_child_value(response.get(), 0)); + + if (tupleContent == nullptr) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + GAutoPtr value(g_variant_get_variant(tupleContent.get())); + + if (value == nullptr) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + gsize size; + const uint8_t * data = reinterpret_cast(g_variant_get_fixed_array(value.get(), &size, sizeof(guchar))); + ReturnErrorOnFailure(mDataset.Init(ByteSpan(data, size))); + } + + dataset.Init(mDataset.AsByteSpan()); + + return CHIP_NO_ERROR; +} + +bool ThreadStackManagerImpl::_IsThreadProvisioned() +{ + return static_cast(mDataset).IsCommissioned(); +} + +void ThreadStackManagerImpl::_ErasePersistentInfo() +{ + static_cast(mDataset).Clear(); +} + +bool ThreadStackManagerImpl::_IsThreadEnabled() +{ + VerifyOrReturnError(mProxy, false); + + GAutoPtr err; + GAutoPtr response(g_dbus_proxy_call_sync(G_DBUS_PROXY(mProxy.get()), "org.freedesktop.DBus.Properties.Get", + g_variant_new("(ss)", "io.openthread.BorderRouter", "DeviceRole"), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err.GetReceiver())); + + if (err) + { + ChipLogError(DeviceLayer, "openthread: failed to read DeviceRole property: %s", err->message); + return false; + } + + if (response == nullptr) + { + return false; + } + + GAutoPtr tupleContent(g_variant_get_child_value(response.get(), 0)); + + if (tupleContent == nullptr) + { + return false; + } + + GAutoPtr value(g_variant_get_variant(tupleContent.get())); + + if (value == nullptr) + { + return false; + } + + const gchar * role = g_variant_get_string(value.get(), nullptr); + + if (role == nullptr) + { + return false; + } + + return (strcmp(role, kOpenthreadDeviceRoleDisabled) != 0); +} + +bool ThreadStackManagerImpl::_IsThreadAttached() const +{ + return mAttached; +} + +CHIP_ERROR ThreadStackManagerImpl::GLibMatterContextCallAttach(ThreadStackManagerImpl * self) +{ + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + openthread_io_openthread_border_router_call_attach(self->mProxy.get(), nullptr, _OnThreadBrAttachFinished, self); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ThreadStackManagerImpl::_SetThreadEnabled(bool val) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + if (val) + { + CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextCallAttach, this); + VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to attach")); + } + else + { + GAutoPtr err; + gboolean result = openthread_io_openthread_border_router_call_reset_sync(mProxy.get(), nullptr, &err.GetReceiver()); + if (err) + { + ChipLogError(DeviceLayer, "openthread: _SetThreadEnabled calling %s failed: %s", "Reset", err->message); + return CHIP_ERROR_INTERNAL; + } + + if (!result) + { + ChipLogError(DeviceLayer, "openthread: _SetThreadEnabled calling %s failed: %s", "Reset", "return false"); + return CHIP_ERROR_INTERNAL; + } + } + return CHIP_NO_ERROR; +} + +void ThreadStackManagerImpl::_OnThreadBrAttachFinished(GObject * source_object, GAsyncResult * res, gpointer user_data) +{ + ThreadStackManagerImpl * this_ = reinterpret_cast(user_data); + GAutoPtr attachRes; + GAutoPtr err; + { + gboolean result = openthread_io_openthread_border_router_call_attach_finish(this_->mProxy.get(), res, &err.GetReceiver()); + if (!result) + { + ChipLogError(DeviceLayer, "Failed to perform finish Thread network scan: %s", + err == nullptr ? "unknown error" : err->message); + DeviceLayer::SystemLayer().ScheduleLambda([this_]() { + if (this_->mpConnectCallback != nullptr) + { + // TODO: Replace this with actual thread attach result. + this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kUnknownError, CharSpan(), 0); + this_->mpConnectCallback = nullptr; + } + }); + } + else + { + DeviceLayer::SystemLayer().ScheduleLambda([this_]() { + if (this_->mpConnectCallback != nullptr) + { + // TODO: Replace this with actual thread attach result. + this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kSuccess, CharSpan(), 0); + this_->mpConnectCallback = nullptr; + } + }); + } + } +} + +ConnectivityManager::ThreadDeviceType ThreadStackManagerImpl::_GetThreadDeviceType() +{ + ConnectivityManager::ThreadDeviceType type = ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + if (!mProxy) + { + ChipLogError(DeviceLayer, "Cannot get device role with Thread api client: %s", ""); + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + } + + GAutoPtr role(openthread_io_openthread_border_router_dup_device_role(mProxy.get())); + if (!role) + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + if (strcmp(role.get(), kOpenthreadDeviceRoleDetached) == 0 || strcmp(role.get(), kOpenthreadDeviceRoleDisabled) == 0) + { + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + } + if (strcmp(role.get(), kOpenthreadDeviceRoleChild) == 0) + { + GAutoPtr linkMode(openthread_io_openthread_border_router_dup_link_mode(mProxy.get())); + if (!linkMode) + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + gboolean rx_on_when_idle; + gboolean device_type; + gboolean network_data; + g_variant_get(linkMode.get(), "(bbb)", &rx_on_when_idle, &device_type, &network_data); + if (!rx_on_when_idle) + { + type = ConnectivityManager::ThreadDeviceType::kThreadDeviceType_SleepyEndDevice; + } + else + { + type = device_type ? ConnectivityManager::ThreadDeviceType::kThreadDeviceType_FullEndDevice + : ConnectivityManager::ThreadDeviceType::kThreadDeviceType_MinimalEndDevice; + } + return type; + } + if (strcmp(role.get(), kOpenthreadDeviceRoleLeader) == 0 || strcmp(role.get(), kOpenthreadDeviceRoleRouter) == 0) + { + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_Router; + } + + ChipLogError(DeviceLayer, "Unknown Thread role: %s", role.get()); + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; +} + +CHIP_ERROR ThreadStackManagerImpl::_SetThreadDeviceType(ConnectivityManager::ThreadDeviceType deviceType) +{ + gboolean rx_on_when_idle = true; + gboolean device_type = true; + gboolean network_data = true; + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + if (deviceType == ConnectivityManager::ThreadDeviceType::kThreadDeviceType_MinimalEndDevice) + { + network_data = false; + } + else if (deviceType == ConnectivityManager::ThreadDeviceType::kThreadDeviceType_SleepyEndDevice) + { + rx_on_when_idle = false; + network_data = false; + } + + if (!network_data) + { + GAutoPtr linkMode(g_variant_new("(bbb)", rx_on_when_idle, device_type, network_data)); + if (!linkMode) + return CHIP_ERROR_NO_MEMORY; + openthread_io_openthread_border_router_set_link_mode(mProxy.get(), linkMode.release()); + } + + return CHIP_NO_ERROR; +} + +#if CHIP_CONFIG_ENABLE_ICD_SERVER +CHIP_ERROR ThreadStackManagerImpl::_SetPollingInterval(System::Clock::Milliseconds32 pollingInterval) +{ + (void) pollingInterval; + ChipLogError(DeviceLayer, "Set ICD Polling on linux"); + return CHIP_ERROR_NOT_IMPLEMENTED; +} +#endif /* CHIP_CONFIG_ENABLE_ICD_SERVER */ + +bool ThreadStackManagerImpl::_HaveMeshConnectivity() +{ + // TODO: Remove Weave legacy APIs + // For a leader with a child, the child is considered to have mesh connectivity + // and the leader is not, which is a very confusing definition. + // This API is Weave legacy and should be removed. + + ChipLogError(DeviceLayer, "HaveMeshConnectivity has confusing behavior and shouldn't be called"); + return false; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetAndLogThreadStatsCounters() +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetAndLogThreadTopologyMinimal() +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetAndLogThreadTopologyFull() +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetPrimary802154MACAddress(uint8_t * buf) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + guint64 extAddr = openthread_io_openthread_border_router_get_extended_address(mProxy.get()); + + for (size_t i = 0; i < sizeof(extAddr); i++) + { + buf[sizeof(uint64_t) - i - 1] = (extAddr & UINT8_MAX); + extAddr >>= CHAR_BIT; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetExternalIPv6Address(chip::Inet::IPAddress & addr) +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetPollPeriod(uint32_t & buf) +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_JoinerStart() +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::GLibMatterContextCallScan(ThreadStackManagerImpl * self) +{ + VerifyOrDie(g_main_context_get_thread_default() != nullptr); + openthread_io_openthread_border_router_call_scan(self->mProxy.get(), nullptr, _OnNetworkScanFinished, self); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ThreadStackManagerImpl::_StartThreadScan(ThreadDriver::ScanCallback * callback) +{ + // There is another ongoing scan request, reject the new one. + VerifyOrReturnError(mpScanCallback == nullptr, CHIP_ERROR_INCORRECT_STATE); + mpScanCallback = callback; + CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextCallScan, this); + VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to start scan")); + return CHIP_NO_ERROR; +} + +void ThreadStackManagerImpl::_OnNetworkScanFinished(GObject * source_object, GAsyncResult * res, gpointer user_data) +{ + ThreadStackManagerImpl * this_ = reinterpret_cast(user_data); + this_->_OnNetworkScanFinished(res); +} + +void ThreadStackManagerImpl::_OnNetworkScanFinished(GAsyncResult * res) +{ + GAutoPtr scan_result; + GAutoPtr err; + { + gboolean result = openthread_io_openthread_border_router_call_scan_finish(mProxy.get(), &scan_result.GetReceiver(), res, + &err.GetReceiver()); + if (!result) + { + ChipLogError(DeviceLayer, "Failed to perform finish Thread network scan: %s", + err == nullptr ? "unknown error" : err->message); + DeviceLayer::SystemLayer().ScheduleLambda([this]() { + if (mpScanCallback != nullptr) + { + LinuxScanResponseIterator iter(nullptr); + mpScanCallback->OnFinished(Status::kUnknownError, CharSpan(), &iter); + } + mpScanCallback = nullptr; + }); + } + } + + std::vector * scanResult = + new std::vector(); + + if (g_variant_n_children(scan_result.get()) > 0) + { + GAutoPtr iter; + g_variant_get(scan_result.get(), "a(tstayqqynyybb)", &iter.GetReceiver()); + if (!iter) + { + delete scanResult; + return; + } + + guint64 ext_address; + const gchar * network_name; + guint64 ext_panid; + const gchar * steering_data; + guint16 panid; + guint16 joiner_udp_port; + guint8 channel; + gint16 rssi; + guint8 lqi; + guint8 version; + gboolean is_native; + gboolean is_joinable; + + while (g_variant_iter_loop(iter.get(), "(tstayqqynyybb)", &ext_address, &network_name, &ext_panid, &steering_data, &panid, + &joiner_udp_port, &channel, &rssi, &lqi, &version, &is_native, &is_joinable)) + { + ChipLogProgress(DeviceLayer, + "Thread Network: %s (" ChipLogFormatX64 ") ExtPanId(" ChipLogFormatX64 ") RSSI %d LQI %u" + " Version %u", + network_name, ChipLogValueX64(ext_address), ChipLogValueX64(ext_panid), rssi, lqi, version); + NetworkCommissioning::ThreadScanResponse networkScanned; + networkScanned.panId = panid; + networkScanned.extendedPanId = ext_panid; + size_t networkNameLen = strlen(network_name); + if (networkNameLen > 16) + { + ChipLogProgress(DeviceLayer, "Network name is too long, ignore it."); + continue; + } + networkScanned.networkNameLen = static_cast(networkNameLen); + memcpy(networkScanned.networkName, network_name, networkNameLen); + networkScanned.channel = channel; + networkScanned.version = version; + networkScanned.extendedAddress = 0; + if (rssi > std::numeric_limits::max()) + { + networkScanned.rssi = std::numeric_limits::max(); + } + else if (rssi < std::numeric_limits::min()) + { + networkScanned.rssi = std::numeric_limits::min(); + } + else + { + networkScanned.rssi = static_cast(rssi); + } + networkScanned.lqi = lqi; + + scanResult->push_back(networkScanned); + } + } + + DeviceLayer::SystemLayer().ScheduleLambda([this, scanResult]() { + // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copiable. This results in the use of + // const_cast but should be fine for almost all cases, since we actually handled the ownership of this element to this + // lambda. + if (mpScanCallback != nullptr) + { + LinuxScanResponseIterator iter( + const_cast *>(scanResult)); + mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter); + mpScanCallback = nullptr; + } + delete const_cast *>(scanResult); + }); +} + +void ThreadStackManagerImpl::_ResetThreadNetworkDiagnosticsCounts() {} + +CHIP_ERROR +ThreadStackManagerImpl::_AttachToThreadNetwork(const Thread::OperationalDataset & dataset, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * callback) +{ + // Reset the previously set callback since it will never be called in case incorrect dataset was supplied. + mpConnectCallback = nullptr; + ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadEnabled(false)); + ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadProvision(dataset.AsByteSpan())); + + if (dataset.IsCommissioned()) + { + ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadEnabled(true)); + mpConnectCallback = callback; + } + + return CHIP_NO_ERROR; +} + +void ThreadStackManagerImpl::_UpdateNetworkStatus() +{ + // Thread is not enabled, then we are not trying to connect to the network. + VerifyOrReturn(IsThreadEnabled() && mpStatusChangeCallback != nullptr); + + Thread::OperationalDataset dataset; + uint8_t extpanid[Thread::kSizeExtendedPanId]; + + // If we have not provisioned any Thread network, return the status from last network scan, + // If we have provisioned a network, we assume the ot-br-posix is activitely connecting to that network. + CHIP_ERROR err = ThreadStackMgrImpl().GetThreadProvision(dataset); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get configured network when updating network status: %s", err.AsString()); + return; + } + + // The Thread network is not enabled, but has a different extended pan id. + VerifyOrReturn(dataset.GetExtendedPanId(extpanid) == CHIP_NO_ERROR); + + // We have already connected to the network, thus return success. + if (ThreadStackMgrImpl().IsThreadAttached()) + { + mpStatusChangeCallback->OnNetworkingStatusChange(Status::kSuccess, MakeOptional(ByteSpan(extpanid)), NullOptional); + } + else + { + mpStatusChangeCallback->OnNetworkingStatusChange(Status::kNetworkNotFound, MakeOptional(ByteSpan(extpanid)), NullOptional); + } +} + +ThreadStackManager & ThreadStackMgr() +{ + return chip::DeviceLayer::ThreadStackManagerImpl::sInstance; +} + +ThreadStackManagerImpl & ThreadStackMgrImpl() +{ + return chip::DeviceLayer::ThreadStackManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/ThreadStackManagerImpl.h b/src/platform/NuttX/ThreadStackManagerImpl.h new file mode 100644 index 00000000000000..5ef3d8ed36555f --- /dev/null +++ b/src/platform/NuttX/ThreadStackManagerImpl.h @@ -0,0 +1,179 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { + +template <> +struct GAutoPtrDeleter +{ + using deleter = GObjectDeleter; +}; + +namespace DeviceLayer { + +class ThreadStackManagerImpl : public ThreadStackManager +{ +public: + ThreadStackManagerImpl(); + + void + SetNetworkStatusChangeCallback(NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * statusChangeCallback) + { + mpStatusChangeCallback = statusChangeCallback; + } + + CHIP_ERROR _InitThreadStack(); + void _ProcessThreadActivity(); + + CHIP_ERROR _StartThreadTask() { return CHIP_NO_ERROR; } // Intentionally left blank + void _LockThreadStack() {} // Intentionally left blank + bool _TryLockThreadStack() { return false; } // Intentionally left blank + void _UnlockThreadStack() {} // Intentionally left blank + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD_SRP_CLIENT + void _WaitOnSrpClearAllComplete() {} + void _NotifySrpClearAllComplete() {} +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD_SRP_CLIENT + + bool _HaveRouteToAddress(const Inet::IPAddress & destAddr); + + void _OnPlatformEvent(const ChipDeviceEvent * event); + + CHIP_ERROR _GetThreadProvision(Thread::OperationalDataset & dataset); + + CHIP_ERROR _SetThreadProvision(ByteSpan netInfo); + + void _OnNetworkScanFinished(GAsyncResult * res); + static void _OnNetworkScanFinished(GObject * source_object, GAsyncResult * res, gpointer user_data); + + CHIP_ERROR GetExtendedPanId(uint8_t extPanId[Thread::kSizeExtendedPanId]); + + void _ErasePersistentInfo(); + + bool _IsThreadProvisioned(); + + bool _IsThreadEnabled(); + + bool _IsThreadAttached() const; + + CHIP_ERROR _AttachToThreadNetwork(const Thread::OperationalDataset & dataset, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * callback); + + CHIP_ERROR _SetThreadEnabled(bool val); + + void _OnThreadAttachFinished(void); + + void _UpdateNetworkStatus(); + + static void _OnThreadBrAttachFinished(GObject * source_object, GAsyncResult * res, gpointer user_data); + + ConnectivityManager::ThreadDeviceType _GetThreadDeviceType(); + + CHIP_ERROR _SetThreadDeviceType(ConnectivityManager::ThreadDeviceType deviceType); + +#if CHIP_CONFIG_ENABLE_ICD_SERVER + CHIP_ERROR _SetPollingInterval(System::Clock::Milliseconds32 pollingInterval); +#endif /* CHIP_CONFIG_ENABLE_ICD_SERVER */ + + bool _HaveMeshConnectivity(); + + CHIP_ERROR _GetAndLogThreadStatsCounters(); + + CHIP_ERROR _GetAndLogThreadTopologyMinimal(); + + CHIP_ERROR _GetAndLogThreadTopologyFull(); + + CHIP_ERROR _GetPrimary802154MACAddress(uint8_t * buf); + + CHIP_ERROR _GetExternalIPv6Address(chip::Inet::IPAddress & addr); + + CHIP_ERROR _GetPollPeriod(uint32_t & buf); + + CHIP_ERROR _JoinerStart(); + + void _ResetThreadNetworkDiagnosticsCounts(); + + CHIP_ERROR _StartThreadScan(NetworkCommissioning::ThreadDriver::ScanCallback * callback); + + ~ThreadStackManagerImpl() = default; + + static ThreadStackManagerImpl sInstance; + +private: + static constexpr char kDBusOpenThreadService[] = "io.openthread.BorderRouter.wpan0"; + static constexpr char kDBusOpenThreadObjectPath[] = "/io/openthread/BorderRouter/wpan0"; + + static constexpr char kOpenthreadDeviceRoleDisabled[] = "disabled"; + static constexpr char kOpenthreadDeviceRoleDetached[] = "detached"; + static constexpr char kOpenthreadDeviceRoleChild[] = "child"; + static constexpr char kOpenthreadDeviceRoleRouter[] = "router"; + static constexpr char kOpenthreadDeviceRoleLeader[] = "leader"; + + static constexpr char kPropertyDeviceRole[] = "DeviceRole"; + + struct ThreadNetworkScanned + { + uint16_t panId; + uint64_t extendedPanId; + uint8_t networkName[16]; + uint8_t networkNameLen; + uint16_t channel; + uint8_t version; + uint64_t extendedAddress; + int8_t rssi; + uint8_t lqi; + }; + + GAutoPtr mProxy; + + static CHIP_ERROR GLibMatterContextInitThreadStack(ThreadStackManagerImpl * self); + static CHIP_ERROR GLibMatterContextCallAttach(ThreadStackManagerImpl * self); + static CHIP_ERROR GLibMatterContextCallScan(ThreadStackManagerImpl * self); + static void OnDbusPropertiesChanged(OpenthreadIoOpenthreadBorderRouter * proxy, GVariant * changed_properties, + const gchar * const * invalidated_properties, gpointer user_data); + void ThreadDeviceRoleChangedHandler(const gchar * role); + + Thread::OperationalDataset mDataset = {}; + + NetworkCommissioning::ThreadDriver::ScanCallback * mpScanCallback; + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * mpConnectCallback; + NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * mpStatusChangeCallback = nullptr; + + bool mAttached; +}; + +inline void ThreadStackManagerImpl::_OnThreadAttachFinished(void) +{ + // stub for ThreadStackManager.h +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/NuttX/WirelessDefs.h b/src/platform/NuttX/WirelessDefs.h new file mode 100644 index 00000000000000..d01338fd03baf9 --- /dev/null +++ b/src/platform/NuttX/WirelessDefs.h @@ -0,0 +1,186 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * IEEE 802.11 Frame type definitions. + */ + +#pragma once + +/* Status codes (IEEE Std 802.11-2016, 9.4.1.9, Table 9-46) */ +#define WLAN_STATUS_SUCCESS 0 +#define WLAN_STATUS_UNSPECIFIED_FAILURE 1 +#define WLAN_STATUS_TDLS_WAKEUP_ALTERNATE 2 +#define WLAN_STATUS_TDLS_WAKEUP_REJECT 3 +#define WLAN_STATUS_SECURITY_DISABLED 5 +#define WLAN_STATUS_UNACCEPTABLE_LIFETIME 6 +#define WLAN_STATUS_NOT_IN_SAME_BSS 7 +#define WLAN_STATUS_CAPS_UNSUPPORTED 10 +#define WLAN_STATUS_REASSOC_NO_ASSOC 11 +#define WLAN_STATUS_ASSOC_DENIED_UNSPEC 12 +#define WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG 13 +#define WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION 14 +#define WLAN_STATUS_CHALLENGE_FAIL 15 +#define WLAN_STATUS_AUTH_TIMEOUT 16 +#define WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA 17 +#define WLAN_STATUS_ASSOC_DENIED_RATES 18 +#define WLAN_STATUS_ASSOC_DENIED_NOSHORT 19 +#define WLAN_STATUS_SPEC_MGMT_REQUIRED 22 +#define WLAN_STATUS_PWR_CAPABILITY_NOT_VALID 23 +#define WLAN_STATUS_SUPPORTED_CHANNEL_NOT_VALID 24 +#define WLAN_STATUS_ASSOC_DENIED_NO_SHORT_SLOT_TIME 25 +#define WLAN_STATUS_ASSOC_DENIED_NO_HT 27 +#define WLAN_STATUS_R0KH_UNREACHABLE 28 +#define WLAN_STATUS_ASSOC_DENIED_NO_PCO 29 +#define WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY 30 +#define WLAN_STATUS_ROBUST_MGMT_FRAME_POLICY_VIOLATION 31 +#define WLAN_STATUS_UNSPECIFIED_QOS_FAILURE 32 +#define WLAN_STATUS_DENIED_INSUFFICIENT_BANDWIDTH 33 +#define WLAN_STATUS_DENIED_POOR_CHANNEL_CONDITIONS 34 +#define WLAN_STATUS_DENIED_QOS_NOT_SUPPORTED 35 +#define WLAN_STATUS_REQUEST_DECLINED 37 +#define WLAN_STATUS_INVALID_PARAMETERS 38 +#define WLAN_STATUS_REJECTED_WITH_SUGGESTED_CHANGES 39 +#define WLAN_STATUS_INVALID_IE 40 +#define WLAN_STATUS_GROUP_CIPHER_NOT_VALID 41 +#define WLAN_STATUS_PAIRWISE_CIPHER_NOT_VALID 42 +#define WLAN_STATUS_AKMP_NOT_VALID 43 +#define WLAN_STATUS_UNSUPPORTED_RSN_IE_VERSION 44 +#define WLAN_STATUS_INVALID_RSN_IE_CAPAB 45 +#define WLAN_STATUS_CIPHER_REJECTED_PER_POLICY 46 +#define WLAN_STATUS_TS_NOT_CREATED 47 +#define WLAN_STATUS_DIRECT_LINK_NOT_ALLOWED 48 +#define WLAN_STATUS_DEST_STA_NOT_PRESENT 49 +#define WLAN_STATUS_DEST_STA_NOT_QOS_STA 50 +#define WLAN_STATUS_ASSOC_DENIED_LISTEN_INT_TOO_LARGE 51 +#define WLAN_STATUS_INVALID_FT_ACTION_FRAME_COUNT 52 +#define WLAN_STATUS_INVALID_PMKID 53 +#define WLAN_STATUS_INVALID_MDIE 54 +#define WLAN_STATUS_INVALID_FTIE 55 +#define WLAN_STATUS_REQUESTED_TCLAS_NOT_SUPPORTED 56 +#define WLAN_STATUS_INSUFFICIENT_TCLAS_PROCESSING_RESOURCES 57 +#define WLAN_STATUS_TRY_ANOTHER_BSS 58 +#define WLAN_STATUS_GAS_ADV_PROTO_NOT_SUPPORTED 59 +#define WLAN_STATUS_NO_OUTSTANDING_GAS_REQ 60 +#define WLAN_STATUS_GAS_RESP_NOT_RECEIVED 61 +#define WLAN_STATUS_STA_TIMED_OUT_WAITING_FOR_GAS_RESP 62 +#define WLAN_STATUS_GAS_RESP_LARGER_THAN_LIMIT 63 +#define WLAN_STATUS_REQ_REFUSED_HOME 64 +#define WLAN_STATUS_ADV_SRV_UNREACHABLE 65 +#define WLAN_STATUS_REQ_REFUSED_SSPN 67 +#define WLAN_STATUS_REQ_REFUSED_UNAUTH_ACCESS 68 +#define WLAN_STATUS_INVALID_RSNIE 72 +#define WLAN_STATUS_U_APSD_COEX_NOT_SUPPORTED 73 +#define WLAN_STATUS_U_APSD_COEX_MODE_NOT_SUPPORTED 74 +#define WLAN_STATUS_BAD_INTERVAL_WITH_U_APSD_COEX 75 +#define WLAN_STATUS_ANTI_CLOGGING_TOKEN_REQ 76 +#define WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED 77 +#define WLAN_STATUS_CANNOT_FIND_ALT_TBTT 78 +#define WLAN_STATUS_TRANSMISSION_FAILURE 79 +#define WLAN_STATUS_REQ_TCLAS_NOT_SUPPORTED 80 +#define WLAN_STATUS_TCLAS_RESOURCES_EXCHAUSTED 81 +#define WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION 82 +#define WLAN_STATUS_REJECT_WITH_SCHEDULE 83 +#define WLAN_STATUS_REJECT_NO_WAKEUP_SPECIFIED 84 +#define WLAN_STATUS_SUCCESS_POWER_SAVE_MODE 85 +#define WLAN_STATUS_PENDING_ADMITTING_FST_SESSION 86 +#define WLAN_STATUS_PERFORMING_FST_NOW 87 +#define WLAN_STATUS_PENDING_GAP_IN_BA_WINDOW 88 +#define WLAN_STATUS_REJECT_U_PID_SETTING 89 +#define WLAN_STATUS_REFUSED_EXTERNAL_REASON 92 +#define WLAN_STATUS_REFUSED_AP_OUT_OF_MEMORY 93 +#define WLAN_STATUS_REJECTED_EMERGENCY_SERVICE_NOT_SUPPORTED 94 +#define WLAN_STATUS_QUERY_RESP_OUTSTANDING 95 +#define WLAN_STATUS_REJECT_DSE_BAND 96 +#define WLAN_STATUS_TCLAS_PROCESSING_TERMINATED 97 +#define WLAN_STATUS_TS_SCHEDULE_CONFLICT 98 +#define WLAN_STATUS_DENIED_WITH_SUGGESTED_BAND_AND_CHANNEL 99 +#define WLAN_STATUS_MCCAOP_RESERVATION_CONFLICT 100 +#define WLAN_STATUS_MAF_LIMIT_EXCEEDED 101 +#define WLAN_STATUS_MCCA_TRACK_LIMIT_EXCEEDED 102 +#define WLAN_STATUS_DENIED_DUE_TO_SPECTRUM_MANAGEMENT 103 +#define WLAN_STATUS_ASSOC_DENIED_NO_VHT 104 +#define WLAN_STATUS_ENABLEMENT_DENIED 105 +#define WLAN_STATUS_RESTRICTION_FROM_AUTHORIZED_GDB 106 +#define WLAN_STATUS_AUTHORIZATION_DEENABLED 107 +#define WLAN_STATUS_FILS_AUTHENTICATION_FAILURE 112 +#define WLAN_STATUS_UNKNOWN_AUTHENTICATION_SERVER 113 +#define WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER 123 + +/* Reason codes (IEEE Std 802.11-2016, 9.4.1.7, Table 9-45) */ +#define WLAN_REASON_UNSPECIFIED 1 +#define WLAN_REASON_PREV_AUTH_NOT_VALID 2 +#define WLAN_REASON_DEAUTH_LEAVING 3 +#define WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY 4 +#define WLAN_REASON_DISASSOC_AP_BUSY 5 +#define WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA 6 +#define WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA 7 +#define WLAN_REASON_DISASSOC_STA_HAS_LEFT 8 +#define WLAN_REASON_STA_REQ_ASSOC_WITHOUT_AUTH 9 +#define WLAN_REASON_PWR_CAPABILITY_NOT_VALID 10 +#define WLAN_REASON_SUPPORTED_CHANNEL_NOT_VALID 11 +#define WLAN_REASON_BSS_TRANSITION_DISASSOC 12 +#define WLAN_REASON_INVALID_IE 13 +#define WLAN_REASON_MICHAEL_MIC_FAILURE 14 +#define WLAN_REASON_4WAY_HANDSHAKE_TIMEOUT 15 +#define WLAN_REASON_GROUP_KEY_UPDATE_TIMEOUT 16 +#define WLAN_REASON_IE_IN_4WAY_DIFFERS 17 +#define WLAN_REASON_GROUP_CIPHER_NOT_VALID 18 +#define WLAN_REASON_PAIRWISE_CIPHER_NOT_VALID 19 +#define WLAN_REASON_AKMP_NOT_VALID 20 +#define WLAN_REASON_UNSUPPORTED_RSN_IE_VERSION 21 +#define WLAN_REASON_INVALID_RSN_IE_CAPAB 22 +#define WLAN_REASON_IEEE_802_1X_AUTH_FAILED 23 +#define WLAN_REASON_CIPHER_SUITE_REJECTED 24 +#define WLAN_REASON_TDLS_TEARDOWN_UNREACHABLE 25 +#define WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED 26 +#define WLAN_REASON_SSP_REQUESTED_DISASSOC 27 +#define WLAN_REASON_NO_SSP_ROAMING_AGREEMENT 28 +#define WLAN_REASON_BAD_CIPHER_OR_AKM 29 +#define WLAN_REASON_NOT_AUTHORIZED_THIS_LOCATION 30 +#define WLAN_REASON_SERVICE_CHANGE_PRECLUDES_TS 31 +#define WLAN_REASON_UNSPECIFIED_QOS_REASON 32 +#define WLAN_REASON_NOT_ENOUGH_BANDWIDTH 33 +#define WLAN_REASON_DISASSOC_LOW_ACK 34 +#define WLAN_REASON_EXCEEDED_TXOP 35 +#define WLAN_REASON_STA_LEAVING 36 +#define WLAN_REASON_END_TS_BA_DLS 37 +#define WLAN_REASON_UNKNOWN_TS_BA 38 +#define WLAN_REASON_TIMEOUT 39 +#define WLAN_REASON_PEERKEY_MISMATCH 45 +#define WLAN_REASON_AUTHORIZED_ACCESS_LIMIT_REACHED 46 +#define WLAN_REASON_EXTERNAL_SERVICE_REQUIREMENTS 47 +#define WLAN_REASON_INVALID_FT_ACTION_FRAME_COUNT 48 +#define WLAN_REASON_INVALID_PMKID 49 +#define WLAN_REASON_INVALID_MDE 50 +#define WLAN_REASON_INVALID_FTE 51 +#define WLAN_REASON_MESH_PEERING_CANCELLED 52 +#define WLAN_REASON_MESH_MAX_PEERS 53 +#define WLAN_REASON_MESH_CONFIG_POLICY_VIOLATION 54 +#define WLAN_REASON_MESH_CLOSE_RCVD 55 +#define WLAN_REASON_MESH_MAX_RETRIES 56 +#define WLAN_REASON_MESH_CONFIRM_TIMEOUT 57 +#define WLAN_REASON_MESH_INVALID_GTK 58 +#define WLAN_REASON_MESH_INCONSISTENT_PARAMS 59 +#define WLAN_REASON_MESH_INVALID_SECURITY_CAP 60 +#define WLAN_REASON_MESH_PATH_ERROR_NO_PROXY_INFO 61 +#define WLAN_REASON_MESH_PATH_ERROR_NO_FORWARDING_INFO 62 +#define WLAN_REASON_MESH_PATH_ERROR_DEST_UNREACHABLE 63 +#define WLAN_REASON_MAC_ADDRESS_ALREADY_EXISTS_IN_MBSS 64 +#define WLAN_REASON_MESH_CHANNEL_SWITCH_REGULATORY_REQ 65 +#define WLAN_REASON_MESH_CHANNEL_SWITCH_UNSPECIFIED 66 diff --git a/src/platform/NuttX/args.gni b/src/platform/NuttX/args.gni new file mode 100644 index 00000000000000..48dfba5396a100 --- /dev/null +++ b/src/platform/NuttX/args.gni @@ -0,0 +1,15 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +chip_device_platform = "nuttx" diff --git a/src/platform/device.gni b/src/platform/device.gni index dbbc38abf4ca88..332b4b3ee85716 100644 --- a/src/platform/device.gni +++ b/src/platform/device.gni @@ -40,6 +40,8 @@ if (chip_device_platform == "auto") { chip_device_platform = "webos" } else if (current_os == "zephyr") { chip_device_platform = "zephyr" + } else if (current_os == "nuttx") { + chip_device_platform = "nuttx" } else { chip_device_platform = "none" } @@ -92,7 +94,8 @@ declare_args() { chip_device_platform == "ameba" || chip_device_platform == "webos" || chip_device_platform == "cc32xx" || chip_device_platform == "mw320" || chip_device_platform == "beken" || chip_device_platform == "mt793x" || - chip_device_platform == "asr" || chip_device_platform == "openiotsdk") { + chip_device_platform == "asr" || chip_device_platform == "openiotsdk" || + chip_device_platform == "nuttx") { chip_mdns = "minimal" } else if (chip_device_platform == "darwin" || chip_device_platform == "cc13x4_26x4" || current_os == "android" || @@ -106,7 +109,7 @@ declare_args() { # Enable Subscription persistence / resumption for CI and supported platforms if (chip_device_platform == "darwin" || chip_device_platform == "linux" || chip_device_platform == "fake" || chip_device_platform == "efr32" || - chip_device_platform == "SiWx917") { + chip_device_platform == "SiWx917" || chip_device_platform == "nuttx") { chip_persist_subscriptions = true } else { chip_persist_subscriptions = false @@ -189,6 +192,8 @@ if (chip_device_platform == "cc13x4_26x4") { _chip_device_layer = "openiotsdk" } else if (chip_device_platform == "asr") { _chip_device_layer = "ASR" +} else if (chip_device_platform == "nuttx") { + _chip_device_layer = "NuttX" } else if (chip_device_platform == "stm32") { _chip_device_layer = "stm32" } @@ -261,5 +266,5 @@ assert( chip_device_platform == "bl702l" || chip_device_platform == "mt793x" || chip_device_platform == "SiWx917" || chip_device_platform == "openiotsdk" || chip_device_platform == "asr" || - chip_device_platform == "stm32", + chip_device_platform == "stm32" || chip_device_platform == "nuttx", "Please select a valid value for chip_device_platform")