Skip to content

Commit 38cf510

Browse files
woody-appleclaude
andcommitted
[Darwin] Surface granular pairing-failure context in MTRError + DNS-SD / BLE error preservation
Consumers of MTRError currently see most commissioning failures surface as a single "Error Domain=com.apple.MatterSupport Code=1 (null)". This change plumbs structured detail into the bridged NSError so callers can disambiguate failure modes, and tightens Darwin's CoreBluetooth / DNS-SD error mapping at the same time. Scope: this commit is intentionally limited to the Darwin framework (MTRError), two new core DNS-SD CHIP_ERROR codes + their descriptions, and the Darwin platform glue (DnssdError mapping, BLE error preservation). It is additive only - it touches 13 files and reverts nothing on master. The earlier core-controller plumbing (CompletionStatus widening, DevicePairingDelegate overload, PASE/CASE status-code mapping) is already merged on master via #72227/#72228 and is NOT part of this commit. Darwin framework (MTRError): - Add NSError userInfo keys with unprefixed string values (MTR_PROVISIONALLY_AVAILABLE): MTRAttestationVerificationResultKey (= @"attestationVerificationResult") MTRDeviceBasicInformationVendorIDKey (= @"deviceBasicInformationVendorID") MTRDeviceBasicInformationProductIDKey (= @"deviceBasicInformationProductID") MTRUnderlyingErrorCodeKey (= @"errorCode" - same string as the key already documented for MTRErrorCodeGeneralError). The Device*BasicInformation* names reflect what the keys actually store: the VID/PID the device asserts in its BasicInformation cluster, which is NOT guaranteed to match the device's certification declaration (mismatch is itself one of the attestation-failure modes). - Surface AttestationVerificationResult enum + BasicInformation VID/PID in attestation-failure NSErrors. - New errorForCHIPErrorCode:logContext:additionalUserInfo: overload lets bridge call sites attach attestation metadata without losing the existing MTRErrorHolder association, and merges additionalUserInfo on both the IM-status and core-error bridge paths (framework keys win). - NSError (Matter) category: mtr_underlyingMatterErrorSourceFile and mtr_underlyingMatterErrorSourceLine read source location on demand from the existing MTRErrorHolder associated object via a single helper. DNS-SD codes (src/lib/core): - New CHIP_ERROR_DNSSD_NXDOMAIN (0xBE) - DNS-SD operational instance does not exist. - New CHIP_ERROR_DNSSD_SERVICE_NOT_RUNNING (0xC7) - DNS-SD platform service is not running. - Description arms in CHIPError.cpp; test entries in TestCHIPErrorStr.cpp; ERROR_CODES.md regenerated. Platform/Darwin: - DnssdError.cpp: map kDNSServiceErr_NoSuchName / NoSuchRecord to CHIP_ERROR_DNSSD_NXDOMAIN, kDNSServiceErr_ServiceNotRunning to CHIP_ERROR_DNSSD_SERVICE_NOT_RUNNING, kDNSServiceErr_Timeout to CHIP_ERROR_TIMEOUT (instead of collapsing to CHIP_ERROR_INTERNAL). - BleConnectionDelegateImpl.mm: WrapCBErrorCodeAsKOS preserves the CoreBluetooth NSError.code in a kOS-range CHIP_ERROR for triage, with a 24-bit overflow guard that falls back to the cross-platform BLE_ERROR_GATT_* sentinel; the existing HandleConnectionError contract is unchanged for non-Darwin consumers. Tests: - MTRErrorMappingTests.m (new): MTRUnderlyingErrorCodeKey population + round-trip via errorToCHIPIntegerCode:, pins all 4 userInfo key string values (rename guard), proves the new key resolves to the @"errorCode" string already documented for MTRErrorCodeGeneralError, and guards additionalUserInfo merge on both the IM-status and core-error paths. - MTRErrorTests.m: basename-only source-path assertions + MTRErrorBasenameForPath mixed-separator coverage. - TestCHIPErrorStr.cpp: the 2 new DNS-SD codes (harness asserts each listed code yields a non-default description). Known test-coverage limitations: - DnssdError.cpp's DNSServiceErrorType -> CHIP_ERROR mapping has no direct unit test: src/platform/Darwin has no unit-test target, and standing one up for a 3-case switch is disproportionate. The underlying error codes are unit-tested in TestCHIPErrorStr.cpp. - WrapCBErrorCodeAsKOS is a file-local static inline; testing it directly would require exposing a triage helper in the platform API surface, which is not worth the API cost for a defensive overflow guard (CoreBluetooth codes are documented small non-negative integers). - Attestation-key population in MTRDeviceAttestationDelegateBridge.mm requires an end-to-end attestation failure to exercise and is not unit tested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8a162c6 commit 38cf510

16 files changed

Lines changed: 1162 additions & 72 deletions

docs/ids_and_codes/ERROR_CODES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ This file was **AUTOMATICALLY** generated by
138138
| 187 | 0xBB | `CHIP_ERROR_MAXIMUM_PATHS_PER_INVOKE_EXCEEDED` |
139139
| 188 | 0xBC | `CHIP_ERROR_PEER_NODE_NOT_FOUND` |
140140
| 189 | 0xBD | `CHIP_ERROR_HSM` |
141+
| 190 | 0xBE | `CHIP_ERROR_DNS_SD_NXDOMAIN` |
141142
| 191 | 0xBF | `CHIP_ERROR_REAL_TIME_NOT_SYNCED` |
142143
| 192 | 0xC0 | `CHIP_ERROR_UNEXPECTED_EVENT` |
143144
| 193 | 0xC1 | `CHIP_ERROR_ENDPOINT_POOL_FULL` |
@@ -146,6 +147,7 @@ This file was **AUTOMATICALLY** generated by
146147
| 196 | 0xC4 | `CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED` |
147148
| 197 | 0xC5 | `CHIP_ERROR_INVALID_PUBLIC_KEY` |
148149
| 198 | 0xC6 | `CHIP_ERROR_FABRIC_MISMATCH_ON_ICA` |
150+
| 199 | 0xC7 | `CHIP_ERROR_DNS_SD_SERVICE_NOT_RUNNING` |
149151
| 201 | 0xC9 | `CHIP_ERROR_NO_SHARED_TRUSTED_ROOT` |
150152
| 202 | 0xCA | `CHIP_ERROR_IM_STATUS_CODE_RECEIVED` |
151153
| 215 | 0xD7 | `CHIP_ERROR_IM_MALFORMED_DATA_VERSION_FILTER_IB` |

src/darwin/Framework/CHIP/MTRDeviceAttestationDelegateBridge.mm

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,6 @@
6262

6363
void * deviceHandle = device;
6464

65-
// TODO: Consider exposing the actual attestation verification result in MTRDeviceAttestationDeviceInfo; need to
66-
// figure out how best to do that.
67-
6865
dispatch_async(mQueue, ^{
6966
// Hide things that are not passed to us by value, so we don't use them by accident.
7067
mtr_hide(deviceCommissioner);
@@ -75,6 +72,19 @@
7572

7673
mResult = attestationResult;
7774

75+
// Surface the AttestationVerificationResult enum and the VID/PID the device asserts in
76+
// its BasicInformation cluster, in NSError userInfo, so callers can disambiguate the
77+
// 40+ distinct attestation-failure modes that previously all collapsed to a single
78+
// MTRErrorCodeIntegrityCheckFailed. The BasicInformation VID/PID may not match the IDs
79+
// in the device's certification declaration — VID/PID mismatch is itself one of the
80+
// attestation-failure modes — so the userInfo keys are named to reflect that these are
81+
// the device-asserted values, not certified identity.
82+
NSDictionary<NSErrorUserInfoKey, id> * attestationUserInfo = @{
83+
MTRAttestationVerificationResultKey : @(chip::to_underlying(attestationResult)),
84+
MTRDeviceBasicInformationVendorIDKey : basicInformationVendorID,
85+
MTRDeviceBasicInformationProductIDKey : basicInformationProductID,
86+
};
87+
7888
id<MTRDeviceAttestationDelegate> strongDelegate = mDeviceAttestationDelegate;
7989
if ([strongDelegate respondsToSelector:@selector(deviceAttestationCompletedForController:opaqueDeviceHandle:attestationDeviceInfo:error:)]
8090
|| [strongDelegate respondsToSelector:@selector(deviceAttestation:completedForDevice:attestationDeviceInfo:error:)]) {
@@ -92,7 +102,9 @@
92102
basicInformationProductID:basicInformationProductID];
93103
NSError * error = (attestationResult == chip::Credentials::AttestationVerificationResult::kSuccess)
94104
? nil
95-
: [MTRError errorForCHIPErrorCode:CHIP_ERROR_INTEGRITY_CHECK_FAILED];
105+
: [MTRError errorForCHIPErrorCode:CHIP_ERROR_INTEGRITY_CHECK_FAILED
106+
logContext:nil
107+
additionalUserInfo:attestationUserInfo];
96108
if ([strongDelegate respondsToSelector:@selector(deviceAttestationCompletedForController:opaqueDeviceHandle:attestationDeviceInfo:error:)]) {
97109
[strongDelegate deviceAttestationCompletedForController:mDeviceController
98110
opaqueDeviceHandle:deviceHandle
@@ -111,7 +123,9 @@
111123

112124
MTRDeviceController * strongController = mDeviceController;
113125
if (strongController) {
114-
NSError * error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INTEGRITY_CHECK_FAILED];
126+
NSError * error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INTEGRITY_CHECK_FAILED
127+
logContext:nil
128+
additionalUserInfo:attestationUserInfo];
115129
if ([strongDelegate respondsToSelector:@selector(deviceAttestationFailedForController:opaqueDeviceHandle:error:)]) {
116130
[strongDelegate deviceAttestationFailedForController:mDeviceController opaqueDeviceHandle:deviceHandle error:error];
117131
} else {

src/darwin/Framework/CHIP/MTRError.h

Lines changed: 133 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,53 @@ NS_ASSUME_NONNULL_BEGIN
2424
MTR_EXTERN NSErrorDomain const MTRErrorDomain MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1));
2525
MTR_EXTERN NSErrorDomain const MTRInteractionErrorDomain MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1));
2626

27+
/**
28+
* NSNumber userInfo key carrying the numeric attestation-verification result for an
29+
* MTRErrorCodeIntegrityCheckFailed error emitted from device-attestation verification.
30+
* Lets callers distinguish, e.g., attestation-nonce mismatch (502) from PAA-not-found
31+
* (101) without otherwise being collapsed to a single integrity-check failure.
32+
*
33+
* The integer values match the underlying Matter SDK attestation-verification-result enum
34+
* and are intended for triage / structured discrimination; specific numeric values are not
35+
* stable across releases.
36+
*/
37+
MTR_EXTERN NSErrorUserInfoKey const MTRAttestationVerificationResultKey MTR_PROVISIONALLY_AVAILABLE;
38+
39+
/**
40+
* NSNumber userInfo keys carrying the vendor ID and product ID the device asserts in its
41+
* BasicInformation cluster, captured at the time of attestation verification. Present in
42+
* NSError userInfo for attestation-related failures so callers can identify which device's
43+
* chain failed validation without walking the attestation-device-info struct themselves.
44+
*
45+
* These values are exactly what the device claims in its BasicInformation cluster — there
46+
* is no guarantee the device is certified. In fact, one of the possible attestation-failure
47+
* modes is that these BasicInformation values do not match the IDs in the device's
48+
* certification declaration. Useful for triage and incident correlation, but should not
49+
* be trusted as authoritative identity until attestation succeeds.
50+
*/
51+
MTR_EXTERN NSErrorUserInfoKey const MTRDeviceBasicInformationVendorIDKey MTR_PROVISIONALLY_AVAILABLE;
52+
MTR_EXTERN NSErrorUserInfoKey const MTRDeviceBasicInformationProductIDKey MTR_PROVISIONALLY_AVAILABLE;
53+
54+
/**
55+
* NSNumber userInfo key carrying the raw 32-bit underlying-error integer backing this NSError
56+
* when it was bridged from an underlying Matter SDK error. Present on every error in
57+
* MTRErrorDomain produced by the error bridge.
58+
*
59+
* The string value associated with this constant — i.e. the literal NSString returned by
60+
* this NSErrorUserInfoKey when used as a dictionary key — is @"errorCode". That is the
61+
* same literal key documented for MTRErrorCodeGeneralError below, so a caller reading
62+
* userInfo[MTRUnderlyingErrorCodeKey] sees exactly the same NSNumber as a caller reading
63+
* userInfo[@"errorCode"].
64+
*
65+
* This value is always populated by the bridge when an NSError in MTRErrorDomain is produced
66+
* from a Matter SDK error, and is not overridable by internal callers — it is the
67+
* authoritative integer underlying the bridged NSError.
68+
*
69+
* Intended for triage and log correlation; specific integer values are not stable across
70+
* releases.
71+
*/
72+
MTR_EXTERN NSErrorUserInfoKey const MTRUnderlyingErrorCodeKey MTR_PROVISIONALLY_AVAILABLE;
73+
2774
/**
2875
* MTRErrorDomain contains errors caused by data processing the framework
2976
* itself is performing. These can be caused by invalid values provided to a
@@ -35,26 +82,33 @@ MTR_EXTERN NSErrorDomain const MTRInteractionErrorDomain MTR_AVAILABLE(ios(16.1)
3582
* Errors reported by the server side of a Matter interaction via the normal
3683
* Matter error-reporting mechanisms use MTRInteractionErrorDomain instead.
3784
*/
38-
typedef NS_ERROR_ENUM(MTRErrorDomain, MTRErrorCode){
85+
typedef NS_ERROR_ENUM(MTRErrorDomain, MTRErrorCode) {
3986
/**
4087
* MTRErrorCodeGeneralError represents a generic Matter error with no
4188
* further categorization.
4289
*
43-
* The userInfo will have a key named @"errorCode" whose value will be an
44-
* integer representing the underlying Matter error code. These integer
45-
* values should not be assumed to be stable across releases, but may be
46-
* useful in logging and debugging.
90+
* The userInfo will have MTRUnderlyingErrorCodeKey (string value
91+
* @"errorCode") populated, whose value will be an NSNumber representing
92+
* the underlying Matter error code. These integer values should not be
93+
* assumed to be stable across releases, but may be useful in logging and
94+
* debugging.
95+
*
96+
* Note: as of this release, MTRUnderlyingErrorCodeKey is present on
97+
* EVERY error in MTRErrorDomain produced by the bridge, not just
98+
* MTRErrorCodeGeneralError. Do not use the presence of this key as a
99+
* discriminator for GeneralError; check error.code ==
100+
* MTRErrorCodeGeneralError instead.
47101
*/
48-
MTRErrorCodeGeneralError = 1,
49-
MTRErrorCodeInvalidStringLength = 2,
50-
MTRErrorCodeInvalidIntegerValue = 3,
51-
MTRErrorCodeInvalidArgument = 4,
102+
MTRErrorCodeGeneralError = 1,
103+
MTRErrorCodeInvalidStringLength = 2,
104+
MTRErrorCodeInvalidIntegerValue = 3,
105+
MTRErrorCodeInvalidArgument = 4,
52106
MTRErrorCodeInvalidMessageLength = 5,
53-
MTRErrorCodeInvalidState = 6,
54-
MTRErrorCodeWrongAddressType = 7,
107+
MTRErrorCodeInvalidState = 6,
108+
MTRErrorCodeWrongAddressType = 7,
55109
MTRErrorCodeIntegrityCheckFailed = 8,
56-
MTRErrorCodeTimeout = 9,
57-
MTRErrorCodeBufferTooSmall = 10,
110+
MTRErrorCodeTimeout = 9,
111+
MTRErrorCodeBufferTooSmall = 10,
58112

59113
/**
60114
* MTRErrorCodeFabricExists is returned when trying to commission a device
@@ -125,39 +179,75 @@ typedef NS_ERROR_ENUM(MTRErrorDomain, MTRErrorCode){
125179
* was reported. This key will be absent if there was no cluster-specific
126180
* status.
127181
*/
128-
typedef NS_ERROR_ENUM(MTRInteractionErrorDomain, MTRInteractionErrorCode){
182+
typedef NS_ERROR_ENUM(MTRInteractionErrorDomain, MTRInteractionErrorCode) {
129183
// These values come from the general status code table in the Matter
130184
// Interaction Model specification.
131-
MTRInteractionErrorCodeFailure = 0x01,
132-
MTRInteractionErrorCodeInvalidSubscription = 0x7d,
133-
MTRInteractionErrorCodeUnsupportedAccess = 0x7e,
134-
MTRInteractionErrorCodeUnsupportedEndpoint = 0x7f,
135-
MTRInteractionErrorCodeInvalidAction = 0x80,
136-
MTRInteractionErrorCodeUnsupportedCommand = 0x81,
137-
MTRInteractionErrorCodeInvalidCommand = 0x85,
138-
MTRInteractionErrorCodeUnsupportedAttribute = 0x86,
139-
MTRInteractionErrorCodeConstraintError = 0x87,
140-
MTRInteractionErrorCodeUnsupportedWrite = 0x88,
141-
MTRInteractionErrorCodeResourceExhausted = 0x89,
142-
MTRInteractionErrorCodeNotFound = 0x8b,
143-
MTRInteractionErrorCodeUnreportableAttribute = 0x8c,
144-
MTRInteractionErrorCodeInvalidDataType = 0x8d,
145-
MTRInteractionErrorCodeUnsupportedRead = 0x8f,
146-
MTRInteractionErrorCodeDataVersionMismatch = 0x92,
147-
MTRInteractionErrorCodeTimeout = 0x94,
148-
MTRInteractionErrorCodeBusy = 0x9c,
149-
MTRInteractionErrorCodeAccessRestricted MTR_AVAILABLE(ios(26.0), macos(26.0), watchos(26.0), tvos(26.0)) = 0x9d,
150-
MTRInteractionErrorCodeUnsupportedCluster = 0xc3,
151-
MTRInteractionErrorCodeNoUpstreamSubscription = 0xc5,
152-
MTRInteractionErrorCodeNeedsTimedInteraction = 0xc6,
153-
MTRInteractionErrorCodeUnsupportedEvent = 0xc7,
154-
MTRInteractionErrorCodePathsExhausted = 0xc8,
155-
MTRInteractionErrorCodeTimedRequestMismatch = 0xc9,
156-
MTRInteractionErrorCodeFailsafeRequired = 0xca,
157-
MTRInteractionErrorCodeInvalidInState MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6)) = 0xcb,
185+
MTRInteractionErrorCodeFailure = 0x01,
186+
MTRInteractionErrorCodeInvalidSubscription = 0x7d,
187+
MTRInteractionErrorCodeUnsupportedAccess = 0x7e,
188+
MTRInteractionErrorCodeUnsupportedEndpoint = 0x7f,
189+
MTRInteractionErrorCodeInvalidAction = 0x80,
190+
MTRInteractionErrorCodeUnsupportedCommand = 0x81,
191+
MTRInteractionErrorCodeInvalidCommand = 0x85,
192+
MTRInteractionErrorCodeUnsupportedAttribute = 0x86,
193+
MTRInteractionErrorCodeConstraintError = 0x87,
194+
MTRInteractionErrorCodeUnsupportedWrite = 0x88,
195+
MTRInteractionErrorCodeResourceExhausted = 0x89,
196+
MTRInteractionErrorCodeNotFound = 0x8b,
197+
MTRInteractionErrorCodeUnreportableAttribute = 0x8c,
198+
MTRInteractionErrorCodeInvalidDataType = 0x8d,
199+
MTRInteractionErrorCodeUnsupportedRead = 0x8f,
200+
MTRInteractionErrorCodeDataVersionMismatch = 0x92,
201+
MTRInteractionErrorCodeTimeout = 0x94,
202+
MTRInteractionErrorCodeBusy = 0x9c,
203+
MTRInteractionErrorCodeAccessRestricted MTR_AVAILABLE(ios(26.0), macos(26.0), watchos(26.0), tvos(26.0)) = 0x9d,
204+
MTRInteractionErrorCodeUnsupportedCluster = 0xc3,
205+
MTRInteractionErrorCodeNoUpstreamSubscription = 0xc5,
206+
MTRInteractionErrorCodeNeedsTimedInteraction = 0xc6,
207+
MTRInteractionErrorCodeUnsupportedEvent = 0xc7,
208+
MTRInteractionErrorCodePathsExhausted = 0xc8,
209+
MTRInteractionErrorCodeTimedRequestMismatch = 0xc9,
210+
MTRInteractionErrorCodeFailsafeRequired = 0xca,
211+
MTRInteractionErrorCodeInvalidInState MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6)) = 0xcb,
158212
MTRInteractionErrorCodeNoCommandResponse MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6)) = 0xcc,
159-
MTRInteractionErrorCodeDynamicConstraintError MTR_PROVISIONALLY_AVAILABLE = 0xcf,
160-
MTRInteractionErrorCodeInvalidTransportType MTR_PROVISIONALLY_AVAILABLE = 0xd1,
213+
MTRInteractionErrorCodeDynamicConstraintError MTR_PROVISIONALLY_AVAILABLE = 0xcf,
214+
MTRInteractionErrorCodeInvalidTransportType MTR_PROVISIONALLY_AVAILABLE = 0xd1,
161215
} MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1));
162216

217+
/**
218+
* Accessors for source-location information that may be attached to NSError objects
219+
* bridged from the underlying Matter error type. The information is available when the
220+
* underlying SDK was built to record source locations; otherwise the accessors return
221+
* nil / 0.
222+
*
223+
* Lifetime caveat: these accessors are backed by an associated object attached to the
224+
* NSError instance at bridge time. Associated objects are not preserved by NSCoding or
225+
* NSCopying — if the NSError is archived, unarchived, or copied through any mechanism
226+
* that does not propagate associated objects (NSKeyedArchiver, [error copy], crossing
227+
* an XPC boundary, etc.), these accessors will return nil / 0 on the resulting object
228+
* even when the original would have returned a value.
229+
*
230+
* For MTRInteractionErrorDomain errors the source location reflects the StatusIB
231+
* encoding site inside the Matter SDK, not the device-reported origin of the status.
232+
*/
233+
@interface NSError (Matter)
234+
235+
/**
236+
* The source-file basename where the underlying Matter error was originally created.
237+
* Returns nil when the SDK did not record a source location, when the error was not
238+
* bridged from a Matter error, or when no source string was captured.
239+
*
240+
* Only the basename is exposed — the full path is intentionally elided to avoid leaking
241+
* build-host paths. Intended for triage; not stable across releases.
242+
*/
243+
@property (nonatomic, readonly, nullable) NSString * mtr_underlyingMatterErrorSourceFile MTR_PROVISIONALLY_AVAILABLE;
244+
245+
/**
246+
* The source-line where the underlying Matter error was originally created. Returns 0
247+
* when not available.
248+
*/
249+
@property (nonatomic, readonly) NSUInteger mtr_underlyingMatterErrorSourceLine MTR_PROVISIONALLY_AVAILABLE;
250+
251+
@end
252+
163253
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)