Skip to content

Commit 370089c

Browse files
woody-appleclaude
andcommitted
rdar://177309700 (Setup payload parse failures (Manual + QR) silently flattened to InvalidArgument)
Add NSError**-bearing inits on MTRSetupPayload (-initWithManualPairingCode:error: public, -initWithQRCode:error: internal) that preserve the underlying CHIP_ERROR via MTRError, so callers can distinguish typo/integrity-check failures from generic parse rejection. Route MTRManualSetupPayloadParser, MTRQRCodeSetupPayloadParser, +setupPayloadWithOnboardingPayload:error:, and -pairDevice:onboardingPayload:error: through the new variants. No-error inits preserved as thin wrappers. The error-bearing -initWithManualPairingCode:error: is declared once, on the public MTRSetupPayload.h; the redundant MTR_DIRECT_MEMBERS re-declaration in MTRSetupPayload_Internal.h is removed to avoid the duplicate/direct-conflict declaration that broke the Darwin/tvOS framework builds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8a162c6 commit 370089c

8 files changed

Lines changed: 421 additions & 12 deletions

src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2383,11 +2383,12 @@ - (BOOL)pairDevice:(uint64_t)deviceID
23832383

23842384
- (BOOL)pairDevice:(uint64_t)deviceID onboardingPayload:(NSString *)onboardingPayload error:(NSError * __autoreleasing *)error
23852385
{
2386-
auto * setupPayload = [[MTRSetupPayload alloc] initWithPayload:onboardingPayload];
2386+
// Use the fine-grained -initWithPayload:error: (NOT the deprecated
2387+
// +setupPayloadWithOnboardingPayload:error:, which flattens to
2388+
// MTRErrorCodeInvalidArgument) so a mistyped manual pairing code surfaces
2389+
// as MTRErrorCodeIntegrityCheckFailed to the commissioning UI.
2390+
auto * setupPayload = [[MTRSetupPayload alloc] initWithPayload:onboardingPayload error:error];
23872391
if (!setupPayload) {
2388-
if (error) {
2389-
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
2390-
}
23912392
return NO;
23922393
}
23932394

src/darwin/Framework/CHIP/MTRManualSetupPayloadParser.mm

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,12 @@ - (id)initWithDecimalStringRepresentation:(NSString *)decimalStringRepresentatio
3333

3434
- (MTRSetupPayload *)populatePayload:(NSError * __autoreleasing *)error
3535
{
36-
MTRSetupPayload * payload = [[MTRSetupPayload alloc] initWithManualPairingCode:_decimalStringRepresentation];
37-
if (!payload && error) {
36+
MTRSetupPayload * payload = [[MTRSetupPayload alloc] initWithManualPairingCode:_decimalStringRepresentation
37+
error:nil];
38+
if (payload == nil && error != nil) {
39+
// Deprecated surface: flatten to MTRErrorCodeInvalidArgument for the
40+
// legacy contract. Callers wanting the fine-grained code should use
41+
// -[MTRSetupPayload initWithManualPairingCode:error:].
3842
*error = [MTRError errorWithCode:MTRErrorCodeInvalidArgument];
3943
}
4044
return payload;

src/darwin/Framework/CHIP/MTRQRCodeSetupPayloadParser.mm

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,14 @@ - (id)initWithBase38Representation:(NSString *)base38Representation
3333

3434
- (MTRSetupPayload *)populatePayload:(NSError * __autoreleasing *)error
3535
{
36-
MTRSetupPayload * payload = [[MTRSetupPayload alloc] initWithQRCode:_base38Representation];
37-
if (!payload && error) {
36+
// Pass _base38Representation through unchanged so the set of inputs this
37+
// surface accepts/rejects stays identical to the prior implementation; we
38+
// only route through the new -initWithQRCode:error: as a thin shim.
39+
MTRSetupPayload * payload = [[MTRSetupPayload alloc] initWithQRCode:_base38Representation error:nil];
40+
if (payload == nil && error != nil) {
41+
// Deprecated surface: flatten to MTRErrorCodeInvalidArgument for the
42+
// legacy contract. Callers wanting the fine-grained code should use
43+
// -[MTRSetupPayload initWithQRCode:error:].
3844
*error = [MTRError errorWithCode:MTRErrorCodeInvalidArgument];
3945
}
4046
return payload;

src/darwin/Framework/CHIP/MTRSetupPayload.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,44 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
121121
*/
122122
- (nullable instancetype)initWithPayload:(NSString *)payload MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6));
123123

124+
/**
125+
* Initializes the payload object from the provided QR Code or Manual Pairing
126+
* Code string, reporting a fine-grained error on failure.
127+
*
128+
* Returns nil and populates `error` (if non-nil) if the string is not a valid
129+
* payload. The returned error is in MTRErrorDomain and PRESERVES the underlying
130+
* parse-failure code rather than flattening it: in particular, a Manual Pairing
131+
* Code with an incorrect Verhoeff check digit reports
132+
* `MTRErrorCodeIntegrityCheckFailed`, letting a commissioning UI distinguish a
133+
* mistyped setup code from other parse failures.
134+
*
135+
* This is the error-bearing counterpart of -initWithPayload:. Unlike the
136+
* deprecated +setupPayloadWithOnboardingPayload:error:, it does not flatten all
137+
* failures to MTRErrorCodeInvalidArgument.
138+
*
139+
* @note MTRErrorCodeInvalidArgument may still be returned for structurally
140+
* valid payloads whose contents fail semantic validation.
141+
*/
142+
- (nullable instancetype)initWithPayload:(NSString *)payload
143+
error:(NSError * __autoreleasing *)error MTR_PROVISIONALLY_AVAILABLE;
144+
145+
/**
146+
* Initializes the payload object from the provided Manual Pairing Code string.
147+
*
148+
* Returns nil and populates `error` (if non-nil) if the string is not a valid
149+
* Manual Pairing Code. The returned error is in MTRErrorDomain; in particular,
150+
* a Manual Pairing Code with an incorrect Verhoeff check digit will report
151+
* `MTRErrorCodeIntegrityCheckFailed`, allowing callers to distinguish a
152+
* mistyped setup code from other parse failures.
153+
*
154+
* @note MTRErrorCodeInvalidArgument may still be returned for structurally
155+
* valid manual codes (e.g. correct Verhoeff digit) whose contents fail
156+
* semantic validation, distinct from MTRErrorCodeIntegrityCheckFailed
157+
* which indicates a check-digit failure.
158+
*/
159+
- (nullable instancetype)initWithManualPairingCode:(NSString *)manualCode
160+
error:(NSError * __autoreleasing *)error MTR_PROVISIONALLY_AVAILABLE;
161+
124162
/**
125163
* Whether this object represents a concatenated QR Code payload consisting
126164
* of two or more underlying payloads. If YES, then:

src/darwin/Framework/CHIP/MTRSetupPayload.mm

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,16 @@ + (void)initialize
225225

226226
- (nullable instancetype)initWithPayload:(NSString *)payload
227227
{
228-
return ([payload hasPrefix:@"MT:"]) ? [self initWithQRCode:payload] : [self initWithManualPairingCode:payload];
228+
return [self initWithPayload:payload error:nil];
229+
}
230+
231+
- (nullable instancetype)initWithPayload:(NSString *)payload error:(NSError * __autoreleasing *)error
232+
{
233+
// Dispatch by the "MT:" prefix and preserve the fine-grained underlying
234+
// error code (unlike +setupPayloadWithOnboardingPayload:error:, which
235+
// flattens to MTRErrorCodeInvalidArgument).
236+
return ([payload hasPrefix:@"MT:"]) ? [self initWithQRCode:payload error:error]
237+
: [self initWithManualPairingCode:payload error:error];
229238
}
230239

231240
- (CHIP_ERROR)initializeFromQRCode:(NSString *)qrCode validate:(BOOL)validate
@@ -269,17 +278,32 @@ - (CHIP_ERROR)initializeFromQRCode:(NSString *)qrCode validate:(BOOL)validate
269278
}
270279

271280
- (nullable instancetype)initWithQRCode:(NSString *)qrCodePayload
281+
{
282+
return [self initWithQRCode:qrCodePayload error:nil];
283+
}
284+
285+
- (nullable instancetype)initWithQRCode:(NSString *)qrCodePayload
286+
error:(NSError * __autoreleasing *)error
272287
{
273288
self = [super init];
274289
CHIP_ERROR err = [self initializeFromQRCode:qrCodePayload validate:YES];
275290
if (err != CHIP_NO_ERROR) {
291+
if (error) {
292+
*error = [MTRError errorForCHIPErrorCode:err];
293+
}
276294
return nil;
277295
}
278296

279297
return self;
280298
}
281299

282300
- (nullable instancetype)initWithManualPairingCode:(NSString *)manualCode
301+
{
302+
return [self initWithManualPairingCode:manualCode error:nil];
303+
}
304+
305+
- (nullable instancetype)initWithManualPairingCode:(NSString *)manualCode
306+
error:(NSError * __autoreleasing *)error
283307
{
284308
self = [super init];
285309

@@ -288,10 +312,16 @@ - (nullable instancetype)initWithManualPairingCode:(NSString *)manualCode
288312
CHIP_ERROR err = parser.populatePayload(_payload);
289313
if (err != CHIP_NO_ERROR) {
290314
MTR_LOG_ERROR("Failed to parse Manual Pairing Code: %" CHIP_ERROR_FORMAT, err.Format());
315+
if (error) {
316+
*error = [MTRError errorForCHIPErrorCode:err];
317+
}
291318
return nil;
292319
}
293320
if (!_payload.isValidManualCode(chip::PayloadContents::ValidationMode::kConsume)) {
294321
MTR_LOG_ERROR("Invalid Manual Pairing Code");
322+
if (error) {
323+
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
324+
}
295325
return nil;
296326
}
297327

@@ -678,7 +708,13 @@ - (instancetype)initWithCoder:(NSCoder *)coder
678708
// did not encode it. When present, it carries almost the entire state of the object.
679709
NSString * qrCode = [coder decodeObjectOfClass:NSString.class forKey:MTRSetupPayloadCodingKeyQRCode];
680710
if (qrCode != nil) {
681-
[self initializeFromQRCode:qrCode validate:NO];
711+
// Intentionally fault-tolerant: don't -failWithError: on parse failure
712+
// (older archives may hold payloads we no longer parse cleanly). Just
713+
// log a breadcrumb and fall through to the fields decoded below.
714+
CHIP_ERROR err = [self initializeFromQRCode:qrCode validate:NO];
715+
if (err != CHIP_NO_ERROR) {
716+
MTR_LOG_ERROR("initWithCoder: failed to restore QR code from archive: %" CHIP_ERROR_FORMAT, err.Format());
717+
}
682718
} else {
683719
self.version = [coder decodeObjectOfClass:NSNumber.class forKey:MTRSetupPayloadCodingKeyVersion];
684720
self.vendorID = [coder decodeObjectOfClass:NSNumber.class forKey:MTRSetupPayloadCodingKeyVendorID];
@@ -781,8 +817,13 @@ + (instancetype)new
781817
+ (MTRSetupPayload * _Nullable)setupPayloadWithOnboardingPayload:(NSString *)onboardingPayload
782818
error:(NSError * __autoreleasing *)error
783819
{
784-
MTRSetupPayload * payload = [[MTRSetupPayload alloc] initWithPayload:onboardingPayload];
785-
if (!payload && error) {
820+
// Deprecated surface: flatten every parse failure to
821+
// MTRErrorCodeInvalidArgument, preserving the legacy contract for callers
822+
// that switch on it. Callers wanting the fine-grained code should migrate
823+
// to -initWithPayload:error:. (We pass nil for the underlying error so its
824+
// localized description can't contradict the flattened code we report.)
825+
MTRSetupPayload * payload = [[MTRSetupPayload alloc] initWithPayload:onboardingPayload error:nil];
826+
if (payload == nil && error != nil) {
786827
*error = [MTRError errorWithCode:MTRErrorCodeInvalidArgument];
787828
}
788829
return payload;

src/darwin/Framework/CHIP/MTRSetupPayload_Internal.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ MTR_DIRECT_MEMBERS
2828

2929
- (instancetype)initWithSetupPayload:(const chip::SetupPayload &)setupPayload;
3030
- (nullable instancetype)initWithQRCode:(NSString *)qrCodePayload;
31+
/**
32+
* Initializes the payload object from a "MT:"-prefixed QR Code string.
33+
*
34+
* Returns nil and populates `error` (if non-nil) on parse failure. The
35+
* returned error is in MTRErrorDomain; per-failure codes from the underlying
36+
* Base38 decode (e.g. MTRErrorCodeInvalidIntegerValue for an out-of-alphabet
37+
* character, MTRErrorCodeInvalidStringLength for a malformed chunk) are
38+
* preserved so callers can distinguish typo-class failures from structural
39+
* rejection.
40+
*
41+
* @note MTRErrorCodeInvalidArgument may still be returned for structurally
42+
* valid Base38 payloads whose contents fail semantic QR Code validation
43+
* (the !isValidQRCodePayload branch). This is intentionally identical
44+
* to the corresponding branch in -initWithManualPairingCode:error:.
45+
*/
46+
- (nullable instancetype)initWithQRCode:(NSString *)qrCodePayload error:(NSError * __autoreleasing *)error;
3147
- (nullable instancetype)initWithManualPairingCode:(NSString *)manualCode;
3248

3349
@end

src/darwin/Framework/CHIPTests/MTRPairingBackwardsCompatTests.m

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,33 @@ - (void)test002_PairDeviceWithString
118118
[self waitForExpectations:@[ expectation ] timeout:kPairingTimeoutInSeconds];
119119
}
120120

121+
- (void)test003_PairDeviceWithBadManualPairingCode_SurfacesIntegrityCheckFailed
122+
{
123+
// Integration regression pin: pairing through MTRDeviceController with a
124+
// manual pairing code whose Verhoeff check digit is wrong must surface
125+
// MTRErrorCodeIntegrityCheckFailed end-to-end, not the pre-fix generic
126+
// MTRErrorCodeInvalidArgument.
127+
//
128+
// Deliberately does NOT call -startServerApp (unlike test001/test002): the
129+
// bad check digit is rejected synchronously in
130+
// -[MTRDeviceController pairDevice:onboardingPayload:error:] when it parses
131+
// the payload via -[MTRSetupPayload initWithPayload:error:], which fails
132+
// before any commissionable-node discovery or PASE handshake is attempted.
133+
// The call returns NO inline, so no server, network, or paired device is
134+
// needed and there is no asynchronous delegate callback to await. Adding a
135+
// server app here would be incorrect -- it would never be contacted.
136+
__auto_type * controller = [self createControllerOnTestFabric];
137+
XCTAssertNotNil(controller);
138+
139+
NSError * error;
140+
BOOL ok = [controller pairDevice:sDeviceId
141+
onboardingPayload:@"02684354589"
142+
error:&error];
143+
XCTAssertFalse(ok);
144+
XCTAssertNotNil(error);
145+
XCTAssertEqualObjects(error.domain, MTRErrorDomain);
146+
XCTAssertEqual(error.code, MTRErrorCodeIntegrityCheckFailed);
147+
XCTAssertNotEqual(error.code, MTRErrorCodeInvalidArgument);
148+
}
149+
121150
@end

0 commit comments

Comments
 (0)