blob: abf171b54d168aa6263af220014a4dad4849d791 [file]
/*
* Copyright (c) 2026 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 <Matter/Matter.h>
#import <XCTest/XCTest.h>
#import "MTRTestCase.h"
#import "MTRTestDeclarations.h"
#import "MTRTestKeys.h"
#import "MTRTestStorage.h"
static const uint16_t kLocalPort = 5541;
static const uint16_t kTestVendorId = 0xFFF1u;
@interface MTRDeviceReadThroughTests : MTRTestCase
@end
@implementation MTRDeviceReadThroughTests {
MTRDeviceController * _controller;
MTRDevice * _device;
}
- (void)setUp
{
[super setUp];
[self setContinueAfterFailure:NO];
__auto_type * factory = [MTRDeviceControllerFactory sharedInstance];
__auto_type * factoryParams = [[MTRDeviceControllerFactoryParams alloc] initWithStorage:[[MTRTestStorage alloc] init]];
factoryParams.port = @(kLocalPort);
XCTAssertTrue([factory startControllerFactory:factoryParams error:nil]);
__auto_type * testKeys = [[MTRTestKeys alloc] init];
__auto_type * params = [[MTRDeviceControllerStartupParams alloc] initWithIPK:testKeys.ipk fabricID:@(1) nocSigner:testKeys];
params.vendorID = @(kTestVendorId);
_controller = [factory createControllerOnNewFabric:params error:nil];
XCTAssertNotNil(_controller);
// No device has this node ID, and an MTRDevice without a delegate does not
// subscribe, so its cache holds only what the tests inject.
_device = [MTRDevice deviceWithNodeID:@(1) controller:_controller];
}
- (void)tearDown
{
[_controller shutdown];
[[MTRDeviceControllerFactory sharedInstance] stopControllerFactory];
[super tearDown];
}
- (MTRReadParams *)paramsAssumingUnknownAttributesReportable:(BOOL)reportable
{
MTRReadParams * params = [[MTRReadParams alloc] init];
params.assumeUnknownAttributesReportable = reportable;
return params;
}
- (void)reportClusterRevision:(NSUInteger)revision forClusterPath:(MTRClusterPath *)clusterPath
{
[_device unitTestInjectAttributeReport:@[ @{
MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:clusterPath.endpoint
clusterID:clusterPath.cluster
attributeID:@(MTRAttributeIDTypeGlobalAttributeClusterRevisionID)],
MTRDataKey : @ {
MTRTypeKey : MTRUnsignedIntegerValueType,
MTRValueKey : @(revision),
},
} ]
fromSubscription:YES];
// The injected report is handled on the Matter queue and then the device queue.
[_controller syncRunOnWorkQueue:^{
} error:nil];
[_device unitTestSyncRunOnDeviceQueue:^ {}];
}
- (void)testExistingDeviceForNodeIDDoesNotCreateDevice
{
XCTAssertEqual([_controller _existingDeviceForNodeID:@(1)], _device);
XCTAssertNil([_controller _existingDeviceForNodeID:@(2)]);
// Asking did not create an MTRDevice for node 2.
XCTAssertEqual(_controller.devices.count, 1);
}
- (void)testChangesOmittedAttributeNeedsReadThrough
{
MTRAttributePath * utcTime = [MTRAttributePath attributePathWithEndpointID:@(0)
clusterID:@(MTRClusterIDTypeTimeSynchronizationID)
attributeID:@(MTRAttributeIDTypeClusterTimeSynchronizationAttributeUTCTimeID)];
XCTAssertTrue([_device _attributeReadNeedsReadThroughForPath:utcTime params:nil]);
// assumeUnknownAttributesReportable only applies to attributes that are not in the spec.
XCTAssertTrue([_device _attributeReadNeedsReadThroughForPath:utcTime params:[self paramsAssumingUnknownAttributesReportable:YES]]);
XCTAssertTrue([_device _attributeReadNeedsReadThroughForPath:utcTime params:[self paramsAssumingUnknownAttributesReportable:NO]]);
}
- (void)testChangesOmittedQualityComesFromClusterXML
{
// The cluster XML gives these attributes the Changes Omitted quality.
NSArray<MTRAttributePath *> * paths = @[
[MTRAttributePath attributePathWithEndpointID:@(0)
clusterID:@(MTRClusterIDTypeGroupKeyManagementID)
attributeID:@(MTRAttributeIDTypeClusterGroupKeyManagementAttributeGroupKeyMapID)],
[MTRAttributePath attributePathWithEndpointID:@(0)
clusterID:@(MTRClusterIDTypeSoftwareDiagnosticsID)
attributeID:@(MTRAttributeIDTypeClusterSoftwareDiagnosticsAttributeCurrentHeapUsedID)],
[MTRAttributePath attributePathWithEndpointID:@(0)
clusterID:@(MTRClusterIDTypeICDManagementID)
attributeID:@(MTRAttributeIDTypeClusterICDManagementAttributeICDCounterID)],
];
for (MTRAttributePath * path in paths) {
XCTAssertTrue([_device _attributeReadNeedsReadThroughForPath:path params:nil], @"%@", path);
}
}
- (void)testFormerlyChangesOmittedAttributeNeedsReadThroughOnOlderClusterRevisions
{
MTRClusterPath * powerSource = [MTRClusterPath clusterPathWithEndpointID:@(1) clusterID:@(MTRClusterIDTypePowerSourceID)];
MTRAttributePath * batPercentRemaining = [MTRAttributePath attributePathWithEndpointID:powerSource.endpoint
clusterID:powerSource.cluster
attributeID:@(MTRAttributeIDTypeClusterPowerSourceAttributeBatPercentRemainingID)];
// Power Source revision 3 replaced the C quality on BatPercentRemaining with Q.
XCTAssertTrue([_device _attributeReadNeedsReadThroughForPath:batPercentRemaining params:nil]);
[self reportClusterRevision:2 forClusterPath:powerSource];
XCTAssertTrue([_device _attributeReadNeedsReadThroughForPath:batPercentRemaining params:nil]);
[self reportClusterRevision:3 forClusterPath:powerSource];
XCTAssertFalse([_device _attributeReadNeedsReadThroughForPath:batPercentRemaining params:nil]);
// The revision is per cluster instance: another endpoint has not reported one.
MTRAttributePath * otherBatPercentRemaining = [MTRAttributePath attributePathWithEndpointID:@(2)
clusterID:powerSource.cluster
attributeID:batPercentRemaining.attribute];
XCTAssertTrue([_device _attributeReadNeedsReadThroughForPath:otherBatPercentRemaining params:nil]);
}
- (void)testReportableAttributeDoesNotNeedReadThrough
{
MTRAttributePath * onOff = [MTRAttributePath attributePathWithEndpointID:@(1)
clusterID:@(MTRClusterIDTypeOnOffID)
attributeID:@(MTRAttributeIDTypeClusterOnOffAttributeOnOffID)];
XCTAssertFalse([_device _attributeReadNeedsReadThroughForPath:onOff params:nil]);
XCTAssertFalse([_device _attributeReadNeedsReadThroughForPath:onOff params:[self paramsAssumingUnknownAttributesReportable:YES]]);
// assumeUnknownAttributesReportable only applies to attributes that are not in the spec.
XCTAssertFalse([_device _attributeReadNeedsReadThroughForPath:onOff params:[self paramsAssumingUnknownAttributesReportable:NO]]);
}
- (void)testUnknownAttributeNeedsReadThroughOnlyWhenParamsSayItIsNotReportable
{
MTRAttributePath * vendorAttribute = [MTRAttributePath attributePathWithEndpointID:@(1) clusterID:@(0xFFF1FC99) attributeID:@(0x0001)];
XCTAssertFalse([_device _attributeReadNeedsReadThroughForPath:vendorAttribute params:nil]);
// assumeUnknownAttributesReportable defaults to YES.
XCTAssertFalse([_device _attributeReadNeedsReadThroughForPath:vendorAttribute params:[[MTRReadParams alloc] init]]);
XCTAssertTrue([_device _attributeReadNeedsReadThroughForPath:vendorAttribute params:[self paramsAssumingUnknownAttributesReportable:NO]]);
}
@end