Skip to content

Commit 1123570

Browse files
chrisdecenzorestyled-io[bot]restyled-commits
authored andcommitted
Add network retry and case failsafe timer to AutoCommissioner (#23209)
* Draft: Add network config retry and case failsafe timer to AutoCommissioner * address comments * Address feedback * Restyle Draft: Add network retry and case failsafe timer to AutoCommissioner (#23516) * Restyled by whitespace * Restyled by google-java-format Co-authored-by: Restyled.io <[email protected]> Co-authored-by: restyled-io[bot] <32688539+restyled-io[bot]@users.noreply.github.com> Co-authored-by: Restyled.io <[email protected]>
1 parent e7e6ecd commit 1123570

7 files changed

+205
-42
lines changed

src/controller/AutoCommissioner.cpp

+92-37
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
1919
#include <controller/AutoCommissioner.h>
2020

2121
#include <app/InteractionModelTimeout.h>
22+
#include <controller-clusters/zap-generated/CHIPClusters.h>
2223
#include <controller/CHIPDeviceController.h>
2324
#include <credentials/CHIPCert.h>
2425
#include <lib/support/SafeInt.h>
2526

2627
namespace chip {
2728
namespace Controller {
2829

30+
using namespace chip::app::Clusters;
31+
2932
AutoCommissioner::AutoCommissioner()
3033
{
3134
SetCommissioningParameters(CommissioningParameters());
@@ -51,6 +54,12 @@ CHIP_ERROR AutoCommissioner::SetCommissioningParameters(const CommissioningParam
5154
mParams.SetFailsafeTimerSeconds(params.GetFailsafeTimerSeconds().Value());
5255
}
5356

57+
if (params.GetCASEFailsafeTimerSeconds().HasValue())
58+
{
59+
ChipLogProgress(Controller, "Setting CASE failsafe timer from parameters");
60+
mParams.SetCASEFailsafeTimerSeconds(params.GetCASEFailsafeTimerSeconds().Value());
61+
}
62+
5463
if (params.GetAdminSubject().HasValue())
5564
{
5665
ChipLogProgress(Controller, "Setting adminSubject from parameters");
@@ -169,6 +178,27 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStage(CommissioningStag
169178
return nextStage;
170179
}
171180

181+
CommissioningStage AutoCommissioner::GetNextCommissioningStageNetworkSetup(CommissioningStage currentStage, CHIP_ERROR & lastErr)
182+
{
183+
if (mParams.GetWiFiCredentials().HasValue() && mDeviceCommissioningInfo.network.wifi.endpoint != kInvalidEndpointId)
184+
{
185+
return CommissioningStage::kWiFiNetworkSetup;
186+
}
187+
if (mParams.GetThreadOperationalDataset().HasValue() && mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId)
188+
{
189+
return CommissioningStage::kThreadNetworkSetup;
190+
}
191+
192+
ChipLogError(Controller, "Required network information not provided in commissioning parameters");
193+
ChipLogError(Controller, "Parameters supplied: wifi (%s) thread (%s)", mParams.GetWiFiCredentials().HasValue() ? "yes" : "no",
194+
mParams.GetThreadOperationalDataset().HasValue() ? "yes" : "no");
195+
ChipLogError(Controller, "Device supports: wifi (%s) thread(%s)",
196+
mDeviceCommissioningInfo.network.wifi.endpoint == kInvalidEndpointId ? "no" : "yes",
197+
mDeviceCommissioningInfo.network.thread.endpoint == kInvalidEndpointId ? "no" : "yes");
198+
lastErr = CHIP_ERROR_INVALID_ARGUMENT;
199+
return CommissioningStage::kCleanup;
200+
}
201+
172202
CommissioningStage AutoCommissioner::GetNextCommissioningStageInternal(CommissioningStage currentStage, CHIP_ERROR & lastErr)
173203
{
174204
if (mStopCommissioning)
@@ -194,27 +224,6 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStageInternal(Commissio
194224
}
195225
return CommissioningStage::kArmFailsafe;
196226
case CommissioningStage::kArmFailsafe:
197-
if (mNeedsNetworkSetup)
198-
{
199-
// if there is a WiFi or a Thread endpoint, then perform scan
200-
if ((mParams.GetAttemptWiFiNetworkScan().ValueOr(false) &&
201-
mDeviceCommissioningInfo.network.wifi.endpoint != kInvalidEndpointId) ||
202-
(mParams.GetAttemptThreadNetworkScan().ValueOr(false) &&
203-
mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId))
204-
{
205-
return CommissioningStage::kScanNetworks;
206-
}
207-
ChipLogProgress(Controller, "No NetworkScan enabled or WiFi/Thread endpoint not specified, skipping ScanNetworks");
208-
}
209-
else
210-
{
211-
ChipLogProgress(Controller, "Not a BLE connection, skipping ScanNetworks");
212-
}
213-
// skip scan step
214-
return CommissioningStage::kConfigRegulatory;
215-
case CommissioningStage::kScanNetworks:
216-
return CommissioningStage::kNeedsNetworkCreds;
217-
case CommissioningStage::kNeedsNetworkCreds:
218227
return CommissioningStage::kConfigRegulatory;
219228
case CommissioningStage::kConfigRegulatory:
220229
return CommissioningStage::kSendPAICertificateRequest;
@@ -240,34 +249,30 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStageInternal(Commissio
240249
// operational network because the provisioning of certificates will trigger the device to start operational advertising.
241250
if (mNeedsNetworkSetup)
242251
{
243-
if (mParams.GetWiFiCredentials().HasValue() && mDeviceCommissioningInfo.network.wifi.endpoint != kInvalidEndpointId)
244-
{
245-
return CommissioningStage::kWiFiNetworkSetup;
246-
}
247-
if (mParams.GetThreadOperationalDataset().HasValue() &&
248-
mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId)
252+
// if there is a WiFi or a Thread endpoint, then perform scan
253+
if (IsScanNeeded())
249254
{
250-
return CommissioningStage::kThreadNetworkSetup;
255+
// Perform Scan (kScanNetworks) and collect credentials (kNeedsNetworkCreds) right before configuring network.
256+
// This order of steps allows the workflow to return to collect credentials again if network enablement fails.
257+
return CommissioningStage::kScanNetworks;
251258
}
259+
ChipLogProgress(Controller, "No NetworkScan enabled or WiFi/Thread endpoint not specified, skipping ScanNetworks");
252260

253-
ChipLogError(Controller, "Required network information not provided in commissioning parameters");
254-
ChipLogError(Controller, "Parameters supplied: wifi (%s) thread (%s)",
255-
mParams.GetWiFiCredentials().HasValue() ? "yes" : "no",
256-
mParams.GetThreadOperationalDataset().HasValue() ? "yes" : "no");
257-
ChipLogError(Controller, "Device supports: wifi (%s) thread(%s)",
258-
mDeviceCommissioningInfo.network.wifi.endpoint == kInvalidEndpointId ? "no" : "yes",
259-
mDeviceCommissioningInfo.network.thread.endpoint == kInvalidEndpointId ? "no" : "yes");
260-
lastErr = CHIP_ERROR_INVALID_ARGUMENT;
261-
return CommissioningStage::kCleanup;
261+
return GetNextCommissioningStageNetworkSetup(currentStage, lastErr);
262262
}
263263
else
264264
{
265+
SetCASEFailsafeTimerIfNeeded();
265266
if (mParams.GetSkipCommissioningComplete().ValueOr(false))
266267
{
267268
return CommissioningStage::kCleanup;
268269
}
269270
return CommissioningStage::kFindOperational;
270271
}
272+
case CommissioningStage::kScanNetworks:
273+
return CommissioningStage::kNeedsNetworkCreds;
274+
case CommissioningStage::kNeedsNetworkCreds:
275+
return GetNextCommissioningStageNetworkSetup(currentStage, lastErr);
271276
case CommissioningStage::kWiFiNetworkSetup:
272277
if (mParams.GetThreadOperationalDataset().HasValue() &&
273278
mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId)
@@ -296,13 +301,16 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStageInternal(Commissio
296301
}
297302
else if (mParams.GetSkipCommissioningComplete().ValueOr(false))
298303
{
304+
SetCASEFailsafeTimerIfNeeded();
299305
return CommissioningStage::kCleanup;
300306
}
301307
else
302308
{
309+
SetCASEFailsafeTimerIfNeeded();
303310
return CommissioningStage::kFindOperational;
304311
}
305312
case CommissioningStage::kThreadNetworkEnable:
313+
SetCASEFailsafeTimerIfNeeded();
306314
if (mParams.GetSkipCommissioningComplete().ValueOr(false))
307315
{
308316
return CommissioningStage::kCleanup;
@@ -321,6 +329,38 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStageInternal(Commissio
321329
return CommissioningStage::kError;
322330
}
323331

332+
// No specific actions to take when an error happens since this command can fail and commissioning can still succeed.
333+
static void OnFailsafeFailureForCASE(void * context, CHIP_ERROR error)
334+
{
335+
ChipLogProgress(Controller, "ExtendFailsafe received failure response %s\n", chip::ErrorStr(error));
336+
}
337+
338+
// No specific actions to take upon success.
339+
static void
340+
OnExtendFailsafeSuccessForCASE(void * context,
341+
const app::Clusters::GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType & data)
342+
{
343+
ChipLogProgress(Controller, "ExtendFailsafe received ArmFailSafe response errorCode=%u", to_underlying(data.errorCode));
344+
}
345+
346+
void AutoCommissioner::SetCASEFailsafeTimerIfNeeded()
347+
{
348+
// if there is a final fail-safe timer configured then, send it
349+
if (mParams.GetCASEFailsafeTimerSeconds().HasValue() && mCommissioneeDeviceProxy != nullptr)
350+
{
351+
// send the command via the PASE session (mCommissioneeDeviceProxy) since the CASE portion of commissioning
352+
// might be done by a different service (ex. PASE is done by a phone app and CASE is done by a Hub).
353+
// Also, we want the CASE failsafe timer to apply for the time it takes the Hub to perform operational discovery,
354+
// CASE establishment, and receipt of the commissioning complete command.
355+
// We know that the mCommissioneeDeviceProxy is still valid at this point since it gets cleared during cleanup
356+
// and SetCASEFailsafeTimerIfNeeded is always called before that stage.
357+
mCommissioner->ExtendArmFailSafe(mCommissioneeDeviceProxy, CommissioningStage::kFindOperational,
358+
mParams.GetCASEFailsafeTimerSeconds().Value(),
359+
GetCommandTimeout(mCommissioneeDeviceProxy, CommissioningStage::kArmFailsafe),
360+
OnExtendFailsafeSuccessForCASE, OnFailsafeFailureForCASE);
361+
}
362+
}
363+
324364
EndpointId AutoCommissioner::GetEndpoint(const CommissioningStage & stage) const
325365
{
326366
switch (stage)
@@ -481,8 +521,23 @@ CHIP_ERROR AutoCommissioner::CommissioningStepFinished(CHIP_ERROR err, Commissio
481521
}
482522
else if (report.Is<NetworkCommissioningStatusInfo>())
483523
{
524+
// This report type is used when an error happens in either NetworkConfig or ConnectNetwork commands
484525
completionStatus.networkCommissioningStatus =
485526
MakeOptional(report.Get<NetworkCommissioningStatusInfo>().networkCommissioningStatus);
527+
528+
// If we are configured to scan networks, then don't error out.
529+
// Instead, allow the app to try another network.
530+
if (IsScanNeeded())
531+
{
532+
if (completionStatus.err == CHIP_NO_ERROR)
533+
{
534+
completionStatus.err = err;
535+
}
536+
err = CHIP_NO_ERROR;
537+
// Walk back the completed stage to kScanNetworks.
538+
// This will allow the app to try another network.
539+
report.stageCompleted = CommissioningStage::kScanNetworks;
540+
}
486541
}
487542
}
488543
else

src/controller/AutoCommissioner.h

+19
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class AutoCommissioner : public CommissioningDelegate
5151

5252
private:
5353
DeviceProxy * GetDeviceProxyForStep(CommissioningStage nextStage);
54+
55+
// Adjust the failsafe timer if CommissioningDelegate GetCASEFailsafeTimerSeconds is set
56+
void SetCASEFailsafeTimerIfNeeded();
5457
void ReleaseDAC();
5558
void ReleasePAI();
5659

@@ -69,6 +72,22 @@ class AutoCommissioner : public CommissioningDelegate
6972
EndpointId GetEndpoint(const CommissioningStage & stage) const;
7073
CommissioningStage GetNextCommissioningStageInternal(CommissioningStage currentStage, CHIP_ERROR & lastErr);
7174

75+
// Helper function to determine whether next stage should be kWiFiNetworkSetup,
76+
// kThreadNetworkSetup or kCleanup, depending whether network information has
77+
// been provided that matches the thread/wifi endpoint of the target.
78+
CommissioningStage GetNextCommissioningStageNetworkSetup(CommissioningStage currentStage, CHIP_ERROR & lastErr);
79+
80+
// Helper function to determine if a scan attempt should be made given the
81+
// scan attempt commissioning params and the corresponding network endpoint of
82+
// the target.
83+
bool IsScanNeeded()
84+
{
85+
return ((mParams.GetAttemptWiFiNetworkScan().ValueOr(false) &&
86+
mDeviceCommissioningInfo.network.wifi.endpoint != kInvalidEndpointId) ||
87+
(mParams.GetAttemptThreadNetworkScan().ValueOr(false) &&
88+
mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId));
89+
};
90+
7291
bool mStopCommissioning = false;
7392

7493
DeviceCommissioner * mCommissioner = nullptr;

src/controller/CHIPDeviceController.cpp

+15-5
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,18 @@ void DeviceCommissioner::OnFailedToExtendedArmFailSafeDeviceAttestation(void * c
11281128
commissioner->CommissioningStageComplete(CHIP_ERROR_INTERNAL, report);
11291129
}
11301130

1131+
void DeviceCommissioner::ExtendArmFailSafe(DeviceProxy * proxy, CommissioningStage step, uint16_t armFailSafeTimeout,
1132+
Optional<System::Clock::Timeout> commandTimeout, OnExtendFailsafeSuccess onSuccess,
1133+
OnExtendFailsafeFailure onFailure)
1134+
{
1135+
uint64_t breadcrumb = static_cast<uint64_t>(step);
1136+
GeneralCommissioning::Commands::ArmFailSafe::Type request;
1137+
request.expiryLengthSeconds = armFailSafeTimeout;
1138+
request.breadcrumb = breadcrumb;
1139+
ChipLogProgress(Controller, "Arming failsafe (%u seconds)", request.expiryLengthSeconds);
1140+
SendCommand<GeneralCommissioningCluster>(proxy, request, onSuccess, onFailure, kRootEndpointId, commandTimeout);
1141+
}
1142+
11311143
void DeviceCommissioner::ExtendArmFailSafeForDeviceAttestation(const Credentials::DeviceAttestationVerifier::AttestationInfo & info,
11321144
Credentials::AttestationVerificationResult result)
11331145
{
@@ -2098,11 +2110,9 @@ void DeviceCommissioner::PerformCommissioningStep(DeviceProxy * proxy, Commissio
20982110
switch (step)
20992111
{
21002112
case CommissioningStage::kArmFailsafe: {
2101-
GeneralCommissioning::Commands::ArmFailSafe::Type request;
2102-
request.expiryLengthSeconds = params.GetFailsafeTimerSeconds().ValueOr(kDefaultFailsafeTimeout);
2103-
request.breadcrumb = breadcrumb;
2104-
ChipLogProgress(Controller, "Arming failsafe (%u seconds)", request.expiryLengthSeconds);
2105-
SendCommand<GeneralCommissioningCluster>(proxy, request, OnArmFailSafe, OnBasicFailure, endpoint, timeout);
2113+
VerifyOrDie(endpoint == kRootEndpointId);
2114+
ExtendArmFailSafe(proxy, step, params.GetFailsafeTimerSeconds().ValueOr(kDefaultFailsafeTimeout), timeout, OnArmFailSafe,
2115+
OnBasicFailure);
21062116
}
21072117
break;
21082118
case CommissioningStage::kReadCommissioningInfo: {

src/controller/CHIPDeviceController.h

+17
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,13 @@ using UdcTransportMgr = TransportMgr<Transport::UDP /* IPv6 */
352352
>;
353353
#endif
354354

355+
/**
356+
* @brief Callback prototype for ExtendArmFailSafe command.
357+
*/
358+
typedef void (*OnExtendFailsafeSuccess)(
359+
void * context, const app::Clusters::GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType & data);
360+
typedef void (*OnExtendFailsafeFailure)(void * context, CHIP_ERROR error);
361+
355362
/**
356363
* @brief
357364
* The commissioner applications can use this class to pair new/unpaired CHIP devices. The application is
@@ -560,6 +567,11 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController,
560567
* or it may call this method after obtaining network credentials using asyncronous methods (prompting user, cloud API call,
561568
* etc).
562569
*
570+
* If an error happens in the subsequent network commissioning step (either NetworkConfig or ConnectNetwork commands)
571+
* then the DevicePairingDelegate will receive the error in completionStatus.networkCommissioningStatus and the
572+
* commissioning stage will return to kNeedsNetworkCreds so that the DevicePairingDelegate can re-attempt with new
573+
* network information. The DevicePairingDelegate can exit the commissioning process by calling StopPairing.
574+
*
563575
* @return CHIP_ERROR The return status. Returns CHIP_ERROR_INCORRECT_STATE if not in the correct state (kNeedsNetworkCreds).
564576
*/
565577
CHIP_ERROR NetworkCredentialsReady();
@@ -670,6 +682,11 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController,
670682
return mDefaultCommissioner == nullptr ? NullOptional : MakeOptional(mDefaultCommissioner->GetCommissioningParameters());
671683
}
672684

685+
// Reset the arm failsafe timer during commissioning.
686+
void ExtendArmFailSafe(DeviceProxy * proxy, CommissioningStage step, uint16_t armFailSafeTimeout,
687+
Optional<System::Clock::Timeout> commandTimeout, OnExtendFailsafeSuccess onSuccess,
688+
OnExtendFailsafeFailure onFailure);
689+
673690
private:
674691
DevicePairingDelegate * mPairingDelegate;
675692

src/controller/CommissioningDelegate.h

+19
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ enum CommissioningStage : uint8_t
5656
/// ScanNetworks can happen anytime after kArmFailsafe.
5757
/// However, the cirque tests fail if it is earlier in the list
5858
kScanNetworks,
59+
/// Waiting for the higher layer to provide network credentials before continuing the workflow.
60+
/// Call CHIPDeviceController::NetworkCredentialsReady() when CommissioningParameters is populated with
61+
/// network credentials to use in kWiFiNetworkSetup or kThreadNetworkSetup steps.
5962
kNeedsNetworkCreds,
6063
};
6164

@@ -105,6 +108,15 @@ class CommissioningParameters
105108
// This value should be set before running PerformCommissioningStep for the kArmFailsafe step.
106109
const Optional<uint16_t> GetFailsafeTimerSeconds() const { return mFailsafeTimerSeconds; }
107110

111+
// Value to use when re-setting the commissioning failsafe timer immediately prior to operational discovery.
112+
// If a CASE failsafe timer value is passed in as part of the commissioning parameters, then the failsafe timer
113+
// will be reset using this value before operational discovery begins. If not supplied, then the AutoCommissioner
114+
// will not automatically reset the failsafe timer before operational discovery begins. It can be useful for the
115+
// commissioner to set the CASE failsafe timer to a small value (ex. 30s) when the regular failsafe timer is set
116+
// to a larger value to accommodate user interaction during setup (network credential selection, user consent
117+
// after device attestation).
118+
const Optional<uint16_t> GetCASEFailsafeTimerSeconds() const { return mCASEFailsafeTimerSeconds; }
119+
108120
// The location (indoor/outdoor) of the node being commissioned.
109121
// The node regulartory location (indoor/outdoor) should be set by the commissioner explicitly as it may be different than the
110122
// location of the commissioner. This location will be set on the node if the node supports configurable regulatory location
@@ -237,6 +249,12 @@ class CommissioningParameters
237249
return *this;
238250
}
239251

252+
CommissioningParameters & SetCASEFailsafeTimerSeconds(uint16_t seconds)
253+
{
254+
mCASEFailsafeTimerSeconds.SetValue(seconds);
255+
return *this;
256+
}
257+
240258
CommissioningParameters & SetDeviceRegulatoryLocation(app::Clusters::GeneralCommissioning::RegulatoryLocationType location)
241259
{
242260
mDeviceRegulatoryLocation.SetValue(location);
@@ -410,6 +428,7 @@ class CommissioningParameters
410428
private:
411429
// Items that can be set by the commissioner
412430
Optional<uint16_t> mFailsafeTimerSeconds;
431+
Optional<uint16_t> mCASEFailsafeTimerSeconds;
413432
Optional<app::Clusters::GeneralCommissioning::RegulatoryLocationType> mDeviceRegulatoryLocation;
414433
Optional<ByteSpan> mCSRNonce;
415434
Optional<ByteSpan> mAttestationNonce;

0 commit comments

Comments
 (0)