diff --git a/config/telink/chip-module/Kconfig.defaults b/config/telink/chip-module/Kconfig.defaults index 35dc5ae0e87764..4dfeb6f329da6d 100644 --- a/config/telink/chip-module/Kconfig.defaults +++ b/config/telink/chip-module/Kconfig.defaults @@ -364,6 +364,9 @@ config OPENTHREAD_IP6_MAX_EXT_MCAST_ADDRS default 2 if PM default 8 +config CHIP_OPENTHREAD_NETWORK_SWITCH_PATH + default y + endif # NET_L2_OPENTHREAD config NET_TX_STACK_SIZE diff --git a/config/zephyr/Kconfig b/config/zephyr/Kconfig index ab39ff0d7b7bba..9a6e0c9a347f0d 100644 --- a/config/zephyr/Kconfig +++ b/config/zephyr/Kconfig @@ -170,6 +170,13 @@ config CHIP_FACTORY_RESET_ERASE_NVS configuration is supposed to be cleared on a factory reset, including device-specific entries. +config CHIP_OPENTHREAD_NETWORK_SWITCH_PATH + bool "Keep Thread enabled when switching between commissioned datasets" + default n + help + Keep Thread enabled while switching between commissioned + datasets to reduce detached window during fail-safe rollback/connect. + module = MATTER module-str = Matter source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" diff --git a/src/app/clusters/network-commissioning/NetworkCommissioningCluster.cpp b/src/app/clusters/network-commissioning/NetworkCommissioningCluster.cpp index e63e3f9f36c62c..0668e5fec60ef6 100644 --- a/src/app/clusters/network-commissioning/NetworkCommissioningCluster.cpp +++ b/src/app/clusters/network-commissioning/NetworkCommissioningCluster.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,7 @@ #include #include #include +#include namespace chip { namespace app { @@ -61,6 +63,17 @@ using namespace chip::app::Clusters::NetworkCommissioning; namespace { +#if !CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION + +bool IsConnectNetworkRequestOverPASE(CommandHandler & handler) +{ + Messaging::ExchangeContext * exchangeCtx = handler.GetExchangeContext(); + return exchangeCtx && exchangeCtx->HasSessionHandle() && exchangeCtx->GetSessionHandle()->IsSecureSession() && + exchangeCtx->GetSessionHandle()->AsSecureSession()->GetSecureSessionType() == Transport::SecureSession::Type::kPASE; +} + +#endif // CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION + // Note: CHIP_CONFIG_NETWORK_COMMISSIONING_DEBUG_TEXT_BUFFER_SIZE can be 0, this disables debug text using DebugTextStorage = std::array; @@ -638,11 +651,22 @@ NetworkCommissioningCluster::HandleConnectNetwork(CommandHandler & handler, cons mpWirelessDriver->ConnectNetwork(req.networkID, this); #else - // In Non-concurrent mode postpone the final execution of ConnectNetwork until the operational - // network has been fully brought up and kOperationalNetworkStarted is delivered. - // mConnectingNetworkIDLen and mConnectingNetworkID contain the received SSID - // As per spec, send the ConnectNetworkResponse(Success) prior to releasing the commissioning channel - SendNonConcurrentConnectNetworkResponse(); + mConnectNetworkResponseSentEarly = false; + // In non-concurrent mode there are two execution paths: + // 1) PASE/BLE: send ConnectNetworkResponse early before tearing down the commissioning + // transport; actual connect is started later from OnPlatformEventHandler. + // 2) CASE: start connect immediately here and send ConnectNetworkResponse from OnResult + // after attach finishes. + if (IsConnectNetworkRequestOverPASE(handler)) + { + // PASE path must respond before commissioning transport is torn down. + mConnectNetworkResponseSentEarly = true; + SendNonConcurrentConnectNetworkResponse(); + } + else + { + HandleNonConcurrentConnectNetwork(); + } #endif return std::nullopt; } @@ -650,8 +674,26 @@ NetworkCommissioningCluster::HandleConnectNetwork(CommandHandler & handler, cons std::optional NetworkCommissioningCluster::HandleNonConcurrentConnectNetwork() { ByteSpan nonConcurrentNetworkID = ByteSpan(mConnectingNetworkID, mConnectingNetworkIDLen); - ChipLogProgress(NetworkProvisioning, "Non-concurrent mode, Connect to Network SSID=%s", - NullTerminated(mConnectingNetworkID, mConnectingNetworkIDLen).c_str()); + if (mFeatureFlags.Has(Feature::kThreadNetworkInterface)) + { + constexpr size_t kThreadNetworkIdHexMax = (2 * kMaxNetworkIDLen) + 1; + char threadNetworkIdHex[kThreadNetworkIdHexMax]; + if (Encoding::BytesToUppercaseHexString(nonConcurrentNetworkID.data(), nonConcurrentNetworkID.size(), threadNetworkIdHex, + sizeof(threadNetworkIdHex)) == CHIP_NO_ERROR) + { + ChipLogProgress(NetworkProvisioning, "Non-concurrent mode, Connect to Thread Network ID=%s", threadNetworkIdHex); + } + else + { + ChipLogProgress(NetworkProvisioning, "Non-concurrent mode, Connect to Thread Network ID (len=%u)", + static_cast(nonConcurrentNetworkID.size())); + } + } + else + { + ChipLogProgress(NetworkProvisioning, "Non-concurrent mode, Connect to Wi-Fi SSID=%s", + NullTerminated(mConnectingNetworkID, mConnectingNetworkIDLen).c_str()); + } mpWirelessDriver->ConnectNetwork(nonConcurrentNetworkID, this); return std::nullopt; } @@ -790,12 +832,11 @@ void NetworkCommissioningCluster::DisconnectLingeringConnection() void NetworkCommissioningCluster::OnResult(Status commissioningError, CharSpan debugText, int32_t interfaceStatus) { auto commandHandleRef = std::move(mAsyncCommandHandle); - + auto commandHandle = commandHandleRef.Get(); // In Non-concurrent mode the commandHandle will be null here, the ConnectNetworkResponse // has already been sent and the BLE will have been stopped, however the other functionality // is still required #if CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION - auto commandHandle = commandHandleRef.Get(); if (commandHandle == nullptr) { // When the platform shut down, interaction model engine will invalidate all commandHandle to avoid dangling references. @@ -825,14 +866,23 @@ void NetworkCommissioningCluster::OnResult(Status commissioningError, CharSpan d SetLastNetworkId(ByteSpan{ mConnectingNetworkID, mConnectingNetworkIDLen }); SetLastNetworkingStatusValue(MakeNullable(commissioningError)); + bool shouldSendConnectNetworkResponse = true; #if (CONFIG_NETWORK_LAYER_BLE || CHIP_DEVICE_CONFIG_ENABLE_THREAD_MESHCOP) && !CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION - ChipLogProgress(NetworkProvisioning, "Non-concurrent mode, ConnectNetworkResponse will NOT be sent"); - // Do not send the ConnectNetworkResponse if in non-concurrent mode - // TODO(#30576) raised to modify CommandHandler to notify it if no response required - // -----> Is this required here: commandHandle->FinishCommand(); -#else - commandHandle->AddResponse(mAsyncCommandPath, response); -#endif // CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION + if (mConnectNetworkResponseSentEarly) + { + ChipLogProgress(NetworkProvisioning, "Non-concurrent mode, ConnectNetworkResponse was already sent"); + shouldSendConnectNetworkResponse = false; + } +#endif + + if (shouldSendConnectNetworkResponse && commandHandle != nullptr) + { + commandHandle->AddResponse(mAsyncCommandPath, response); + } + +#if !CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION + mConnectNetworkResponseSentEarly = false; +#endif if (commissioningError == Status::kSuccess) { @@ -932,6 +982,10 @@ void NetworkCommissioningCluster::OnFailSafeTimerExpired() { VerifyOrReturn(mpWirelessDriver != nullptr); +#if !CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION + mConnectNetworkResponseSentEarly = false; +#endif + ChipLogDetail(Zcl, "Failsafe timeout, tell platform driver to revert network credentials."); TEMPORARY_RETURN_IGNORED mpWirelessDriver->RevertConfiguration(); mAsyncCommandHandle.Release(); diff --git a/src/app/clusters/network-commissioning/NetworkCommissioningCluster.h b/src/app/clusters/network-commissioning/NetworkCommissioningCluster.h index 24cf743a4afd93..1d376d44ae4966 100644 --- a/src/app/clusters/network-commissioning/NetworkCommissioningCluster.h +++ b/src/app/clusters/network-commissioning/NetworkCommissioningCluster.h @@ -240,6 +240,12 @@ class NetworkCommissioningCluster : private NetworkCommissioningLogicListNode, uint8_t mLastNetworkIDLen = 0; Optional mCurrentOperationBreadcrumb; bool mScanningWasDirected = false; +#if !CHIP_DEVICE_CONFIG_SUPPORTS_CONCURRENT_CONNECTION + // Tracks whether ConnectNetworkResponse was already sent from HandleConnectNetwork() + // on the PASE/BLE path. OnResult() uses this to avoid sending a duplicate response + // after the attach callback, and the flag is reset at command start / fail-safe expiry. + bool mConnectNetworkResponseSentEarly = false; +#endif Context mClusterContext; void SetLastNetworkingStatusValue(NetworkCommissioning::Attributes::LastNetworkingStatus::TypeInfo::Type networkingStatusValue); diff --git a/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp b/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp index a0193e61faa098..81d95a37ee83af 100644 --- a/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp +++ b/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp @@ -391,6 +391,16 @@ CHIP_ERROR GenericThreadStackManagerImpl_OpenThread::_AttachToThreadN // Reset the previously set callback since it will never be called in case incorrect dataset was supplied. mpConnectCallback = nullptr; + +#if defined(CONFIG_CHIP_OPENTHREAD_NETWORK_SWITCH_PATH) && CONFIG_CHIP_OPENTHREAD_NETWORK_SWITCH_PATH + if (callback == nullptr && dataset.IsCommissioned() && current_dataset.IsCommissioned() && + !dataset.AsByteSpan().data_equal(current_dataset.AsByteSpan()) && Impl()->IsThreadEnabled()) + { + ReturnErrorOnFailure(Impl()->SetThreadProvision(dataset.AsByteSpan())); + return CHIP_NO_ERROR; + } +#endif + ReturnErrorOnFailure(Impl()->SetThreadEnabled(false)); ReturnErrorOnFailure(Impl()->SetThreadProvision(dataset.AsByteSpan()));