Skip to content

Commit 648e3f6

Browse files
Add a test to exercise MTRCommandWithRequiredResponse encode/decode. (#37521)
1 parent 1675464 commit 648e3f6

File tree

2 files changed

+165
-4
lines changed

2 files changed

+165
-4
lines changed

src/darwin/Framework/CHIP/MTRCommandWithRequiredResponse.mm

+27-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
* limitations under the License.
1515
*/
1616

17+
#import <Matter/Matter.h>
18+
1719
#import "MTRDeviceDataValidation.h"
1820
#import "MTRLogging_Internal.h"
19-
#import <Matter/Matter.h>
21+
#import "MTRUtilities.h"
2022

2123
@implementation MTRCommandWithRequiredResponse
2224
- (instancetype)initWithPath:(MTRCommandPath *)path
@@ -66,7 +68,14 @@ - (nullable instancetype)initWithCoder:(NSCoder *)decoder
6668
return nil;
6769
}
6870

69-
_commandFields = [decoder decodeObjectOfClasses:[NSSet setWithArray:@[ [NSDictionary class], [NSString class], [NSNumber class], [NSArray class], [NSData class] ]] forKey:sFieldsKey];
71+
// The classes of things that can appear in a data-value dictionary.
72+
static NSSet * const sDataValueClasses = [NSSet setWithArray:@[ NSDictionary.class, NSArray.class, NSData.class, NSString.class, NSNumber.class ]];
73+
74+
// Unfortunately, decodeDictionaryWithKeysOfClasses:objectsOfClasses:forKey:
75+
// does not work when the objects stored in the dictionary can include
76+
// collections, so we have to use decodeObjectOfClasses: and then manually
77+
// validate we got a dictionary.
78+
_commandFields = [decoder decodeObjectOfClasses:sDataValueClasses forKey:sFieldsKey];
7079
if (_commandFields) {
7180
if (![_commandFields isKindOfClass:NSDictionary.class]) {
7281
MTR_LOG_ERROR("MTRCommandWithRequiredResponse decoded %@ for commandFields, not NSDictionary.", _commandFields);
@@ -79,7 +88,7 @@ - (nullable instancetype)initWithCoder:(NSCoder *)decoder
7988
}
8089
}
8190

82-
_requiredResponse = [decoder decodeObjectOfClasses:[NSSet setWithArray:@[ [NSDictionary class], [NSString class], [NSNumber class], [NSArray class], [NSData class] ]] forKey:sExpectedResultKey];
91+
_requiredResponse = [decoder decodeObjectOfClasses:sDataValueClasses forKey:sExpectedResultKey];
8392
if (_requiredResponse) {
8493
if (![_requiredResponse isKindOfClass:NSDictionary.class]) {
8594
MTR_LOG_ERROR("MTRCommandWithRequiredResponse decoded %@ for requiredResponse, not NSDictionary.", _requiredResponse);
@@ -116,4 +125,19 @@ - (void)encodeWithCoder:(NSCoder *)coder
116125
}
117126
}
118127

128+
- (BOOL)_isEqualToOther:(MTRCommandWithRequiredResponse *)other
129+
{
130+
return MTREqualObjects(_path, other.path)
131+
&& MTREqualObjects(_commandFields, other.commandFields)
132+
&& MTREqualObjects(_requiredResponse, other.requiredResponse);
133+
}
134+
135+
- (BOOL)isEqual:(id)object
136+
{
137+
if ([object class] != [self class]) {
138+
return NO;
139+
}
140+
return [self _isEqualToOther:object];
141+
}
142+
119143
@end

src/darwin/Framework/CHIPTests/MTRDeviceTests.m

+138-1
Original file line numberDiff line numberDiff line change
@@ -3350,14 +3350,23 @@ - (void)test031_MTRDeviceAttributeCacheLocalTestStorage
33503350
XCTAssertTrue(storedAttributeCountDifferenceFromMTRDeviceReport > 300);
33513351
}
33523352

3353-
- (void)doEncodeDecodeRoundTrip:(id<NSSecureCoding>)encodable
3353+
- (NSData *)_encodeEncodable:(id<NSSecureCoding>)encodable
33543354
{
33553355
// We know all our encodables are in fact NSObject.
33563356
NSObject * obj = (NSObject *) encodable;
33573357

33583358
NSError * encodeError;
33593359
NSData * encodedData = [NSKeyedArchiver archivedDataWithRootObject:encodable requiringSecureCoding:YES error:&encodeError];
33603360
XCTAssertNil(encodeError, @"Failed to encode %@", NSStringFromClass(obj.class));
3361+
return encodedData;
3362+
}
3363+
3364+
- (void)doEncodeDecodeRoundTrip:(id<NSSecureCoding>)encodable
3365+
{
3366+
NSData * encodedData = [self _encodeEncodable:encodable];
3367+
3368+
// We know all our encodables are in fact NSObject.
3369+
NSObject * obj = (NSObject *) encodable;
33613370

33623371
NSError * decodeError;
33633372
id decodedValue = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObject:obj.class] fromData:encodedData error:&decodeError];
@@ -3367,6 +3376,19 @@ - (void)doEncodeDecodeRoundTrip:(id<NSSecureCoding>)encodable
33673376
XCTAssertEqualObjects(obj, decodedValue, @"Decoding for %@ did not round-trip correctly", NSStringFromClass([obj class]));
33683377
}
33693378

3379+
- (void)_ensureDecodeFails:(id<NSSecureCoding>)encodable
3380+
{
3381+
NSData * encodedData = [self _encodeEncodable:encodable];
3382+
3383+
// We know all our encodables are in fact NSObject.
3384+
NSObject * obj = (NSObject *) encodable;
3385+
3386+
NSError * decodeError;
3387+
id decodedValue = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObject:obj.class] fromData:encodedData error:&decodeError];
3388+
XCTAssertNil(decodedValue);
3389+
XCTAssertNotNil(decodeError);
3390+
}
3391+
33703392
- (void)test032_MTRPathClassesEncoding
33713393
{
33723394
// Test attribute path encode / decode
@@ -6023,6 +6045,121 @@ - (void)test045_MTRDeviceInvokeGroups
60236045
[self waitForExpectations:@[ updateFabricLabelExpectingWrongValueExpectation ] timeout:(2 * kTimeoutInSeconds)];
60246046
}
60256047

6048+
- (void)test046_MTRCommandWithRequiredResponseEncoding
6049+
{
6050+
// Basic test with no command fields or required response.
6051+
__auto_type * onPath = [MTRCommandPath commandPathWithEndpointID:@(1)
6052+
clusterID:@(MTRClusterIDTypeOnOffID)
6053+
commandID:@(MTRCommandIDTypeClusterOnOffCommandOnID)];
6054+
__auto_type * onCommand = [[MTRCommandWithRequiredResponse alloc] initWithPath:onPath commandFields:nil requiredResponse:nil];
6055+
[self doEncodeDecodeRoundTrip:onCommand];
6056+
6057+
// Test with both command fields and an interesting required response.
6058+
//
6059+
// NSSecureCoding tracks object identity, so we need to create new objects
6060+
// for every instance of a thing we decode/encode with a given coder to make
6061+
// sure all codepaths are exercised. Use a block that returns a new
6062+
// dictionary each time to handle this.
6063+
__auto_type structureWithAllTypes = ^{
6064+
return @{
6065+
MTRTypeKey : MTRStructureValueType,
6066+
MTRValueKey : @[
6067+
@{
6068+
MTRContextTagKey : @(0),
6069+
MTRDataKey : @ {
6070+
MTRTypeKey : MTRSignedIntegerValueType,
6071+
MTRValueKey : @(5),
6072+
},
6073+
},
6074+
@{
6075+
MTRContextTagKey : @(1),
6076+
MTRDataKey : @ {
6077+
MTRTypeKey : MTRUnsignedIntegerValueType,
6078+
MTRValueKey : @(5),
6079+
},
6080+
},
6081+
@{
6082+
MTRContextTagKey : @(2),
6083+
MTRDataKey : @ {
6084+
MTRTypeKey : MTRBooleanValueType,
6085+
MTRValueKey : @(YES),
6086+
},
6087+
},
6088+
@{
6089+
MTRContextTagKey : @(3),
6090+
MTRDataKey : @ {
6091+
MTRTypeKey : MTRUTF8StringValueType,
6092+
MTRValueKey : @("abc"),
6093+
},
6094+
},
6095+
@{
6096+
MTRContextTagKey : @(4),
6097+
MTRDataKey : @ {
6098+
MTRTypeKey : MTROctetStringValueType,
6099+
MTRValueKey : [[NSData alloc] initWithBase64EncodedString:@"APJj" options:0],
6100+
},
6101+
},
6102+
@{
6103+
MTRContextTagKey : @(5),
6104+
MTRDataKey : @ {
6105+
MTRTypeKey : MTRFloatValueType,
6106+
MTRValueKey : @(1.0),
6107+
},
6108+
},
6109+
@{
6110+
MTRContextTagKey : @(6),
6111+
MTRDataKey : @ {
6112+
MTRTypeKey : MTRDoubleValueType,
6113+
MTRValueKey : @(5.0),
6114+
},
6115+
},
6116+
@{
6117+
MTRContextTagKey : @(7),
6118+
MTRDataKey : @ {
6119+
MTRTypeKey : MTRNullValueType,
6120+
},
6121+
},
6122+
@{
6123+
MTRContextTagKey : @(8),
6124+
MTRDataKey : @ {
6125+
MTRTypeKey : MTRArrayValueType,
6126+
MTRValueKey : @[
6127+
@{
6128+
MTRDataKey : @ {
6129+
MTRTypeKey : MTRUnsignedIntegerValueType,
6130+
MTRValueKey : @(9),
6131+
},
6132+
},
6133+
],
6134+
}
6135+
},
6136+
],
6137+
};
6138+
};
6139+
6140+
// Invalid commandFields (not a dictionary)
6141+
onCommand.commandFields = (id) @[];
6142+
[self _ensureDecodeFails:onCommand];
6143+
6144+
// Invalid required response (not a dictionary)
6145+
onCommand.commandFields = nil;
6146+
onCommand.requiredResponse = (id) @[];
6147+
[self _ensureDecodeFails:onCommand];
6148+
6149+
// Invalid required response (key is not NSNumber)
6150+
onCommand.requiredResponse = @{
6151+
@("abc") : structureWithAllTypes(),
6152+
};
6153+
[self _ensureDecodeFails:onCommand];
6154+
6155+
onCommand.commandFields = structureWithAllTypes();
6156+
onCommand.requiredResponse = @{
6157+
@(1) : structureWithAllTypes(),
6158+
@(13) : structureWithAllTypes(),
6159+
};
6160+
[self doEncodeDecodeRoundTrip:onCommand];
6161+
}
6162+
60266163
@end
60276164

60286165
@interface MTRDeviceEncoderTests : XCTestCase

0 commit comments

Comments
 (0)