@@ -539,6 +539,8 @@ - (BOOL)deviceCachePrimed
539
539
return NO ;
540
540
}
541
541
542
+ #pragma mark - Suspend/resume management
543
+
542
544
- (void )controllerSuspended
543
545
{
544
546
// Nothing to do for now.
@@ -549,6 +551,166 @@ - (void)controllerResumed
549
551
// Nothing to do for now.
550
552
}
551
553
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
+
552
714
@end
553
715
554
716
/* BEGIN DRAGONS: Note methods here cannot be renamed, and are used by private callers, do not rename, remove or modify behavior here */
0 commit comments