diff --git a/examples/chip-tool/BUILD.gn b/examples/chip-tool/BUILD.gn index 4c19ca0d627708..74d713d85c5d3a 100644 --- a/examples/chip-tool/BUILD.gn +++ b/examples/chip-tool/BUILD.gn @@ -28,10 +28,6 @@ import("${chip_root}/src/crypto/crypto.gni") assert(chip_build_tools) -# Thread Commissioner is using newer version of mbedtls -_enable_thread_meshcop = chip_device_platform == "linux" && - chip_enable_ot_commissioner && chip_crypto != "mbedtls" - config("config") { include_dirs = [ ".", @@ -55,12 +51,6 @@ config("config") { defines += [ "CONFIG_ENABLE_HTTPS_REQUESTS" ] } - if (_enable_thread_meshcop) { - defines += [ "CHIP_ENABLE_OT_COMMISSIONER=1" ] - } else { - defines += [ "CHIP_ENABLE_OT_COMMISSIONER=0" ] - } - cflags = [ "-Wconversion" ] } @@ -155,11 +145,6 @@ static_library("chip-tool-utils") { [ "${chip_root}/examples/common/tracing:trace_handlers_decoder" ] } - if (_enable_thread_meshcop) { - sources += [ "commands/pairing/CommissionProxy.cpp" ] - public_deps += [ "${chip_root}/third_party/ot-commissioner" ] - } - output_dir = root_out_dir } diff --git a/examples/chip-tool/args.gni b/examples/chip-tool/args.gni index 30f4000365e4ef..9c163f1cabd2b1 100644 --- a/examples/chip-tool/args.gni +++ b/examples/chip-tool/args.gni @@ -31,3 +31,4 @@ matter_log_json_payload_decode_full = true # make chip-tool very strict by default chip_tlv_validate_char_string_on_read = true chip_tlv_validate_char_string_on_write = true +chip_support_thread_meshcop = true diff --git a/examples/chip-tool/chip-tool.gni b/examples/chip-tool/chip-tool.gni index 743164c8a5b09e..0bc5dcbb5cbed4 100644 --- a/examples/chip-tool/chip-tool.gni +++ b/examples/chip-tool/chip-tool.gni @@ -22,7 +22,4 @@ declare_args() { config_enable_yaml_tests = true config_use_local_storage = true config_enable_https_requests = true - - # Disable OpenThread Commissioner if cross-compiling by default. - chip_enable_ot_commissioner = current_cpu == host_cpu && current_os == host_os } diff --git a/examples/chip-tool/commands/pairing/Commands.h b/examples/chip-tool/commands/pairing/Commands.h index 55c51389c1a8f0..3a046867670048 100644 --- a/examples/chip-tool/commands/pairing/Commands.h +++ b/examples/chip-tool/commands/pairing/Commands.h @@ -53,6 +53,16 @@ class PairCodePase : public PairingCommand {} }; +#if CHIP_SUPPORT_THREAD_MESHCOP +class PairThreadMeshcop : public PairingCommand +{ +public: + PairThreadMeshcop(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("thread-meshcop", PairingMode::ThreadMeshcop, PairingNetworkType::Thread, credsIssuerConfig) + {} +}; +#endif + class PairCodeWifi : public PairingCommand { public: @@ -277,6 +287,9 @@ void registerCommandsPairing(Commands & commands, CredentialIssuerCommands * cre make_unique(credsIssuerConfig), #if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF make_unique(credsIssuerConfig), +#endif +#if CHIP_SUPPORT_THREAD_MESHCOP + make_unique(credsIssuerConfig), #endif make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), diff --git a/examples/chip-tool/commands/pairing/PairingCommand.cpp b/examples/chip-tool/commands/pairing/PairingCommand.cpp index cc6ad468a7d841..9fba19dd8ff319 100644 --- a/examples/chip-tool/commands/pairing/PairingCommand.cpp +++ b/examples/chip-tool/commands/pairing/PairingCommand.cpp @@ -106,13 +106,6 @@ CHIP_ERROR PairingCommand::RunInternal(NodeId remoteId) err = Unpair(remoteId); break; case PairingMode::Code: -#if CHIP_ENABLE_OT_COMMISSIONER - if (mThreadBaHost.HasValue() && mThreadBaPort.HasValue()) - { - err = PairWithMeshCoP(); - break; - } -#endif #if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF chip::DeviceLayer::ConnectivityMgr().WiFiPafSetApFreq( mApFreqStr.HasValue() ? static_cast(std::stol(mApFreqStr.Value())) : 0); @@ -148,6 +141,17 @@ CHIP_ERROR PairingCommand::RunInternal(NodeId remoteId) mApFreqStr.HasValue() ? static_cast(std::stol(mApFreqStr.Value())) : 0); err = Pair(remoteId, PeerAddress::WiFiPAF(remoteId)); break; +#endif +#if CHIP_SUPPORT_THREAD_MESHCOP + case PairingMode::ThreadMeshcop: { + Inet::IPAddress ipAddr; + + VerifyOrReturnError(mThreadBaHost.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mThreadBaPort.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(Inet::IPAddress::FromString(mThreadBaHost.Value(), ipAddr), CHIP_ERROR_INVALID_ADDRESS); + err = Pair(remoteId, PeerAddress::ThreadMeshcop(ipAddr, mThreadBaPort.Value())); + break; + } #endif case PairingMode::AlreadyDiscovered: err = Pair(remoteId, PeerAddress::UDP(mRemoteAddr.address, mRemotePort, mRemoteAddr.interfaceId)); @@ -303,12 +307,37 @@ CHIP_ERROR PairingCommand::PairWithCode(NodeId remoteId) CHIP_ERROR PairingCommand::Pair(NodeId remoteId, PeerAddress address) { - VerifyOrDieWithMsg(mSetupPINCode.has_value(), chipTool, "Using mSetupPINCode in a mode when we have not gotten one"); - auto params = RendezvousParameters().SetSetupPINCode(mSetupPINCode.value()).SetPeerAddress(address); - if (mDiscriminator.has_value()) + auto params = RendezvousParameters().SetPeerAddress(address); + if (mOnboardingPayload != nullptr) + { + SetupPayload payload; + + ReturnErrorOnFailure(ParseSetupPayload(payload, mOnboardingPayload)); + params.SetSetupPINCode(payload.setUpPINCode); + params.SetSetupDiscriminator(payload.discriminator); + } + else + { + VerifyOrDieWithMsg(mSetupPINCode.has_value(), chipTool, "Using mSetupPINCode in a mode when we have not gotten one"); + params.SetSetupPINCode(mSetupPINCode.value()); + if (mDiscriminator.has_value()) + { + params.SetDiscriminator(mDiscriminator.value()); + } + } + +#if CHIP_SUPPORT_THREAD_MESHCOP + if (address.GetTransportType() == Transport::Type::kThreadMeshcop) { - params.SetDiscriminator(mDiscriminator.value()); + CurrentCommissioner().RegisterDeviceDiscoveryDelegate(this); + CommissioningParameters commissioningParams = GetCommissioningParameters(); + + commissioningParams.SetThreadOperationalDataset(mOperationalDataset); + auto error = CurrentCommissioner().PairDevice(remoteId, params, commissioningParams); + CurrentCommissioner().RegisterDeviceDiscoveryDelegate(nullptr); + return error; } +#endif // CHIP_SUPPORT_THREAD_MESHCOP CHIP_ERROR err = CHIP_NO_ERROR; if (mPaseOnly.ValueOr(false)) @@ -419,57 +448,6 @@ CHIP_ERROR PairingCommand::PairWithMdns(NodeId remoteId) return CurrentCommissioner().DiscoverCommissionableNodes(filter); } -#if CHIP_ENABLE_OT_COMMISSIONER -CHIP_ERROR PairingCommand::PairWithMeshCoP() -{ - SetupPayload payload; - - ReturnErrorOnFailure(ParseSetupPayload(payload, mOnboardingPayload)); - - if (payload.rendezvousInformation.HasValue() && !payload.rendezvousInformation.Value().Has(RendezvousInformationFlag::kThread)) - { - // Proceed even if the device doesn't claim rendezvous over Thread MeshCoP because in-market devices may not - // be able to update their QR Code. - ChipLogProgress(chipTool, "WARNING: device may not support commissioning over Thread meshcop"); - } - - mSetupPINCode.emplace(payload.setUpPINCode); - - Thread::DiscoveryCode code; - if (payload.discriminator.IsShortDiscriminator()) - { - code = Thread::DiscoveryCode(payload.discriminator.GetShortValue()); - ChipLogProgress(chipTool, "Discovery code from short discriminator: 0x%" PRIx64, code.AsUInt64()); - } - else - { - code = Thread::DiscoveryCode(payload.discriminator.GetLongValue()); - ChipLogProgress(chipTool, "Discovery code from long discriminator: 0x%" PRIx64, code.AsUInt64()); - } - - uint8_t pskc[Thread::kSizePSKc]; - - { - Thread::OperationalDatasetView dataset; - ReturnErrorAndLogOnFailure(dataset.Init(mOperationalDataset), chipTool, "Failed to parse Thread dataset"); - - ReturnErrorAndLogOnFailure(dataset.GetPSKc(pskc), chipTool, "Failed to retrieve PSKc"); - } - - { - Dnssd::DiscoveredNodeData discoveredNodeData; - ReturnErrorOnFailure(mCommissionProxy.Discover(pskc, mThreadBaHost.Value(), mThreadBaPort.Value(), code, - payload.discriminator, discoveredNodeData, mTimeout.ValueOr(30))); - - CurrentCommissioner().RegisterDeviceDiscoveryDelegate(this); - CurrentCommissioner().OnNodeDiscovered(discoveredNodeData); - } - - ChipLogProgress(chipTool, "Joiner discovered"); - return CHIP_NO_ERROR; -} -#endif // CHIP_ENABLE_OT_COMMISSIONER - CHIP_ERROR PairingCommand::Unpair(NodeId remoteId) { mCurrentFabricRemover = Platform::MakeUnique(&CurrentCommissioner()); diff --git a/examples/chip-tool/commands/pairing/PairingCommand.h b/examples/chip-tool/commands/pairing/PairingCommand.h index 86ffbbd12ffcd9..b50201dadb2e39 100644 --- a/examples/chip-tool/commands/pairing/PairingCommand.h +++ b/examples/chip-tool/commands/pairing/PairingCommand.h @@ -19,9 +19,6 @@ #pragma once #include "../common/CHIPCommand.h" -#if CHIP_ENABLE_OT_COMMISSIONER -#include "CommissionProxy.h" -#endif #include #include @@ -47,6 +44,9 @@ enum class PairingMode AlreadyDiscoveredByIndexWithCode, OnNetwork, Nfc, +#if CHIP_SUPPORT_THREAD_MESHCOP + ThreadMeshcop, +#endif }; enum class PairingNetworkType @@ -109,6 +109,12 @@ class PairingCommand : public CHIPCommand, { case PairingMode::None: break; +#if CHIP_SUPPORT_THREAD_MESHCOP + case PairingMode::ThreadMeshcop: + AddArgument("thread-ba-host", &mThreadBaHost, "Thread Border Agent host"); + AddArgument("thread-ba-port", 0, UINT16_MAX, &mThreadBaPort, "Thread Border Agent port"); + FALLTHROUGH; +#endif case PairingMode::Code: AddArgument("skip-commissioning-complete", 0, 1, &mSkipCommissioningComplete); AddArgument("dcl-hostname", &mDCLHostName, @@ -119,10 +125,6 @@ class PairingCommand : public CHIPCommand, case PairingMode::CodePaseOnly: AddArgument("payload", &mOnboardingPayload); AddArgument("discover-once", 0, 1, &mDiscoverOnce); -#if CHIP_ENABLE_OT_COMMISSIONER - AddArgument("thread-ba-host", &mThreadBaHost, "Thread Border Agent host"); - AddArgument("thread-ba-port", 0, UINT16_MAX, &mThreadBaPort, "Thread Border Agent port"); -#endif AddArgument("use-only-onnetwork-discovery", 0, 1, &mUseOnlyOnNetworkDiscovery, "Whether to only use DNS-SD for discovery. The default is true if no network credentials are provided, " "false otherwise."); @@ -270,9 +272,6 @@ class PairingCommand : public CHIPCommand, CHIP_ERROR RunInternal(NodeId remoteId); CHIP_ERROR Pair(NodeId remoteId, PeerAddress address); CHIP_ERROR PairWithMdns(NodeId remoteId); -#if CHIP_ENABLE_OT_COMMISSIONER - CHIP_ERROR PairWithMeshCoP(); -#endif CHIP_ERROR PairWithCode(NodeId remoteId); CHIP_ERROR PaseWithCode(NodeId remoteId); CHIP_ERROR PairWithMdnsOrBleByIndex(NodeId remoteId, uint16_t index); @@ -341,10 +340,9 @@ class PairingCommand : public CHIPCommand, static void OnCurrentFabricRemove(void * context, NodeId remoteNodeId, CHIP_ERROR status); void PersistIcdInfo(); -#if CHIP_ENABLE_OT_COMMISSIONER +#if CHIP_SUPPORT_THREAD_MESHCOP chip::Optional mThreadBaHost; chip::Optional mThreadBaPort; - CommissionProxy mCommissionProxy; #endif std::optional mPrompterThread; diff --git a/examples/jf-control-app/args.gni b/examples/jf-control-app/args.gni index 3801ef04cc3fb8..3db324d4b6db65 100644 --- a/examples/jf-control-app/args.gni +++ b/examples/jf-control-app/args.gni @@ -38,3 +38,4 @@ chip_tlv_validate_char_string_on_write = true # enable Joint Fabric features in the core SDK code # (e.g.: commissioner related code from src/controller) chip_device_config_enable_joint_fabric = true +chip_support_thread_meshcop = false diff --git a/integrations/docker/images/stage-2/chip-cirque-device-base/Dockerfile b/integrations/docker/images/stage-2/chip-cirque-device-base/Dockerfile index 5bb085051724c7..65196e3b06c14c 100644 --- a/integrations/docker/images/stage-2/chip-cirque-device-base/Dockerfile +++ b/integrations/docker/images/stage-2/chip-cirque-device-base/Dockerfile @@ -28,6 +28,7 @@ RUN apt-get update \ libavahi-client3 \ libcairo2-dev \ libdbus-1-dev \ + libevent-dev \ libgif-dev \ libgirepository1.0-dev \ libglib2.0-dev \ diff --git a/scripts/build_python.sh b/scripts/build_python.sh index e1267a48781410..705a02a55bf82f 100755 --- a/scripts/build_python.sh +++ b/scripts/build_python.sh @@ -67,6 +67,7 @@ Input Options: -b, --enable_ble Enable BLE in the controller (default=$enable_ble) -p, --enable_wifi_paf Enable Wi-Fi PAF discovery in the controller (default=SDK default behavior) -4, --enable_ipv4 Enable IPv4 in the controller (default=$enable_ipv4) + -M, --enable_thread_meshcop Enable Thread Meshcop support. -d, --chip_detail_logging Specify ChipDetailLoggingValue as true or false. By default it is $chip_detail_logging. -m, --chip_mdns ChipMDNSValue Specify ChipMDNSValue as platform or minimal. @@ -127,6 +128,15 @@ while (($#)); do wifi_paf_config="chip_device_config_enable_wifipaf=$wifi_paf_arg" shift ;; + --enable_thread_meshcop | -M) + declare thread_meshcop_arg="$2" + if [[ "$thread_meshcop_arg" != "true" && "$thread_meshcop_arg" != "false" ]]; then + echo "Error: --enable_thread_meshcop/-M should have a true/false value, not '$thread_meshcop_arg'" >&2 + exit 1 + fi + thread_meshcop_config="chip_support_thread_meshcop=$thread_meshcop_arg" + shift + ;; --enable_ipv4 | -4) enable_ipv4=$2 if [[ "$enable_ipv4" != "true" && "$enable_ipv4" != "false" ]]; then @@ -233,6 +243,9 @@ echo " enable_nfc=\"$enable_nfc\"" if [[ -n $wifi_paf_config ]]; then echo " $wifi_paf_config" fi +if [[ -n $thread_meshcop_config ]]; then + echo " $thread_meshcop_config" +fi echo " enable_ipv4=\"$enable_ipv4\"" echo " chip_build_controller_dynamic_server=\"$chip_build_controller_dynamic_server\"" echo " chip_support_webrtc_python_bindings=\"$enable_webrtc\"" @@ -309,6 +322,9 @@ fi if [[ -n $wifi_paf_config ]]; then gn_args+=("$wifi_paf_config") fi +if [[ -n $thread_meshcop_config ]]; then + gn_args+=("$thread_meshcop_config") +fi # Append extra arguments provided by the user. gn_args+=("${extra_gn_args[@]}") diff --git a/scripts/tests/chiptest/test_definition.py b/scripts/tests/chiptest/test_definition.py index 2209ba182d07e4..d93565ecdf4790 100644 --- a/scripts/tests/chiptest/test_definition.py +++ b/scripts/tests/chiptest/test_definition.py @@ -510,7 +510,8 @@ def _RunImpl(self, target: TestTarget, runner: Runner, apps_register: AppsRegist if op_network == 'Thread': # The node id must not conflict with ThreadBorderRouter.NODE_ID subproc = subproc.with_args("--thread-node-id=2") - elif ble_controller_app is not None: + + if ble_controller_app is not None: subproc = subproc.with_args("--ble-controller", str(ble_controller_app)) if op_network == 'WiFi': subproc = subproc.with_args("--wifi") @@ -576,7 +577,7 @@ def _RunImpl(self, target: TestTarget, runner: Runner, apps_register: AppsRegist pairing_server_args = ["--ble-controller", str(ble_controller_tool)] elif op_network == 'Thread' and thread_ba_host is not None and thread_ba_port is not None: pairing_cmd = pairing_cmd.with_args( - "pairing", "code-thread", TEST_NODE_ID, f"hex:{TEST_THREAD_DATASET}", setupCode, + "pairing", "thread-meshcop", TEST_NODE_ID, f"hex:{TEST_THREAD_DATASET}", setupCode, "--thread-ba-host", thread_ba_host, "--thread-ba-port", str(thread_ba_port)) else: pairing_cmd = pairing_cmd.with_args('pairing', 'code', TEST_NODE_ID, setupCode) diff --git a/src/controller/BUILD.gn b/src/controller/BUILD.gn index 0636935fcb9df1..ee7d0948a530ff 100644 --- a/src/controller/BUILD.gn +++ b/src/controller/BUILD.gn @@ -15,6 +15,7 @@ import("//build_overrides/chip.gni") import("${chip_root}/src/app/common_flags.gni") import("${chip_root}/src/controller/flags.gni") +import("${chip_root}/src/crypto/crypto.gni") import("${chip_root}/src/lib/lib.gni") import("${chip_root}/src/platform/device.gni") @@ -123,5 +124,16 @@ static_library("controller") { public_deps += [ "${chip_root}/src/controller/webrtc:chip_webrtc" ] } + if (chip_device_platform == "linux" && chip_crypto != "mbedtls" && + chip_support_thread_meshcop && current_cpu == host_cpu && + current_os == host_os) { + sources += [ + "ThreadMeshcopCommissionProxy.cpp", + "ThreadMeshcopCommissionProxy.h", + ] + deps += [ "${chip_root}/src/lib/dnssd/minimal_mdns:minimal_mdns" ] + public_deps += [ "${chip_root}/third_party/ot-commissioner" ] + } + public_configs = [ "${chip_root}/src:includes" ] } diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index c1d8c2765b2dfa..88fd87038f21fa 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -703,10 +703,56 @@ CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParam return errorCode; } +#if CHIP_SUPPORT_THREAD_MESHCOP +CHIP_ERROR DeviceCommissioner::PairThreadMeshcop(RendezvousParameters & rendezvousParams, + CommissioningParameters & commissioningParams) +{ + VerifyOrReturnError(rendezvousParams.GetSetupDiscriminator().has_value(), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(commissioningParams.GetThreadOperationalDataset().HasValue(), CHIP_ERROR_INVALID_ARGUMENT); + auto discriminator = rendezvousParams.GetSetupDiscriminator().value(); + Thread::DiscoveryCode code; + if (rendezvousParams.GetSetupDiscriminator().value().IsShortDiscriminator()) + { + code = Thread::DiscoveryCode(discriminator.GetShortValue()); + ChipLogProgress(Controller, "Discovery code from short discriminator: 0x%" PRIx64, code.AsUInt64()); + } + else + { + code = Thread::DiscoveryCode(discriminator.GetLongValue()); + ChipLogProgress(Controller, "Discovery code from long discriminator: 0x%" PRIx64, code.AsUInt64()); + } + + uint8_t pskcBuffer[Thread::kSizePSKc]; + ByteSpan pskc(pskcBuffer); + { + Thread::OperationalDatasetView dataset; + ReturnErrorOnFailure(dataset.Init(commissioningParams.GetThreadOperationalDataset().Value())); + + ReturnErrorOnFailure(dataset.GetPSKc(pskcBuffer)); + } + + { + Dnssd::DiscoveredNodeData discoveredNodeData; + ReturnErrorOnFailure(mThreadMeshcopCommissionProxy.Discover(pskc, rendezvousParams.GetPeerAddress(), code, discriminator, + discoveredNodeData, 30)); + + ChipLogProgress(Controller, "Joiner discovered"); + OnNodeDiscovered(discoveredNodeData); + } + return CHIP_NO_ERROR; +} +#endif // CHIP_SUPPORT_THREAD_MESHCOP + CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParameters & rendezvousParams, CommissioningParameters & commissioningParams) { MATTER_TRACE_SCOPE("PairDevice", "DeviceCommissioner"); +#if CHIP_SUPPORT_THREAD_MESHCOP + if (rendezvousParams.GetPeerAddress().GetTransportType() == Transport::Type::kThreadMeshcop) + { + return PairThreadMeshcop(rendezvousParams, commissioningParams); + } +#endif ReturnErrorOnFailureWithMetric(kMetricDeviceCommissionerCommission, EstablishPASEConnection(remoteDeviceId, rendezvousParams)); auto errorCode = Commission(remoteDeviceId, commissioningParams); VerifyOrDoWithMetric(kMetricDeviceCommissionerCommission, CHIP_NO_ERROR == errorCode, errorCode); diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index f516257057db4b..ba246e966ca6d0 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -76,6 +76,10 @@ #endif #include +#if CHIP_SUPPORT_THREAD_MESHCOP +#include +#endif + namespace chip { namespace Controller { @@ -1137,6 +1141,10 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, bool IsAttestationInformationMissing(const CommissioningParameters & params); +#if CHIP_SUPPORT_THREAD_MESHCOP + CHIP_ERROR PairThreadMeshcop(RendezvousParameters & rendezvousParams, CommissioningParameters & commissioningParams); +#endif + chip::Callback::Callback mOnDeviceConnectedCallback; chip::Callback::Callback mOnDeviceConnectionFailureCallback; #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES @@ -1162,6 +1170,10 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, Optional mTrustedIcacPublicKeyB; EndpointId mPeerAdminJFAdminClusterEndpointId = kInvalidEndpointId; #endif + +#if CHIP_SUPPORT_THREAD_MESHCOP + ThreadMeshcopCommissionProxy mThreadMeshcopCommissionProxy; +#endif }; } // namespace Controller diff --git a/examples/chip-tool/commands/pairing/CommissionProxy.cpp b/src/controller/ThreadMeshcopCommissionProxy.cpp similarity index 67% rename from examples/chip-tool/commands/pairing/CommissionProxy.cpp rename to src/controller/ThreadMeshcopCommissionProxy.cpp index 9e4b827648b434..fe89154ba7714f 100644 --- a/examples/chip-tool/commands/pairing/CommissionProxy.cpp +++ b/src/controller/ThreadMeshcopCommissionProxy.cpp @@ -16,7 +16,7 @@ * */ -#include "CommissionProxy.h" +#include "ThreadMeshcopCommissionProxy.h" #include #include @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include #include @@ -46,7 +46,7 @@ class CommissionerLogger : public ot::commissioner::Logger public: void Log(ot::commissioner::LogLevel level, const std::string & region, const std::string & message) override { - ChipLogProgress(chipTool, "[ot-commissioner][%u][%s] %s", static_cast(level), region.c_str(), message.c_str()); + ChipLogProgress(Controller, "[ot-commissioner][%u][%s] %s", static_cast(level), region.c_str(), message.c_str()); } }; @@ -66,12 +66,15 @@ std::vector DiscoveryCodeToVector(Thread::DiscoveryCode code) } } // namespace -CommissionProxy::CommissionProxy() : mState(State::kConnecting), mPromiseFulfilled(false) +namespace chip { +namespace Controller { + +ThreadMeshcopCommissionProxy::ThreadMeshcopCommissionProxy() : mState(State::kConnecting), mPromiseFulfilled(false) { mCommissioner = ot::commissioner::Commissioner::Create(*this); } -CommissionProxy::~CommissionProxy() +ThreadMeshcopCommissionProxy::~ThreadMeshcopCommissionProxy() { if (mProxyFd != -1) { @@ -85,29 +88,29 @@ CommissionProxy::~CommissionProxy() } } -void CommissionProxy::SetState(State state) +void ThreadMeshcopCommissionProxy::SetState(State state) { mState = state; } -void CommissionProxy::OnHeader(mdns::Minimal::ConstHeaderRef & header) +void ThreadMeshcopCommissionProxy::OnHeader(mdns::Minimal::ConstHeaderRef & header) { - ChipLogDetail(chipTool, "mDNS Response: ID=%u, Answers=%u, Additional=%u", header.GetMessageId(), header.GetAnswerCount(), + ChipLogDetail(Controller, "mDNS Response: ID=%u, Answers=%u, Additional=%u", header.GetMessageId(), header.GetAnswerCount(), header.GetAdditionalCount()); } -void CommissionProxy::OnQuery(const mdns::Minimal::QueryData & data) +void ThreadMeshcopCommissionProxy::OnQuery(const mdns::Minimal::QueryData & data) { if (mState != State::kDiscovering) { - ChipLogProgress(chipTool, "Received mDNS query but proxy is not in discovery state"); + ChipLogProgress(Controller, "Received mDNS query but proxy is not in discovery state"); } - ChipLogDetail(chipTool, "mDNS query: %s", mdns::Minimal::QNameString(data.GetName()).c_str()); + ChipLogDetail(Controller, "mDNS query: %s", mdns::Minimal::QNameString(data.GetName()).c_str()); mNodeData.Set(); } -void CommissionProxy::OnResource(mdns::Minimal::ResourceType section, const mdns::Minimal::ResourceData & data) +void ThreadMeshcopCommissionProxy::OnResource(mdns::Minimal::ResourceType section, const mdns::Minimal::ResourceData & data) { if (mState != State::kDiscovering) { @@ -130,13 +133,13 @@ void CommissionProxy::OnResource(mdns::Minimal::ResourceType section, const mdns mdns::Minimal::SrvRecord srv; if (!srv.Parse(data.GetData(), mDnsPacket)) { - ChipLogError(chipTool, "Failed to parse mDNS SRV record"); + ChipLogError(Controller, "Failed to parse mDNS SRV record"); return; } if (!name.EndsWith(kMatterCServiceSuffix)) { - ChipLogDetail(chipTool, "Ignoring non-Matter service: %s", name.c_str()); + ChipLogDetail(Controller, "Ignoring non-Matter service: %s", name.c_str()); return; } @@ -156,7 +159,7 @@ void CommissionProxy::OnResource(mdns::Minimal::ResourceType section, const mdns CHIP_ERROR err = CreateProxySocket(commissionData); if (err != CHIP_NO_ERROR) { - ChipLogError(chipTool, "Failed to setup proxy socket: %" CHIP_ERROR_FORMAT, err.Format()); + ChipLogError(Controller, "Failed to setup proxy socket: %" CHIP_ERROR_FORMAT, err.Format()); SetState(State::kAborted); } } @@ -172,7 +175,7 @@ void CommissionProxy::OnResource(mdns::Minimal::ResourceType section, const mdns } } -CHIP_ERROR CommissionProxy::CreateProxySocket(chip::Dnssd::CommissionNodeData & commissionData) +CHIP_ERROR ThreadMeshcopCommissionProxy::CreateProxySocket(chip::Dnssd::CommissionNodeData & commissionData) { mProxyFd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); VerifyOrReturnError(mProxyFd >= 0, CHIP_ERROR_POSIX(errno)); @@ -202,11 +205,11 @@ CHIP_ERROR CommissionProxy::CreateProxySocket(chip::Dnssd::CommissionNodeData & commissionData.ipAddress[0] = Inet::IPAddress::FromSockAddr(addr); commissionData.interfaceId = Inet::InterfaceId::FromIPAddress(commissionData.ipAddress[0]); - ChipLogProgress(chipTool, "Proxy socket created on port %u", commissionData.port); + ChipLogProgress(Controller, "Proxy socket created on port %u", commissionData.port); return CHIP_NO_ERROR; } -void CommissionProxy::OnRecord(const mdns::Minimal::BytesRange & name, const mdns::Minimal::BytesRange & value) +void ThreadMeshcopCommissionProxy::OnRecord(const mdns::Minimal::BytesRange & name, const mdns::Minimal::BytesRange & value) { ByteSpan key(name.Start(), name.Size()); ByteSpan val(value.Start(), value.Size()); @@ -214,8 +217,8 @@ void CommissionProxy::OnRecord(const mdns::Minimal::BytesRange & name, const mdn Dnssd::FillNodeDataFromTxt(key, val, mNodeData.Get()); } -void CommissionProxy::ProcessAnnouncement(const std::vector & joinerIdBytes, uint16_t joinerPort, - const std::vector & payload) +void ThreadMeshcopCommissionProxy::ProcessAnnouncement(const std::vector & joinerIdBytes, uint16_t joinerPort, + const std::vector & payload) { std::lock_guard lock(mMutex); @@ -229,16 +232,16 @@ void CommissionProxy::ProcessAnnouncement(const std::vector & joinerIdB if (!mdns::Minimal::ParsePacket(mDnsPacket, this)) { - ChipLogError(chipTool, "Failed to parse joiner mDNS announcement"); + ChipLogError(Controller, "Failed to parse joiner mDNS announcement"); return; } uint32_t discoveredDiscriminator = mNodeData.Get().longDiscriminator; - ChipLogProgress(chipTool, "Discovered joiner with discriminator: %u", discoveredDiscriminator); + ChipLogProgress(Controller, "Discovered joiner with discriminator: %u", discoveredDiscriminator); if (!mExpectedDiscriminator.MatchesLongDiscriminator(static_cast(discoveredDiscriminator))) { - ChipLogProgress(chipTool, "Discriminator mismatch (Expected %u, Got %u). Ignoring announcement.", + ChipLogProgress(Controller, "Discriminator mismatch (Expected %u, Got %u). Ignoring announcement.", mExpectedDiscriminator.GetLongValue(), discoveredDiscriminator); return; } @@ -267,7 +270,7 @@ void CommissionProxy::ProcessAnnouncement(const std::vector & joinerIdB int rval = connect(mProxyFd, reinterpret_cast(&addr), len); if (rval < 0) { - ChipLogError(chipTool, "Failed to connect to Matter Commissioner: %s", strerror(errno)); + ChipLogError(Controller, "Failed to connect to Matter Commissioner: %s", strerror(errno)); continue; } SetState(State::kCommissioning); @@ -280,21 +283,21 @@ void CommissionProxy::ProcessAnnouncement(const std::vector & joinerIdB auto error = mCommissioner->SendToJoiner(id, mServicePort, pkt); if (error != ot::commissioner::ErrorCode::kNone) { - ChipLogError(chipTool, "Failed to send packet to joiner: %s", error.GetMessage().c_str()); + ChipLogError(Controller, "Failed to send packet to joiner: %s", error.GetMessage().c_str()); return; } break; } default: - ChipLogError(chipTool, "Invalid CommissionProxy state: %d", static_cast(mState.load())); + ChipLogError(Controller, "Invalid CommissionProxy state: %d", static_cast(mState.load())); return; } } }); } -void CommissionProxy::OnJoinerMessage(const std::vector & joinerIdBytes, uint16_t joinerPort, - const std::vector & payload) +void ThreadMeshcopCommissionProxy::OnJoinerMessage(const std::vector & joinerIdBytes, uint16_t joinerPort, + const std::vector & payload) { std::lock_guard lock(mMutex); @@ -304,7 +307,7 @@ void CommissionProxy::OnJoinerMessage(const std::vector & joinerIdBytes } uint64_t joinerId = JoinerIdFromBytes(joinerIdBytes); - ChipLogDetail(chipTool, "Message from joiner 0x%" PRIx64 " on port %u", joinerId, joinerPort); + ChipLogDetail(Controller, "Message from joiner 0x%" PRIx64 " on port %u", joinerId, joinerPort); if (mJoinerId == 0) { @@ -312,7 +315,7 @@ void CommissionProxy::OnJoinerMessage(const std::vector & joinerIdBytes } else if (mJoinerId != joinerId) { - ChipLogProgress(chipTool, "Ignoring message from unexpected joiner 0x%" PRIx64, joinerId); + ChipLogProgress(Controller, "Ignoring message from unexpected joiner 0x%" PRIx64, joinerId); return; } @@ -323,7 +326,7 @@ void CommissionProxy::OnJoinerMessage(const std::vector & joinerIdBytes { if (send(mProxyFd, payload.data(), payload.size(), 0) < 0) { - ChipLogError(chipTool, "Failed to forward packet to local proxy: %s", strerror(errno)); + ChipLogError(Controller, "Failed to forward packet to local proxy: %s", strerror(errno)); SetState(State::kAborted); } } @@ -338,12 +341,12 @@ void CommissionProxy::OnJoinerMessage(const std::vector & joinerIdBytes ProcessAnnouncement(joinerIdBytes, joinerPort, payload); break; case State::kDiscovered: - ChipLogProgress(chipTool, "WARNING ignore unsolicited messages after joiner is already discovered"); + ChipLogProgress(Controller, "WARNING ignore unsolicited messages after joiner is already discovered"); break; } } -ot::commissioner::CommissionerDataset CommissionProxy::MakeCommissionerDataset(Thread::DiscoveryCode code) +ot::commissioner::CommissionerDataset ThreadMeshcopCommissionProxy::MakeCommissionerDataset(Thread::DiscoveryCode code) { ot::commissioner::CommissionerDataset dataset; @@ -366,29 +369,32 @@ ot::commissioner::CommissionerDataset CommissionProxy::MakeCommissionerDataset(T dataset.mPresentFlags |= ot::commissioner::CommissionerDataset::kSteeringDataBit; return dataset; } -CHIP_ERROR CommissionProxy::InitializeCommissioner(uint8_t (&pskc)[Thread::kSizePSKc]) +CHIP_ERROR ThreadMeshcopCommissionProxy::InitializeCommissioner(ByteSpan & pskc) { + VerifyOrReturnError(pskc.size() == Thread::kSizePSKc, CHIP_ERROR_INVALID_ARGUMENT); ot::commissioner::Config config; config.mLogger = std::make_shared(); config.mEnableCcm = false; config.mProxyMode = true; - config.mPSKc = std::vector(pskc, pskc + Thread::kSizePSKc); + config.mPSKc = std::vector(pskc.begin(), pskc.end()); auto error = mCommissioner->Init(config); if (error != ot::commissioner::ErrorCode::kNone) { - ChipLogError(chipTool, "OT Commissioner Init failed: %s", error.GetMessage().c_str()); + ChipLogError(Controller, "OT Commissioner Init failed: %s", error.GetMessage().c_str()); return CHIP_ERROR_INTERNAL; } return CHIP_NO_ERROR; } -CHIP_ERROR CommissionProxy::Discover(uint8_t (&pskc)[Thread::kSizePSKc], const char * host, uint16_t port, +CHIP_ERROR ThreadMeshcopCommissionProxy::Discover(ByteSpan & pskc, const Transport::PeerAddress & peerAddr, + const Thread::DiscoveryCode code, SetupDiscriminator expectedDiscriminator, + Dnssd::DiscoveredNodeData & nodeData, uint16_t timeout) +{ + using ot::commissioner::Error; - const Thread::DiscoveryCode code, SetupDiscriminator expectedDiscriminator, + Error error; - Dnssd::DiscoveredNodeData & nodeData, uint16_t timeout) -{ // Reset the promise and state for a new discovery session std::future future; { @@ -403,34 +409,41 @@ CHIP_ERROR CommissionProxy::Discover(uint8_t (&pskc)[Thread::kSizePSKc], const c ReturnErrorOnFailure(InitializeCommissioner(pskc)); - ChipLogProgress(chipTool, "Petitioning Thread Border Agent at %s:%u", host, port); - std::string id; - auto error = mCommissioner->Petition(id, std::string(host), port); - if (error != ot::commissioner::ErrorCode::kNone) { - ChipLogError(chipTool, "Petition failed: %s", error.GetMessage().c_str()); - SetState(State::kAborted); - return CHIP_ERROR_INTERNAL; - } + std::string id; + char host[Inet::IPAddress::kMaxStringLength]; + peerAddr.GetIPAddress().ToString(host); - ChipLogProgress(chipTool, "Thread Commissioner active with ID: %s", id.c_str()); + ChipLogProgress(Controller, "Petitioning Thread Border Agent at %s:%u", host, peerAddr.GetPort()); + error = mCommissioner->Petition(id, std::string(host), peerAddr.GetPort()); + if (error != ot::commissioner::ErrorCode::kNone) + { + ChipLogError(Controller, "Petition failed: %s", error.GetMessage().c_str()); + SetState(State::kAborted); + return CHIP_ERROR_INTERNAL; + } + + ChipLogProgress(Controller, "Thread Commissioner active with ID: %s", id.c_str()); + } error = mCommissioner->SetCommissionerDataset(MakeCommissionerDataset(code)); if (error != ot::commissioner::ErrorCode::kNone) { - ChipLogError(chipTool, "Failed to set Steering Data: %s", error.GetMessage().c_str()); + ChipLogError(Controller, "Failed to set Steering Data: %s", error.GetMessage().c_str()); SetState(State::kAborted); return CHIP_ERROR_INTERNAL; } - ChipLogProgress(chipTool, "Waiting for mDNS announcement from joiner..."); + ChipLogProgress(Controller, "Waiting for mDNS announcement from joiner..."); auto waitDuration = std::chrono::seconds(timeout); if (future.wait_for(waitDuration) == std::future_status::timeout) { - ChipLogError(chipTool, "Timed out waiting for joiner mDNS announcement after %u seconds", timeout); + ChipLogError(Controller, "Timed out waiting for joiner mDNS announcement after %u seconds", timeout); SetState(State::kAborted); return CHIP_ERROR_TIMEOUT; } nodeData = future.get(); return CHIP_NO_ERROR; } +} // namespace Controller +} // namespace chip diff --git a/examples/chip-tool/commands/pairing/CommissionProxy.h b/src/controller/ThreadMeshcopCommissionProxy.h similarity index 77% rename from examples/chip-tool/commands/pairing/CommissionProxy.h rename to src/controller/ThreadMeshcopCommissionProxy.h index aec81de8e1eb62..799e48bde68718 100644 --- a/examples/chip-tool/commands/pairing/CommissionProxy.h +++ b/src/controller/ThreadMeshcopCommissionProxy.h @@ -34,17 +34,20 @@ #include #include #include +#include -#include "../third_party/ot-commissioner/repo/include/commissioner/commissioner.hpp" +#include +namespace chip { +namespace Controller { /** * CommissionProxy acts as a bridge between the OpenThread Commissioner and Matter commissioning. * It handles Thread-specific commissioning (MeshCoP) and proxies mDNS discovery data * to facilitate the transition into Matter's operational commissioning flow. */ -class CommissionProxy : public ot::commissioner::CommissionerHandler, - public mdns::Minimal::ParserDelegate, - public mdns::Minimal::TxtRecordDelegate +class ThreadMeshcopCommissionProxy : public ot::commissioner::CommissionerHandler, + public mdns::Minimal::ParserDelegate, + public mdns::Minimal::TxtRecordDelegate { public: enum class State @@ -56,15 +59,14 @@ class CommissionProxy : public ot::commissioner::CommissionerHandler, kAborted, // Error or user cancellation }; - CommissionProxy(); - ~CommissionProxy() override; + ThreadMeshcopCommissionProxy(); + ~ThreadMeshcopCommissionProxy() override; /** * Entry point to start the Thread commissioning and discover the device. */ - CHIP_ERROR Discover(uint8_t (&pskc)[chip::Thread::kSizePSKc], const char * host, uint16_t port, - const chip::Thread::DiscoveryCode code, chip::SetupDiscriminator expectedDiscriminator, - chip::Dnssd::DiscoveredNodeData & nodeData, uint16_t timeout); + CHIP_ERROR Discover(ByteSpan & pskc, const Transport::PeerAddress & peerAddr, const Thread::DiscoveryCode code, + chip::SetupDiscriminator expectedDiscriminator, Dnssd::DiscoveredNodeData & nodeData, uint16_t timeout); // ot::commissioner::CommissionerHandler void OnJoinerMessage(const std::vector & joinerIdBytes, uint16_t joinerPort, @@ -80,12 +82,12 @@ class CommissionProxy : public ot::commissioner::CommissionerHandler, private: // Internal Helper Methods - CHIP_ERROR InitializeCommissioner(uint8_t (&pskc)[chip::Thread::kSizePSKc]); - CHIP_ERROR CreateProxySocket(chip::Dnssd::CommissionNodeData & commissionData); + CHIP_ERROR InitializeCommissioner(ByteSpan & pskc); + CHIP_ERROR CreateProxySocket(Dnssd::CommissionNodeData & commissionData); void ProcessAnnouncement(const std::vector & joinerIdBytes, uint16_t joinerPort, const std::vector & payload); void SetState(State state); - ot::commissioner::CommissionerDataset MakeCommissionerDataset(chip::Thread::DiscoveryCode code); + ot::commissioner::CommissionerDataset MakeCommissionerDataset(Thread::DiscoveryCode code); // Member Variables chip::Dnssd::DiscoveredNodeData mNodeData; @@ -100,8 +102,11 @@ class CommissionProxy : public ot::commissioner::CommissionerHandler, std::recursive_mutex mMutex; bool mPromiseFulfilled = false; - std::promise mDiscoveredNodePromise; + std::promise mDiscoveredNodePromise; std::shared_ptr mCommissioner; std::thread mProxyThread; }; + +} // namespace Controller +} // namespace chip diff --git a/src/controller/flags.gni b/src/controller/flags.gni index 03ca46dccf5ff8..3fc43a118a3ef8 100644 --- a/src/controller/flags.gni +++ b/src/controller/flags.gni @@ -13,6 +13,9 @@ # limitations under the License. import("//build_overrides/chip.gni") +import("${chip_root}/config/recommended.gni") +import("${chip_root}/src/platform/device.gni") +import("${chip_root}/src/system/system.gni") declare_args() { # Build controller (set to false for a device) @@ -20,4 +23,10 @@ declare_args() { # Build controller with webrtc python bindings chip_support_webrtc_python_bindings = false + + # Disable Thread MeshCoP if cross-compiling by default. + chip_support_thread_meshcop = + matter_enable_recommended && chip_device_platform == "linux" && + !chip_system_config_use_openthread_inet_endpoints && + current_cpu == host_cpu && current_os == host_os } diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp index 711351cc36164d..b19d0e36f19126 100644 --- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp +++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp @@ -185,6 +185,10 @@ PyChipError pychip_DeviceController_OnNetworkCommission(chip::Controller::Device chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, uint64_t nodeId, uint32_t setupPasscode, const uint8_t filterType, const char * filterParam, uint32_t discoveryTimeoutMsec); +PyChipError pychip_DeviceController_ThreadMeshcopCommission(chip::Controller::DeviceCommissioner * devCtrl, + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + uint64_t nodeId, uint32_t setupPasscode, uint16_t discriminator, + const char * borderAgentIPAddr, uint16_t borderAgentPort); PyChipError pychip_DeviceController_PostTaskOnChipThread(ChipThreadTaskRunnerFunct callback, void * pythonContext); @@ -576,6 +580,29 @@ PyChipError pychip_DeviceController_OnNetworkCommission(chip::Controller::Device return ToPyChipError(devCtrl->DiscoverCommissionableNodes(filter)); } +PyChipError pychip_DeviceController_ThreadMeshcopCommission(chip::Controller::DeviceCommissioner * devCtrl, + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + uint64_t nodeId, uint32_t setupPasscode, uint16_t discriminator, + const char * borderAgentIPAddrStr, uint16_t borderAgentPort) +{ +#if CHIP_SUPPORT_THREAD_MESHCOP + const uint32_t kDefaultDiscoveryTimeoutMsec = 1000000; + CHIP_ERROR err = sPairingDeviceDiscoveryDelegate.Init(nodeId, setupPasscode, sCommissioningParameters, pairingDelegate, devCtrl, + kDefaultDiscoveryTimeoutMsec); + VerifyOrReturnError(err == CHIP_NO_ERROR, ToPyChipError(err)); + chip::Transport::PeerAddress address(Transport::Type::kThreadMeshcop); + chip::Inet::IPAddress borderAgentIPAddr; + VerifyOrReturnError(chip::Inet::IPAddress::FromString(borderAgentIPAddrStr, borderAgentIPAddr), + ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT)); + address.SetIPAddress(borderAgentIPAddr).SetPort(borderAgentPort); + + auto params = RendezvousParameters().SetSetupPINCode(setupPasscode).SetDiscriminator(discriminator).SetPeerAddress(address); + return ToPyChipError(devCtrl->PairDevice(nodeId, params, sCommissioningParameters)); +#else + return ToPyChipError(CHIP_ERROR_NOT_IMPLEMENTED); +#endif // CHIP_SUPPORT_THREAD_MESHCOP +} + PyChipError pychip_DeviceController_SetThreadOperationalDataset(const char * threadOperationalDataset, uint32_t size) { VerifyOrReturnError(sThreadBuf.Alloc(size), ToPyChipError(CHIP_ERROR_NO_MEMORY)); diff --git a/src/controller/python/matter/ChipDeviceCtrl.py b/src/controller/python/matter/ChipDeviceCtrl.py index ed1a6331d9fd7e..eee1e9591e6f89 100644 --- a/src/controller/python/matter/ChipDeviceCtrl.py +++ b/src/controller/python/matter/ChipDeviceCtrl.py @@ -2543,6 +2543,15 @@ def _InitLib(self): c_void_p, c_void_p, c_uint64, c_uint32, c_uint8, c_char_p, c_uint32] self._dmLib.pychip_DeviceController_OnNetworkCommission.restype = PyChipError + if hasattr(self._dmLib, "pychip_DeviceController_ThreadMeshcopCommission"): + self._dmLib.pychip_DeviceController_ThreadMeshcopCommission.argtypes = [ + c_void_p, c_void_p, c_uint64, c_uint32, c_uint16, c_char_p, c_uint16] + self._dmLib.pychip_DeviceController_ThreadMeshcopCommission.restype = PyChipError + else: + logging.getLogger(__name__).warning( + "pychip_DeviceController_ThreadMeshcopCommission is not available in the loaded CHIP library; " + "Thread Meshcop commissioning is disabled.") + self._dmLib.pychip_DeviceController_DiscoverCommissionableNodes.argtypes = [ c_void_p, c_uint8, c_char_p] self._dmLib.pychip_DeviceController_DiscoverCommissionableNodes.restype = PyChipError @@ -3182,6 +3191,33 @@ async def CommissionOnNetwork(self, nodeId: int, setupPinCode: int, return await asyncio.futures.wrap_future(ctx.future) + async def CommissionThreadMeshcop(self, nodeId: int, setupPinCode: int, + discriminator: int, borderAgentIPAddr: str, + borderAgentPort: int, threadOperationalDataset: bytes) -> int: + ''' + Commission with the given node ID from the setupPinCode and discriminator + over Thread MeshCoP transport + + Args: + nodeId (int): The node ID of the device. + setupPinCode (int): The setup pin code of the device. + discriminator (int): The long discriminator for the DNS-SD advertisement. Valid range: 0-4095. + borderAgentIPAddr (str): IP address of Border Agent in Thread network + borderAgentPort (int): The port of Border Agent in Thread network + threadOperationalDataset (bytes): The operational dataset of Thread network + ''' + self.CheckIsActive() + + self.SetThreadOperationalDataset(threadOperationalDataset) + async with self._commissioning_context as ctx: + self._enablePairingCompleteCallback(True) + await self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_ThreadMeshcopCommission( + self.devCtrl, self.pairingDelegate, nodeId, setupPinCode, discriminator, borderAgentIPAddr.encode("utf-8"), borderAgentPort) + ) + + return await asyncio.futures.wrap_future(ctx.future) + def get_rcac(self): ''' Passes captured RCAC data back to Python test modules for validation diff --git a/src/platform/BUILD.gn b/src/platform/BUILD.gn index 39dbba7f422f2e..30aa2e0d9f658f 100644 --- a/src/platform/BUILD.gn +++ b/src/platform/BUILD.gn @@ -15,6 +15,8 @@ import("//build_overrides/chip.gni") import("${chip_root}/build/chip/buildconfig_header.gni") +import("${chip_root}/src/controller/flags.gni") +import("${chip_root}/src/crypto/crypto.gni") import("${chip_root}/src/system/system.gni") import("device.gni") @@ -172,6 +174,15 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { "CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR=${chip_enable_ota_requestor}", ] + # TODO : Currently OpenThread Commissioner is only supported in linux, need to add support for more platform + if (chip_device_platform == "linux" && chip_crypto != "mbedtls" && + chip_support_thread_meshcop && current_os == host_os && + current_cpu == host_cpu) { + defines += [ "CHIP_SUPPORT_THREAD_MESHCOP=1" ] + } else { + defines += [ "CHIP_SUPPORT_THREAD_MESHCOP=0" ] + } + if (chip_enable_nfc_based_commissioning) { defines += [ "CHIP_DEVICE_CONFIG_ENABLE_NFC_BASED_COMMISSIONING=1" ] } diff --git a/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp b/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp index b4b6cb79181300..f8dbe33e3aea32 100644 --- a/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp +++ b/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp @@ -970,8 +970,10 @@ void GenericThreadStackManagerImpl_OpenThread::SendRendezvousAnnounce mRendezvousRetransmissionCount++; if (mRendezvousRetransmissionCount < kMaxRendezvousRetransmissions) { + const uint32_t kRendezvousRetransmissionIntervalMs = 1250; ChipLogProgress(DeviceLayer, "Try the current Thread network #%u", mRendezvousRetransmissionCount); - DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(1250), _HandleRendezvousRetransmissionTimer, this); + DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kRendezvousRetransmissionIntervalMs), + _HandleRendezvousRetransmissionTimer, this); } else { diff --git a/src/python_testing/matter_testing_infrastructure/matter/testing/commissioning.py b/src/python_testing/matter_testing_infrastructure/matter/testing/commissioning.py index c3533a03b8915d..07e0e9cfa23d5c 100644 --- a/src/python_testing/matter_testing_infrastructure/matter/testing/commissioning.py +++ b/src/python_testing/matter_testing_infrastructure/matter/testing/commissioning.py @@ -87,6 +87,8 @@ class CommissioningInfo: wifi_ssid: Optional[str] = None tc_version_to_simulate: Optional[int] = None tc_user_response_to_simulate: Optional[int] = None + border_agent_ip_addr: Optional[str] = None + border_agent_port: Optional[int] = None @dataclass @@ -230,6 +232,33 @@ async def commission_device( except ChipStackError as e: # chipstack-ok: Can not use 'with' because we handle and return the exception, not assert it LOGGER.exception("Commissioning failed") return PairingStatus(exception=e) + elif commissioning_info.commissioning_method == "thread-meshcop": + try: + asserts.assert_is_not_none(commissioning_info.thread_operational_dataset, + "Thread dataset must be provided for thread-meshcop commissioning") + # Type assertion to help mypy understand this is not None after the assert + assert commissioning_info.thread_operational_dataset is not None + asserts.assert_is_not_none(commissioning_info.border_agent_ip_addr, + "border_agent_ip_addr must be provided for thread-meshcop commissioning") + # Type assertion to help mypy understand this is not None after the assert + assert commissioning_info.border_agent_ip_addr is not None + asserts.assert_is_not_none(commissioning_info.border_agent_port, + "border_agent_port must be provided for thread-meshcop commissioning") + # Type assertion to help mypy understand this is not None after the assert + assert commissioning_info.border_agent_port is not None + + await dev_ctrl.CommissionThreadMeshcop( + node_id, + info.passcode, + info.filter_value, + commissioning_info.border_agent_ip_addr, + commissioning_info.border_agent_port, + commissioning_info.thread_operational_dataset, + ) + return PairingStatus() + except ChipStackError as e: # chipstack-ok: Can not use 'with' because we handle and return the exception, not assert it + LOGGER.exception("Commissioning failed") + return PairingStatus(exception=e) else: raise ValueError("Invalid commissioning method %s!" % commissioning_info.commissioning_method) @@ -339,6 +368,8 @@ def __init__(self, *args): wifi_ssid=meta_config['wifi_ssid'], tc_version_to_simulate=meta_config['tc_version_to_simulate'], tc_user_response_to_simulate=meta_config['tc_user_response_to_simulate'], + border_agent_ip_addr=meta_config['border_agent_ip_addr'], + border_agent_port=meta_config['border_agent_port'], ) self.setup_payloads: List[SetupPayloadInfo] = get_setup_payload_info_config( global_stash.unstash_globally(test_config.user_params['matter_test_config'])) diff --git a/src/python_testing/matter_testing_infrastructure/matter/testing/matter_test_config.py b/src/python_testing/matter_testing_infrastructure/matter/testing/matter_test_config.py index f2a8e3087c51ff..2ff2c79d2e5422 100644 --- a/src/python_testing/matter_testing_infrastructure/matter/testing/matter_test_config.py +++ b/src/python_testing/matter_testing_infrastructure/matter/testing/matter_test_config.py @@ -52,6 +52,10 @@ class MatterTestConfig: # This allows cert tests to be run without re-commissioning for RR-1.1. maximize_cert_chains: bool = True + # Border Agent information for Thread MeshCoP Commissioning + border_agent_ip_addr: Optional[str] = None + border_agent_port: Optional[int] = None + # By default, let's set validity to 10 years certificate_validity_period = int(timedelta(days=10*365).total_seconds()) diff --git a/src/python_testing/matter_testing_infrastructure/matter/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/matter/testing/matter_testing.py index c8cb5da3ebaf1a..ffd6a845b39f38 100644 --- a/src/python_testing/matter_testing_infrastructure/matter/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/matter/testing/matter_testing.py @@ -879,6 +879,8 @@ async def commission_devices(self) -> bool: wifi_ssid=self.matter_test_config.wifi_ssid, tc_version_to_simulate=self.matter_test_config.tc_version_to_simulate, tc_user_response_to_simulate=self.matter_test_config.tc_user_response_to_simulate, + border_agent_ip_addr=self.matter_test_config.border_agent_ip_addr, + border_agent_port=self.matter_test_config.border_agent_port, ) return await commission_devices(dev_ctrl, dut_node_ids, setup_payloads, commissioning_info) diff --git a/src/python_testing/matter_testing_infrastructure/matter/testing/runner.py b/src/python_testing/matter_testing_infrastructure/matter/testing/runner.py index fb3f93356e6ee3..241c8b13b6c54d 100644 --- a/src/python_testing/matter_testing_infrastructure/matter/testing/runner.py +++ b/src/python_testing/matter_testing_infrastructure/matter/testing/runner.py @@ -707,7 +707,7 @@ def populate_commissioning_args(args: argparse.Namespace, config) -> bool: return False wifi_args = ['ble-wifi', 'nfc-wifi'] - thread_args = ['ble-thread', 'nfc-thread'] + thread_args = ['ble-thread', 'nfc-thread', 'thread-meshcop'] if commissioning_method in wifi_args: if args.wifi_ssid is None: print("error: missing --wifi-ssid for --commissioning-method " @@ -724,9 +724,15 @@ def populate_commissioning_args(args: argparse.Namespace, config) -> bool: elif commissioning_method in thread_args: if args.thread_dataset_hex is None: print("error: missing --thread-dataset-hex for --commissioning-method or " - "--in-test-commissioning-method ble-thread or nfc-thread!") + "--in-test-commissioning-method ble-thread, nfc-thread or thread-meshcop!") return False config.thread_operational_dataset = args.thread_dataset_hex + if commissioning_method == 'thread-meshcop': + if args.border_agent_ip_addr is None or args.border_agent_port is None: + print("error: missing --border-agent-ip-addr or --border-agent-port for --commissioning-method thread-meshcop!") + return False + config.border_agent_ip_addr = args.border_agent_ip_addr + config.border_agent_port = args.border_agent_port elif config.commissioning_method == "on-network-ip": if args.ip_addr is None: print("error: missing --ip-addr for --commissioning-method on-network-ip") @@ -874,11 +880,11 @@ def parse_matter_test_args(argv: Optional[List[str]] = None): commission_group.add_argument('-m', '--commissioning-method', type=str, metavar='METHOD_NAME', - choices=["on-network", "ble-wifi", "ble-thread", "nfc-thread", "nfc-wifi"], + choices=["on-network", "ble-wifi", "ble-thread", "nfc-thread", "nfc-wifi", "thread-meshcop"], help='Name of commissioning method to use') commission_group.add_argument('--in-test-commissioning-method', type=str, metavar='METHOD_NAME', - choices=["on-network", "ble-wifi", "ble-thread", "nfc-thread", "nfc-wifi"], + choices=["on-network", "ble-wifi", "ble-thread", "nfc-thread", "nfc-wifi", "thread-meshcop"], help='Name of commissioning method to use, for commissioning tests') commission_group.add_argument('-d', '--discriminator', type=int_decimal_or_hex, metavar='LONG_DISCRIMINATOR', @@ -908,6 +914,10 @@ def parse_matter_test_args(argv: Optional[List[str]] = None): commission_group.add_argument('--case-admin-subject', action="store", type=int_decimal_or_hex, metavar="CASE_ADMIN_SUBJECT", help="Set the CASE admin subject to an explicit value (default to commissioner Node ID)") + commission_group.add_argument('--border-agent-ip-addr', action="store", type=str, + help="Border Agent IP address") + commission_group.add_argument('--border-agent-port', action="store", type=int, + help="Border Agent port") commission_group.add_argument('--commission-only', action="store_true", default=False, help="If true, test exits after commissioning without running subsequent tests") diff --git a/src/python_testing/provisional/TC_SC_TC_4_1.py b/src/python_testing/provisional/TC_SC_TC_4_1.py new file mode 100644 index 00000000000000..12aaef48860213 --- /dev/null +++ b/src/python_testing/provisional/TC_SC_TC_4_1.py @@ -0,0 +1,67 @@ +# +# Copyright (c) 2025 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. + + +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: +# run1: +# app: ${ALL_CLUSTERS_APP} +# app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# script-args: > +# --storage-path admin_storage.json +# --commissioning-method thread-meshcop +# --discriminator 1234 +# --passcode 20202021 +# --border-agent-ip-addr 127.0.0.1 +# --border-agent-port 49152 +# --thread-dataset-hex 0e08000000000001000000030000104a0300001635060004001fffe0020884fa18779329ac770708fd269658e44aa21a030f4f70656e5468726561642d32386335010228c50c0402a0f7f8051000112233445566778899aabbccddeeff041000112233445566778899aabbccddeeff +# --trace-to json:${TRACE_TEST_JSON}.json +# --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# factory-reset: true +# quiet: true +# === END CI TEST ARGUMENTS === + +import logging + +from matter import ChipDeviceCtrl +from matter.testing.decorators import async_test_body +from matter.testing.matter_testing import MatterBaseTest, TestStep +from matter.testing.runner import default_matter_test_main + +log = logging.getLogger(__name__) + + +class TC_SC_TC_4_1(MatterBaseTest): + def desc_TC_SC_TC_4_1(self) -> str: + return "[TC-SC-TC-4.1] Message Framing and PASE Session Establishment [DUT – Commissionee]" + + def steps_TC_SC_TC_4_1(self) -> list[TestStep]: + return [ + TestStep(1, "Commissioner petitions the Thread Border Agent to become the Thread Commissioner with a 12-bit discriminator"), + TestStep(2, 'Validate the discriminator and perform the commissioning') + ] + + @async_test_body + async def test_TC_SC_TC_4_1(self): + commissioner: ChipDeviceCtrl.ChipDeviceController = self.default_controller + self.step(1) + commissioner.SetSkipCommissioningComplete(True) + self.step(2) + await self.commission_devices() + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/transport/raw/PeerAddress.h b/src/transport/raw/PeerAddress.h index 913d998f73d26b..d9f28ded46b628 100644 --- a/src/transport/raw/PeerAddress.h +++ b/src/transport/raw/PeerAddress.h @@ -55,7 +55,8 @@ enum class Type : uint8_t kTcp, kWiFiPAF, kNfc, - kLast = kNfc, // This is not an actual transport type, it just refers to the last transport type + kThreadMeshcop, + kLast = kThreadMeshcop, // This is not an actual transport type, it just refers to the last transport type }; /** @@ -183,6 +184,15 @@ class PeerAddress case Type::kNfc: snprintf(buf, bufSize, "NFC:%d", mId.mNFCShortId); break; + case Type::kThreadMeshcop: + mIPAddress.ToString(ip_addr); +#if INET_CONFIG_ENABLE_IPV4 + if (mIPAddress.IsIPv4()) + snprintf(buf, bufSize, "ThreadMeshcop:%s:%d", ip_addr, mPort); + else +#endif + snprintf(buf, bufSize, "ThreadMeshcop:[%s]:%d", ip_addr, mPort); + break; default: snprintf(buf, bufSize, "ERROR"); break; @@ -199,6 +209,11 @@ class PeerAddress static constexpr PeerAddress NFC() { return PeerAddress(kUndefinedNFCShortId()); } static constexpr PeerAddress NFC(const uint16_t shortId) { return PeerAddress(shortId); } + static PeerAddress ThreadMeshcop(const Inet::IPAddress & addr, uint16_t port) + { + return PeerAddress(Type::kThreadMeshcop).SetIPAddress(addr).SetPort(port); + } + static PeerAddress UDP(const Inet::IPAddress & addr) { return PeerAddress(addr, Type::kUdp); } static PeerAddress UDP(const Inet::IPAddress & addr, uint16_t port) { return UDP(addr).SetPort(port); }