Skip to content

Commit 6c85140

Browse files
[Controller] Preserve granular commissioning-failure context in CompletionStatus (#72228)
* Preserve granular commissioning-failure context in CompletionStatus Today OnCommissioningFailure collapses several distinguishable failure modes into a single CHIP_ERROR, leaving controllers unable to disambiguate without parsing logs. This change threads structured failure context through the commissioning pipeline so consumers can branch on the device-reported cause. DevicePairingDelegate: - New OnCommissioningFailure(PeerId, const CompletionStatus &) overload whose default implementation forwards to the legacy 4-arg form. Existing subclasses keep working unchanged. CompletionStatus widened with optional structured fields: - commissioningError - GeneralCommissioning::CommissioningErrorEnum - networkCommissioningStatus + connectNetworkErrorValue - operationalCertStatus - NodeOperationalCertStatusEnum from NOCResponse - commissioningDebugText, networkCommissioningDebugText - owned std::string copies of device-supplied debug text Three report variants updated to carry the additional data: - CommissioningErrorInfo gains std::string debugText - NetworkCommissioningStatusInfo gains std::string debugText + Optional<int32_t> connectNetworkErrorValue - New OperationalCertErrorInfo carrying the cert-status enum AutoCommissioner::CommissioningStepFinished populates CompletionStatus from these report variants on the failure path. CHIPDeviceController response handlers populate the corresponding reports: OnArmFailSafe, OnSetRegulatoryConfigResponse, OnSetTCAcknowledgementsResponse, OnCommissioningCompleteResponse, OnOperationalCertificateAddResponse, OnConnectNetworkResponse, OnNetworkConfigResponse. PASESession: - OnFailureStatusReport maps kProtocolCodeNoSharedRoot to CHIP_ERROR_NO_SHARED_TRUSTED_ROOT (PASE has no shared-root semantics, so a peer sending this is misconfigured, but the mapping is more useful than CHIP_ERROR_INTERNAL). - OnFailureStatusReport maps kProtocolCodeBusy to CHIP_ERROR_BUSY (uncommon in PASE but not spec-forbidden). Backward compatibility: - New OnCommissioningFailure overload defaults to forwarding to the legacy 4-arg form. Existing subclasses keep compiling and running. - All new fields on CompletionStatus are Optional or std::string with empty as the absence marker. - No public API removed. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 4f49d6c commit 6c85140

6 files changed

Lines changed: 289 additions & 16 deletions

File tree

src/controller/AutoCommissioner.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -800,13 +800,27 @@ CHIP_ERROR AutoCommissioner::CommissioningStepFinished(CHIP_ERROR err, Commissio
800800
}
801801
else if (report.Is<CommissioningErrorInfo>())
802802
{
803-
completionStatus.commissioningError = MakeOptional(report.Get<CommissioningErrorInfo>().commissioningError);
803+
const auto & commissioningInfo = report.Get<CommissioningErrorInfo>();
804+
completionStatus.commissioningError = MakeOptional(commissioningInfo.commissioningError);
805+
completionStatus.commissioningDebugText = commissioningInfo.debugText;
806+
}
807+
else if (report.Is<OperationalCertErrorInfo>())
808+
{
809+
// Preserve the NodeOperationalCertStatusEnum from the device's NOCResponse
810+
// (kInvalidPublicKey, kInvalidNodeOpId, kInvalidNOC, kFabricConflict, kLabelConflict,
811+
// kInvalidFabricIndex, etc.) so callers can distinguish without losing fidelity to a
812+
// generic CHIP_ERROR.
813+
completionStatus.operationalCertStatus = MakeOptional(report.Get<OperationalCertErrorInfo>().operationalCertStatus);
804814
}
805815
else if (report.Is<NetworkCommissioningStatusInfo>())
806816
{
807817
// This report type is used when an error happens in either NetworkConfig or ConnectNetwork commands
808-
completionStatus.networkCommissioningStatus =
809-
MakeOptional(report.Get<NetworkCommissioningStatusInfo>().networkCommissioningStatus);
818+
const auto & networkInfo = report.Get<NetworkCommissioningStatusInfo>();
819+
completionStatus.networkCommissioningStatus = MakeOptional(networkInfo.networkCommissioningStatus);
820+
// Preserve the optional ConnectNetworkResponse.errorValue (driver-level detail
821+
// distinct from the spec-level networkingStatus). Null for NetworkConfigResponse.
822+
completionStatus.connectNetworkErrorValue = networkInfo.connectNetworkErrorValue;
823+
completionStatus.networkCommissioningDebugText = networkInfo.debugText;
810824

811825
// If we are configured to scan networks, then don't error out.
812826
// Instead, allow the app to try another network.

src/controller/CHIPDeviceController.cpp

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1872,7 +1872,22 @@ void DeviceCommissioner::OnOperationalCertificateAddResponse(
18721872
if (err != CHIP_NO_ERROR)
18731873
{
18741874
ChipLogProgress(Controller, "Add NOC failed with error: %" CHIP_ERROR_FORMAT, err.Format());
1875-
commissioner->CommissioningStageComplete(err);
1875+
// Preserve the device-reported NodeOperationalCertStatusEnum (kInvalidPublicKey,
1876+
// kInvalidNodeOpId, kInvalidNOC, kFabricConflict, kLabelConflict, kInvalidFabricIndex,
1877+
// kTableFull, etc.) in the report so OnCommissioningFailure consumers can distinguish
1878+
// these without losing fidelity to the generic CHIP_ERROR returned by
1879+
// ConvertFromOperationalCertStatus.
1880+
CommissioningDelegate::CommissioningReport report;
1881+
// ConvertFromOperationalCertStatus succeeds on kOk and returns a non-success CHIP_ERROR
1882+
// for any other value, but `err` at this point can also be a downstream local failure
1883+
// (e.g. from OnOperationalCredentialsProvisioningCompletion) while data.statusCode is
1884+
// still kOk. Guard so we never publish a "success enum" alongside a non-success err to
1885+
// OnCommissioningFailure consumers.
1886+
if (data.statusCode != OperationalCredentials::NodeOperationalCertStatusEnum::kOk)
1887+
{
1888+
report.Set<OperationalCertErrorInfo>(data.statusCode);
1889+
}
1890+
commissioner->CommissioningStageComplete(err, report);
18761891
}
18771892
}
18781893

@@ -2172,10 +2187,7 @@ void DeviceCommissioner::SendCommissioningCompleteCallbacks(NodeId nodeId, const
21722187
}
21732188
else
21742189
{
2175-
// TODO: We should propogate detailed error information (commissioningError, networkCommissioningStatus) from
2176-
// completionStatus.
2177-
mPairingDelegate->OnCommissioningFailure(peerId, completionStatus.err, completionStatus.failedStage.ValueOr(kError),
2178-
completionStatus.attestationResult);
2190+
mPairingDelegate->OnCommissioningFailure(peerId, completionStatus);
21792191
}
21802192
}
21812193

@@ -2975,7 +2987,9 @@ void DeviceCommissioner::OnArmFailSafe(void * context,
29752987
if (data.errorCode != GeneralCommissioning::CommissioningErrorEnum::kOk)
29762988
{
29772989
err = CHIP_ERROR_INTERNAL;
2978-
report.Set<CommissioningErrorInfo>(data.errorCode);
2990+
// Preserve the device-supplied debugText so failure consumers can disambiguate
2991+
// ambiguous error codes (e.g. kBusyWithOtherAdmin specifics).
2992+
report.Set<CommissioningErrorInfo>(data.errorCode, data.debugText);
29792993
}
29802994

29812995
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
@@ -2992,7 +3006,7 @@ void DeviceCommissioner::OnSetRegulatoryConfigResponse(
29923006
if (data.errorCode != GeneralCommissioning::CommissioningErrorEnum::kOk)
29933007
{
29943008
err = CHIP_ERROR_INTERNAL;
2995-
report.Set<CommissioningErrorInfo>(data.errorCode);
3009+
report.Set<CommissioningErrorInfo>(data.errorCode, data.debugText);
29963010
}
29973011
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
29983012
commissioner->CommissioningStageComplete(err, report);
@@ -3008,6 +3022,7 @@ void DeviceCommissioner::OnSetTCAcknowledgementsResponse(
30083022
if (data.errorCode != GeneralCommissioning::CommissioningErrorEnum::kOk)
30093023
{
30103024
err = CHIP_ERROR_INTERNAL;
3025+
// SetTCAcknowledgementsResponse has no debugText field per spec.
30113026
report.Set<CommissioningErrorInfo>(data.errorCode);
30123027
}
30133028
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
@@ -3099,7 +3114,9 @@ void DeviceCommissioner::OnNetworkConfigResponse(void * context,
30993114
if (data.networkingStatus != NetworkCommissioning::NetworkCommissioningStatusEnum::kSuccess)
31003115
{
31013116
err = CHIP_ERROR_INTERNAL;
3102-
report.Set<NetworkCommissioningStatusInfo>(data.networkingStatus);
3117+
// Preserve debugText alongside the status enum so callers can distinguish
3118+
// ambiguous statuses (e.g. kAuthFailure: "wrong password" vs "regulatory restriction").
3119+
report.Set<NetworkCommissioningStatusInfo>(data.networkingStatus, data.debugText.ValueOr(CharSpan{}));
31033120
}
31043121
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
31053122
commissioner->CommissioningStageComplete(err, report);
@@ -3115,7 +3132,12 @@ void DeviceCommissioner::OnConnectNetworkResponse(
31153132
if (data.networkingStatus != NetworkCommissioning::NetworkCommissioningStatusEnum::kSuccess)
31163133
{
31173134
err = CHIP_ERROR_INTERNAL;
3118-
report.Set<NetworkCommissioningStatusInfo>(data.networkingStatus);
3135+
// Preserve debugText alongside the status enum (see OnNetworkConfigResponse). Also
3136+
// surface the device-specific errorValue which carries driver-level failure detail
3137+
// (TX-power-limited / interference / association-failure code) distinct from the
3138+
// spec-level networkingStatus enum.
3139+
Optional<int32_t> errorValue = data.errorValue.IsNull() ? NullOptional : MakeOptional(data.errorValue.Value());
3140+
report.Set<NetworkCommissioningStatusInfo>(data.networkingStatus, data.debugText.ValueOr(CharSpan{}), errorValue);
31193141
}
31203142
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
31213143
commissioner->CommissioningStageComplete(err, report);
@@ -3131,7 +3153,7 @@ void DeviceCommissioner::OnCommissioningCompleteResponse(
31313153
if (data.errorCode != GeneralCommissioning::CommissioningErrorEnum::kOk)
31323154
{
31333155
err = CHIP_ERROR_INTERNAL;
3134-
report.Set<CommissioningErrorInfo>(data.errorCode);
3156+
report.Set<CommissioningErrorInfo>(data.errorCode, data.debugText);
31353157
}
31363158
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
31373159
commissioner->CommissioningStageComplete(err, report);

src/controller/CommissioningDelegate.h

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@
2626
#include <credentials/attestation_verifier/DeviceAttestationVerifier.h>
2727
#include <credentials/jcm/TrustVerification.h>
2828
#include <crypto/CHIPCryptoPAL.h>
29+
#include <lib/support/CharSpanToStdString.h>
2930
#include <lib/support/Span.h>
3031
#include <lib/support/Variant.h>
3132
#include <matter/tracing/build_config.h>
3233
#include <system/SystemClock.h>
3334

35+
#include <string>
36+
3437
namespace chip {
3538
namespace Controller {
3639

@@ -137,11 +140,30 @@ struct NOCChainGenerationParameters
137140
struct CompletionStatus
138141
{
139142
CompletionStatus() : err(CHIP_NO_ERROR), failedStage(NullOptional), attestationResult(NullOptional) {}
143+
140144
CHIP_ERROR err;
141145
Optional<CommissioningStage> failedStage;
142146
Optional<Credentials::AttestationVerificationResult> attestationResult;
143147
Optional<app::Clusters::GeneralCommissioning::CommissioningErrorEnum> commissioningError;
144148
Optional<app::Clusters::NetworkCommissioning::NetworkCommissioningStatusEnum> networkCommissioningStatus;
149+
/// Optional device-reported low-level error from ConnectNetworkResponse.errorValue (e.g.
150+
/// driver-specific TX-power-limited / interference / association-failure code), distinct
151+
/// from the spec-level NetworkCommissioningStatusEnum already captured above.
152+
Optional<int32_t> connectNetworkErrorValue;
153+
/// Optional NodeOperationalCertStatusEnum from the device's NOCResponse during the
154+
/// AddNOC / UpdateNOC / RemoveFabric flows. Lets callers distinguish kInvalidPublicKey
155+
/// from kInvalidNodeOpId from kFabricConflict from kLabelConflict, etc., without losing
156+
/// fidelity to a generic CHIP_ERROR.
157+
Optional<app::Clusters::OperationalCredentials::NodeOperationalCertStatusEnum> operationalCertStatus;
158+
159+
/// Owned copy of the device-supplied GeneralCommissioning debugText from
160+
/// ArmFailSafeResponse / SetRegulatoryConfigResponse / CommissioningCompleteResponse.
161+
/// Empty if no debug text was provided.
162+
std::string commissioningDebugText;
163+
164+
/// Owned copy of the device-supplied NetworkCommissioning debugText from
165+
/// NetworkConfigResponse / ConnectNetworkResponse. Empty if no debug text was provided.
166+
std::string networkCommissioningDebugText;
145167
};
146168

147169
inline constexpr uint16_t kDefaultFailsafeTimeout = 60;
@@ -842,15 +864,46 @@ struct AttestationErrorInfo
842864
struct CommissioningErrorInfo
843865
{
844866
CommissioningErrorInfo(app::Clusters::GeneralCommissioning::CommissioningErrorEnum result) : commissioningError(result) {}
867+
CommissioningErrorInfo(app::Clusters::GeneralCommissioning::CommissioningErrorEnum result, CharSpan text) :
868+
commissioningError(result), debugText(CharSpanToStdString(text))
869+
{}
845870
app::Clusters::GeneralCommissioning::CommissioningErrorEnum commissioningError;
871+
/// Owned copy of the device-supplied `debugText` from
872+
/// ArmFailSafeResponse / SetRegulatoryConfigResponse / CommissioningCompleteResponse.
873+
/// Empty when the device did not provide debug text.
874+
std::string debugText;
875+
};
876+
877+
struct OperationalCertErrorInfo
878+
{
879+
OperationalCertErrorInfo(app::Clusters::OperationalCredentials::NodeOperationalCertStatusEnum result) :
880+
operationalCertStatus(result)
881+
{}
882+
app::Clusters::OperationalCredentials::NodeOperationalCertStatusEnum operationalCertStatus;
846883
};
847884

848885
struct NetworkCommissioningStatusInfo
849886
{
850887
NetworkCommissioningStatusInfo(app::Clusters::NetworkCommissioning::NetworkCommissioningStatusEnum result) :
851888
networkCommissioningStatus(result)
852889
{}
890+
NetworkCommissioningStatusInfo(app::Clusters::NetworkCommissioning::NetworkCommissioningStatusEnum result, CharSpan text) :
891+
networkCommissioningStatus(result), debugText(CharSpanToStdString(text))
892+
{}
893+
NetworkCommissioningStatusInfo(app::Clusters::NetworkCommissioning::NetworkCommissioningStatusEnum result, CharSpan text,
894+
Optional<int32_t> errorValue) :
895+
networkCommissioningStatus(result),
896+
debugText(CharSpanToStdString(text)), connectNetworkErrorValue(errorValue)
897+
{}
853898
app::Clusters::NetworkCommissioning::NetworkCommissioningStatusEnum networkCommissioningStatus;
899+
/// Owned copy of the device-supplied `debugText` from
900+
/// `NetworkConfigResponse` / `ConnectNetworkResponse`. Empty when the device did not
901+
/// provide debug text.
902+
std::string debugText;
903+
/// Optional device-specific connect failure code from ConnectNetworkResponse.errorValue.
904+
/// Only populated for ConnectNetwork failures; null for NetworkConfig failures (which
905+
/// don't carry an errorValue field).
906+
Optional<int32_t> connectNetworkErrorValue;
854907
};
855908

856909
class CommissioningDelegate
@@ -874,7 +927,7 @@ class CommissioningDelegate
874927
* kSendOpCertSigningRequest: CSRResponse
875928
* kGenerateNOCChain: NocChain
876929
* kSendTrustedRootCert: None
877-
* kSendNOC: None
930+
* kSendNOC: OperationalCertErrorInfo if AddNOC returned a non-success NodeOperationalCertStatusEnum
878931
* kConfigureTrustedTimeSource: None
879932
* kWiFiNetworkSetup: NetworkCommissioningStatusInfo if there is an error
880933
* kThreadNetworkSetup: NetworkCommissioningStatusInfo if there is an error
@@ -889,8 +942,8 @@ class CommissioningDelegate
889942
*/
890943
struct CommissioningReport
891944
: Variant<RequestedCertificate, AttestationResponse, CSRResponse, NocChain, OperationalNodeFoundData, ReadCommissioningInfo,
892-
AttestationErrorInfo, CommissioningErrorInfo, NetworkCommissioningStatusInfo, TimeZoneResponseInfo,
893-
Credentials::JCM::TrustVerificationError>
945+
AttestationErrorInfo, CommissioningErrorInfo, OperationalCertErrorInfo, NetworkCommissioningStatusInfo,
946+
TimeZoneResponseInfo, Credentials::JCM::TrustVerificationError>
894947
{
895948
CommissioningReport() : stageCompleted(CommissioningStage::kError) {}
896949
CommissioningStage stageCompleted;

src/controller/DevicePairingDelegate.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,33 @@ class DLL_EXPORT DevicePairingDelegate
103103
Optional<Credentials::AttestationVerificationResult> additionalErrorInfo)
104104
{}
105105

106+
/**
107+
* @brief
108+
* Called when commissioning fails, with the full structured CompletionStatus from the
109+
* commissioning state machine. Provides access to detailed status that the 4-arg overload
110+
* above drops on the floor.
111+
*
112+
* Default implementation forwards to the legacy 4-arg overload above, so existing
113+
* delegates keep working unchanged. The legacy form only carries `err`, `failedStage`,
114+
* and `attestationResult` — the following CompletionStatus fields are silently dropped
115+
* when the default forwarder runs:
116+
*
117+
* - commissioningError (GeneralCommissioning cluster CommissioningErrorEnum)
118+
* - networkCommissioningStatus + connectNetworkErrorValue
119+
* - operationalCertStatus (OperationalCredentials NOCResponse status)
120+
* - commissioningDebugText (device-supplied text from ArmFailSafe /
121+
* SetRegulatoryConfig / CommissioningComplete)
122+
* - networkCommissioningDebugText (device-supplied text from NetworkConfig /
123+
* ConnectNetwork)
124+
*
125+
* Override this overload (instead of the 4-arg one) to receive those fields.
126+
*/
127+
virtual void OnCommissioningFailure(PeerId peerId, const CompletionStatus & completionStatus)
128+
{
129+
OnCommissioningFailure(peerId, completionStatus.err, completionStatus.failedStage.ValueOr(CommissioningStage::kError),
130+
completionStatus.attestationResult);
131+
}
132+
106133
virtual void OnCommissioningStatusUpdate(PeerId peerId, CommissioningStage stageCompleted, CHIP_ERROR error) {}
107134

108135
/**

0 commit comments

Comments
 (0)