Make MTRServerCluster threadsafe. (#32245)

* Make MTRServerCluster threadsafe.

If two API clients are both touching the same instance of MTRServerCluster on
different threads, we should handle that correctly.

* Address review comments.
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.h
index 7d8c27b..f9c7e7c 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.h
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.h
@@ -24,6 +24,8 @@
  * A representation of an attribute implemented on a server cluster by an
  * MTRDeviceController.  An attribute has an identifier and a value, and may or
  * may not be writable.
+ *
+ * MTRServerAttribute's API can be accessed from any thread.
  */
 NS_SWIFT_SENDABLE
 MTR_NEWLY_AVAILABLE
@@ -51,13 +53,13 @@
  */
 - (BOOL)setValue:(NSDictionary<NSString *, id> *)value;
 
-@property (nonatomic, copy, readonly) NSNumber * attributeID;
-@property (nonatomic, copy, readonly) NSDictionary<NSString *, id> * value;
+@property (atomic, copy, readonly) NSNumber * attributeID;
+@property (atomic, copy, readonly) NSDictionary<NSString *, id> * value;
 /**
  * The privilege level necessary to read this attribute.
  */
-@property (nonatomic, assign, readonly) MTRAccessControlEntryPrivilege requiredReadPrivilege;
-@property (nonatomic, assign, readonly, getter=isWritable) BOOL writable;
+@property (atomic, assign, readonly) MTRAccessControlEntryPrivilege requiredReadPrivilege;
+@property (atomic, assign, readonly, getter=isWritable) BOOL writable;
 
 @end
 
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.h
index 02b0f4b..591d3af 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.h
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.h
@@ -23,6 +23,8 @@
 
 /**
  * A representation of a server cluster implemented by an MTRDeviceController.
+ *
+ * MTRServerCluster's API can be accessed from any thread.
  */
 NS_SWIFT_SENDABLE
 MTR_NEWLY_AVAILABLE
@@ -90,9 +92,9 @@
  */
 + (MTRServerCluster *)newDescriptorCluster;
 
-@property (nonatomic, copy, readonly) NSNumber * clusterID;
+@property (atomic, copy, readonly) NSNumber * clusterID;
 
-@property (nonatomic, copy, readonly) NSNumber * clusterRevision;
+@property (atomic, copy, readonly) NSNumber * clusterRevision;
 
 /**
  * The list of entities that are allowed to access this cluster instance.  This
@@ -100,12 +102,12 @@
  *
  * Defaults to empty list, which means no additional access grants.
  */
-@property (nonatomic, copy, readonly) NSArray<MTRAccessGrant *> * accessGrants;
+@property (atomic, copy, readonly) NSArray<MTRAccessGrant *> * accessGrants;
 
 /**
  * The list of attributes supported by the cluster.
  */
-@property (nonatomic, copy, readonly) NSArray<MTRServerAttribute *> * attributes;
+@property (atomic, copy, readonly) NSArray<MTRServerAttribute *> * attributes;
 
 @end
 
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm
index c0fba9e..7439c0f 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm
@@ -20,11 +20,12 @@
 #import "MTRServerAttribute_Internal.h"
 #import "MTRServerCluster_Internal.h"
 #import "MTRServerEndpoint_Internal.h"
+#import "MTRUnfairLock.h"
+#import "NSDataSpanConversion.h"
+
 #import <Matter/MTRClusterConstants.h>
 #import <Matter/MTRServerCluster.h>
 
-#import "NSDataSpanConversion.h"
-
 #include <app/AttributeAccessInterface.h>
 #include <app/clusters/descriptor/descriptor.h>
 #include <app/data-model/PreEncodedValue.h>
@@ -71,11 +72,26 @@
     std::unique_ptr<MTRServerAttributeAccessInterface> _attributeAccessInterface;
     // We can't use something like std::unique_ptr<EmberAfAttributeMetadata[]>
     // because EmberAfAttributeMetadata does not have a default constructor, so
-    // we can't alloc and then initializer later.
+    // we can't alloc and then initialize later.
     std::vector<EmberAfAttributeMetadata> _matterAttributeMetadata;
 
     std::unique_ptr<CommandId[]> _matterAcceptedCommandList;
     std::unique_ptr<CommandId[]> _matterGeneratedCommandList;
+
+    NSSet<MTRAccessGrant *> * _matterAccessGrants;
+
+    chip::EndpointId _parentEndpoint;
+
+    // _acceptedCommands and _generatedCommands are touched directly by our API
+    // consumer.
+    NSArray<NSNumber *> * _acceptedCommands;
+    NSArray<NSNumber *> * _generatedCommands;
+
+    /**
+     * _lock always protects access to all our mutable ivars (the ones that are
+     * modified after init).
+     */
+    os_unfair_lock _lock;
 }
 
 - (nullable instancetype)initWithClusterID:(NSNumber *)clusterID revision:(NSNumber *)revision
@@ -117,6 +133,7 @@
         return nil;
     }
 
+    _lock = OS_UNFAIR_LOCK_INIT;
     _clusterID = [clusterID copy];
     _clusterRevision = [revision copy];
     _accessGrants = [[NSMutableSet alloc] init];
@@ -141,6 +158,8 @@
 
 - (void)updateMatterAccessGrants
 {
+    os_unfair_lock_assert_owner(&_lock);
+
     MTRDeviceController * deviceController = _deviceController;
     if (deviceController == nil) {
         // _matterAccessGrants will be updated when we get bound to a controller.
@@ -149,6 +168,7 @@
 
     NSSet * grants = [_accessGrants copy];
     [deviceController asyncDispatchToMatterQueue:^{
+        std::lock_guard lock(self->_lock);
         self->_matterAccessGrants = grants;
     }
                                     errorHandler:nil];
@@ -156,6 +176,8 @@
 
 - (void)addAccessGrant:(MTRAccessGrant *)accessGrant
 {
+    std::lock_guard lock(self->_lock);
+
     [_accessGrants addObject:accessGrant];
 
     [self updateMatterAccessGrants];
@@ -163,13 +185,24 @@
 
 - (void)removeAccessGrant:(MTRAccessGrant *)accessGrant;
 {
+    std::lock_guard lock(self->_lock);
+
     [_accessGrants removeObject:accessGrant];
 
     [self updateMatterAccessGrants];
 }
 
+- (NSArray<MTRAccessGrant *> *)matterAccessGrants
+{
+    std::lock_guard lock(self->_lock);
+
+    return [_matterAccessGrants allObjects];
+}
+
 - (BOOL)addAttribute:(MTRServerAttribute *)attribute
 {
+    std::lock_guard lock(self->_lock);
+
     MTRDeviceController * deviceController = _deviceController;
     if (deviceController != nil) {
         MTR_LOG_ERROR("Cannot add attribute on cluster %llx which is already in use", _clusterID.unsignedLongLongValue);
@@ -213,6 +246,8 @@
 
 - (BOOL)associateWithController:(nullable MTRDeviceController *)controller
 {
+    std::lock_guard lock(self->_lock);
+
     MTRDeviceController * existingController = _deviceController;
     if (existingController != nil) {
 #if MTR_PER_CONTROLLER_STORAGE_ENABLED
@@ -313,7 +348,7 @@
 
     _deviceController = controller;
 
-    MTR_LOG_DEFAULT("Associated %@, attribute count %llu, with controller", self,
+    MTR_LOG_DEFAULT("Associated %@, attribute count %llu, with controller", [self _descriptionWhileLocked],
         static_cast<unsigned long long>(attributeCount));
 
     return YES;
@@ -321,6 +356,8 @@
 
 - (void)invalidate
 {
+    std::lock_guard lock(_lock);
+
     // Undo any work associateWithController did.
     for (MTRServerAttribute * attr in _attributes) {
         [attr invalidate];
@@ -342,6 +379,8 @@
 {
     assertChipStackLockedByCurrentThread();
 
+    std::lock_guard lock(_lock);
+
     if (!registerAttributeAccessOverride(_attributeAccessInterface.get())) {
         // This should only happen if we somehow managed to register an
         // AttributeAccessInterface for the same (endpoint, cluster) pair.
@@ -354,6 +393,8 @@
 {
     assertChipStackLockedByCurrentThread();
 
+    std::lock_guard lock(_lock);
+
     if (_attributeAccessInterface != nullptr) {
         unregisterAttributeAccessOverride(_attributeAccessInterface.get());
     }
@@ -361,38 +402,84 @@
 
 - (NSArray<MTRAccessGrant *> *)accessGrants
 {
+    std::lock_guard lock(_lock);
+
     return [_accessGrants allObjects];
 }
 
 - (NSArray<MTRServerAttribute *> *)attributes
 {
+    std::lock_guard lock(_lock);
+
     return [_attributes copy];
 }
 
-- (void)setParentEndpoint:(EndpointId)endpoint
+- (BOOL)addToEndpoint:(chip::EndpointId)endpoint
 {
+    std::lock_guard lock(_lock);
+
+    if (_parentEndpoint != kInvalidEndpointId) {
+        MTR_LOG_ERROR("Cannot add cluster " ChipLogFormatMEI " to endpoint %" PRIu32 "; already added to endpoint %" PRIu32,
+            ChipLogValueMEI(_clusterID.unsignedLongLongValue), endpoint, _parentEndpoint);
+        return NO;
+    }
+
     _parentEndpoint = endpoint;
     // Update it on all the attributes, in case the attributes were added to us
     // before we were added to the endpoint.
     for (MTRServerAttribute * attr in _attributes) {
         [attr updateParentCluster:ConcreteClusterPath(endpoint, static_cast<ClusterId>(_clusterID.unsignedLongLongValue))];
     }
+    return YES;
+}
+
+- (chip::EndpointId)parentEndpoint
+{
+    std::lock_guard lock(_lock);
+    return _parentEndpoint;
 }
 
 - (Span<const EmberAfAttributeMetadata>)matterAttributeMetadata
 {
     // This is always called after our _matterAttributeMetadata has been set up
     // by associateWithController.
+    std::lock_guard lock(_lock);
     return Span<const EmberAfAttributeMetadata>(_matterAttributeMetadata.data(), _matterAttributeMetadata.size());
 }
 
+- (void)setAcceptedCommands:(NSArray<NSNumber *> *)acceptedCommands
+{
+    std::lock_guard lock(_lock);
+    _acceptedCommands = [acceptedCommands copy];
+}
+
+- (NSArray<NSNumber *> *)acceptedCommands
+{
+    std::lock_guard lock(_lock);
+    return [_acceptedCommands copy];
+}
+
+- (void)setGeneratedCommands:(NSArray<NSNumber *> *)generatedCommands
+{
+    std::lock_guard lock(_lock);
+    _generatedCommands = [generatedCommands copy];
+}
+
+- (NSArray<NSNumber *> *)generatedCommands
+{
+    std::lock_guard lock(_lock);
+    return [_generatedCommands copy];
+}
+
 - (CommandId *)matterAcceptedCommands
 {
+    std::lock_guard lock(_lock);
     return _matterAcceptedCommandList.get();
 }
 
 - (CommandId *)matterGeneratedCommands
 {
+    std::lock_guard lock(_lock);
     return _matterGeneratedCommandList.get();
 }
 
@@ -413,6 +500,13 @@
 
 - (NSString *)description
 {
+    std::lock_guard lock(_lock);
+    return [self _descriptionWhileLocked];
+}
+
+- (NSString *)_descriptionWhileLocked
+{
+    os_unfair_lock_assert_owner(&_lock);
     return [NSString stringWithFormat:@"<MTRServerCluster endpoint %u, id " ChipLogFormatMEI ">",
                      _parentEndpoint, ChipLogValueMEI(_clusterID.unsignedLongLongValue)];
 }
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h
index 4ae3d0d..253885b 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h
@@ -57,40 +57,46 @@
 - (void)invalidate;
 
 /**
- * The access grants the Matter stack can observe.  Only modified while in
- * Initializing state or on the Matter queue.
+ * Add the cluster to an endpoint with the given endpoint ID.  Will return NO
+ * if the cluster is already added to an endpoint.
  */
-@property (nonatomic, strong, readonly) NSSet<MTRAccessGrant *> * matterAccessGrants;
+- (BOOL)addToEndpoint:(chip::EndpointId)endpoint;
+
+/**
+ * The access grants the Matter stack can observe.  Only modified while
+ * associating with a controller or on the Matter queue.
+ */
+@property (atomic, copy, readonly) NSArray<MTRAccessGrant *> * matterAccessGrants;
 
 /**
  * parentEndpoint will be kInvalidEndpointId until the cluster is added to an endpoint.
  */
-@property (nonatomic, assign) chip::EndpointId parentEndpoint;
+@property (atomic, assign, readonly) chip::EndpointId parentEndpoint;
 
 /**
  * The attribute metadata for the cluster.  Only valid after associateWithController: has succeeded.
  */
-@property (nonatomic, assign, readonly) chip::Span<const EmberAfAttributeMetadata> matterAttributeMetadata;
+@property (atomic, assign, readonly) chip::Span<const EmberAfAttributeMetadata> matterAttributeMetadata;
 
 /**
  * The list of accepted command IDs.
  */
-@property (nonatomic, copy, nullable) NSArray<NSNumber *> * acceptedCommands;
+@property (atomic, copy, nullable) NSArray<NSNumber *> * acceptedCommands;
 
 /**
  * The list of generated command IDs.
  */
-@property (nonatomic, copy, nullable) NSArray<NSNumber *> * generatedCommands;
+@property (atomic, copy, nullable) NSArray<NSNumber *> * generatedCommands;
 
 /**
  * The list of accepted commands IDs in the format the Matter stack needs.
  */
-@property (nonatomic, assign, nullable, readonly) chip::CommandId * matterAcceptedCommands;
+@property (atomic, assign, nullable, readonly) chip::CommandId * matterAcceptedCommands;
 
 /**
  * The list of generated commands IDs in the format the Matter stack needs.
  */
-@property (nonatomic, assign, nullable, readonly) chip::CommandId * matterGeneratedCommands;
+@property (atomic, assign, nullable, readonly) chip::CommandId * matterGeneratedCommands;
 
 @end
 
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm
index c58b1ac..c755323 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm
@@ -154,20 +154,18 @@
         return NO;
     }
 
-    if (serverCluster.parentEndpoint != kInvalidEndpointId) {
-        MTR_LOG_ERROR("Cannot add cluster to endpoint %llu; already added to endpoint %" PRIu32, _endpointID.unsignedLongLongValue, serverCluster.parentEndpoint);
-        return NO;
-    }
-
     for (MTRServerCluster * existingCluster in _serverClusters) {
         if ([existingCluster.clusterID isEqual:serverCluster.clusterID]) {
-            MTR_LOG_ERROR("Cannot add second cluster with ID %llx on endpoint %llu", serverCluster.clusterID.unsignedLongLongValue, _endpointID.unsignedLongLongValue);
+            MTR_LOG_ERROR("Cannot add second cluster with ID " ChipLogFormatMEI " on endpoint %llu", ChipLogValueMEI(serverCluster.clusterID.unsignedLongLongValue), _endpointID.unsignedLongLongValue);
             return NO;
         }
     }
 
+    if (![serverCluster addToEndpoint:static_cast<EndpointId>(_endpointID.unsignedLongLongValue)]) {
+        return NO;
+    }
     [_serverClusters addObject:serverCluster];
-    serverCluster.parentEndpoint = static_cast<EndpointId>(_endpointID.unsignedLongLongValue);
+
     return YES;
 }
 
@@ -401,7 +399,7 @@
     NSMutableArray<MTRAccessGrant *> * grants = [[_matterAccessGrants allObjects] mutableCopy];
     for (MTRServerCluster * cluster in _serverClusters) {
         if ([cluster.clusterID isEqual:clusterID]) {
-            [grants addObjectsFromArray:[cluster.matterAccessGrants allObjects]];
+            [grants addObjectsFromArray:cluster.matterAccessGrants];
         }
     }