From 91874ddde1e9e61e9faf36ccc2e87f55d622aa0a Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Fri, 24 Feb 2023 15:36:49 -0500 Subject: [PATCH] Add a continuous browse for Matter operational advertisements on Darwin. The information is propagated to MTRDevice, but we're not making use of it there yet. The fabricIndex changes were due to a pre-existing issue: we are in fact getting it from the wrong thread/queue in various places (MTRDevice init, MTRDevice command invocation), such that an assertChipStackLockedByCurrentThread() in the getter failed. --- src/darwin/Framework/CHIP/MTRDevice.mm | 11 ++ .../Framework/CHIP/MTRDeviceController.mm | 43 ++++++- .../CHIP/MTRDeviceControllerFactory.mm | 24 ++++ .../MTRDeviceControllerFactory_Internal.h | 7 ++ .../CHIP/MTRDeviceController_Internal.h | 15 ++- .../Framework/CHIP/MTRDevice_Internal.h | 5 + .../Framework/CHIP/MTROperationalBrowser.h | 47 ++++++++ .../Framework/CHIP/MTROperationalBrowser.mm | 112 ++++++++++++++++++ .../Matter.xcodeproj/project.pbxproj | 8 ++ 9 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 src/darwin/Framework/CHIP/MTROperationalBrowser.h create mode 100644 src/darwin/Framework/CHIP/MTROperationalBrowser.mm diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 642a45ae622993..e61ae946392b19 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -254,6 +254,17 @@ - (void)invalidate os_unfair_lock_unlock(&self->_lock); } +- (void)nodeMayBeAdvertisingOperational +{ + // TODO: Figure out what to do with that information. If we're not waiting + // to subscribe/resubscribe, do nothing, otherwise perhaps trigger the + // subscribe/resubscribe immediately? We need to have much better tracking + // of our internal state for that, and may need to add something on + // ReadClient to cancel its outstanding timer and try to resubscribe + // immediately.... + MTR_LOG_DEFAULT("%@ saw new operational advertisement", self); +} + // assume lock is held - (void)_changeState:(MTRDeviceState)state { diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index 5dd543f74195d5..d91888ec30c341 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -48,10 +48,13 @@ #include #include #include +#include #include #include #include +#include + #import static NSString * const kErrorCommissionerInit = @"Init failure while initializing a commissioner"; @@ -82,7 +85,10 @@ typedef id (^SyncWorkQueueBlockWithReturnValue)(void); typedef BOOL (^SyncWorkQueueBlockWithBoolReturnValue)(void); -@interface MTRDeviceController () +@interface MTRDeviceController () { + // Atomic because it can be touched from multiple threads. + std::atomic _storedFabricIndex; +} // queue used to serialize all work performed by the MTRDeviceController @property (atomic, readonly) dispatch_queue_t chipWorkQueue; @@ -123,6 +129,8 @@ - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory queue:(dis if ([self checkForInitError:(_operationalCredentialsDelegate != nullptr) logMsg:kErrorOperationalCredentialsInit]) { return nil; } + + _storedFabricIndex = chip::kUndefinedFabricIndex; } return self; } @@ -152,12 +160,15 @@ - (void)cleanupAfterStartup // in a very specific way that only MTRDeviceControllerFactory knows about. - (void)shutDownCppController { + assertChipStackLockedByCurrentThread(); + if (_cppCommissioner) { auto * commissionerToShutDown = _cppCommissioner; // Flag ourselves as not running before we start shutting down // _cppCommissioner, so we're not in a state where we claim to be // running but are actually partially shut down. _cppCommissioner = nullptr; + _storedFabricIndex = chip::kUndefinedFabricIndex; commissionerToShutDown->Shutdown(); delete commissionerToShutDown; if (_operationalCredentialsDelegate != nil) { @@ -345,6 +356,7 @@ - (BOOL)startup:(MTRDeviceControllerStartupParamsInternal *)startupParams return; } + self->_storedFabricIndex = fabricIdx; commissionerInitialized = YES; }); @@ -813,17 +825,26 @@ - (BOOL)syncRunOnWorkQueueWithBoolReturnValue:(SyncWorkQueueBlockWithBoolReturnV - (chip::FabricIndex)fabricIndex { + return _storedFabricIndex; +} + +- (nullable NSNumber *)compressedFabricID +{ + assertChipStackLockedByCurrentThread(); + if (!_cppCommissioner) { - return chip::kUndefinedFabricIndex; + return nil; } - return _cppCommissioner->GetFabricIndex(); + return @(_cppCommissioner->GetCompressedFabricId()); } - (CHIP_ERROR)isRunningOnFabric:(chip::FabricTable *)fabricTable fabricIndex:(chip::FabricIndex)fabricIndex isRunning:(BOOL *)isRunning { + assertChipStackLockedByCurrentThread(); + if (![self isRunning]) { *isRunning = NO; return CHIP_NO_ERROR; @@ -861,6 +882,22 @@ - (void)invalidateCASESessionForNode:(chip::NodeId)nodeID; [self syncRunOnWorkQueue:block error:nil]; } +- (void)operationalInstanceAdded:(chip::NodeId)nodeID +{ + // Don't use deviceForNodeID here, because we don't want to create the + // device if it does not already exist. + os_unfair_lock_lock(&_deviceMapLock); + MTRDevice * device = self.nodeIDToDeviceMap[@(nodeID)]; + os_unfair_lock_unlock(&_deviceMapLock); + + if (device == nil) { + return; + } + + ChipLogProgress(Controller, "Notifying device about node 0x" ChipLogFormatX64 " advertising", ChipLogValueX64(nodeID)); + [device nodeMayBeAdvertisingOperational]; +} + @end /** diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm index 9c96f7e98ad308..0e57fa215a131b 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm @@ -28,6 +28,7 @@ #import "MTRFramework.h" #import "MTRLogging_Internal.h" #import "MTROTAProviderDelegateBridge.h" +#import "MTROperationalBrowser.h" #import "MTRP256KeypairBridge.h" #import "MTRPersistentStorageDelegateBridge.h" #import "NSDataSpanConversion.h" @@ -81,6 +82,7 @@ @interface MTRDeviceControllerFactory () @property (readonly) NSMutableArray * controllers; @property (readonly) PersistentStorageOperationalKeystore * keystore; @property (readonly) Credentials::PersistentStorageOpCertStore * opCertStore; +@property (readonly) MTROperationalBrowser * operationalBrowser; @property () chip::Credentials::DeviceAttestationVerifier * deviceAttestationVerifier; - (BOOL)findMatchingFabric:(FabricTable &)fabricTable @@ -643,6 +645,9 @@ - (MTRDeviceController * _Nullable)createController // Bringing up the first controller. Start the event loop now. If we // fail to bring it up, its cleanup will stop the event loop again. chip::DeviceLayer::PlatformMgrImpl().StartEventLoopTask(); + dispatch_sync(_chipWorkQueue, ^{ + self->_operationalBrowser = new MTROperationalBrowser(self, self->_chipWorkQueue); + }); } // Add the controller to _controllers now, so if we fail partway through its @@ -742,6 +747,10 @@ - (void)controllerShuttingDown:(MTRDeviceController *)controller [_controllers removeObject:controller]; if ([_controllers count] == 0) { + dispatch_sync(_chipWorkQueue, ^{ + delete self->_operationalBrowser; + self->_operationalBrowser = nullptr; + }); // That was our last controller. Stop the event loop before it // shuts down, because shutdown of the last controller will tear // down most of the world. @@ -777,6 +786,21 @@ - (nullable MTRDeviceController *)runningControllerForFabricIndex:(chip::FabricI return nil; } +- (void)operationalInstanceAdded:(chip::PeerId &)operationalID +{ + for (MTRDeviceController * controller in _controllers) { + auto * compressedFabricId = controller.compressedFabricID; + if (compressedFabricId != nil && compressedFabricId.unsignedLongLongValue == operationalID.GetCompressedFabricId()) { + ChipLogProgress(Controller, "Notifying controller at fabric index %u about new operational node 0x" ChipLogFormatX64, + controller.fabricIndex, ChipLogValueX64(operationalID.GetNodeId())); + [controller operationalInstanceAdded:operationalID.GetNodeId()]; + } + + // Keep going: more than one controller might match a given compressed + // fabric id, though the chances are low. + } +} + - (MTRPersistentStorageDelegateBridge *)storageDelegateBridge { return _persistentStorageDelegateBridge; diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h index a92d00b1c1e8b3..91b5813c896fb8 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h @@ -24,6 +24,7 @@ #import "MTRDeviceControllerFactory.h" #include +#include class MTRPersistentStorageDelegateBridge; @@ -47,6 +48,12 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable MTRDeviceController *)runningControllerForFabricIndex:(chip::FabricIndex)fabricIndex; +/** + * Notify the controller factory that a new operational instance with the given + * compressed fabric id and node id has been observed. + */ +- (void)operationalInstanceAdded:(chip::PeerId &)operationalID; + @property (readonly) MTRPersistentStorageDelegateBridge * storageDelegateBridge; @property (readonly) chip::Credentials::GroupDataProvider * groupData; @property (readonly) chip::Credentials::DeviceAttestationVerifier * deviceAttestationVerifier; diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h index e3e795ac15bb46..28beb9b8244a13 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h @@ -62,10 +62,17 @@ NS_ASSUME_NONNULL_BEGIN /** * Will return chip::kUndefinedFabricIndex if we do not have a fabric index. - * This property MUST be gotten from the Matter work queue. */ @property (readonly) chip::FabricIndex fabricIndex; +/** + * Will return the compressed fabric id of the fabric if the controller is + * running, else nil. + * + * This property MUST be gotten from the Matter work queue. + */ +@property (readonly, nullable) NSNumber * compressedFabricID; + /** * Init a newly created controller. * @@ -185,6 +192,12 @@ NS_ASSUME_NONNULL_BEGIN */ - (MTRBaseDevice *)baseDeviceForNodeID:(NSNumber *)nodeID; +/** + * Notify the controller that a new operational instance with the given node id + * and a compressed fabric id that matches this controller has been observed. + */ +- (void)operationalInstanceAdded:(chip::NodeId)nodeID; + #pragma mark - Device-specific data and SDK access // DeviceController will act as a central repository for this opaque dictionary that MTRDevice manages - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID; diff --git a/src/darwin/Framework/CHIP/MTRDevice_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_Internal.h index 1cb11e83df748f..cccff5027e2cb2 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDevice_Internal.h @@ -36,6 +36,11 @@ typedef void (^MTRDevicePerformAsyncBlock)(MTRBaseDevice * baseDevice); // called by controller to clean up and shutdown - (void)invalidate; +// Called by controller when a new operational advertisement for what we think +// is this device's identity has been observed. This could have +// false-positives, for example due to compressed fabric id collisions. +- (void)nodeMayBeAdvertisingOperational; + @property (nonatomic, readonly) MTRDeviceController * deviceController; @property (nonatomic, readonly, copy) NSNumber * nodeID; // Queue used for various internal bookkeeping work. In general endWork calls diff --git a/src/darwin/Framework/CHIP/MTROperationalBrowser.h b/src/darwin/Framework/CHIP/MTROperationalBrowser.h new file mode 100644 index 00000000000000..0cdcbf6d869736 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROperationalBrowser.h @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#import +#import +#import + +class MTROperationalBrowser +{ +public: + // Should be created at a point when the factory starts up the event loop, + // and destroyed when the event loop is stopped. + MTROperationalBrowser(MTRDeviceControllerFactory * aFactory, dispatch_queue_t aQueue); + + ~MTROperationalBrowser(); + +private: + static void OnBrowse(DNSServiceRef aServiceRef, DNSServiceFlags aFlags, uint32_t aInterfaceId, DNSServiceErrorType aError, + const char * aName, const char * aType, const char * aDomain, void * aContext); + + void TryToStartBrowse(); + + MTRDeviceControllerFactory * const mDeviceControllerFactory; + dispatch_queue_t mQueue; + DNSServiceRef mBrowseRef; + + // If mInitialized is true, mBrowseRef is valid. + bool mInitialized = false; + + // If mIsDestroying is true, we're in our destructor, shutting things down. + bool mIsDestroying = false; +}; diff --git a/src/darwin/Framework/CHIP/MTROperationalBrowser.mm b/src/darwin/Framework/CHIP/MTROperationalBrowser.mm new file mode 100644 index 00000000000000..b547b35b842aa7 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTROperationalBrowser.mm @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "MTRDeviceControllerFactory_Internal.h" +#import "MTROperationalBrowser.h" + +#include +#include +#include +#include + +namespace { +constexpr const char kLocalDot[] = "local."; +constexpr const char kOperationalType[] = "_matter._tcp"; +constexpr DNSServiceFlags kBrowseFlags = 0; +} + +MTROperationalBrowser::MTROperationalBrowser(MTRDeviceControllerFactory * aFactory, dispatch_queue_t aQueue) + : mDeviceControllerFactory(aFactory) + , mQueue(aQueue) +{ + // If we fail to start a browse, there's nothing our consumer would do + // differently, so we might as well do this in the constructor. + TryToStartBrowse(); +} + +void MTROperationalBrowser::TryToStartBrowse() +{ + assertChipStackLockedByCurrentThread(); + + ChipLogProgress(Controller, "Trying to start operational browse"); + + auto err + = DNSServiceBrowse(&mBrowseRef, kBrowseFlags, kDNSServiceInterfaceIndexAny, kOperationalType, kLocalDot, OnBrowse, this); + if (err != kDNSServiceErr_NoError) { + ChipLogError(Controller, "Failed to start operational browse: %" PRId32, err); + return; + } + + err = DNSServiceSetDispatchQueue(mBrowseRef, mQueue); + if (err != kDNSServiceErr_NoError) { + ChipLogError(Controller, "Failed to set up dispatch queue properly"); + DNSServiceRefDeallocate(mBrowseRef); + return; + } + + mInitialized = true; +} + +void MTROperationalBrowser::OnBrowse(DNSServiceRef aServiceRef, DNSServiceFlags aFlags, uint32_t aInterfaceId, + DNSServiceErrorType aError, const char * aName, const char * aType, const char * aDomain, void * aContext) +{ + assertChipStackLockedByCurrentThread(); + + auto self = static_cast(aContext); + + // We only expect to get notified about our type/domain. + if (aError != kDNSServiceErr_NoError) { + ChipLogError(Controller, "Operational browse failure: %" PRId32, aError); + DNSServiceRefDeallocate(self->mBrowseRef); + self->mInitialized = false; + + // We shouldn't really get callbacks under our destructor, but guard + // against it just in case. + if (!self->mIsDestroying) { + // Try to start a new browse, so we have one going. + self->TryToStartBrowse(); + } + return; + } + + if (!(aFlags & kDNSServiceFlagsAdd)) { + // We only care about new things appearing. + return; + } + + chip::PeerId peerId; + CHIP_ERROR err = chip::Dnssd::ExtractIdFromInstanceName(aName, &peerId); + if (err != CHIP_NO_ERROR) { + ChipLogError(Controller, "Invalid instance name: '%s'\n", aName); + return; + } + + ChipLogProgress(Controller, "Notifying controller factory about new operational instance: '%s'", aName); + [self->mDeviceControllerFactory operationalInstanceAdded:peerId]; +} + +MTROperationalBrowser::~MTROperationalBrowser() +{ + assertChipStackLockedByCurrentThread(); + + mIsDestroying = true; + + if (mInitialized) { + DNSServiceRefDeallocate(mBrowseRef); + } +} diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 44b5e1cc9d9840..7033180257c717 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -126,6 +126,8 @@ 3DFCB32C29678C9500332B35 /* MTRConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DFCB32B29678C9500332B35 /* MTRConversion.h */; }; 51029DF6293AA6100087AFB0 /* MTROperationalCertificateIssuer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51029DF5293AA6100087AFB0 /* MTROperationalCertificateIssuer.mm */; }; 510CECA8297F72970064E0B3 /* MTROperationalCertificateIssuerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 510CECA6297F72470064E0B3 /* MTROperationalCertificateIssuerTests.m */; }; + 5117DD3829A931AE00FFA1AA /* MTROperationalBrowser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5117DD3629A931AD00FFA1AA /* MTROperationalBrowser.mm */; }; + 5117DD3929A931AE00FFA1AA /* MTROperationalBrowser.h in Headers */ = {isa = PBXBuildFile; fileRef = 5117DD3729A931AE00FFA1AA /* MTROperationalBrowser.h */; }; 511913FB28C100EF009235E9 /* MTRBaseSubscriptionCallback.mm in Sources */ = {isa = PBXBuildFile; fileRef = 511913F928C100EF009235E9 /* MTRBaseSubscriptionCallback.mm */; }; 511913FC28C100EF009235E9 /* MTRBaseSubscriptionCallback.h in Headers */ = {isa = PBXBuildFile; fileRef = 511913FA28C100EF009235E9 /* MTRBaseSubscriptionCallback.h */; }; 5129BCFD26A9EE3300122DDF /* MTRError.h in Headers */ = {isa = PBXBuildFile; fileRef = 5129BCFC26A9EE3300122DDF /* MTRError.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -401,6 +403,8 @@ 3DFCB32B29678C9500332B35 /* MTRConversion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRConversion.h; sourceTree = ""; }; 51029DF5293AA6100087AFB0 /* MTROperationalCertificateIssuer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROperationalCertificateIssuer.mm; sourceTree = ""; }; 510CECA6297F72470064E0B3 /* MTROperationalCertificateIssuerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTROperationalCertificateIssuerTests.m; sourceTree = ""; }; + 5117DD3629A931AD00FFA1AA /* MTROperationalBrowser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROperationalBrowser.mm; sourceTree = ""; }; + 5117DD3729A931AE00FFA1AA /* MTROperationalBrowser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTROperationalBrowser.h; sourceTree = ""; }; 511913F928C100EF009235E9 /* MTRBaseSubscriptionCallback.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRBaseSubscriptionCallback.mm; sourceTree = ""; }; 511913FA28C100EF009235E9 /* MTRBaseSubscriptionCallback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRBaseSubscriptionCallback.h; sourceTree = ""; }; 5129BCFC26A9EE3300122DDF /* MTRError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRError.h; sourceTree = ""; }; @@ -998,6 +1002,8 @@ 997DED152695343400975E97 /* MTRThreadOperationalDataset.mm */, 3D843710294977000070D20A /* NSDataSpanConversion.h */, 3D84370E294977000070D20A /* NSStringSpanConversion.h */, + 5117DD3729A931AE00FFA1AA /* MTROperationalBrowser.h */, + 5117DD3629A931AD00FFA1AA /* MTROperationalBrowser.mm */, 3D69867E29382E58007314E7 /* Resources */, ); path = CHIP; @@ -1098,6 +1104,7 @@ 2CB7163B252E8A7B0026E2BB /* MTRDeviceControllerDelegateBridge.h in Headers */, 5ACDDD7A27CD129700EFD68A /* MTRClusterStateCacheContainer.h in Headers */, 5A6FEC9227B5669C00F25F42 /* MTRDeviceControllerOverXPC.h in Headers */, + 5117DD3929A931AE00FFA1AA /* MTROperationalBrowser.h in Headers */, 2C1B027B2641DB4E00780EF1 /* MTROperationalCredentialsDelegate.h in Headers */, 3D843717294979230070D20A /* MTRClusters_Internal.h in Headers */, 7596A85728788557004DAE0E /* MTRClusters.h in Headers */, @@ -1384,6 +1391,7 @@ 511913FB28C100EF009235E9 /* MTRBaseSubscriptionCallback.mm in Sources */, 5ACDDD7D27CD16D200EFD68A /* MTRClusterStateCacheContainer.mm in Sources */, 513DDB8A2761F6F900DAA01A /* MTRAttributeTLVValueDecoder.mm in Sources */, + 5117DD3829A931AE00FFA1AA /* MTROperationalBrowser.mm in Sources */, 2FD775552695557E00FF4B12 /* error-mapping.cpp in Sources */, 3D843757294AD25A0070D20A /* MTRCertificateInfo.mm in Sources */, 5A7947E427C0129600434CF2 /* MTRDeviceController+XPC.mm in Sources */,