Skip to content

Commit e36bde7

Browse files
Add an MTRDevice method for checking whether a data-value satisfies some expectation. (#36151)
1 parent f32d005 commit e36bde7

File tree

6 files changed

+525
-20
lines changed

6 files changed

+525
-20
lines changed

src/darwin/Framework/CHIP/MTRDevice.mm

+162
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,8 @@ - (BOOL)deviceCachePrimed
539539
return NO;
540540
}
541541

542+
#pragma mark - Suspend/resume management
543+
542544
- (void)controllerSuspended
543545
{
544546
// Nothing to do for now.
@@ -549,6 +551,166 @@ - (void)controllerResumed
549551
// Nothing to do for now.
550552
}
551553

554+
#pragma mark - Value comparisons
555+
556+
- (BOOL)_attributeDataValue:(MTRDeviceDataValueDictionary)one isEqualToDataValue:(MTRDeviceDataValueDictionary)theOther
557+
{
558+
// Sanity check for nil cases
559+
if (!one && !theOther) {
560+
MTR_LOG_ERROR("%@ attribute data-value comparison does not expect comparing two nil dictionaries", self);
561+
return YES;
562+
}
563+
if (!one || !theOther) {
564+
// Comparing against nil is expected, and should return NO quietly
565+
return NO;
566+
}
567+
568+
// Attribute data-value dictionaries are equal if type and value are equal, and specifically, this should return true if values are both nil
569+
return [one[MTRTypeKey] isEqual:theOther[MTRTypeKey]] && ((one[MTRValueKey] == theOther[MTRValueKey]) || [one[MTRValueKey] isEqual:theOther[MTRValueKey]]);
570+
}
571+
572+
// _attributeDataValue:satisfiesExpectedDataValue: checks whether the newly
573+
// received attribute data value satisfies the expectation we have.
574+
//
575+
// For now, a value is considered to satisfy the expectation if it's equal to
576+
// the expected value, though we allow the fields of structs to be in a
577+
// different order than expected: while in theory the spec does require a
578+
// specific ordering for struct fields, in practice we should not force certain
579+
// API consumers to deal with knowing what that ordering is.
580+
//
581+
// Things to consider for future:
582+
//
583+
// 1) Should a value that has _extra_ fields in a struct compared to the expected
584+
// value be considered as satisfying the expectation? Arguably, yes.
585+
//
586+
// 2) Should lists actually enforce order (as now), or should they allow
587+
// reordering entries?
588+
//
589+
// 3) For fabric-scoped lists, should we have a way to check for just "our
590+
// fabric's" entries?
591+
- (BOOL)_attributeDataValue:(MTRDeviceDataValueDictionary)observed satisfiesValueExpectation:(MTRDeviceDataValueDictionary)expected
592+
{
593+
// Sanity check for nil cases (which really should not happen!)
594+
if (!observed && !expected) {
595+
MTR_LOG_ERROR("%@ observed to expected attribute data-value comparison does not expect comparing two nil dictionaries", self);
596+
return YES;
597+
}
598+
599+
if (!observed || !expected) {
600+
// Again, not expected here. But clearly the expectation is not really
601+
// satisfied, in some sense.
602+
MTR_LOG_ERROR("@ observed to expected attribute data-value comparison does not expect a nil %s", observed ? "expected" : "observed");
603+
return NO;
604+
}
605+
606+
if (![observed[MTRTypeKey] isEqual:expected[MTRTypeKey]]) {
607+
// Different types, does not satisfy expectation.
608+
return NO;
609+
}
610+
611+
if ([MTRArrayValueType isEqual:expected[MTRTypeKey]]) {
612+
// For array-values, check that sizes are same and entries satisfy expectations.
613+
if (![observed[MTRValueKey] isKindOfClass:NSArray.class] || ![expected[MTRValueKey] isKindOfClass:NSArray.class]) {
614+
// Malformed data, just claim expectation is not satisfied.
615+
MTR_LOG_ERROR("%@ at least one of observed and expected value is not an NSArrray: %@, %@", self, observed, expected);
616+
return NO;
617+
}
618+
619+
NSArray<NSDictionary<NSString *, MTRDeviceDataValueDictionary> *> * observedArray = observed[MTRValueKey];
620+
NSArray<NSDictionary<NSString *, MTRDeviceDataValueDictionary> *> * expectedArray = expected[MTRValueKey];
621+
622+
if (observedArray.count != expectedArray.count) {
623+
return NO;
624+
}
625+
626+
for (NSUInteger i = 0; i < observedArray.count; ++i) {
627+
NSDictionary<NSString *, MTRDeviceDataValueDictionary> * observedEntry = observedArray[i];
628+
NSDictionary<NSString *, MTRDeviceDataValueDictionary> * expectedEntry = expectedArray[i];
629+
630+
if (![observedEntry isKindOfClass:NSDictionary.class] || ![expectedEntry isKindOfClass:NSDictionary.class]) {
631+
MTR_LOG_ERROR("%@ expected or observed array-value contains entries that are not NSDictionary: %@, %@", self, observedEntry, expectedEntry);
632+
return NO;
633+
}
634+
635+
if (![self _attributeDataValue:observedEntry[MTRDataKey] satisfiesValueExpectation:expectedEntry[MTRDataKey]]) {
636+
return NO;
637+
}
638+
}
639+
640+
return YES;
641+
}
642+
643+
if (![MTRStructureValueType isEqual:expected[MTRTypeKey]]) {
644+
// For everything except arrays and structs, expectation is satisfied
645+
// exactly when the values are equal.
646+
return [self _attributeDataValue:observed isEqualToDataValue:expected];
647+
}
648+
649+
// Now we have two structure-values. Make sure they have the same number of fields
650+
// in them.
651+
if (![observed[MTRValueKey] isKindOfClass:NSArray.class] || ![expected[MTRValueKey] isKindOfClass:NSArray.class]) {
652+
// Malformed data, just claim not equivalent.
653+
MTR_LOG_ERROR("%@ at least one of observed and expected value is not an NSArrray: %@, %@", self, observed, expected);
654+
return NO;
655+
}
656+
657+
NSArray<NSDictionary<NSString *, id> *> * observedArray = observed[MTRValueKey];
658+
NSArray<NSDictionary<NSString *, id> *> * expectedArray = expected[MTRValueKey];
659+
660+
if (observedArray.count != expectedArray.count) {
661+
return NO;
662+
}
663+
664+
for (NSDictionary<NSString *, id> * expectedField in expectedArray) {
665+
if (![expectedField[MTRContextTagKey] isKindOfClass:NSNumber.class] || ![expectedField[MTRDataKey] isKindOfClass:NSDictionary.class]) {
666+
MTR_LOG_ERROR("%@ expected structure-value contains invalid field %@", self, expectedField);
667+
return NO;
668+
}
669+
670+
NSNumber * expectedContextTag = expectedField[MTRContextTagKey];
671+
672+
// Make sure it's present in the other array. In practice, these are
673+
// pretty small arrays, so the O(N^2) behavior here is ok.
674+
BOOL found = NO;
675+
for (NSDictionary<NSString *, id> * observedField in observedArray) {
676+
if (![observedField[MTRContextTagKey] isKindOfClass:NSNumber.class] || ![observedField[MTRDataKey] isKindOfClass:NSDictionary.class]) {
677+
MTR_LOG_ERROR("%@ observed structure-value contains invalid field %@", self, observedField);
678+
return NO;
679+
}
680+
681+
NSNumber * observedContextTag = observedField[MTRContextTagKey];
682+
if ([expectedContextTag isEqual:observedContextTag]) {
683+
found = YES;
684+
685+
// Compare the data.
686+
if (![self _attributeDataValue:observedField[MTRDataKey] satisfiesValueExpectation:expectedField[MTRDataKey]]) {
687+
return NO;
688+
}
689+
690+
// Found a match for the context tag, stop looking.
691+
break;
692+
}
693+
}
694+
695+
if (!found) {
696+
// Context tag present in expected but not observed.
697+
return NO;
698+
}
699+
}
700+
701+
// All entries in the first field array matched entries in the second field
702+
// array. Since the lengths are equal, the two arrays must match, as long
703+
// as all the context tags listed are distinct. If someone produces invalid
704+
// TLV with the same context tag set in it multiple times, this method could
705+
// claim two structure-values are equivalent when the first has two fields
706+
// with context tag N and the second has a field with context tag N and
707+
// another field with context tag M. That should be ok, in practice, but if
708+
// we discover it's not we will need a better algorithm here. It's not
709+
// clear what "equivalent" should mean for such malformed TLV, expecially if
710+
// the same context tag maps to different values in one of the structs.
711+
return YES;
712+
}
713+
552714
@end
553715

554716
/* BEGIN DRAGONS: Note methods here cannot be renamed, and are used by private callers, do not rename, remove or modify behavior here */

src/darwin/Framework/CHIP/MTRDeviceDataValueDictionary.h

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
NS_ASSUME_NONNULL_BEGIN
2020

21+
/**
22+
* A data-value as defined in MTRBaseDevice.h.
23+
*/
2124
typedef NSDictionary<NSString *, id> * MTRDeviceDataValueDictionary;
2225

2326
NS_ASSUME_NONNULL_END

src/darwin/Framework/CHIP/MTRDevice_Concrete.mm

+3-19
Original file line numberDiff line numberDiff line change
@@ -3307,22 +3307,6 @@ - (void)_performScheduledExpirationCheck
33073307
return nil;
33083308
}
33093309

3310-
- (BOOL)_attributeDataValue:(NSDictionary *)one isEqualToDataValue:(NSDictionary *)theOther
3311-
{
3312-
// Sanity check for nil cases
3313-
if (!one && !theOther) {
3314-
MTR_LOG_ERROR("%@ attribute data-value comparison does not expect comparing two nil dictionaries", self);
3315-
return YES;
3316-
}
3317-
if (!one || !theOther) {
3318-
// Comparing against nil is expected, and should return NO quietly
3319-
return NO;
3320-
}
3321-
3322-
// Attribute data-value dictionaries are equal if type and value are equal, and specifically, this should return true if values are both nil
3323-
return [one[MTRTypeKey] isEqual:theOther[MTRTypeKey]] && ((one[MTRValueKey] == theOther[MTRValueKey]) || [one[MTRValueKey] isEqual:theOther[MTRValueKey]]);
3324-
}
3325-
33263310
// Utility to return data value dictionary without data version
33273311
- (NSDictionary *)_dataValueWithoutDataVersion:(NSDictionary *)attributeValue
33283312
{
@@ -3538,9 +3522,9 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
35383522
NSMutableArray * attributePathsToReport = [NSMutableArray array];
35393523
for (NSDictionary<NSString *, id> * attributeResponseValue in reportedAttributeValues) {
35403524
MTRAttributePath * attributePath = attributeResponseValue[MTRAttributePathKey];
3541-
NSDictionary * attributeDataValue = attributeResponseValue[MTRDataKey];
3542-
NSError * attributeError = attributeResponseValue[MTRErrorKey];
3543-
NSDictionary * previousValue;
3525+
MTRDeviceDataValueDictionary _Nullable attributeDataValue = attributeResponseValue[MTRDataKey];
3526+
NSError * _Nullable attributeError = attributeResponseValue[MTRErrorKey];
3527+
MTRDeviceDataValueDictionary _Nullable previousValue;
35443528

35453529
// sanity check either data value or error must exist
35463530
if (!attributeDataValue && !attributeError) {

src/darwin/Framework/CHIP/MTRDevice_Internal.h

+5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#import "MTRAsyncWorkQueue.h"
2424
#import "MTRDefines_Internal.h"
25+
#import "MTRDeviceDataValueDictionary.h"
2526
#import "MTRDeviceStorageBehaviorConfiguration_Internal.h"
2627

2728
NS_ASSUME_NONNULL_BEGIN
@@ -164,6 +165,10 @@ MTR_DIRECT_MEMBERS
164165
- (void)controllerSuspended;
165166
- (void)controllerResumed;
166167

168+
// Methods for comparing attribute data values.
169+
- (BOOL)_attributeDataValue:(MTRDeviceDataValueDictionary)one isEqualToDataValue:(MTRDeviceDataValueDictionary)theOther;
170+
- (BOOL)_attributeDataValue:(MTRDeviceDataValueDictionary)observed satisfiesValueExpectation:(MTRDeviceDataValueDictionary)expected;
171+
167172
@end
168173

169174
#pragma mark - MTRDevice internal state monitoring

0 commit comments

Comments
 (0)