[darwin-framework-tool] Set device delegate when using MTRDevice with –use-mtr-device (#36263)

diff --git a/examples/darwin-framework-tool/BUILD.gn b/examples/darwin-framework-tool/BUILD.gn
index 0d1e142..1c492ba 100644
--- a/examples/darwin-framework-tool/BUILD.gn
+++ b/examples/darwin-framework-tool/BUILD.gn
@@ -194,6 +194,8 @@
     "commands/common/CertificateIssuer.mm",
     "commands/common/ControllerStorage.h",
     "commands/common/ControllerStorage.mm",
+    "commands/common/DeviceDelegate.h",
+    "commands/common/DeviceDelegate.mm",
     "commands/common/MTRDevice_Externs.h",
     "commands/common/MTRError.mm",
     "commands/common/MTRError_Utils.h",
diff --git a/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h b/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h
index cd8125e..ba34283 100644
--- a/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h
+++ b/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h
@@ -105,9 +105,7 @@
             LogNSError("Error reading attribute", error);
             RemoteDataModelLogger::LogAttributeErrorAsJSON(endpoint, cluster, attribute, error);
         } else {
-            for (id item in values) {
-                NSLog(@"Response Item: %@", [item description]);
-            }
+            NSLog(@"cluster (0x%08" PRIX32 ") ReadAttribute (0x%08" PRIX32 ") on endpoint %u: %@", mClusterId, mAttributeId, endpointId, values);
             RemoteDataModelLogger::LogAttributeAsJSON(endpoint, cluster, attribute, values);
         }
 
diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h
index 90f3a6c..2f53c0d 100644
--- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h
+++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h
@@ -52,6 +52,7 @@
         AddArgument("commissioner-vendor-id", 0, UINT16_MAX, &mCommissionerVendorId,
             "The vendor id to use for darwin-framework-tool. If not provided, chip::VendorId::TestVendor1 (65521, 0xFFF1) will be "
             "used.");
+        AddArgument("pretend-thread-enabled", 0, 1, &mPretendThreadEnabled, "When the command is issued using an MTRDevice (via -use-mtr-device), instructs the MTRDevice to treat the target device as a Thread device.");
     }
 
     /////////// Command Interface /////////
@@ -164,4 +165,5 @@
     chip::Optional<char *> mPaaTrustStorePath;
     chip::Optional<chip::VendorId> mCommissionerVendorId;
     std::string mCurrentIdentity;
+    chip::Optional<bool> mPretendThreadEnabled;
 };
diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm
index b237aca..2eba24c 100644
--- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm
+++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm
@@ -28,12 +28,15 @@
 #import "CHIPCommandStorageDelegate.h"
 #import "CertificateIssuer.h"
 #import "ControllerStorage.h"
+#import "DeviceDelegate.h"
 #include "MTRError_Utils.h"
 
 #include <map>
 #include <string>
 
 static CHIPToolPersistentStorageDelegate * storage = nil;
+static DeviceDelegate * sDeviceDelegate = nil;
+static dispatch_queue_t sDeviceDelegateDispatchQueue = nil;
 std::set<CHIPCommandBridge *> CHIPCommandBridge::sDeferredCleanups;
 std::map<std::string, MTRDeviceController *> CHIPCommandBridge::mControllers;
 dispatch_queue_t CHIPCommandBridge::mOTAProviderCallbackQueue;
@@ -302,6 +305,18 @@
     __auto_type * device = [MTRDevice deviceWithNodeID:@(nodeId) controller:controller];
     VerifyOrReturnValue(nil != device, nil);
 
+    // The device delegate is initialized only once, when the first MTRDevice is created.
+    // As a result, subsequent commands using --use-mtr-device don’t need to specify the
+    // `--pretend-thread-enabled 1` argument again. Any further attempts to set it to `0` will also be ignored.
+    if (sDeviceDelegate == nil) {
+        sDeviceDelegate = [[DeviceDelegate alloc] init];
+        sDeviceDelegateDispatchQueue = dispatch_queue_create("com.chip.devicedelegate", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
+        if (mPretendThreadEnabled.ValueOr(false)) {
+            [sDeviceDelegate setPretendThreadEnabled:YES];
+        }
+    }
+    [device addDelegate:sDeviceDelegate queue:sDeviceDelegateDispatchQueue];
+
     return device;
 }
 
diff --git a/examples/darwin-framework-tool/commands/common/DeviceDelegate.h b/examples/darwin-framework-tool/commands/common/DeviceDelegate.h
new file mode 100644
index 0000000..a3f5cf4
--- /dev/null
+++ b/examples/darwin-framework-tool/commands/common/DeviceDelegate.h
@@ -0,0 +1,23 @@
+/*
+ *   Copyright (c) 2024 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 <Matter/Matter.h>
+
+@interface DeviceDelegate : NSObject <MTRDeviceDelegate>
+- (void)setPretendThreadEnabled:(BOOL)threadEnabled;
+@end
diff --git a/examples/darwin-framework-tool/commands/common/DeviceDelegate.mm b/examples/darwin-framework-tool/commands/common/DeviceDelegate.mm
new file mode 100644
index 0000000..ecf8e70
--- /dev/null
+++ b/examples/darwin-framework-tool/commands/common/DeviceDelegate.mm
@@ -0,0 +1,69 @@
+/*
+ *   Copyright (c) 2024 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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 "DeviceDelegate.h"
+
+#include <lib/support/logging/CHIPLogging.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface DeviceDelegate ()
+@property (nonatomic, readwrite) BOOL threadEnabled;
+@end
+
+@implementation DeviceDelegate
+- (instancetype)init
+{
+    if (self = [super init]) {
+        _threadEnabled = NO;
+    }
+    return self;
+}
+
+- (void)device:(MTRDevice *)device stateChanged:(MTRDeviceState)state
+{
+}
+
+- (void)device:(MTRDevice *)device receivedAttributeReport:(NSArray<NSDictionary<NSString *, id> *> *)attributeReport
+{
+}
+
+- (void)device:(MTRDevice *)device receivedEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventReport
+{
+}
+
+- (void)deviceCachePrimed:(MTRDevice *)device
+{
+}
+
+- (void)deviceConfigurationChanged:(MTRDevice *)device
+{
+}
+
+- (void)setPretendThreadEnabled:(BOOL)threadEnabled
+{
+    _threadEnabled = threadEnabled;
+}
+
+- (BOOL)unitTestPretendThreadEnabled:(MTRDevice *)device
+{
+    return _threadEnabled;
+}
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
index 6a80290..673f1ca 100644
--- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
+++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
@@ -336,6 +336,8 @@
 		B2E0D7B7245B0B5C003C5B48 /* MTRQRCodeSetupPayloadParser.mm in Sources */ = {isa = PBXBuildFile; fileRef = B2E0D7AE245B0B5C003C5B48 /* MTRQRCodeSetupPayloadParser.mm */; };
 		B2E0D7B8245B0B5C003C5B48 /* MTRSetupPayload.h in Headers */ = {isa = PBXBuildFile; fileRef = B2E0D7AF245B0B5C003C5B48 /* MTRSetupPayload.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		B2E0D7B9245B0B5C003C5B48 /* MTRSetupPayload.mm in Sources */ = {isa = PBXBuildFile; fileRef = B2E0D7B0245B0B5C003C5B48 /* MTRSetupPayload.mm */; };
+		B409D0AE2CCFB89600A7ED5A /* DeviceDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B409D0AC2CCFB89600A7ED5A /* DeviceDelegate.h */; };
+		B409D0AF2CCFB89600A7ED5A /* DeviceDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = B409D0AD2CCFB89600A7ED5A /* DeviceDelegate.mm */; };
 		B43B39EA2CB859A5006AA284 /* DumpMemoryGraphCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B43B39E62CB859A5006AA284 /* DumpMemoryGraphCommand.mm */; };
 		B43B39EB2CB859A5006AA284 /* LeaksTool.mm in Sources */ = {isa = PBXBuildFile; fileRef = B43B39E82CB859A5006AA284 /* LeaksTool.mm */; };
 		B43B39EC2CB859A5006AA284 /* DumpMemoryGraphCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = B43B39E52CB859A5006AA284 /* DumpMemoryGraphCommand.h */; };
@@ -800,6 +802,8 @@
 		B2E0D7AE245B0B5C003C5B48 /* MTRQRCodeSetupPayloadParser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRQRCodeSetupPayloadParser.mm; sourceTree = "<group>"; };
 		B2E0D7AF245B0B5C003C5B48 /* MTRSetupPayload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRSetupPayload.h; sourceTree = "<group>"; };
 		B2E0D7B0245B0B5C003C5B48 /* MTRSetupPayload.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRSetupPayload.mm; sourceTree = "<group>"; };
+		B409D0AC2CCFB89600A7ED5A /* DeviceDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeviceDelegate.h; sourceTree = "<group>"; };
+		B409D0AD2CCFB89600A7ED5A /* DeviceDelegate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DeviceDelegate.mm; sourceTree = "<group>"; };
 		B43B39E42CB859A5006AA284 /* Commands.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Commands.h; sourceTree = "<group>"; };
 		B43B39E52CB859A5006AA284 /* DumpMemoryGraphCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DumpMemoryGraphCommand.h; sourceTree = "<group>"; };
 		B43B39E62CB859A5006AA284 /* DumpMemoryGraphCommand.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DumpMemoryGraphCommand.mm; sourceTree = "<group>"; };
@@ -1013,6 +1017,8 @@
 		037C3D9B2991BD4F00B7EEE2 /* common */ = {
 			isa = PBXGroup;
 			children = (
+				B409D0AC2CCFB89600A7ED5A /* DeviceDelegate.h */,
+				B409D0AD2CCFB89600A7ED5A /* DeviceDelegate.mm */,
 				B43B39EF2CB99090006AA284 /* CertificateIssuer.h */,
 				B43B39F02CB99090006AA284 /* CertificateIssuer.mm */,
 				B43B39F12CB99090006AA284 /* ControllerStorage.h */,
@@ -1673,6 +1679,7 @@
 				B4F773CA2CB54B61008C6B23 /* LeakChecker.h in Headers */,
 				B43B39F82CB99090006AA284 /* CertificateIssuer.h in Headers */,
 				B43B39F92CB99090006AA284 /* PreferencesStorage.h in Headers */,
+				B409D0AE2CCFB89600A7ED5A /* DeviceDelegate.h in Headers */,
 				B43B39FA2CB99090006AA284 /* ControllerStorage.h in Headers */,
 				037C3DB82991BD5000B7EEE2 /* ClusterCommandBridge.h in Headers */,
 				037C3DC82991BD5100B7EEE2 /* CHIPToolKeypair.h in Headers */,
@@ -2058,6 +2065,7 @@
 				0382FA302992F40C00247BBB /* ComplexArgumentParser.cpp in Sources */,
 				039145E12993102B00257B3E /* main.mm in Sources */,
 				037C3DD42991BD5200B7EEE2 /* logging.mm in Sources */,
+				B409D0AF2CCFB89600A7ED5A /* DeviceDelegate.mm in Sources */,
 				512431282BA0C8BF000BC136 /* SetMRPParametersCommand.mm in Sources */,
 				512431292BA0C8BF000BC136 /* ResetMRPParametersCommand.mm in Sources */,
 				037C3DB32991BD5000B7EEE2 /* OpenCommissioningWindowCommand.mm in Sources */,