Implement support for more configurable server endpoints in Matter.framework. (#31814)
* Implement support for more configurable server endpoints in Matter.framework.
* Public APIs on MTRDeviceController to add/remove an endpoint.
* Internal APIs on MTRDeviceController to query the access grants for a cluster
path and the declared "minimum privilege needed" to read a given attribute.
* Changes to the controller factory to stop using app/dynamic_server and instead
use the new infrastructure to expose OTA Provider on endpoint 0.
* Internal APIs on the controller factory to query access grants and declared
privileges.
* An implemenation of AccessControl::Delegate to do ACL checks using the
above-mentioned APIs.
* A fix to MTRServerAttribute's setValue for arrays: it was not expecting the
correct data-value structure for an array. This requires fixing some tests
too, to provide the right structures.
* Changes to the MTRServer* data structures to allow passing nil to
associateWithController, to support the OTA endpoint which is not associated
with any controller.
* Changes to MTRServerCluster to create an AttributeAccessInterface, the set of
EmberAfAttributeMetadata needed to represent its attributes, and various other
things needed to register a cluster with the "ember" bits.
* Changes to MTRServerEndpoint to create an EmberAfEndpointType, a list of
EmberAfCluster, and various other things needed to register an endpoint with
the "ember" bits.
* (Re-)addition of MTRIMDispatch to handle command dispatch for OTA and host a
few other functions the "ember" bits expect to exist.
* Addition of some headers that the "ember" bits expect to exist at specific
paths and with some specific content: "app/PluginApplicationCallbacks.h" and
"zap-generated/endpoint_config.h". Importantly, the latter sets
FIXED_ENDPOINT_COUNT to 0.
* Addition of unit tests that exercise the non-OTA bits of the above (OTA is
covered by existing tests), including the ACL checks and so on.
* Including a bunch of src/app and src/app/util files needed for the "ember"
stuff to work in the framework.
* Turning off the chip_build_controller_dynamic_server bit that we are no longer
using (and which conflicts with the above bits).
* Configure Darwin to support 254 dynamic endpoints (the maximum that makes
sense) by default.
* Adjusting include paths for the Xcode darwin-framework-tool project, so that
it sees the new headers that were added.
* Address review comments.
* Fix test timeout due to resolving IPv4 non-locahost addresses.
* Remove stale comment.
diff --git a/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h b/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h
index ab65655..d113b5d 100644
--- a/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h
+++ b/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h
@@ -215,6 +215,8 @@
}
if (!callbackBridge->mQueue) {
+ ChipLogDetail(Controller, "%s %f seconds: can't dispatch response; no queue", callbackBridge->mCookie.UTF8String,
+ -[callbackBridge->mRequestTime timeIntervalSinceNow]);
if (!callbackBridge->mKeepAlive) {
delete callbackBridge;
}
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.h b/src/darwin/Framework/CHIP/MTRDeviceController.h
index a3757e6..ea2f649 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceController.h
@@ -22,6 +22,7 @@
#import <Matter/MTROperationalCertificateIssuer.h>
@class MTRBaseDevice;
+@class MTRServerEndpoint; // Defined in MTRServerEndpoint.h, which imports MTRAccessGrant.h, which imports MTRBaseClusters.h, which imports this file, so we can't import it.
#if MTR_PER_CONTROLLER_STORAGE_ENABLED
@class MTRDeviceControllerAbstractParameters;
@@ -229,6 +230,29 @@
MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));
/**
+ * Add a server endpoint for this controller. The endpoint starts off enabled.
+ *
+ * Will fail in the following cases:
+ *
+ * 1) There is already an endpoint defined with the given endpoint id.
+ * 2) There are too many endpoints defined already.
+ */
+- (BOOL)addServerEndpoint:(MTRServerEndpoint *)endpoint MTR_NEWLY_AVAILABLE;
+
+/**
+ * Remove the given server endpoint from this controller. If the endpoint is
+ * not attached to this controller, will just call the completion and do nothing
+ * else.
+ */
+- (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint queue:(dispatch_queue_t)queue completion:(dispatch_block_t)completion MTR_NEWLY_AVAILABLE;
+
+/**
+ * Remove the given server endpoint without being notified when the removal
+ * completes.
+ */
+- (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint MTR_NEWLY_AVAILABLE;
+
+/**
* Compute a PASE verifier for the desired setup passcode.
*
* @param[in] setupPasscode The desired passcode to use.
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm
index decd879..9621b44 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm
@@ -41,6 +41,7 @@
#import "MTROperationalCredentialsDelegate.h"
#import "MTRP256KeypairBridge.h"
#import "MTRPersistentStorageDelegateBridge.h"
+#import "MTRServerEndpoint_Internal.h"
#import "MTRSetupPayload.h"
#import "NSDataSpanConversion.h"
#import "NSStringSpanConversion.h"
@@ -55,6 +56,7 @@
#include <app-common/zap-generated/cluster-objects.h>
#include <app/data-model/List.h>
+#include <app/server/Dnssd.h>
#include <controller/CHIPDeviceController.h>
#include <controller/CHIPDeviceControllerFactory.h>
#include <controller/CommissioningWindowOpener.h>
@@ -62,6 +64,7 @@
#include <credentials/GroupDataProvider.h>
#include <credentials/attestation_verifier/DacOnlyPartialAttestationVerifier.h>
#include <credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h>
+#include <inet/InetInterface.h>
#include <lib/core/CHIPVendorIdentifiers.hpp>
#include <platform/LockTracker.h>
#include <platform/PlatformManager.h>
@@ -69,6 +72,7 @@
#include <system/SystemClock.h>
#include <atomic>
+#include <dns_sd.h>
#import <os/lock.h>
@@ -121,6 +125,9 @@
os_unfair_lock _deviceMapLock; // protects nodeIDToDeviceMap
MTRCommissionableBrowser * _commissionableBrowser;
MTRAttestationTrustStoreBridge * _attestationTrustStoreBridge;
+
+ // _serverEndpoints is only touched on the Matter queue.
+ NSMutableArray<MTRServerEndpoint *> * _serverEndpoints;
}
- (nullable instancetype)initWithParameters:(MTRDeviceControllerAbstractParameters *)parameters error:(NSError * __autoreleasing *)error
@@ -221,6 +228,7 @@
_factory = factory;
_deviceMapLock = OS_UNFAIR_LOCK_INIT;
_nodeIDToDeviceMap = [NSMutableDictionary dictionary];
+ _serverEndpoints = [[NSMutableArray alloc] init];
_commissionableBrowser = nil;
_deviceControllerDelegateBridge = new MTRDeviceControllerDelegateBridge();
@@ -285,6 +293,11 @@
{
assertChipStackLockedByCurrentThread();
+ // Shut down all our endpoints.
+ for (MTRServerEndpoint * endpoint in [_serverEndpoints copy]) {
+ [self removeServerEndpointOnMatterQueue:endpoint];
+ }
+
if (_cppCommissioner) {
auto * commissionerToShutDown = _cppCommissioner;
// Flag ourselves as not running before we start shutting down
@@ -947,6 +960,71 @@
return [self syncRunOnWorkQueueWithReturnValue:block error:nil];
}
+- (BOOL)addServerEndpoint:(MTRServerEndpoint *)endpoint
+{
+ VerifyOrReturnValue([self checkIsRunning], NO);
+
+ if (![_factory addServerEndpoint:endpoint]) {
+ return NO;
+ }
+
+ if (![endpoint associateWithController:self]) {
+ MTR_LOG_ERROR("Failed to associate MTRServerEndpoint with MTRDeviceController");
+ [_factory removeServerEndpoint:endpoint];
+ return NO;
+ }
+
+ [self asyncDispatchToMatterQueue:^() {
+ [self->_serverEndpoints addObject:endpoint];
+ [endpoint registerMatterEndpoint];
+ }
+ errorHandler:^(NSError * error) {
+ MTR_LOG_ERROR("Unexpected failure dispatching to Matter queue on running controller in addServerEndpoint");
+ }];
+ return YES;
+}
+
+- (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint queue:(dispatch_queue_t)queue completion:(dispatch_block_t)completion
+{
+ [self removeServerEndpointInternal:endpoint queue:queue completion:completion];
+}
+
+- (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint
+{
+ [self removeServerEndpointInternal:endpoint queue:nil completion:nil];
+}
+
+- (void)removeServerEndpointInternal:(MTRServerEndpoint *)endpoint queue:(dispatch_queue_t _Nullable)queue completion:(dispatch_block_t _Nullable)completion
+{
+ VerifyOrReturn([self checkIsRunning]);
+
+ // We need to unhook the endpoint from the Matter side before we can start
+ // tearing it down.
+ [self asyncDispatchToMatterQueue:^() {
+ [self removeServerEndpointOnMatterQueue:endpoint];
+ if (queue != nil && completion != nil) {
+ dispatch_async(queue, completion);
+ }
+ }
+ errorHandler:^(NSError * error) {
+ // Error means we got shut down, so the endpoint is removed now.
+ if (queue != nil && completion != nil) {
+ dispatch_async(queue, completion);
+ }
+ }];
+}
+
+- (void)removeServerEndpointOnMatterQueue:(MTRServerEndpoint *)endpoint
+{
+ assertChipStackLockedByCurrentThread();
+
+ [endpoint unregisterMatterEndpoint];
+ [_serverEndpoints removeObject:endpoint];
+ [endpoint invalidate];
+
+ [_factory removeServerEndpoint:endpoint];
+}
+
- (BOOL)checkForInitError:(BOOL)condition logMsg:(NSString *)logMsg
{
if (condition) {
@@ -1230,6 +1308,52 @@
completion:completion];
}
+- (NSArray<MTRAccessGrant *> *)accessGrantsForClusterPath:(MTRClusterPath *)clusterPath
+{
+ assertChipStackLockedByCurrentThread();
+
+ for (MTRServerEndpoint * endpoint in _serverEndpoints) {
+ if ([clusterPath.endpoint isEqual:endpoint.endpointID]) {
+ return [endpoint matterAccessGrantsForCluster:clusterPath.cluster];
+ }
+ }
+
+ // Nothing matched, no grants.
+ return @[];
+}
+
+- (nullable NSNumber *)neededReadPrivilegeForClusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
+{
+ assertChipStackLockedByCurrentThread();
+
+ for (MTRServerEndpoint * endpoint in _serverEndpoints) {
+ for (MTRServerCluster * cluster in endpoint.serverClusters) {
+ if (![cluster.clusterID isEqual:clusterID]) {
+ continue;
+ }
+
+ for (MTRServerAttribute * attr in cluster.attributes) {
+ if (![attr.attributeID isEqual:attributeID]) {
+ continue;
+ }
+
+ return @(attr.requiredReadPrivilege);
+ }
+ }
+ }
+
+ return nil;
+}
+
+#ifdef DEBUG
++ (void)forceLocalhostAdvertisingOnly
+{
+ auto interfaceIndex = chip::Inet::InterfaceId::PlatformType(kDNSServiceInterfaceIndexLocalOnly);
+ auto interfaceId = chip::Inet::InterfaceId(interfaceIndex);
+ chip::app::DnssdServer::Instance().SetInterfaceId(interfaceId);
+}
+#endif // DEBUG
+
@end
/**
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
index 319c33f..457abea 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
@@ -25,6 +25,9 @@
#import "MTRDeviceControllerParameters_Wrapper.h"
#endif // MTR_PER_CONTROLLER_STORAGE_ENABLED
+#import <Matter/MTRClusterConstants.h>
+#import <Matter/MTRServerCluster.h>
+
#import "MTRCertificates.h"
#import "MTRDemuxingStorage.h"
#import "MTRDeviceController.h"
@@ -40,12 +43,15 @@
#import "MTROperationalBrowser.h"
#import "MTRP256KeypairBridge.h"
#import "MTRPersistentStorageDelegateBridge.h"
+#import "MTRServerAccessControl.h"
+#import "MTRServerCluster_Internal.h"
+#import "MTRServerEndpoint_Internal.h"
#import "MTRSessionResumptionStorageBridge.h"
#import "NSDataSpanConversion.h"
#import <os/lock.h>
-#include <app/dynamic_server/AccessControl.h>
+#include <app/util/af.h>
#include <controller/CHIPDeviceControllerFactory.h>
#include <credentials/CHIPCert.h>
#include <credentials/FabricTable.h>
@@ -75,12 +81,14 @@
static bool sExitHandlerRegistered = false;
static void ShutdownOnExit() { [[MTRDeviceControllerFactory sharedInstance] stopControllerFactory]; }
-@interface MTRDeviceControllerFactory ()
+@interface MTRDeviceControllerFactory () {
+ MTRServerEndpoint * _otaProviderEndpoint;
+ std::unique_ptr<MTROTAProviderDelegateBridge> _otaProviderDelegateBridge;
+}
@property (atomic, readonly) dispatch_queue_t chipWorkQueue;
@property (readonly) DeviceControllerFactory * controllerFactory;
@property (readonly) PersistentStorageDelegate * persistentStorageDelegate;
-@property (readonly) MTROTAProviderDelegateBridge * otaProviderDelegateBridge;
@property (readonly) Crypto::RawKeySessionKeystore * sessionKeystore;
// We use TestPersistentStorageDelegate just to get an in-memory store to back
// our group data provider impl. We initialize this store correctly on every
@@ -170,6 +178,11 @@
// is only accessed on the Matter queue or after the Matter queue has shut
// down.
FabricIndex _nextAvailableFabricIndex;
+
+ // Array of all server endpoints across all controllers, used to ensure
+ // in an atomic way that endpoint IDs are unique.
+ NSMutableArray<MTRServerEndpoint *> * _serverEndpoints;
+ os_unfair_lock _serverEndpointsLock; // Protects access to _serverEndpoints.
}
+ (void)initialize
@@ -232,6 +245,9 @@
return nil;
}
+ _serverEndpoints = [[NSMutableArray alloc] init];
+ _serverEndpointsLock = OS_UNFAIR_LOCK_INIT;
+
return self;
}
@@ -319,10 +335,6 @@
_keystore = nullptr;
}
- if (_otaProviderDelegateBridge) {
- delete _otaProviderDelegateBridge;
- _otaProviderDelegateBridge = nullptr;
- }
_otaProviderDelegateQueue = nil;
_otaProviderDelegate = nil;
@@ -410,7 +422,7 @@
return;
}
- app::dynamic_server::InitAccessControl();
+ InitializeServerAccessControl();
if (startupParams.hasStorage) {
_persistentStorageDelegate = new (std::nothrow) MTRPersistentStorageDelegateBridge(startupParams.storage);
@@ -439,7 +451,6 @@
_otaProviderDelegateQueue = dispatch_queue_create(
"org.csa-iot.matter.framework.otaprovider.workqueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
}
- _otaProviderDelegateBridge = new MTROTAProviderDelegateBridge();
// TODO: Allow passing a different keystore implementation via startupParams.
_keystore = new PersistentStorageOperationalKeystore();
@@ -900,11 +911,44 @@
{
[self _assertCurrentQueueIsNotMatterQueue];
- VerifyOrReturnValue(_otaProviderDelegateBridge != nil, controller);
VerifyOrReturnValue([_controllers count] == 1, controller);
+ _otaProviderEndpoint = [MTRServerEndpoint rootNodeEndpoint];
+
+ // TODO: Have the OTA Provider cluster revision accessible somewhere?
+ auto * otaProviderCluster = [[MTRServerCluster alloc] initWithClusterID:@(MTRClusterIDTypeOTASoftwareUpdateProviderID) revision:@(1)];
+ otaProviderCluster.acceptedCommands = @[
+ @(MTRCommandIDTypeClusterOTASoftwareUpdateProviderCommandQueryImageID),
+ @(MTRCommandIDTypeClusterOTASoftwareUpdateProviderCommandApplyUpdateRequestID),
+ @(MTRCommandIDTypeClusterOTASoftwareUpdateProviderCommandNotifyUpdateAppliedID),
+ ];
+ otaProviderCluster.generatedCommands = @[
+ @(MTRCommandIDTypeClusterOTASoftwareUpdateProviderCommandQueryImageResponseID),
+ @(MTRCommandIDTypeClusterOTASoftwareUpdateProviderCommandApplyUpdateResponseID),
+ ];
+ [otaProviderCluster addAccessGrant:[MTRAccessGrant accessGrantForAllNodesWithPrivilege:MTRAccessControlEntryPrivilegeOperate]];
+
+ // Not expected to fail, since we are following the rules for clusters here.
+ [_otaProviderEndpoint addServerCluster:otaProviderCluster];
+
+ if (![self addServerEndpoint:_otaProviderEndpoint]) {
+ MTR_LOG_ERROR("Failed to add OTA endpoint on factory. Why?");
+ [controller shutdown];
+ return nil;
+ }
+
+ // This endpoint is not actually associated with a specific controller; we
+ // just need to have a working Matter event loop to bring it up.
+ [_otaProviderEndpoint associateWithController:nil];
+
__block CHIP_ERROR err;
dispatch_sync(_chipWorkQueue, ^{
+ [self->_otaProviderEndpoint registerMatterEndpoint];
+
+ // Now that our endpoint exists, go ahead and create the OTA delegate
+ // bridge. Its constructor relies on the endpoint existing.
+ _otaProviderDelegateBridge = std::make_unique<MTROTAProviderDelegateBridge>();
+
auto systemState = _controllerFactory->GetSystemState();
err = _otaProviderDelegateBridge->Init(systemState->SystemLayer(), systemState->ExchangeMgr());
});
@@ -982,6 +1026,16 @@
if (_otaProviderDelegateBridge) {
_otaProviderDelegateBridge->Shutdown();
+ _otaProviderDelegateBridge.reset();
+ }
+
+ if (_otaProviderEndpoint != nil) {
+ [_otaProviderEndpoint unregisterMatterEndpoint];
+ [_otaProviderEndpoint invalidate];
+
+ [self removeServerEndpoint:_otaProviderEndpoint];
+
+ _otaProviderEndpoint = nil;
}
sharedCleanupBlock();
@@ -1071,6 +1125,94 @@
return [self runningControllerForFabricIndex:fabricIndex includeControllerStartingUp:YES includeControllerShuttingDown:YES];
}
+- (BOOL)addServerEndpoint:(MTRServerEndpoint *)endpoint
+{
+ os_unfair_lock_lock(&_serverEndpointsLock);
+ if (_serverEndpoints.count == CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) {
+ os_unfair_lock_unlock(&_serverEndpointsLock);
+
+ MTR_LOG_ERROR("Can't add a server endpoint with endpoint ID %u, because we already have %u endpoints defined", static_cast<EndpointId>(endpoint.endpointID.unsignedLongLongValue), CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT);
+
+ return NO;
+ }
+
+ BOOL haveExisting = NO;
+ for (MTRServerEndpoint * existing in _serverEndpoints) {
+ if ([endpoint.endpointID isEqual:existing.endpointID]) {
+ haveExisting = YES;
+ break;
+ }
+ }
+
+ if (!haveExisting) {
+ [_serverEndpoints addObject:endpoint];
+ }
+ os_unfair_lock_unlock(&_serverEndpointsLock);
+
+ if (haveExisting) {
+ MTR_LOG_ERROR("Trying to add a server endpoint with endpoint ID %u, which already exists", static_cast<EndpointId>(endpoint.endpointID.unsignedLongLongValue));
+ }
+
+ return !haveExisting;
+}
+
+- (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint
+{
+ os_unfair_lock_lock(&_serverEndpointsLock);
+ [_serverEndpoints removeObject:endpoint];
+ os_unfair_lock_unlock(&_serverEndpointsLock);
+}
+
+- (NSArray<MTRAccessGrant *> *)accessGrantsForFabricIndex:(chip::FabricIndex)fabricIndex clusterPath:(MTRClusterPath *)clusterPath
+{
+ assertChipStackLockedByCurrentThread();
+
+ if ([clusterPath.endpoint isEqual:_otaProviderEndpoint.endpointID]) {
+ return [_otaProviderEndpoint matterAccessGrantsForCluster:clusterPath.cluster];
+ }
+
+ // We do not want to use _serverEndpoints here, because that might contain
+ // endpoints that are still being set up and whatnot. Ask the controller
+ // for the relevant fabric index what the relevant access grants are.
+
+ // Include controllers that are shutting down, since this may be an accesss
+ // check for event reports they emit as they shut down.
+ auto * controller = [self runningControllerForFabricIndex:fabricIndex includeControllerStartingUp:NO includeControllerShuttingDown:YES];
+ if (controller == nil) {
+ return @[];
+ }
+
+ return [controller accessGrantsForClusterPath:clusterPath];
+}
+
+- (nullable NSNumber *)neededReadPrivilegeForClusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
+{
+ assertChipStackLockedByCurrentThread();
+
+ for (MTRServerCluster * cluster in _otaProviderEndpoint.serverClusters) {
+ if (![cluster.clusterID isEqual:clusterID]) {
+ continue;
+ }
+
+ for (MTRServerAttribute * attr in cluster.attributes) {
+ if (![attr.attributeID isEqual:attributeID]) {
+ continue;
+ }
+
+ return @(attr.requiredReadPrivilege);
+ }
+ }
+
+ for (MTRDeviceController * controller in [self getRunningControllers]) {
+ NSNumber * _Nullable neededPrivilege = [controller neededReadPrivilegeForClusterID:clusterID attributeID:attributeID];
+ if (neededPrivilege != nil) {
+ return neededPrivilege;
+ }
+ }
+
+ return nil;
+}
+
- (void)downloadLogFromNodeWithID:(NSNumber *)nodeID
controller:(MTRDeviceController *)controller
type:(MTRDiagnosticLogType)type
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h
index e413001..4e0290b 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory_Internal.h
@@ -20,9 +20,12 @@
*/
#import <Foundation/Foundation.h>
+#import <Matter/MTRAccessGrant.h>
+#import <Matter/MTRBaseDevice.h> // for MTRClusterPath
#import <Matter/MTRDefines.h>
#import <Matter/MTRDeviceController.h>
#import <Matter/MTRDiagnosticLogsType.h>
+#import <Matter/MTRServerEndpoint.h>
#if MTR_PER_CONTROLLER_STORAGE_ENABLED
#import <Matter/MTRDeviceControllerParameters.h>
@@ -93,6 +96,38 @@
withParameters:(MTRDeviceControllerParameters *)parameters
error:(NSError * __autoreleasing *)error;
+/**
+ * Add a server endpoint. This will verify that there is no existing server
+ * endpoint with the provided endpoint ID and return NO if there is one. Can be
+ * called on any thread.
+ */
+- (BOOL)addServerEndpoint:(MTRServerEndpoint *)endpoint;
+
+/**
+ * Remove a server endpoint. This must happen after all other teardown for the
+ * endpoint is complete. Can be called on any thread.
+ */
+- (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint;
+
+/**
+ * Get the access grants that apply for the given fabric index and cluster path.
+ *
+ * Only called on the Matter queue.
+ */
+- (NSArray<MTRAccessGrant *> *)accessGrantsForFabricIndex:(chip::FabricIndex)fabricIndex clusterPath:(MTRClusterPath *)clusterPath;
+
+/**
+ * Get the privilege level needed to read the given attribute. There's no
+ * endpoint provided because the expectation is that this information is the
+ * same for all cluster instances.
+ *
+ * Returns nil if we have no such attribute defined on any endpoint, otherwise
+ * one of MTRAccessControlEntry* constants wrapped in NSNumber.
+ *
+ * Only called on the Matter queue.
+ */
+- (nullable NSNumber *)neededReadPrivilegeForClusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID;
+
@property (readonly) chip::PersistentStorageDelegate * storageDelegate;
@property (readonly) chip::Credentials::GroupDataProvider * groupData;
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
index 92873cf..b55d41b 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
@@ -20,6 +20,8 @@
*/
#import <Foundation/Foundation.h>
+#import <Matter/MTRAccessGrant.h>
+#import <Matter/MTRBaseDevice.h> // for MTRClusterPath
#import "MTRDeviceConnectionBridge.h" // For MTRInternalDeviceConnectionCallback
#import "MTRDeviceController.h"
@@ -243,6 +245,23 @@
queue:(dispatch_queue_t)queue
completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion;
+/**
+ * Get the access grants that apply for the given cluster path.
+ */
+- (NSArray<MTRAccessGrant *> *)accessGrantsForClusterPath:(MTRClusterPath *)clusterPath;
+
+/**
+ * Get the privilege level needed to read the given attribute. There's no
+ * endpoint provided because the expectation is that this information is the
+ * same for all cluster instances.
+ *
+ * Returns nil if we have no such attribute defined on any endpoint, otherwise
+ * one of MTRAccessControlEntry* constants wrapped in NSNumber.
+ *
+ * Only called on the Matter queue.
+ */
+- (nullable NSNumber *)neededReadPrivilegeForClusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID;
+
#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/ServerEndpoint/MTRIMDispatch.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRIMDispatch.mm
new file mode 100644
index 0000000..a1f1f47
--- /dev/null
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRIMDispatch.mm
@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) 2024 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.
+ */
+
+#include <app-common/zap-generated/callback.h>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app/CommandHandler.h>
+#include <app/ConcreteCommandPath.h>
+#include <app/att-storage.h>
+#include <app/util/af-enums.h>
+#include <app/util/af-types.h>
+#include <app/util/privilege-storage.h>
+#include <lib/core/Optional.h>
+#include <lib/core/TLVReader.h>
+#include <platform/LockTracker.h>
+#include <protocols/interaction_model/StatusCode.h>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::app::Clusters;
+
+void emberAfClusterInitCallback(EndpointId endpoint, ClusterId clusterId)
+{
+ assertChipStackLockedByCurrentThread();
+
+ // No-op: Descriptor and OTA do not need this, and our client-defined
+ // clusters dont use it.
+}
+
+EmberAfStatus emAfWriteAttributeExternal(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
+ EmberAfAttributeType dataType)
+{
+ assertChipStackLockedByCurrentThread();
+
+ // All of our attributes are handled via AttributeAccessInterface, so this
+ // should be unreached.
+ return EMBER_ZCL_STATUS_UNSUPPORTED_ATTRIBUTE;
+}
+
+namespace chip {
+namespace app {
+
+ void DispatchSingleClusterCommand(const ConcreteCommandPath & aPath, TLV::TLVReader & aReader, CommandHandler * aCommandObj)
+ {
+ // TODO: Consider having MTRServerCluster register a
+ // CommandHandlerInterface for command dispatch. But OTA would need
+ // some special-casing in any case, to call into the existing cluster
+ // implementation.
+ using Protocols::InteractionModel::Status;
+ // This command passed ServerClusterCommandExists so we know it's one of our
+ // supported commands.
+ using namespace OtaSoftwareUpdateProvider::Commands;
+
+ bool wasHandled = false;
+ CHIP_ERROR err = CHIP_NO_ERROR;
+
+ switch (aPath.mCommandId) {
+ case QueryImage::Id: {
+ QueryImage::DecodableType commandData;
+ err = DataModel::Decode(aReader, commandData);
+ if (err == CHIP_NO_ERROR) {
+ wasHandled = emberAfOtaSoftwareUpdateProviderClusterQueryImageCallback(aCommandObj, aPath, commandData);
+ }
+ break;
+ }
+ case ApplyUpdateRequest::Id: {
+ ApplyUpdateRequest::DecodableType commandData;
+ err = DataModel::Decode(aReader, commandData);
+ if (err == CHIP_NO_ERROR) {
+ wasHandled = emberAfOtaSoftwareUpdateProviderClusterApplyUpdateRequestCallback(aCommandObj, aPath, commandData);
+ }
+ break;
+ }
+ case NotifyUpdateApplied::Id: {
+ NotifyUpdateApplied::DecodableType commandData;
+ err = DataModel::Decode(aReader, commandData);
+ if (err == CHIP_NO_ERROR) {
+ wasHandled = emberAfOtaSoftwareUpdateProviderClusterNotifyUpdateAppliedCallback(aCommandObj, aPath, commandData);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (CHIP_NO_ERROR != err || !wasHandled) {
+ aCommandObj->AddStatus(aPath, Status::InvalidCommand);
+ }
+ }
+
+} // namespace app
+} // namespace chip
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAccessControl.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAccessControl.h
new file mode 100644
index 0000000..49d542a
--- /dev/null
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAccessControl.h
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2024 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 <Foundation/Foundation.h>
+#import <Matter/MTRDefines.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Initialize the access control module. Must be called on the Matter task
+ * queue.
+ */
+void InitializeServerAccessControl();
+
+NS_ASSUME_NONNULL_END
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAccessControl.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAccessControl.mm
new file mode 100644
index 0000000..9847165
--- /dev/null
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAccessControl.mm
@@ -0,0 +1,192 @@
+/**
+ * Copyright (c) 2024 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 "MTRServerAccessControl.h"
+
+#import <Matter/MTRAccessGrant.h>
+#import <Matter/MTRBaseDevice.h> // for MTRClusterPath
+#import <Matter/MTRDeviceControllerFactory.h>
+
+#import "MTRDeviceControllerFactory_Internal.h"
+#import "MTRLogging_Internal.h"
+
+#include <access/AccessControl.h>
+#include <access/Privilege.h>
+#include <access/RequestPath.h>
+#include <access/SubjectDescriptor.h>
+#include <app/InteractionModelEngine.h>
+#include <lib/core/CHIPError.h>
+#include <lib/core/Global.h>
+#include <lib/core/NodeId.h>
+
+#include <app/util/privilege-storage.h>
+
+using namespace chip;
+using namespace chip::Access;
+
+namespace {
+
+class DeviceTypeResolver : public Access::AccessControl::DeviceTypeResolver {
+public:
+ bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) override
+ {
+ return app::IsDeviceTypeOnEndpoint(deviceType, endpoint);
+ }
+};
+
+class AccessControlDelegate : public AccessControl::Delegate {
+ CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath,
+ Privilege requestPrivilege) override
+ {
+ auto * clusterPath = [MTRClusterPath clusterPathWithEndpointID:@(requestPath.endpoint) clusterID:@(requestPath.cluster)];
+ auto * grants = [[MTRDeviceControllerFactory sharedInstance] accessGrantsForFabricIndex:subjectDescriptor.fabricIndex clusterPath:clusterPath];
+
+ for (MTRAccessGrant * grant in grants) {
+ if (!GrantSubjectMatchesDescriptor(grant, subjectDescriptor)) {
+ continue;
+ }
+
+ // Check whether the desired privilege is granted. See the Access Control "Overall
+ // Algorithm" section in the spec for which privileges imply which other privileges.
+ switch (grant.grantedPrivilege) {
+ case MTRAccessControlEntryPrivilegeView:
+ if (requestPrivilege == Privilege::kView) {
+ return CHIP_NO_ERROR;
+ }
+ break;
+ case MTRAccessControlEntryPrivilegeProxyView:
+ if (requestPrivilege == Privilege::kView || requestPrivilege == Privilege::kProxyView) {
+ return CHIP_NO_ERROR;
+ }
+ break;
+ case MTRAccessControlEntryPrivilegeOperate:
+ if (requestPrivilege == Privilege::kView || requestPrivilege == Privilege::kOperate) {
+ return CHIP_NO_ERROR;
+ }
+ break;
+ case MTRAccessControlEntryPrivilegeManage:
+ if (requestPrivilege == Privilege::kView || requestPrivilege == Privilege::kOperate || requestPrivilege == Privilege::kManage) {
+ return CHIP_NO_ERROR;
+ }
+ break;
+ case MTRAccessControlEntryPrivilegeAdminister:
+ if (requestPrivilege == Privilege::kView || requestPrivilege == Privilege::kProxyView || requestPrivilege == Privilege::kOperate || requestPrivilege == Privilege::kManage || requestPrivilege == Privilege::kAdminister) {
+ return CHIP_NO_ERROR;
+ }
+ break;
+ default:
+ MTR_LOG_ERROR("Uknown granted privilege %u, ignoring", grant.grantedPrivilege);
+ break;
+ }
+
+ // If this grant did not match, just move on to the next one.
+ }
+
+ // None of the grants matched.
+ return CHIP_ERROR_ACCESS_DENIED;
+ }
+
+ bool GrantSubjectMatchesDescriptor(MTRAccessGrant * grant, const SubjectDescriptor & descriptor)
+ {
+ if (grant.subjectID == nil) {
+ // This is an all-nodes grant for CASE access only.
+ return descriptor.authMode == AuthMode::kCase;
+ }
+
+ NodeId grantSubjectNodeId = grant.subjectID.unsignedLongLongValue;
+ if (IsOperationalNodeId(grantSubjectNodeId)) {
+ return descriptor.authMode == AuthMode::kCase && descriptor.subject == grantSubjectNodeId;
+ }
+
+ if (IsGroupId(grantSubjectNodeId)) {
+ return descriptor.authMode == AuthMode::kGroup && descriptor.subject == grantSubjectNodeId;
+ }
+
+ if (IsCASEAuthTag(grantSubjectNodeId)) {
+ return descriptor.cats.CheckSubjectAgainstCATs(grantSubjectNodeId);
+ }
+
+ MTR_LOG_ERROR("Unexpected grant subject: 0x%llx", grantSubjectNodeId);
+ return false;
+ }
+};
+
+struct ControllerAccessControl {
+ DeviceTypeResolver mDeviceTypeResolver;
+ AccessControlDelegate mDelegate;
+ ControllerAccessControl() { GetAccessControl().Init(&mDelegate, mDeviceTypeResolver); }
+};
+
+Global<ControllerAccessControl> gControllerAccessControl;
+
+} // anonymous namespace
+
+int MatterGetAccessPrivilegeForReadEvent(ClusterId cluster, EventId event)
+{
+ // We don't support any event bits yet.
+ return kMatterAccessPrivilegeAdminister;
+}
+
+int MatterGetAccessPrivilegeForInvokeCommand(ClusterId cluster, CommandId command)
+{
+ // For now we only have OTA, which uses Operate.
+ return kMatterAccessPrivilegeOperate;
+}
+
+int MatterGetAccessPrivilegeForReadAttribute(ClusterId cluster, AttributeId attribute)
+{
+ NSNumber * _Nullable neededPrivilege = [[MTRDeviceControllerFactory sharedInstance] neededReadPrivilegeForClusterID:@(cluster) attributeID:@(attribute)];
+ if (neededPrivilege == nil) {
+ // No privileges declared for this attribute on this cluster. Treat as
+ // "needs admin privileges", so we fail closed.
+ return kMatterAccessPrivilegeAdminister;
+ }
+
+ switch (neededPrivilege.unsignedLongLongValue) {
+ case MTRAccessControlEntryPrivilegeView:
+ return kMatterAccessPrivilegeView;
+ case MTRAccessControlEntryPrivilegeOperate:
+ return kMatterAccessPrivilegeOperate;
+ case MTRAccessControlEntryPrivilegeManage:
+ return kMatterAccessPrivilegeManage;
+ case MTRAccessControlEntryPrivilegeAdminister:
+ return kMatterAccessPrivilegeAdminister;
+ case MTRAccessControlEntryPrivilegeProxyView:
+ // Just treat this as an unknown value; there is no value for this in privilege-storage.
+ FALLTHROUGH;
+ default:
+ break;
+ }
+
+ // To be safe, treat unknown values as "needs admin privileges". That way the failure case
+ // disallows access that maybe should be allowed, instead of allowing access that maybe
+ // should be disallowed.
+ return kMatterAccessPrivilegeAdminister;
+}
+
+int MatterGetAccessPrivilegeForWriteAttribute(ClusterId cluster, AttributeId attribute)
+{
+ // We don't have any writable attributes yet, but default to Operate.
+ return kMatterAccessPrivilegeOperate;
+}
+
+void InitializeServerAccessControl()
+{
+ assertChipStackLockedByCurrentThread();
+
+ // Ensure the access control bits are created. No-op after the first call.
+ gControllerAccessControl.get();
+}
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.mm
index 0337d55..22fd267 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.mm
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.mm
@@ -95,8 +95,13 @@
return NO;
}
for (id item in dataValueList) {
+ if (![item isKindOfClass:NSDictionary.class]) {
+ MTR_LOG_ERROR("MTRServerAttribute value array should contain dictionaries");
+ }
+ NSDictionary<NSString *, id> * itemDictionary = item;
+
NSError * encodingError;
- NSData * encodedItem = MTREncodeTLVFromDataValueDictionary(item, &encodingError);
+ NSData * encodedItem = MTREncodeTLVFromDataValueDictionary(itemDictionary[MTRDataKey], &encodingError);
if (encodedItem == nil) {
return NO;
}
@@ -132,7 +137,7 @@
return YES;
}
-- (BOOL)associateWithController:(MTRDeviceController *)controller
+- (BOOL)associateWithController:(nullable MTRDeviceController *)controller
{
MTRDeviceController * existingController = _deviceController;
if (existingController != nil) {
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute_Internal.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute_Internal.h
index dbc7ceb..f6b423f 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute_Internal.h
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute_Internal.h
@@ -21,15 +21,19 @@
#include <app/ConcreteClusterPath.h>
+NS_ASSUME_NONNULL_BEGIN
+
@interface MTRServerAttribute ()
/**
- * Mark this attribute as associated with a particular controller.
+ * Mark this attribute as associated with a particular controller. The
+ * controller can be nil to indicate that the endpoint is not associated with a
+ * specific controller but rather with the controller factory.
*/
-- (BOOL)associateWithController:(MTRDeviceController *)controller;
+- (BOOL)associateWithController:(nullable MTRDeviceController *)controller;
/**
- * Mark this attribute as part of an Defunct-state endpoint.
+ * Mark this attribute as part of an endpoint that is no longer being used.
*/
- (void)invalidate;
@@ -46,3 +50,5 @@
@property (nonatomic, assign) chip::app::ConcreteClusterPath parentCluster;
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm
index 8af4ca9..8de78a6 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm
@@ -23,12 +23,40 @@
#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>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
+#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>
+#include <protocols/interaction_model/StatusCode.h>
+
+// TODO: These attribute-*.h bits are a hack that should eventually go away.
+#include <app/util/attribute-metadata.h>
+#include <app/util/attribute-storage.h>
using namespace chip;
+using namespace chip::app;
+
+class MTRServerAttributeAccessInterface : public AttributeAccessInterface {
+public:
+ MTRServerAttributeAccessInterface(EndpointId aEndpointID, ClusterId aClusterID, NSArray<MTRServerAttribute *> * aAttributes,
+ NSNumber * aClusterRevision)
+ : AttributeAccessInterface(MakeOptional(aEndpointID), aClusterID)
+ , mAttributes(aAttributes)
+ , mClusterRevision(aClusterRevision)
+ {
+ }
+
+ CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
+
+private:
+ NSArray<MTRServerAttribute *> * mAttributes;
+ NSNumber * mClusterRevision;
+};
MTR_DIRECT_MEMBERS
@implementation MTRServerCluster {
@@ -39,6 +67,15 @@
NSMutableSet<MTRAccessGrant *> * _accessGrants;
NSMutableArray<MTRServerAttribute *> * _attributes;
MTRDeviceController * __weak _deviceController;
+
+ 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.
+ std::vector<EmberAfAttributeMetadata> _matterAttributeMetadata;
+
+ std::unique_ptr<CommandId[]> _matterAcceptedCommandList;
+ std::unique_ptr<CommandId[]> _matterGeneratedCommandList;
}
- (nullable instancetype)initWithClusterID:(NSNumber *)clusterID revision:(NSNumber *)revision
@@ -71,7 +108,7 @@
+ (MTRServerCluster *)newDescriptorCluster
{
- return [[MTRServerCluster alloc] initInternalWithClusterID:@(MTRClusterIDTypeDescriptorID) revision:@(app::Clusters::Descriptor::kClusterRevision) accessGrants:[NSSet set] attributes:@[]];
+ return [[MTRServerCluster alloc] initInternalWithClusterID:@(MTRClusterIDTypeDescriptorID) revision:@(Clusters::Descriptor::kClusterRevision) accessGrants:[NSSet set] attributes:@[]];
}
- (instancetype)initInternalWithClusterID:(NSNumber *)clusterID revision:(NSNumber *)revision accessGrants:(NSSet *)accessGrants attributes:(NSArray *)attributes
@@ -165,11 +202,18 @@
}
[_attributes addObject:attribute];
- attribute.parentCluster = app::ConcreteClusterPath(_parentEndpoint, static_cast<ClusterId>(_clusterID.unsignedLongLongValue));
+ attribute.parentCluster = ConcreteClusterPath(_parentEndpoint, static_cast<ClusterId>(_clusterID.unsignedLongLongValue));
return YES;
}
-- (BOOL)associateWithController:(MTRDeviceController *)controller
+static constexpr EmberAfAttributeMetadata sDescriptorAttributesMetadata[] = {
+ DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributeDeviceTypeListID, ARRAY, 0, 0),
+ DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributeServerListID, ARRAY, 0, 0),
+ DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributeClientListID, ARRAY, 0, 0),
+ DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributePartsListID, ARRAY, 0, 0),
+};
+
+- (BOOL)associateWithController:(nullable MTRDeviceController *)controller
{
MTRDeviceController * existingController = _deviceController;
if (existingController != nil) {
@@ -191,6 +235,84 @@
// Snapshot _matterAccessGrants now; after this point it will only be
// updated on the Matter queue.
_matterAccessGrants = [_accessGrants copy];
+
+ // _attributes shouldn't be able to change anymore, so we can now construct
+ // our EmberAfAttributeMetadata array.
+ size_t attributeCount = _attributes.count;
+
+ // Figure out whether we need to synthesize a FeatureMap attribute.
+ bool needsFeatureMap = true;
+ for (MTRServerAttribute * attr in _attributes) {
+ if ([attr.attributeID isEqual:@(MTRClusterGlobalAttributeFeatureMapID)]) {
+ needsFeatureMap = false;
+ break;
+ }
+ }
+
+ bool needsDescriptorAttributes = [_clusterID isEqual:@(MTRClusterIDTypeDescriptorID)];
+
+ if (needsFeatureMap) {
+ ++attributeCount;
+ }
+
+ if (needsDescriptorAttributes) {
+ attributeCount += ArraySize(sDescriptorAttributesMetadata);
+ }
+
+ // And add one for ClusterRevision
+ ++attributeCount;
+
+ if (attributeCount >= UINT16_MAX) {
+ MTR_LOG_ERROR("Unable to have %llu attributes in a single cluster (clusterID: " ChipLogFormatMEI ")",
+ static_cast<unsigned long long>(attributeCount), ChipLogValueMEI(_clusterID.unsignedLongLongValue));
+ return NO;
+ }
+
+ size_t attrIndex = 0;
+ for (; attrIndex < _attributes.count; ++attrIndex) {
+ auto * attr = _attributes[attrIndex];
+ _matterAttributeMetadata.emplace_back(EmberAfAttributeMetadata(DECLARE_DYNAMIC_ATTRIBUTE(static_cast<AttributeId>(attr.attributeID.unsignedLongLongValue),
+ // The type does not actually matter, since we plan to
+ // handle this entirely via AttributeAccessInterface.
+ // Claim Array because that one will keep random IM
+ // code from trying to do things with the attribute
+ // store.
+ ARRAY,
+ // Size in bytes does not matter, since we plan to
+ // handle this entirely via AttributeAccessInterface.
+ 0,
+ // ATTRIBUTE_MASK_NULLABLE is not relevant because we
+ // are handling this all via AttributeAccessInterface.
+ 0)));
+ }
+
+ if (needsFeatureMap) {
+ _matterAttributeMetadata.emplace_back(EmberAfAttributeMetadata(DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeGlobalAttributeFeatureMapID,
+ BITMAP32, 4, 0)));
+ ++attrIndex;
+ }
+
+ if (needsDescriptorAttributes) {
+ for (auto & data : sDescriptorAttributesMetadata) {
+ _matterAttributeMetadata.emplace_back(data);
+ ++attrIndex;
+ }
+ }
+
+ // Add our ClusterRevision bit.
+ _matterAttributeMetadata.emplace_back(EmberAfAttributeMetadata(DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeGlobalAttributeClusterRevisionID,
+ INT16U, 2, 0)));
+ ++attrIndex;
+
+ _attributeAccessInterface = std::make_unique<MTRServerAttributeAccessInterface>(_parentEndpoint,
+ static_cast<ClusterId>(_clusterID.unsignedLongLongValue),
+ _attributes,
+ _clusterRevision);
+ // _attributeAccessInterface needs to be registered on the Matter queue; that will happen later.
+
+ _matterAcceptedCommandList = [MTRServerCluster makeMatterCommandList:_acceptedCommands];
+ _matterGeneratedCommandList = [MTRServerCluster makeMatterCommandList:_generatedCommands];
+
_deviceController = controller;
return YES;
@@ -198,13 +320,44 @@
- (void)invalidate
{
+ // Undo any work associateWithController did.
for (MTRServerAttribute * attr in _attributes) {
[attr invalidate];
}
+ // We generally promise to only touch _matterAccessGrants on the Matter
+ // queue after associateWithController succeeds, but we are no longer being
+ // looked at from that queue, so it's safe to reset it here.
+ _matterAccessGrants = [NSSet set];
+ _matterAttributeMetadata.clear();
+ _attributeAccessInterface.reset();
+ _matterAcceptedCommandList.reset();
+ _matterGeneratedCommandList.reset();
+
_deviceController = nil;
}
+- (void)registerMatterCluster
+{
+ assertChipStackLockedByCurrentThread();
+
+ if (!registerAttributeAccessOverride(_attributeAccessInterface.get())) {
+ // This should only happen if we somehow managed to register an
+ // AttributeAccessInterface for the same (endpoint, cluster) pair.
+ MTR_LOG_ERROR("Could not register AttributeAccessInterface for endpoint %u, cluster 0x%llx",
+ _parentEndpoint, _clusterID.unsignedLongLongValue);
+ }
+}
+
+- (void)unregisterMatterCluster
+{
+ assertChipStackLockedByCurrentThread();
+
+ if (_attributeAccessInterface != nullptr) {
+ unregisterAttributeAccessOverride(_attributeAccessInterface.get());
+ }
+}
+
- (NSArray<MTRAccessGrant *> *)accessGrants
{
return [_accessGrants allObjects];
@@ -221,8 +374,87 @@
// 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.parentCluster = app::ConcreteClusterPath(endpoint, static_cast<ClusterId>(_clusterID.unsignedLongLongValue));
+ attr.parentCluster = ConcreteClusterPath(endpoint, static_cast<ClusterId>(_clusterID.unsignedLongLongValue));
}
}
+- (Span<const EmberAfAttributeMetadata>)matterAttributeMetadata
+{
+ // This is always called after our _matterAttributeMetadata has been set up
+ // by associateWithController.
+ return Span<const EmberAfAttributeMetadata>(_matterAttributeMetadata.data(), _matterAttributeMetadata.size());
+}
+
+- (CommandId *)matterAcceptedCommands
+{
+ return _matterAcceptedCommandList.get();
+}
+
+- (CommandId *)matterGeneratedCommands
+{
+ return _matterGeneratedCommandList.get();
+}
+
++ (std::unique_ptr<CommandId[]>)makeMatterCommandList:(NSArray<NSNumber *> * _Nullable)commandList
+{
+ if (commandList.count == 0) {
+ return nullptr;
+ }
+
+ // Lists of accepted/generated commands are terminated by kInvalidClusterId.
+ auto matterCommandList = std::make_unique<CommandId[]>(commandList.count + 1);
+ for (size_t index = 0; index < commandList.count; ++index) {
+ matterCommandList[index] = static_cast<CommandId>(commandList[index].unsignedLongLongValue);
+ }
+ matterCommandList[commandList.count] = kInvalidClusterId;
+ return matterCommandList;
+}
+
@end
+
+CHIP_ERROR MTRServerAttributeAccessInterface::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
+{
+ using DataModel::PreEncodedValue;
+
+ // Find the right attribute in our list.
+ MTRServerAttribute * foundAttr = nil;
+ for (MTRServerAttribute * attr in mAttributes) {
+ if ([attr.attributeID isEqual:@(aPath.mAttributeId)]) {
+ foundAttr = attr;
+ break;
+ }
+ }
+
+ if (foundAttr) {
+ id value = foundAttr.serializedValue;
+ if (![value isKindOfClass:NSArray.class]) {
+ // It's a single value, so NSData.
+ NSData * data = value;
+ return aEncoder.Encode(PreEncodedValue(AsByteSpan(data)));
+ }
+
+ // It's a list of data values.
+ NSArray<NSData *> * dataList = value;
+ return aEncoder.EncodeList([dataList](const auto & itemEncoder) {
+ for (NSData * item in dataList) {
+ ReturnErrorOnFailure(itemEncoder.Encode(PreEncodedValue(AsByteSpan(item))));
+ }
+ return CHIP_NO_ERROR;
+ });
+ }
+
+ // This must be the FeatureMap attribute we synthesized.
+ if (aPath.mAttributeId == MTRAttributeIDTypeGlobalAttributeFeatureMapID) {
+ // Feature map defaults to 0.
+ constexpr uint32_t defaultFeatureMap = 0;
+ return aEncoder.Encode(defaultFeatureMap);
+ }
+
+ if (aPath.mAttributeId == MTRAttributeIDTypeGlobalAttributeClusterRevisionID) {
+ return aEncoder.Encode(mClusterRevision.unsignedLongLongValue);
+ }
+
+ // Note: This code is not reached for the descriptor cluster, which uses its own AttributeAccessInterface.
+
+ return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
+}
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h
index 5bd9ba0..4ae3d0d 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h
@@ -21,15 +21,38 @@
#include <lib/core/DataModelTypes.h>
+// TODO: These attribute-*.h and Span bits are a hack that should eventually go away.
+#include <app/util/attribute-metadata.h>
+#include <lib/support/Span.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
@interface MTRServerCluster ()
/**
- * Mark this cluster as associated with a particular controller.
+ * Mark this cluster as associated with a particular controller. The
+ * controller can be nil to indicate that the endpoint is not associated with a
+ * specific controller but rather with the controller factory. This method does
+ * NOT perform any cleanup on failure; it's the caller's responsibility to call
+ * invalidate if it fails.
*/
-- (BOOL)associateWithController:(MTRDeviceController *)controller;
+- (BOOL)associateWithController:(nullable MTRDeviceController *)controller;
/**
- * Mark this cluster as part of an Defunct-state endpoint.
+ * Register this cluster. Always called on the Matter queue.
+ */
+- (void)registerMatterCluster;
+
+/**
+ * Unregister this cluster. Always called on the Matter queue.
+ */
+- (void)unregisterMatterCluster;
+
+/**
+ * Mark this cluster as part of an endpoint that is no longer being used. Can
+ * run on any thread, but will either be called before registerMatterCluster or
+ * after unregisterMatterCluster. This undoes anything associateWithController
+ * did.
*/
- (void)invalidate;
@@ -44,4 +67,31 @@
*/
@property (nonatomic, assign) chip::EndpointId parentEndpoint;
+/**
+ * The attribute metadata for the cluster. Only valid after associateWithController: has succeeded.
+ */
+@property (nonatomic, assign, readonly) chip::Span<const EmberAfAttributeMetadata> matterAttributeMetadata;
+
+/**
+ * The list of accepted command IDs.
+ */
+@property (nonatomic, copy, nullable) NSArray<NSNumber *> * acceptedCommands;
+
+/**
+ * The list of generated command IDs.
+ */
+@property (nonatomic, 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;
+
+/**
+ * The list of generated commands IDs in the format the Matter stack needs.
+ */
+@property (nonatomic, assign, nullable, readonly) chip::CommandId * matterGeneratedCommands;
+
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.h
index 642dc5fb..13dbe84 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.h
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.h
@@ -77,8 +77,9 @@
/**
* A list of server clusters supported on this endpoint. The Descriptor cluster
* does not need to be included unless a TagList attribute is desired on it or
- * unless it has a non-empty PartsList. If not included, the Descriptor cluster
- * will be generated automatically.
+ * it has a non-empty PartsList, or it needs to have cluster-specific access
+ * grants. If not included, the Descriptor cluster will be generated
+ * automatically.
*/
@property (nonatomic, copy, readonly) NSArray<MTRServerCluster *> * serverClusters;
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm
index ee35e4d..5e6df86 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm
@@ -19,11 +19,22 @@
#import "MTRLogging_Internal.h"
#import "MTRServerCluster_Internal.h"
#import "MTRServerEndpoint_Internal.h"
+#import <Matter/MTRClusterConstants.h>
#import <Matter/MTRServerEndpoint.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
#include <lib/support/SafeInt.h>
+#include <platform/LockTracker.h>
+
+// TODO: These af-types.h and att-storage.h and attribute-storage.h and
+// endpoint-config-api.h and probably CodeUtils.h bits are a hack that should
+// eventually go away.
+#include <app/att-storage.h>
+#include <app/util/af-types.h>
+#include <app/util/attribute-storage.h>
+#include <app/util/endpoint-config-api.h>
+#include <lib/support/CodeUtils.h>
using namespace chip;
@@ -36,6 +47,13 @@
NSMutableSet<MTRAccessGrant *> * _accessGrants;
NSMutableArray<MTRServerCluster *> * _serverClusters;
MTRDeviceController * __weak _deviceController;
+ std::unique_ptr<EmberAfCluster[]> _matterClusterMetadata;
+ EmberAfEndpointType _matterEndpointMetadata;
+ std::unique_ptr<EmberAfDeviceType[]> _matterDeviceTypes;
+ std::unique_ptr<DataVersion[]> _matterDataVersions;
+
+ // _endpointIndex has a value only when we have the endpoint configured.
+ std::optional<uint16_t> _endpointIndex;
}
- (nullable instancetype)initWithEndpointID:(NSNumber *)endpointID deviceTypes:(NSArray<MTRDeviceTypeRevision *> *)deviceTypes
@@ -67,6 +85,11 @@
return [self initInternalWithEndpointID:endpointID deviceTypes:deviceTypes accessGrants:[NSSet set] clusters:@[]];
}
++ (MTRServerEndpoint *)rootNodeEndpoint
+{
+ return [[MTRServerEndpoint alloc] initInternalWithEndpointID:@(kRootEndpointId) deviceTypes:@[] accessGrants:[NSSet set] clusters:@[]];
+}
+
- (instancetype)initInternalWithEndpointID:(NSNumber *)endpointID deviceTypes:(NSArray<MTRDeviceTypeRevision *> *)deviceTypes accessGrants:(NSSet *)accessGrants clusters:(NSArray *)clusters
{
if (!(self = [super init])) {
@@ -148,7 +171,21 @@
return YES;
}
-- (BOOL)associateWithController:(MTRDeviceController *)controller
+#define MTR_DECLARE_LIST_ATTRIBUTE(attrID) \
+ DECLARE_DYNAMIC_ATTRIBUTE(attrID, ARRAY, 0, 0)
+
+static constexpr EmberAfAttributeMetadata sDescriptorAttributesMetadata[] = {
+ DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributeDeviceTypeListID, ARRAY, 0, 0),
+ DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributeServerListID, ARRAY, 0, 0),
+ DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributeClientListID, ARRAY, 0, 0),
+ DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributePartsListID, ARRAY, 0, 0),
+ DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeGlobalAttributeFeatureMapID, BITMAP32, 4, 0),
+ DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeGlobalAttributeClusterRevisionID, INT16U, 2, 0),
+};
+
+#undef MTR_DECLARE_LIST_ATTRIBUTE
+
+- (BOOL)associateWithController:(nullable MTRDeviceController *)controller
{
MTRDeviceController * existingController = _deviceController;
if (existingController != nil) {
@@ -161,6 +198,17 @@
return NO;
}
+ // After this point we have to make sure we clean up on any failures.
+ if (![self finishAssociationWithController:controller]) {
+ [self invalidate];
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)finishAssociationWithController:(nullable MTRDeviceController *)controller
+{
for (MTRServerCluster * cluster in _serverClusters) {
if (![cluster associateWithController:controller]) {
return NO;
@@ -170,20 +218,193 @@
// Snapshot _matterAccessGrants now; after this point it will only be
// updated on the Matter queue.
_matterAccessGrants = [_accessGrants copy];
+
+ // _serverClusters shouldn't be able to change anymore, so we can now
+ // construct our EmberAfCluster array.
+ size_t clusterCount = _serverClusters.count;
+
+ // Figure out whether we need to synthesize a Descriptor cluster.
+ bool needsDescriptor = true;
+ for (MTRServerCluster * cluster in _serverClusters) {
+ if ([cluster.clusterID isEqual:@(MTRClusterIDTypeDescriptorID)]) {
+ needsDescriptor = false;
+ break;
+ }
+ }
+
+ if (needsDescriptor) {
+ ++clusterCount;
+ }
+
+ if (clusterCount >= 0xFF) {
+ // The ember bits don't allow this many clusters (they use 0xFF to mean
+ // "no such cluster" in various places.
+ MTR_LOG_ERROR("Unable to create endpoint with %llu clusters; it's too many",
+ static_cast<unsigned long long>(clusterCount));
+ return NO;
+ }
+
+ _matterClusterMetadata = std::make_unique<EmberAfCluster[]>(clusterCount);
+ // std::make_unique never returns null; it will try to throw an exception
+ // and likely crash on OOM.
+
+ size_t clusterIndex = 0;
+ for (; clusterIndex < _serverClusters.count; ++clusterIndex) {
+ auto * cluster = _serverClusters[clusterIndex];
+ auto & metadata = _matterClusterMetadata[clusterIndex];
+
+ metadata.clusterId = static_cast<ClusterId>(cluster.clusterID.unsignedLongLongValue);
+
+ auto attrMetadata = cluster.matterAttributeMetadata;
+ metadata.attributes = attrMetadata.data();
+ // This cast is safe because clusters check for this constraint on
+ // number of attributes.
+ metadata.attributeCount = static_cast<uint16_t>(attrMetadata.size());
+
+ metadata.clusterSize = 0; // All our attributes are external.
+
+ metadata.mask = CLUSTER_MASK_SERVER;
+
+ metadata.functions = nullptr; // None of our clusters, including Descriptor, uses these.
+
+ metadata.acceptedCommandList = cluster.matterAcceptedCommands;
+ metadata.generatedCommandList = cluster.matterGeneratedCommands;
+
+ metadata.eventList = nullptr;
+ metadata.eventCount = 0;
+ }
+
+ if (needsDescriptor) {
+ auto & metadata = _matterClusterMetadata[clusterIndex];
+
+ metadata.clusterId = MTRClusterIDTypeDescriptorID;
+
+ metadata.attributes = sDescriptorAttributesMetadata;
+ metadata.attributeCount = ArraySize(sDescriptorAttributesMetadata);
+
+ metadata.clusterSize = 0; // All our attributes are external.
+
+ metadata.mask = CLUSTER_MASK_SERVER;
+
+ metadata.functions = nullptr; // Descriptor does not use these.
+
+ metadata.acceptedCommandList = nullptr;
+ metadata.generatedCommandList = nullptr;
+
+ metadata.eventList = nullptr;
+ metadata.eventCount = 0;
+
+ ++clusterIndex;
+ }
+
+ _matterEndpointMetadata.cluster = _matterClusterMetadata.get();
+ // Cast is safe, because we did a range check above.
+ _matterEndpointMetadata.clusterCount = static_cast<decltype(_matterEndpointMetadata.clusterCount)>(clusterCount);
+ _matterEndpointMetadata.endpointSize = 0; // All our attributes are external.
+
+ _matterDeviceTypes = std::make_unique<EmberAfDeviceType[]>(_deviceTypes.count);
+ for (size_t index = 0; index < _deviceTypes.count; ++index) {
+ auto * deviceType = _deviceTypes[index];
+ auto & matterType = _matterDeviceTypes[index];
+
+ matterType.deviceId = static_cast<DeviceTypeId>(deviceType.deviceTypeID.unsignedLongLongValue);
+ // TODO: The spec allows 16-bit revisions, but the Ember bits only
+ // support 8-bit....
+ matterType.deviceVersion = static_cast<uint8_t>(deviceType.deviceTypeRevision.unsignedLongLongValue);
+ }
+
+ _matterDataVersions = std::make_unique<DataVersion[]>(clusterCount);
+
_deviceController = controller;
return YES;
}
+- (void)registerMatterEndpoint
+{
+ assertChipStackLockedByCurrentThread();
+
+ static_assert(FIXED_ENDPOINT_COUNT == 0, "Indexing will be off");
+
+ // We can't use emberAfEndpointCount here, because that returns just the
+ // count of fixed endpoints up until the first call to
+ // emberAfSetDynamicEndpoint().
+ uint16_t possibleEndpointCount = MAX_ENDPOINT_COUNT;
+ uint16_t index = 0;
+ for (; index < possibleEndpointCount; ++index) {
+ if (emberAfEndpointFromIndex(index) == kInvalidEndpointId) {
+ break;
+ }
+ }
+
+ if (index == possibleEndpointCount) {
+ // Something is very broken. We shouldn't have this many endpoints!
+ MTR_LOG_ERROR("We somehow ran out of endpoint slots.");
+ return;
+ }
+
+ auto status = emberAfSetDynamicEndpoint(index, static_cast<EndpointId>(_endpointID.unsignedLongLongValue),
+ &_matterEndpointMetadata,
+ Span<DataVersion>(_matterDataVersions.get(), _matterEndpointMetadata.clusterCount),
+ Span<EmberAfDeviceType>(_matterDeviceTypes.get(), _deviceTypes.count));
+ if (status != EMBER_ZCL_STATUS_SUCCESS) {
+ MTR_LOG_ERROR("Unexpected failure to define our Matter endpoint");
+ }
+
+ _endpointIndex.emplace(index);
+
+ for (MTRServerCluster * cluster in _serverClusters) {
+ [cluster registerMatterCluster];
+ }
+}
+
+- (void)unregisterMatterEndpoint
+{
+ assertChipStackLockedByCurrentThread();
+
+ if (_endpointIndex.has_value()) {
+ emberAfClearDynamicEndpoint(_endpointIndex.value());
+ _endpointIndex.reset();
+ }
+
+ for (MTRServerCluster * cluster in _serverClusters) {
+ [cluster unregisterMatterCluster];
+ }
+}
+
- (void)invalidate
{
+ // Undo any work associateWithController did.
for (MTRServerCluster * cluster in _serverClusters) {
[cluster invalidate];
}
+ // We generally promise to only touch _matterAccessGrants on the Matter
+ // queue after associateWithController succeeds, but we are no longer being
+ // looked at from that queue, so it's safe to reset it here.
+ _matterAccessGrants = [NSSet set];
+ _matterEndpointMetadata.cluster = nullptr;
+ _matterEndpointMetadata.clusterCount = 0;
+ _matterClusterMetadata.reset();
+ _matterDeviceTypes.reset();
+ _matterDataVersions.reset();
_deviceController = nil;
}
+- (NSArray<MTRAccessGrant *> *)matterAccessGrantsForCluster:(NSNumber *)clusterID
+{
+ assertChipStackLockedByCurrentThread();
+
+ NSMutableArray<MTRAccessGrant *> * grants = [[_matterAccessGrants allObjects] mutableCopy];
+ for (MTRServerCluster * cluster in _serverClusters) {
+ if ([cluster.clusterID isEqual:clusterID]) {
+ [grants addObjectsFromArray:[cluster.matterAccessGrants allObjects]];
+ }
+ }
+
+ return [grants copy];
+}
+
- (NSArray<MTRAccessGrant *> *)accessGrants
{
return [_accessGrants allObjects];
diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint_Internal.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint_Internal.h
index 97725c2..1ca4d4d 100644
--- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint_Internal.h
+++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint_Internal.h
@@ -18,22 +18,55 @@
#import <Matter/MTRDeviceController.h>
#import <Matter/MTRServerEndpoint.h>
+NS_ASSUME_NONNULL_BEGIN
+
@interface MTRServerEndpoint ()
/**
- * Mark this endpoint as associated with a particular controller.
+ * Mark this endpoint as associated with a particular controller. The
+ * controller can be nil to indicate that the endpoint is not associated with a
+ * specific controller but rather with the controller factory.
+ *
+ * On failure, this method ensures that it undoes any state changes it made.
*/
-- (BOOL)associateWithController:(MTRDeviceController *)controller;
+- (BOOL)associateWithController:(nullable MTRDeviceController *)controller;
/**
- * Mark this endpoint as being in a Defunct state.
+ * Register this endpoint. Always called on the Matter queue.
+ */
+- (void)registerMatterEndpoint;
+
+/**
+ * Unregister this endpoint. Always called on the Matter queue.
+ */
+- (void)unregisterMatterEndpoint;
+
+/**
+ * Mark this endpoint as no longer being in use. Can run on any thread, but
+ * will either be called before registerMatterEndpoint or after
+ * unregisterMatterEndpoint. This undoes anything associateWithController did.
*/
- (void)invalidate;
/**
+ * Get an MTRServerEndpoint for the root node endpoint. This can't be done via
+ * the public initializer, since we don't allow that to create an
+ * MTRServerEndpoint for endpoint 0.
+ */
++ (MTRServerEndpoint *)rootNodeEndpoint;
+
+/**
+ * Returns the list of access grants applicable to the given cluster ID on this
+ * endpoint. Only called on the Matter queue.
+ */
+- (NSArray<MTRAccessGrant *> *)matterAccessGrantsForCluster:(NSNumber *)clusterID;
+
+/**
* The access grants the Matter stack can observe. Only modified while in
* Initializing state or on the Matter queue.
*/
@property (nonatomic, strong, readonly) NSSet<MTRAccessGrant *> * matterAccessGrants;
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/darwin/Framework/CHIP/app/PluginApplicationCallbacks.h b/src/darwin/Framework/CHIP/app/PluginApplicationCallbacks.h
new file mode 100644
index 0000000..f21f1e1
--- /dev/null
+++ b/src/darwin/Framework/CHIP/app/PluginApplicationCallbacks.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2024 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.
+ */
+
+/**
+ * This file is only here to satisfy includes in core files that expect an
+ * app/PluginApplicationCallbacks.h. This file is NOT generated by ZAP, and
+ * provides the bare minimum information needed to allow things to compile.
+ *
+ * TODO: This needs a better setup.
+ */
+
+void MatterDescriptorPluginServerInitCallback();
+
+#define MATTER_PLUGINS_INIT MatterDescriptorPluginServerInitCallback();
diff --git a/src/darwin/Framework/CHIP/zap-generated/endpoint_config.h b/src/darwin/Framework/CHIP/zap-generated/endpoint_config.h
new file mode 100644
index 0000000..27cfbdb
--- /dev/null
+++ b/src/darwin/Framework/CHIP/zap-generated/endpoint_config.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2024 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.
+ */
+
+/**
+ * This file is only here to satisfy includes in core files that expect an
+ * endpoint config. This file is NOT generated by ZAP, and provides the bare
+ * minimum information needed to allow things to compile.
+ *
+ * TO DO: This needs a better setup.
+ */
+#include <app/util/endpoint-config-defines.h>
+
+/**
+ * We don't have any fixed endpoints.
+ */
+#define FIXED_ENDPOINT_COUNT 0
+
+/**
+ * We don't have any attributes not implemented by AttributeAccessInterface. But
+ * using 0 here does not work, so just claim 1.
+ */
+#define ATTRIBUTE_LARGEST 1
+
+#define GENERATED_ATTRIBUTES {}
+
+#define GENERATED_ENDPOINT_TYPES {}
+
+#define FIXED_DEVICE_TYPES {}
+
+#define ZAP_FIXED_ENDPOINT_DATA_VERSION_COUNT 0
+
+#define FIXED_ENDPOINT_ARRAY {}
+
+#define FIXED_DEVICE_TYPE_LENGTHS {}
+
+#define FIXED_DEVICE_TYPE_OFFSETS {}
+
+#define FIXED_ENDPOINT_TYPES {}
diff --git a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m
index 8bbb529..effce2e 100644
--- a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m
+++ b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m
@@ -32,6 +32,12 @@
static NSString * kOnboardingPayload = @"MT:-24J0AFN00KA0648G00";
static const uint16_t kTestVendorId = 0xFFF1u;
+#ifdef DEBUG
+@interface MTRDeviceController (Test)
++ (void)forceLocalhostAdvertisingOnly;
+@end
+#endif // DEBUG
+
@interface MTRPerControllerStorageTestsControllerDelegate : NSObject <MTRDeviceControllerDelegate>
@property (nonatomic, strong) XCTestExpectation * expectation;
@property (nonatomic, strong) NSNumber * deviceID;
@@ -270,6 +276,9 @@
intermediateCertificate:nil
rootCertificate:root];
XCTAssertNotNil(params);
+ // TODO: This is only used by testControllerServer. If that moves
+ // elsewhere, take this back out again.
+ params.shouldAdvertiseOperational = YES;
__auto_type * ourCertificateIssuer = [[MTRPerControllerStorageTestsCertificateIssuer alloc] initWithRootCertificate:root
intermediateCertificate:nil
@@ -1035,6 +1044,471 @@
XCTAssertFalse([controller3 isRunning]);
}
+// TODO: This might want to go in a separate test file, with some shared setup
+// across multiple tests, maybe. Would need to factor out
+// startControllerWithRootKeys into a test helper.
+- (void)testControllerServer
+{
+#ifdef DEBUG
+ // Force our controllers to only advertise on localhost, to avoid DNS-SD
+ // crosstalk.
+ [MTRDeviceController forceLocalhostAdvertisingOnly];
+#endif // DEBUG
+
+ __auto_type queue = dispatch_get_main_queue();
+
+ __auto_type * rootKeys = [[MTRTestKeys alloc] init];
+ XCTAssertNotNil(rootKeys);
+
+ NSNumber * fabricID = @(456);
+
+ __auto_type * operationalKeysServer = [[MTRTestKeys alloc] init];
+ XCTAssertNotNil(operationalKeysServer);
+
+ __auto_type * storageDelegateServer = [[MTRTestPerControllerStorage alloc] initWithControllerID:[NSUUID UUID]];
+ XCTAssertNotNil(storageDelegateServer);
+
+ NSNumber * nodeIDServer = @(123);
+
+ NSError * error;
+ MTRDeviceController * controllerServer = [self startControllerWithRootKeys:rootKeys
+ operationalKeys:operationalKeysServer
+ fabricID:fabricID
+ nodeID:nodeIDServer
+ storage:storageDelegateServer
+ error:&error];
+ XCTAssertNil(error);
+ XCTAssertNotNil(controllerServer);
+ XCTAssertTrue([controllerServer isRunning]);
+ XCTAssertEqualObjects(controllerServer.controllerNodeID, nodeIDServer);
+
+ __auto_type * endpointId1 = @(10);
+ __auto_type * endpointId2 = @(20);
+ __auto_type * endpointId3 = @(30);
+ __auto_type * clusterId1 = @(0xFFF1FC02);
+ __auto_type * clusterId2 = @(0xFFF1FC10);
+ __auto_type * clusterRevision1 = @(3);
+ __auto_type * clusterRevision2 = @(4);
+ __auto_type * attributeId1 = @(0);
+ __auto_type * attributeId2 = @(0xFFF10002);
+
+ __auto_type * unsignedIntValue1 = @{
+ MTRTypeKey : MTRUnsignedIntegerValueType,
+ MTRValueKey : @(5),
+ };
+
+ __auto_type * unsignedIntValue2 = @{
+ MTRTypeKey : MTRUnsignedIntegerValueType,
+ MTRValueKey : @(7),
+ };
+
+ __auto_type * structValue1 = @{
+ MTRTypeKey : MTRStructureValueType,
+ MTRValueKey : @[
+ @{
+ MTRContextTagKey : @(1),
+ MTRDataKey : @ {
+ MTRTypeKey : MTRUnsignedIntegerValueType,
+ MTRValueKey : @(1),
+ },
+ },
+ @{
+ MTRContextTagKey : @(2),
+ MTRDataKey : @ {
+ MTRTypeKey : MTRUTF8StringValueType,
+ MTRValueKey : @"struct1",
+ },
+ },
+ ],
+ };
+
+ __auto_type * structValue2 = @{
+ MTRTypeKey : MTRStructureValueType,
+ MTRValueKey : @[
+ @{
+ MTRContextTagKey : @(1),
+ MTRDataKey : @ {
+ MTRTypeKey : MTRUnsignedIntegerValueType,
+ MTRValueKey : @(2),
+ },
+ },
+ @{
+ MTRContextTagKey : @(2),
+ MTRDataKey : @ {
+ MTRTypeKey : MTRUTF8StringValueType,
+ MTRValueKey : @"struct2",
+ },
+ },
+ ],
+ };
+
+ __auto_type * listOfStructsValue1 = @{
+ MTRTypeKey : MTRArrayValueType,
+ MTRValueKey : @[
+ @{
+ MTRDataKey : structValue1,
+ },
+ @{
+ MTRDataKey : structValue2,
+ },
+ ],
+ };
+
+#if 0
+ __auto_type * listOfStructsValue2 = @{
+ MTRTypeKey : MTRArrayValueType,
+ MTRValueKey : @[
+ @{ MTRDataKey: structValue2, },
+ ],
+ };
+#endif
+
+ __auto_type responsePathFromRequestPath = ^(MTRAttributeRequestPath * path) {
+ return [MTRAttributePath attributePathWithEndpointID:path.endpoint clusterID:path.cluster attributeID:path.attribute];
+ };
+
+ // Set up an endpoint on the server.
+ __auto_type * deviceType1 = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@(0xFFF10001) revision:@(1)];
+ XCTAssertNotNil(deviceType1);
+
+ __auto_type * endpoint1 = [[MTRServerEndpoint alloc] initWithEndpointID:endpointId1 deviceTypes:@[ deviceType1 ]];
+ XCTAssertNotNil(endpoint1);
+
+ __auto_type * cluster1 = [[MTRServerCluster alloc] initWithClusterID:clusterId1 revision:clusterRevision1];
+ XCTAssertNotNil(cluster1);
+
+ __auto_type * cluster2 = [[MTRServerCluster alloc] initWithClusterID:clusterId2 revision:clusterRevision2];
+ XCTAssertNotNil(cluster1);
+
+ __auto_type * attribute1 = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:attributeId1 initialValue:unsignedIntValue1 requiredPrivilege:MTRAccessControlEntryPrivilegeView];
+ XCTAssertNotNil(attribute1);
+ __auto_type * attribute1RequestPath = [MTRAttributeRequestPath requestPathWithEndpointID:endpointId1
+ clusterID:clusterId1
+ attributeID:attributeId1];
+ XCTAssertNotNil(attribute1RequestPath);
+ __auto_type * attribute1ResponsePath = responsePathFromRequestPath(attribute1RequestPath);
+ XCTAssertNotNil(attribute1ResponsePath);
+
+ __auto_type * attribute2 = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:attributeId2 initialValue:listOfStructsValue1 requiredPrivilege:MTRAccessControlEntryPrivilegeManage];
+ XCTAssertNotNil(attribute2);
+ __auto_type * attribute2RequestPath = [MTRAttributeRequestPath requestPathWithEndpointID:endpointId1
+ clusterID:clusterId2
+ attributeID:attributeId2];
+ XCTAssertNotNil(attribute2RequestPath);
+ __auto_type * attribute2ResponsePath = responsePathFromRequestPath(attribute2RequestPath);
+ XCTAssertNotNil(attribute2ResponsePath);
+
+ __auto_type * attribute3 = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:attributeId2 initialValue:unsignedIntValue1 requiredPrivilege:MTRAccessControlEntryPrivilegeOperate];
+ XCTAssertNotNil(attribute3);
+ __auto_type * attribute3RequestPath = [MTRAttributeRequestPath requestPathWithEndpointID:endpointId1
+ clusterID:clusterId1
+ attributeID:attributeId2];
+ XCTAssertNotNil(attribute3RequestPath);
+ __auto_type * attribute3ResponsePath = responsePathFromRequestPath(attribute3RequestPath);
+ XCTAssertNotNil(attribute3ResponsePath);
+
+ XCTAssertTrue([cluster1 addAttribute:attribute1]);
+ XCTAssertTrue([cluster1 addAttribute:attribute3]);
+
+ XCTAssertTrue([cluster2 addAttribute:attribute2]);
+
+ XCTAssertTrue([endpoint1 addServerCluster:cluster1]);
+ XCTAssertTrue([endpoint1 addServerCluster:cluster2]);
+
+ [endpoint1 addAccessGrant:[MTRAccessGrant accessGrantForAllNodesWithPrivilege:MTRAccessControlEntryPrivilegeView]];
+
+ XCTAssertTrue([controllerServer addServerEndpoint:endpoint1]);
+
+ __auto_type * endpoint2 = [[MTRServerEndpoint alloc] initWithEndpointID:endpointId2 deviceTypes:@[ deviceType1 ]];
+ XCTAssertNotNil(endpoint2);
+ // Should be able to add this endpoint as well.
+ XCTAssertTrue([controllerServer addServerEndpoint:endpoint2]);
+
+ __auto_type * endpoint3 = [[MTRServerEndpoint alloc] initWithEndpointID:endpointId2 deviceTypes:@[ deviceType1 ]];
+ XCTAssertNotNil(endpoint3);
+ // Should not be able to add this endpoint, since it's got a duplicate
+ // endpoint id.
+ XCTAssertFalse([controllerServer addServerEndpoint:endpoint3]);
+
+ __auto_type * operationalKeysClient = [[MTRTestKeys alloc] init];
+ XCTAssertNotNil(operationalKeysClient);
+
+ __auto_type * storageDelegateClient = [[MTRTestPerControllerStorage alloc] initWithControllerID:[NSUUID UUID]];
+ XCTAssertNotNil(storageDelegateClient);
+
+ NSNumber * nodeIDClient = @(789);
+
+ MTRDeviceController * controllerClient = [self startControllerWithRootKeys:rootKeys
+ operationalKeys:operationalKeysClient
+ fabricID:fabricID
+ nodeID:nodeIDClient
+ storage:storageDelegateClient
+ error:&error];
+ XCTAssertNil(error);
+ XCTAssertNotNil(controllerClient);
+ XCTAssertTrue([controllerClient isRunning]);
+ XCTAssertEqualObjects(controllerClient.controllerNodeID, nodeIDClient);
+
+ __auto_type * endpoint4 = [[MTRServerEndpoint alloc] initWithEndpointID:endpointId2 deviceTypes:@[ deviceType1 ]];
+ XCTAssertNotNil(endpoint4);
+ // Should not be able to add this endpoint, since it's got a duplicate
+ // endpoint id, even though we are adding on a different controller.
+ XCTAssertFalse([controllerClient addServerEndpoint:endpoint4]);
+
+ __auto_type * endpoint5 = [[MTRServerEndpoint alloc] initWithEndpointID:endpointId3 deviceTypes:@[ deviceType1 ]];
+ XCTAssertNotNil(endpoint5);
+ // Should be able to add this one, though; it's unrelated to any existing endpoints.
+ XCTAssertTrue([controllerClient addServerEndpoint:endpoint5]);
+
+ __auto_type * device = [MTRBaseDevice deviceWithNodeID:nodeIDServer controller:controllerClient];
+
+ __auto_type * requestPath = attribute1RequestPath;
+ __block __auto_type * responsePath = attribute1ResponsePath;
+
+ __auto_type checkSingleValue = ^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error, NSDictionary<NSString *, id> * expectedValue) {
+ // The overall interaction should succeed.
+ XCTAssertNil(error);
+ XCTAssertNotNil(values);
+
+ // And we should get a value for our attribute.
+ XCTAssertEqual(values.count, 1);
+
+ NSDictionary<NSString *, id> * value = values[0];
+ XCTAssertEqualObjects(value[MTRAttributePathKey], responsePath);
+
+ XCTAssertNil(value[MTRErrorKey]);
+ XCTAssertNotNil(value[MTRDataKey]);
+
+ XCTAssertEqualObjects(value[MTRDataKey], expectedValue);
+ };
+
+ __auto_type checkSinglePathError = ^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error, MTRInteractionErrorCode expectedError) {
+ // The overall interaction should succeed.
+ XCTAssertNil(error);
+ XCTAssertNotNil(values);
+
+ // And we should get a value for our attribute.
+ XCTAssertEqual(values.count, 1);
+
+ NSDictionary<NSString *, id> * value = values[0];
+ XCTAssertEqualObjects(value[MTRAttributePathKey], responsePath);
+
+ XCTAssertNil(value[MTRDataKey]);
+ XCTAssertNotNil(value[MTRErrorKey]);
+
+ NSError * pathError = value[MTRErrorKey];
+ XCTAssertEqual(pathError.domain, MTRInteractionErrorDomain);
+ XCTAssertEqual(pathError.code, expectedError);
+ };
+
+ // First try a basic read.
+ XCTestExpectation * readExpectation1 = [self expectationWithDescription:@"Read 1 of attribute complete"];
+ [device readAttributePaths:@[ requestPath ]
+ eventPaths:nil
+ params:nil
+ queue:queue
+ completion:^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
+ checkSingleValue(values, error, unsignedIntValue1);
+ [readExpectation1 fulfill];
+ }];
+ [self waitForExpectations:@[ readExpectation1 ] timeout:kTimeoutInSeconds];
+
+ // Now try a basic subscribe.
+ __block void (^reportHandler)(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error);
+
+ XCTestExpectation * initialValueExpectation = [self expectationWithDescription:@"Got initial value"];
+ reportHandler = ^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
+ checkSingleValue(values, error, unsignedIntValue1);
+ [initialValueExpectation fulfill];
+ };
+
+ XCTestExpectation * subscriptionEstablishedExpectation = [self expectationWithDescription:@"Basic subscription established"];
+ __auto_type * subscribeParams = [[MTRSubscribeParams alloc] initWithMinInterval:@(0) maxInterval:@(10)];
+ [device subscribeToAttributesWithEndpointID:requestPath.endpoint clusterID:requestPath.cluster attributeID:requestPath.attribute
+ params:subscribeParams
+ queue:queue
+ reportHandler:^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
+ reportHandler(values, error);
+ }
+ subscriptionEstablished:^() {
+ [subscriptionEstablishedExpectation fulfill];
+ }];
+ [self waitForExpectations:@[ subscriptionEstablishedExpectation, initialValueExpectation ] timeout:kTimeoutInSeconds];
+
+ // Now change the value and expect to see it on our subscription.
+ XCTestExpectation * valueUpdateExpectation = [self expectationWithDescription:@"We see the new value"];
+ reportHandler = ^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
+ checkSingleValue(values, error, unsignedIntValue2);
+ [valueUpdateExpectation fulfill];
+ };
+
+ [attribute1 setValue:unsignedIntValue2];
+
+ [self waitForExpectations:@[ valueUpdateExpectation ] timeout:kTimeoutInSeconds];
+
+ // Now try a read of an attribute we do not have permissions for.
+ requestPath = attribute2RequestPath;
+ responsePath = attribute2ResponsePath;
+ XCTestExpectation * readNoPermissionsExpectation1 = [self expectationWithDescription:@"Read 1 of attribute with no permissions complete"];
+ [device readAttributePaths:@[ requestPath ]
+ eventPaths:nil
+ params:nil
+ queue:queue
+ completion:^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
+ checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess);
+ [readNoPermissionsExpectation1 fulfill];
+ }];
+ [self waitForExpectations:@[ readNoPermissionsExpectation1 ] timeout:kTimeoutInSeconds];
+
+ // Change the permissions to give Manage access on the cluster to some
+ // random node ID and try again. Should still have no permissions.
+ __auto_type * unrelatedGrant = [MTRAccessGrant accessGrantForNodeID:@(0xabc) privilege:MTRAccessControlEntryPrivilegeManage];
+ XCTAssertNotNil(unrelatedGrant);
+ [cluster2 addAccessGrant:unrelatedGrant];
+
+ XCTestExpectation * readNoPermissionsExpectation2 = [self expectationWithDescription:@"Read 2 of attribute with no permissions complete"];
+ [device readAttributePaths:@[ requestPath ]
+ eventPaths:nil
+ params:nil
+ queue:queue
+ completion:^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
+ checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess);
+ [readNoPermissionsExpectation2 fulfill];
+ }];
+ [self waitForExpectations:@[ readNoPermissionsExpectation2 ] timeout:kTimeoutInSeconds];
+
+ // Change the permissions to give Manage access on the cluster to our client
+ // node ID and try again. Should be able to read the attribute now.
+ __auto_type * clientManageGrant = [MTRAccessGrant accessGrantForNodeID:nodeIDClient privilege:MTRAccessControlEntryPrivilegeManage];
+ XCTAssertNotNil(clientManageGrant);
+ [cluster2 addAccessGrant:clientManageGrant];
+
+ XCTestExpectation * readExpectation2 = [self expectationWithDescription:@"Read 2 of attribute complete"];
+ [device readAttributePaths:@[ requestPath ]
+ eventPaths:nil
+ params:nil
+ queue:queue
+ completion:^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
+ checkSingleValue(values, error, listOfStructsValue1);
+ [readExpectation2 fulfill];
+ }];
+ [self waitForExpectations:@[ readExpectation2 ] timeout:kTimeoutInSeconds];
+
+ // Adding Manage permissions to one cluster should not affect another one.
+ requestPath = attribute3RequestPath;
+ responsePath = attribute3ResponsePath;
+
+ XCTestExpectation * readNoPermissionsExpectation3 = [self expectationWithDescription:@"Read 3 of attribute with no permissions complete"];
+ [device readAttributePaths:@[ requestPath ]
+ eventPaths:nil
+ params:nil
+ queue:queue
+ completion:^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
+ checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess);
+ [readNoPermissionsExpectation3 fulfill];
+ }];
+ [self waitForExpectations:@[ readNoPermissionsExpectation3 ] timeout:kTimeoutInSeconds];
+
+ // But adding Manage permissions on the endpoint should grant Operate on
+ // the cluster.
+ [endpoint1 addAccessGrant:clientManageGrant];
+
+ XCTestExpectation * readExpectation3 = [self expectationWithDescription:@"Read 3 of attribute complete"];
+ [device readAttributePaths:@[ requestPath ]
+ eventPaths:nil
+ params:nil
+ queue:queue
+ completion:^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
+ checkSingleValue(values, error, unsignedIntValue1);
+ [readExpectation3 fulfill];
+ }];
+ [self waitForExpectations:@[ readExpectation3 ] timeout:kTimeoutInSeconds];
+
+ // And removing that grant should remove the permissions again.
+ [endpoint1 removeAccessGrant:clientManageGrant];
+
+ XCTestExpectation * readNoPermissionsExpectation4 = [self expectationWithDescription:@"Read 4 of attribute with no permissions complete"];
+ [device readAttributePaths:@[ requestPath ]
+ eventPaths:nil
+ params:nil
+ queue:queue
+ completion:^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
+ checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess);
+ [readNoPermissionsExpectation4 fulfill];
+ }];
+ [self waitForExpectations:@[ readNoPermissionsExpectation4 ] timeout:kTimeoutInSeconds];
+
+ // Now do a wildcard read on the endpoint and check that this does the right
+ // thing (gets the right things from descriptor, gets both clusters, etc).
+#if 0
+ // Unused bits ifdefed out until we doing more testing on the actual values
+ // we get back.
+ __auto_type globalAttributePath = ^(NSNumber * clusterID, MTRAttributeIDType attributeID) {
+ return [MTRAttributePath attributePathWithEndpointID:endpointId1 clusterID:clusterID attributeID:@(attributeID)];
+ };
+ __auto_type unsignedIntValue = ^(NSUInteger value) {
+ return @{
+ MTRTypeKey: MTRUnsignedIntegerValueType,
+ MTRValueKey: @(value),
+ };
+ };
+ __auto_type arrayOfUnsignedIntegersValue = ^(NSArray<NSNumber *> * values) {
+ __auto_type * mutableArray = [[NSMutableArray alloc] init];
+ for (NSNumber * value in values) {
+ [mutableArray addObject:@{ MTRDataKey: @{
+ MTRTypeKey: MTRUnsignedIntegerValueType,
+ MTRValueKey: value,
+ }, }];
+ }
+ return @{
+ MTRTypeKey: MTRArrayValueType,
+ MTRValueKey: [mutableArray copy],
+ };
+ };
+#endif
+ XCTestExpectation * wildcardReadExpectation = [self expectationWithDescription:@"Wildcard read of our endpoint"];
+ [device readAttributePaths:@[ [MTRAttributeRequestPath requestPathWithEndpointID:endpointId1 clusterID:nil attributeID:nil] ]
+ eventPaths:nil
+ params:nil
+ queue:queue
+ completion:^(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertNotNil(values);
+
+ // TODO: Figure out how to test that values is correct that's not
+ // too fragile if things get returned in different valid order.
+ // For now just check that every path we got has a value, not an
+ // error.
+ for (NSDictionary<NSString *, id> * value in values) {
+ XCTAssertNotNil(value[MTRAttributePathKey]);
+ XCTAssertNil(value[MTRErrorKey]);
+ XCTAssertNotNil(value[MTRDataKey]);
+ }
+#if 0
+ XCTAssertEqualObjects(values, @[
+ // cluster1
+ @{ MTRAttributePathKey: attribute1ResponsePath,
+ MTRDataKey: unsignedIntValue2, },
+ @{ MTRAttributePathKey: globalAttributePath(clusterId1, MTRAttributeIDTypeGlobalAttributeFeatureMapID),
+ MTRDataKey: unsignedIntValue(0), },
+ @{ MTRAttributePathKey: globalAttributePath(clusterId1, MTRAttributeIDTypeGlobalAttributeClusterRevisionID),
+ MTRDataKey: clusterRevision1, },
+ @{ MTRAttributePathKey: globalAttributePath(clusterId1, MTRAttributeIDTypeGlobalAttributeGeneratedCommandListID),
+ MTRDataKey: arrayOfUnsignedIntegersValue(@[]), },
+ @{ MTRAttributePathKey: globalAttributePath(clusterId1, MTRAttributeIDTypeGlobalAttributeAcceptedCommandListID),
+ MTRDataKey: arrayOfUnsignedIntegersValue(@[]), },
+ // etc
+
+ ]);
+#endif
+ [wildcardReadExpectation fulfill];
+ }];
+ [self waitForExpectations:@[ wildcardReadExpectation ] timeout:kTimeoutInSeconds];
+
+ [controllerClient shutdown];
+ [controllerServer shutdown];
+}
+
@end
#endif // MTR_PER_CONTROLLER_STORAGE_ENABLED
diff --git a/src/darwin/Framework/CHIPTests/MTRServerEndpointTests.m b/src/darwin/Framework/CHIPTests/MTRServerEndpointTests.m
index 57b47f8..4a7c31a 100644
--- a/src/darwin/Framework/CHIPTests/MTRServerEndpointTests.m
+++ b/src/darwin/Framework/CHIPTests/MTRServerEndpointTests.m
@@ -130,12 +130,16 @@
MTRTypeKey : MTRArrayValueType,
MTRValueKey : @[
@{
- MTRTypeKey : MTRUTF8StringValueType,
- MTRValueKey : @"str1",
+ MTRDataKey : @ {
+ MTRTypeKey : MTRUTF8StringValueType,
+ MTRValueKey : @"str1",
+ },
},
@{
- MTRTypeKey : MTRUTF8StringValueType,
- MTRValueKey : @"str2",
+ MTRDataKey : @ {
+ MTRTypeKey : MTRUTF8StringValueType,
+ MTRValueKey : @"str2",
+ },
},
],
};
diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
index 5c735e1..552a797 100644
--- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
+++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
@@ -150,6 +150,18 @@
5143851E2A65885500EDC8E6 /* MTRSwiftPairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5143851D2A65885500EDC8E6 /* MTRSwiftPairingTests.swift */; };
514654492A72F9DF00904E61 /* MTRDemuxingStorage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 514654482A72F9DF00904E61 /* MTRDemuxingStorage.mm */; };
5146544B2A72F9F500904E61 /* MTRDemuxingStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 5146544A2A72F9F500904E61 /* MTRDemuxingStorage.h */; };
+ 514C79ED2B62ADCD00DD6D7B /* ember-compatibility-functions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79EC2B62ADCD00DD6D7B /* ember-compatibility-functions.cpp */; };
+ 514C79EE2B62ADCD00DD6D7B /* ember-compatibility-functions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79EC2B62ADCD00DD6D7B /* ember-compatibility-functions.cpp */; };
+ 514C79F02B62ADDA00DD6D7B /* descriptor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79EF2B62ADDA00DD6D7B /* descriptor.cpp */; };
+ 514C79F12B62ADDA00DD6D7B /* descriptor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79EF2B62ADDA00DD6D7B /* descriptor.cpp */; };
+ 514C79F32B62ED5500DD6D7B /* attribute-storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79F22B62ED5500DD6D7B /* attribute-storage.cpp */; };
+ 514C79F42B62ED5500DD6D7B /* attribute-storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79F22B62ED5500DD6D7B /* attribute-storage.cpp */; };
+ 514C79F62B62F0B900DD6D7B /* util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79F52B62F0B900DD6D7B /* util.cpp */; };
+ 514C79F72B62F0B900DD6D7B /* util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79F52B62F0B900DD6D7B /* util.cpp */; };
+ 514C79F92B62F60100DD6D7B /* message.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79F82B62F60100DD6D7B /* message.cpp */; };
+ 514C79FA2B62F60100DD6D7B /* message.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79F82B62F60100DD6D7B /* message.cpp */; };
+ 514C79FC2B62F94C00DD6D7B /* ota-provider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79FB2B62F94C00DD6D7B /* ota-provider.cpp */; };
+ 514C79FD2B62F94C00DD6D7B /* ota-provider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 514C79FB2B62F94C00DD6D7B /* ota-provider.cpp */; };
514C7A012B64223400DD6D7B /* MTRServerAttribute_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 514C79FF2B64223400DD6D7B /* MTRServerAttribute_Internal.h */; };
514C7A022B64223400DD6D7B /* MTRServerEndpoint_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 514C7A002B64223400DD6D7B /* MTRServerEndpoint_Internal.h */; };
514C7A042B6436D500DD6D7B /* MTRServerCluster_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 514C7A032B6436D500DD6D7B /* MTRServerCluster_Internal.h */; };
@@ -160,6 +172,13 @@
51565CB62A7B0D6600469F18 /* MTRDeviceControllerParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = 51565CB52A7B0D6600469F18 /* MTRDeviceControllerParameters.h */; settings = {ATTRIBUTES = (Public, ); }; };
515C1C6F284F9FFB00A48F0C /* MTRFramework.mm in Sources */ = {isa = PBXBuildFile; fileRef = 515C1C6D284F9FFB00A48F0C /* MTRFramework.mm */; };
515C1C70284F9FFB00A48F0C /* MTRFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 515C1C6E284F9FFB00A48F0C /* MTRFramework.h */; };
+ 516411312B6BF70300E67C05 /* DataModelHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 516415FE2B6B132200D5CE11 /* DataModelHandler.cpp */; };
+ 516411322B6BF75700E67C05 /* MTRIMDispatch.mm in Sources */ = {isa = PBXBuildFile; fileRef = 516416002B6B483C00D5CE11 /* MTRIMDispatch.mm */; };
+ 516411332B6BF77700E67C05 /* MTRServerAccessControl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 516415FA2B6ACA8300D5CE11 /* MTRServerAccessControl.mm */; };
+ 516415FC2B6ACA8300D5CE11 /* MTRServerAccessControl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 516415FA2B6ACA8300D5CE11 /* MTRServerAccessControl.mm */; };
+ 516415FD2B6ACA8300D5CE11 /* MTRServerAccessControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 516415FB2B6ACA8300D5CE11 /* MTRServerAccessControl.h */; };
+ 516415FF2B6B132200D5CE11 /* DataModelHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 516415FE2B6B132200D5CE11 /* DataModelHandler.cpp */; };
+ 516416012B6B483C00D5CE11 /* MTRIMDispatch.mm in Sources */ = {isa = PBXBuildFile; fileRef = 516416002B6B483C00D5CE11 /* MTRIMDispatch.mm */; };
51669AF02913204400F4AA36 /* MTRBackwardsCompatTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51669AEF2913204400F4AA36 /* MTRBackwardsCompatTests.m */; };
5173A47529C0E2ED00F67F48 /* MTRFabricInfo_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5173A47229C0E2ED00F67F48 /* MTRFabricInfo_Internal.h */; };
5173A47629C0E2ED00F67F48 /* MTRFabricInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5173A47329C0E2ED00F67F48 /* MTRFabricInfo.mm */; };
@@ -183,8 +202,6 @@
51B22C2A2740CB47008D5055 /* MTRCommandPayloadsObjc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51B22C292740CB47008D5055 /* MTRCommandPayloadsObjc.mm */; };
51C8E3F82825CDB600D47D00 /* MTRTestKeys.m in Sources */ = {isa = PBXBuildFile; fileRef = 51C8E3F72825CDB600D47D00 /* MTRTestKeys.m */; };
51C984622A61CE2A00B0AD9A /* MTRFabricInfoChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 51C984602A61CE2A00B0AD9A /* MTRFabricInfoChecker.m */; };
- 51CFDDB12AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51CFDDB02AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp */; };
- 51CFDDB22AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51CFDDB02AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp */; };
51D0B1272B617246006E3511 /* MTRServerEndpoint.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51D0B1252B617246006E3511 /* MTRServerEndpoint.mm */; };
51D0B1282B617246006E3511 /* MTRServerEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 51D0B1262B617246006E3511 /* MTRServerEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; };
51D0B12A2B61766F006E3511 /* MTRServerEndpointTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51D0B1292B61766F006E3511 /* MTRServerEndpointTests.m */; };
@@ -535,6 +552,12 @@
5143851D2A65885500EDC8E6 /* MTRSwiftPairingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTRSwiftPairingTests.swift; sourceTree = "<group>"; };
514654482A72F9DF00904E61 /* MTRDemuxingStorage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDemuxingStorage.mm; sourceTree = "<group>"; };
5146544A2A72F9F500904E61 /* MTRDemuxingStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDemuxingStorage.h; sourceTree = "<group>"; };
+ 514C79EC2B62ADCD00DD6D7B /* ember-compatibility-functions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "ember-compatibility-functions.cpp"; path = "util/ember-compatibility-functions.cpp"; sourceTree = "<group>"; };
+ 514C79EF2B62ADDA00DD6D7B /* descriptor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = descriptor.cpp; path = clusters/descriptor/descriptor.cpp; sourceTree = "<group>"; };
+ 514C79F22B62ED5500DD6D7B /* attribute-storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "attribute-storage.cpp"; path = "util/attribute-storage.cpp"; sourceTree = "<group>"; };
+ 514C79F52B62F0B900DD6D7B /* util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = util.cpp; path = util/util.cpp; sourceTree = "<group>"; };
+ 514C79F82B62F60100DD6D7B /* message.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = message.cpp; path = util/message.cpp; sourceTree = "<group>"; };
+ 514C79FB2B62F94C00DD6D7B /* ota-provider.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "ota-provider.cpp"; path = "clusters/ota-provider/ota-provider.cpp"; sourceTree = "<group>"; };
514C79FF2B64223400DD6D7B /* MTRServerAttribute_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerAttribute_Internal.h; sourceTree = "<group>"; };
514C7A002B64223400DD6D7B /* MTRServerEndpoint_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerEndpoint_Internal.h; sourceTree = "<group>"; };
514C7A032B6436D500DD6D7B /* MTRServerCluster_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerCluster_Internal.h; sourceTree = "<group>"; };
@@ -545,6 +568,10 @@
51565CB52A7B0D6600469F18 /* MTRDeviceControllerParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceControllerParameters.h; sourceTree = "<group>"; };
515C1C6D284F9FFB00A48F0C /* MTRFramework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRFramework.mm; sourceTree = "<group>"; };
515C1C6E284F9FFB00A48F0C /* MTRFramework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRFramework.h; sourceTree = "<group>"; };
+ 516415FA2B6ACA8300D5CE11 /* MTRServerAccessControl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRServerAccessControl.mm; sourceTree = "<group>"; };
+ 516415FB2B6ACA8300D5CE11 /* MTRServerAccessControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerAccessControl.h; sourceTree = "<group>"; };
+ 516415FE2B6B132200D5CE11 /* DataModelHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DataModelHandler.cpp; path = util/DataModelHandler.cpp; sourceTree = "<group>"; };
+ 516416002B6B483C00D5CE11 /* MTRIMDispatch.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRIMDispatch.mm; sourceTree = "<group>"; };
51669AEF2913204400F4AA36 /* MTRBackwardsCompatTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRBackwardsCompatTests.m; sourceTree = "<group>"; };
5173A47229C0E2ED00F67F48 /* MTRFabricInfo_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRFabricInfo_Internal.h; sourceTree = "<group>"; };
5173A47329C0E2ED00F67F48 /* MTRFabricInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRFabricInfo.mm; sourceTree = "<group>"; };
@@ -575,7 +602,6 @@
51C8E3F72825CDB600D47D00 /* MTRTestKeys.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRTestKeys.m; sourceTree = "<group>"; };
51C984602A61CE2A00B0AD9A /* MTRFabricInfoChecker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRFabricInfoChecker.m; sourceTree = "<group>"; };
51C984612A61CE2A00B0AD9A /* MTRFabricInfoChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRFabricInfoChecker.h; sourceTree = "<group>"; };
- 51CFDDB02AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = EmptyDataModelHandler.cpp; path = ../controller/EmptyDataModelHandler.cpp; sourceTree = "<group>"; };
51D0B1252B617246006E3511 /* MTRServerEndpoint.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRServerEndpoint.mm; sourceTree = "<group>"; };
51D0B1262B617246006E3511 /* MTRServerEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerEndpoint.h; sourceTree = "<group>"; };
51D0B1292B61766F006E3511 /* MTRServerEndpointTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRServerEndpointTests.m; sourceTree = "<group>"; };
@@ -1024,7 +1050,13 @@
isa = PBXGroup;
children = (
5143041F2914CED9004DC7FE /* generic-callback-stubs.cpp */,
- 51CFDDB02AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp */,
+ 514C79F22B62ED5500DD6D7B /* attribute-storage.cpp */,
+ 514C79EF2B62ADDA00DD6D7B /* descriptor.cpp */,
+ 514C79EC2B62ADCD00DD6D7B /* ember-compatibility-functions.cpp */,
+ 516415FE2B6B132200D5CE11 /* DataModelHandler.cpp */,
+ 514C79F52B62F0B900DD6D7B /* util.cpp */,
+ 514C79F82B62F60100DD6D7B /* message.cpp */,
+ 514C79FB2B62F94C00DD6D7B /* ota-provider.cpp */,
);
name = app;
path = ../../../app;
@@ -1138,6 +1170,9 @@
children = (
51D0B12C2B6177D9006E3511 /* MTRAccessGrant.h */,
51D0B12B2B6177D9006E3511 /* MTRAccessGrant.mm */,
+ 516416002B6B483C00D5CE11 /* MTRIMDispatch.mm */,
+ 516415FB2B6ACA8300D5CE11 /* MTRServerAccessControl.h */,
+ 516415FA2B6ACA8300D5CE11 /* MTRServerAccessControl.mm */,
51D0B1362B618CC6006E3511 /* MTRServerAttribute.h */,
51D0B1372B618CC6006E3511 /* MTRServerAttribute.mm */,
514C79FF2B64223400DD6D7B /* MTRServerAttribute_Internal.h */,
@@ -1541,6 +1576,7 @@
754F3DF427FBB94B00E60580 /* MTREventTLVValueDecoder_Internal.h in Headers */,
3CF134AF289D90FF0017A19E /* MTROperationalCertificateIssuer.h in Headers */,
5178E6822AE098520069DF72 /* MTRCommissionableBrowserResult_Internal.h in Headers */,
+ 516415FD2B6ACA8300D5CE11 /* MTRServerAccessControl.h in Headers */,
3CF134AB289D8DF70017A19E /* MTRDeviceAttestationInfo.h in Headers */,
B2E0D7B2245B0B5C003C5B48 /* MTRManualSetupPayloadParser.h in Headers */,
3CF134A7289D8ADA0017A19E /* MTRCSRInfo.h in Headers */,
@@ -1757,8 +1793,10 @@
B45373F32A9FEC1A00807602 /* server-ws.c in Sources */,
03F430AA2994113500166449 /* sysunix.c in Sources */,
B45373C42A9FEA9100807602 /* dummy-callback.c in Sources */,
+ 514C79EE2B62ADCD00DD6D7B /* ember-compatibility-functions.cpp in Sources */,
039145E82993179300257B3E /* GetCommissionerNodeIdCommand.mm in Sources */,
0395469F2991DFC5006D42A8 /* json_reader.cpp in Sources */,
+ 514C79F42B62ED5500DD6D7B /* attribute-storage.cpp in Sources */,
B45373D22A9FEB0C00807602 /* buflist.c in Sources */,
B45373D72A9FEB0C00807602 /* lws_dll2.c in Sources */,
B45373FE2A9FEC4F00807602 /* unix-fds.c in Sources */,
@@ -1768,8 +1806,10 @@
0395469E2991DFC5006D42A8 /* json_writer.cpp in Sources */,
03FB93E02A46200A0048CB35 /* DiscoverCommissionablesCommand.mm in Sources */,
B45373EF2A9FEBFE00807602 /* ops-raw-skt.c in Sources */,
+ 516411332B6BF77700E67C05 /* MTRServerAccessControl.mm in Sources */,
037C3DD52991C2E200B7EEE2 /* CHIPCommandBridge.mm in Sources */,
039546BC2991E1CB006D42A8 /* LogCommands.cpp in Sources */,
+ 516411312B6BF70300E67C05 /* DataModelHandler.cpp in Sources */,
B45373E12A9FEB7F00807602 /* ops-h1.c in Sources */,
B45373EB2A9FEBDB00807602 /* ops-listen.c in Sources */,
0382FA2C2992F06C00247BBB /* Commands.cpp in Sources */,
@@ -1804,25 +1844,29 @@
039145E12993102B00257B3E /* main.mm in Sources */,
037C3DD42991BD5200B7EEE2 /* logging.mm in Sources */,
B45374012A9FEC4F00807602 /* unix-sockets.c in Sources */,
- 51CFDDB22AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp in Sources */,
03F430A82994112B00166449 /* editline.c in Sources */,
B45373E92A9FEBC100807602 /* server.c in Sources */,
037C3DB32991BD5000B7EEE2 /* OpenCommissioningWindowCommand.mm in Sources */,
037C3DAE2991BD4F00B7EEE2 /* PairingCommandBridge.mm in Sources */,
+ 514C79FD2B62F94C00DD6D7B /* ota-provider.cpp in Sources */,
B45373FB2A9FEC4F00807602 /* unix-service.c in Sources */,
B45373F22A9FEC1A00807602 /* ops-ws.c in Sources */,
037C3DCA2991BD5100B7EEE2 /* CHIPCommandStorageDelegate.mm in Sources */,
037C3DCF2991BD5200B7EEE2 /* MTRError.mm in Sources */,
037C3DC72991BD5100B7EEE2 /* CHIPToolKeypair.mm in Sources */,
B45373E52A9FEBA400807602 /* date.c in Sources */,
+ 514C79FA2B62F60100DD6D7B /* message.cpp in Sources */,
+ 514C79F72B62F0B900DD6D7B /* util.cpp in Sources */,
B45373DC2A9FEB5300807602 /* sha-1.c in Sources */,
B45373D12A9FEB0C00807602 /* alloc.c in Sources */,
B45373C62A9FEA9100807602 /* sorted-usec-list.c in Sources */,
037C3DB62991BD5000B7EEE2 /* ModelCommandBridge.mm in Sources */,
+ 516411322B6BF75700E67C05 /* MTRIMDispatch.mm in Sources */,
B45373C22A9FEA9100807602 /* vhost.c in Sources */,
037C3DB42991BD5000B7EEE2 /* DeviceControllerDelegateBridge.mm in Sources */,
039547012992D461006D42A8 /* generic-callback-stubs.cpp in Sources */,
B45373D52A9FEB0C00807602 /* logs.c in Sources */,
+ 514C79F12B62ADDA00DD6D7B /* descriptor.cpp in Sources */,
0382FA312992FD6E00247BBB /* MTRLogging.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1833,6 +1877,7 @@
files = (
2C8C8FC2253E0C2100797F05 /* MTRPersistentStorageDelegateBridge.mm in Sources */,
99AECC802798A57F00B6355B /* MTRCommissioningParameters.mm in Sources */,
+ 514C79F92B62F60100DD6D7B /* message.cpp in Sources */,
2CB7163C252E8A7C0026E2BB /* MTRDeviceControllerDelegateBridge.mm in Sources */,
997DED162695343400975E97 /* MTRThreadOperationalDataset.mm in Sources */,
515C1C6F284F9FFB00A48F0C /* MTRFramework.mm in Sources */,
@@ -1840,14 +1885,18 @@
27A53C1827FBC6920053F131 /* MTRAttestationTrustStoreBridge.mm in Sources */,
93B2CF9A2B56E45C00E4D187 /* MTRClusterNames.mm in Sources */,
998F287126D56940001846C6 /* MTRP256KeypairBridge.mm in Sources */,
+ 516416012B6B483C00D5CE11 /* MTRIMDispatch.mm in Sources */,
51D0B1412B61B3A4006E3511 /* MTRServerCluster.mm in Sources */,
+ 514C79FC2B62F94C00DD6D7B /* ota-provider.cpp in Sources */,
5136661428067D550025EDAE /* MTRDeviceControllerFactory.mm in Sources */,
51D0B1392B618CC6006E3511 /* MTRServerAttribute.mm in Sources */,
51B22C2A2740CB47008D5055 /* MTRCommandPayloadsObjc.mm in Sources */,
51F522682AE70734000C4050 /* MTRDeviceTypeMetadata.mm in Sources */,
75B765C32A1D82D30014719B /* MTRAttributeSpecifiedCheck.mm in Sources */,
AF5F90FF2878D351005503FA /* MTROTAProviderDelegateBridge.mm in Sources */,
+ 516415FF2B6B132200D5CE11 /* DataModelHandler.cpp in Sources */,
51E95DFC2A78443C00A434F0 /* MTRSessionResumptionStorageBridge.mm in Sources */,
+ 514C79ED2B62ADCD00DD6D7B /* ember-compatibility-functions.cpp in Sources */,
7534F12828BFF20300390851 /* MTRDeviceAttestationDelegate.mm in Sources */,
B4C8E6B72B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm in Sources */,
2C5EEEF7268A85C400CAE3D3 /* MTRDeviceConnectionBridge.mm in Sources */,
@@ -1857,10 +1906,12 @@
3CF134A9289D8D800017A19E /* MTRCSRInfo.mm in Sources */,
991DC0892475F47D00C13860 /* MTRDeviceController.mm in Sources */,
B2E0D7B7245B0B5C003C5B48 /* MTRQRCodeSetupPayloadParser.mm in Sources */,
+ 514C79F32B62ED5500DD6D7B /* attribute-storage.cpp in Sources */,
514304202914CED9004DC7FE /* generic-callback-stubs.cpp in Sources */,
1EDCE546289049A100E41EC9 /* MTROTAHeader.mm in Sources */,
51D0B13D2B61B2F2006E3511 /* MTRDeviceTypeRevision.mm in Sources */,
1EC4CE5D25CC26E900D7304F /* MTRBaseClusters.mm in Sources */,
+ 514C79F62B62F0B900DD6D7B /* util.cpp in Sources */,
51565CB22A7AD77600469F18 /* MTRDeviceControllerDataStore.mm in Sources */,
51D0B12F2B617800006E3511 /* MTRAccessGrant.mm in Sources */,
88E6C9482B6334ED001A1FE0 /* MTRMetrics.mm in Sources */,
@@ -1876,11 +1927,12 @@
5ACDDD7D27CD16D200EFD68A /* MTRClusterStateCacheContainer.mm in Sources */,
513DDB8A2761F6F900DAA01A /* MTRAttributeTLVValueDecoder.mm in Sources */,
5117DD3829A931AE00FFA1AA /* MTROperationalBrowser.mm in Sources */,
+ 514C79F02B62ADDA00DD6D7B /* descriptor.cpp in Sources */,
3D843757294AD25A0070D20A /* MTRCertificateInfo.mm in Sources */,
5A7947E427C0129600434CF2 /* MTRDeviceController+XPC.mm in Sources */,
5A6FEC9027B563D900F25F42 /* MTRDeviceControllerOverXPC.mm in Sources */,
+ 516415FC2B6ACA8300D5CE11 /* MTRServerAccessControl.mm in Sources */,
B289D4222639C0D300D4E314 /* MTROnboardingPayloadParser.mm in Sources */,
- 51CFDDB12AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp in Sources */,
3CF134AD289D8E570017A19E /* MTRDeviceAttestationInfo.mm in Sources */,
2C1B027A2641DB4E00780EF1 /* MTROperationalCredentialsDelegate.mm in Sources */,
7560FD1C27FBBD3F005E85B3 /* MTREventTLVValueDecoder.mm in Sources */,
@@ -2022,6 +2074,7 @@
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO;
+ SYSTEM_HEADER_SEARCH_PATHS = "$(CHIP_ROOT)/src/darwin/Framework/CHIP/";
USER_HEADER_SEARCH_PATHS = "";
WARNING_CFLAGS = (
"-Wformat",
@@ -2100,6 +2153,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = macosx;
+ SYSTEM_HEADER_SEARCH_PATHS = "$(CHIP_ROOT)/src/darwin/Framework/CHIP/";
USER_HEADER_SEARCH_PATHS = "";
WARNING_CFLAGS = (
"-Wformat",
@@ -2248,9 +2302,6 @@
"$(CHIP_ROOT)/config/ios",
"$(CHIP_ROOT)/src",
"$(CHIP_ROOT)/src/include",
- "$(CHIP_ROOT)/src/lib",
- "$(CHIP_ROOT)/src/app",
- "$(CHIP_ROOT)/src/app/util",
"$(CHIP_ROOT)/zzz_generated/",
"$(CHIP_ROOT)/zzz_generated/app-common",
"$(CHIP_ROOT)/third_party/nlassert/repo/include",
@@ -2419,9 +2470,6 @@
"$(CHIP_ROOT)/config/ios",
"$(CHIP_ROOT)/src",
"$(CHIP_ROOT)/src/include",
- "$(CHIP_ROOT)/src/lib",
- "$(CHIP_ROOT)/src/app",
- "$(CHIP_ROOT)/src/app/util",
"$(CHIP_ROOT)/zzz_generated/",
"$(CHIP_ROOT)/zzz_generated/app-common",
"$(CHIP_ROOT)/third_party/nlassert/repo/include",
diff --git a/src/darwin/Framework/chip_xcode_build_connector.sh b/src/darwin/Framework/chip_xcode_build_connector.sh
index 54e3594..3ab0464 100755
--- a/src/darwin/Framework/chip_xcode_build_connector.sh
+++ b/src/darwin/Framework/chip_xcode_build_connector.sh
@@ -93,7 +93,7 @@
declare -a args=(
'default_configs_cosmetic=[]' # suppress colorization
'chip_crypto="boringssl"'
- 'chip_build_controller_dynamic_server=true'
+ 'chip_build_controller_dynamic_server=false'
'chip_build_tools=false'
'chip_build_tests=false'
'chip_enable_wifi=false'
diff --git a/src/platform/Darwin/CHIPDevicePlatformConfig.h b/src/platform/Darwin/CHIPDevicePlatformConfig.h
index 8cb7797..15fdaa4 100644
--- a/src/platform/Darwin/CHIPDevicePlatformConfig.h
+++ b/src/platform/Darwin/CHIPDevicePlatformConfig.h
@@ -58,9 +58,8 @@
#define CHIP_DEVICE_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS 1
#endif // CHIP_DEVICE_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS
-// Reserve a single dynamic endpoint that we can use to host things like OTA
-// Provider server.
+// Default to as many dynamic endpoints as we can manage.
#if !defined(CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) || CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT == 0
#undef CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT
-#define CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT 1
+#define CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT 254
#endif // CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT