From 8140979e80fbcc4f6084a7dcca7c0dc3a4102f72 Mon Sep 17 00:00:00 2001
From: Boris Zbarsky <bzbarsky@apple.com>
Date: Tue, 1 Aug 2023 12:41:03 -0400
Subject: [PATCH] Add utilities for converting CATValues to/from NSSet.

---
 .../Framework/CHIP/MTRDeviceController.mm     | 23 +-----
 .../CHIP/MTRDeviceControllerStartupParams.mm  |  9 +--
 .../CHIP/MTROperationalCredentialsDelegate.mm |  6 +-
 .../Framework/CHIP/NSSetCATConversion.h       | 74 +++++++++++++++++++
 .../Matter.xcodeproj/project.pbxproj          |  4 +
 5 files changed, 86 insertions(+), 30 deletions(-)
 create mode 100644 src/darwin/Framework/CHIP/NSSetCATConversion.h

diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm
index 8b34769b09ecea..a0da54001a3a94 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm
@@ -35,6 +35,7 @@
 #import "MTRPersistentStorageDelegateBridge.h"
 #import "MTRSetupPayload.h"
 #import "NSDataSpanConversion.h"
+#import "NSSetCATConversion.h"
 #import "NSStringSpanConversion.h"
 #import <setup_payload/ManualSetupPayloadGenerator.h>
 #import <setup_payload/SetupPayload.h>
@@ -312,27 +313,11 @@ - (BOOL)startup:(MTRDeviceControllerStartupParamsInternal *)startupParams
 
             chip::CATValues cats = chip::kUndefinedCATs;
             if (startupParams.caseAuthenticatedTags != nil) {
-                unsigned long long tagCount = startupParams.caseAuthenticatedTags.count;
-                if (tagCount > chip::kMaxSubjectCATAttributeCount) {
-                    MTR_LOG_ERROR("%llu CASE Authenticated Tags cannot be represented in a certificate.", tagCount);
+                errorCode = ToCATValues(startupParams.caseAuthenticatedTags, cats);
+                if (errorCode != CHIP_NO_ERROR) {
+                    // ToCATValues already handles logging.
                     return;
                 }
-
-                size_t tagIndex = 0;
-                for (NSNumber * boxedTag in startupParams.caseAuthenticatedTags) {
-                    if (!chip::CanCastTo<chip::CASEAuthTag>(boxedTag.unsignedLongLongValue)) {
-                        MTR_LOG_ERROR("0x%llx is not a valid CASE Authenticated Tag value.", boxedTag.unsignedLongLongValue);
-                        return;
-                    }
-
-                    auto tag = static_cast<chip::CASEAuthTag>(boxedTag.unsignedLongLongValue);
-                    if (!chip::IsValidCASEAuthTag(tag)) {
-                        MTR_LOG_ERROR("0x%" PRIx32 " is not a valid CASE Authenticated Tag value.", tag);
-                        return;
-                    }
-
-                    cats.values[tagIndex++] = tag;
-                }
             }
 
             if (commissionerParams.operationalKeypair != nullptr) {
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm
index 771995bf01bc66..7c8885d92e7ae0 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm
@@ -20,6 +20,7 @@
 #import "MTRLogging_Internal.h"
 #import "MTRP256KeypairBridge.h"
 #import "NSDataSpanConversion.h"
+#import "NSSetCATConversion.h"
 
 #include <controller/OperationalCredentialsDelegate.h>
 #include <credentials/CHIPCert.h>
@@ -302,13 +303,7 @@ - (instancetype)initForExistingFabric:(FabricTable *)fabricTable
 
         auto tagCount = cats.GetNumTagsPresent();
         if (tagCount > 0) {
-            auto * catSet = [[NSMutableSet alloc] initWithCapacity:tagCount];
-            for (auto & value : cats.values) {
-                if (value != kUndefinedCAT) {
-                    [catSet addObject:@(value)];
-                }
-            }
-            self.caseAuthenticatedTags = [NSSet setWithSet:catSet];
+            self.caseAuthenticatedTags = FromCATValues(cats);
         } else {
             self.caseAuthenticatedTags = nil;
         }
diff --git a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm
index 7e5282eca51a52..a9edca0b6cdf26 100644
--- a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm
+++ b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm
@@ -25,6 +25,7 @@
 #import "MTRDeviceController_Internal.h"
 #import "MTRLogging_Internal.h"
 #import "NSDataSpanConversion.h"
+#import "NSSetCATConversion.h"
 
 #include <controller/CommissioningDelegate.h>
 #include <credentials/CHIPCert.h>
@@ -469,10 +470,7 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId)
 
     CATValues cats;
     if (caseAuthenticatedTags != nil) {
-        size_t idx = 0;
-        for (NSNumber * cat in [caseAuthenticatedTags.allObjects sortedArrayUsingSelector:@selector(compare:)]) {
-            cats.values[idx++] = [cat unsignedIntValue];
-        }
+        ReturnErrorOnFailure(ToCATValues(caseAuthenticatedTags, cats));
     }
 
     uint8_t nocBuffer[Controller::kMaxCHIPDERCertLength];
diff --git a/src/darwin/Framework/CHIP/NSSetCATConversion.h b/src/darwin/Framework/CHIP/NSSetCATConversion.h
new file mode 100644
index 00000000000000..d6fc8a4b03e8b0
--- /dev/null
+++ b/src/darwin/Framework/CHIP/NSSetCATConversion.h
@@ -0,0 +1,74 @@
+/**
+ *    Copyright (c) 2023 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.
+ */
+
+#pragma once
+
+#import <Foundation/Foundation.h>
+
+#import "MTRLogging_Internal.h"
+
+#include <lib/core/CASEAuthTag.h>
+#include <lib/core/CHIPError.h>
+#include <lib/support/SafeInt.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Utilities for converting between NSSet<NSNumber *> and chip::CATValues.
+ */
+
+inline CHIP_ERROR ToCATValues(NSSet<NSNumber *> * catSet, chip::CATValues & values)
+{
+    values = chip::kUndefinedCATs;
+
+    unsigned long long tagCount = catSet.count;
+    if (tagCount > chip::kMaxSubjectCATAttributeCount) {
+        MTR_LOG_ERROR("%llu CASE Authenticated Tags cannot be represented in a certificate.", tagCount);
+        return CHIP_ERROR_INVALID_ARGUMENT;
+    }
+
+    size_t tagIndex = 0;
+    for (NSNumber * boxedTag in [catSet.allObjects sortedArrayUsingSelector:@selector(compare:)]) {
+        auto unboxedTag = boxedTag.unsignedLongLongValue;
+        if (!chip::CanCastTo<chip::CASEAuthTag>(unboxedTag)) {
+            MTR_LOG_ERROR("0x%llx is not a valid CASE Authenticated Tag value.", unboxedTag);
+            return CHIP_ERROR_INVALID_ARGUMENT;
+        }
+
+        auto tag = static_cast<chip::CASEAuthTag>(unboxedTag);
+        if (!chip::IsValidCASEAuthTag(tag)) {
+            MTR_LOG_ERROR("0x%" PRIx32 " is not a valid CASE Authenticated Tag value.", tag);
+            return CHIP_ERROR_INVALID_ARGUMENT;
+        }
+
+        values.values[tagIndex++] = tag;
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+inline NSSet<NSNumber *> * FromCATValues(const chip::CATValues & values)
+{
+    auto * catSet = [[NSMutableSet alloc] initWithCapacity:values.GetNumTagsPresent()];
+    for (auto & value : values.values) {
+        if (value != chip::kUndefinedCAT) {
+            [catSet addObject:@(value)];
+        }
+    }
+    return [NSSet setWithSet:catSet];
+}
+
+NS_ASSUME_NONNULL_END
diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
index fea8bd9966ac2a..96dfaa203fddfc 100644
--- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
+++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
@@ -154,6 +154,7 @@
 		51431AF927D2973E008A7943 /* MTRIMDispatch.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51431AF827D2973E008A7943 /* MTRIMDispatch.mm */; };
 		51431AFB27D29CA4008A7943 /* ota-provider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51431AFA27D29CA4008A7943 /* ota-provider.cpp */; };
 		5143851E2A65885500EDC8E6 /* MTRSwiftPairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5143851D2A65885500EDC8E6 /* MTRSwiftPairingTests.swift */; };
+		51565CAC2A796B8200469F18 /* NSSetCATConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 51565CAB2A796B8200469F18 /* NSSetCATConversion.h */; };
 		515C1C6F284F9FFB00A48F0C /* MTRFramework.mm in Sources */ = {isa = PBXBuildFile; fileRef = 515C1C6D284F9FFB00A48F0C /* MTRFramework.mm */; };
 		515C1C70284F9FFB00A48F0C /* MTRFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 515C1C6E284F9FFB00A48F0C /* MTRFramework.h */; };
 		51669AF02913204400F4AA36 /* MTRBackwardsCompatTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51669AEF2913204400F4AA36 /* MTRBackwardsCompatTests.m */; };
@@ -458,6 +459,7 @@
 		51431AFA27D29CA4008A7943 /* ota-provider.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "ota-provider.cpp"; path = "clusters/ota-provider/ota-provider.cpp"; sourceTree = "<group>"; };
 		5143851C2A65885400EDC8E6 /* MatterTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MatterTests-Bridging-Header.h"; sourceTree = "<group>"; };
 		5143851D2A65885500EDC8E6 /* MTRSwiftPairingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTRSwiftPairingTests.swift; sourceTree = "<group>"; };
+		51565CAB2A796B8200469F18 /* NSSetCATConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSSetCATConversion.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>"; };
 		51669AEF2913204400F4AA36 /* MTRBackwardsCompatTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRBackwardsCompatTests.m; sourceTree = "<group>"; };
@@ -1098,6 +1100,7 @@
 				997DED152695343400975E97 /* MTRThreadOperationalDataset.mm */,
 				3D843710294977000070D20A /* NSDataSpanConversion.h */,
 				3D84370E294977000070D20A /* NSStringSpanConversion.h */,
+				51565CAB2A796B8200469F18 /* NSSetCATConversion.h */,
 				5117DD3729A931AE00FFA1AA /* MTROperationalBrowser.h */,
 				5117DD3629A931AD00FFA1AA /* MTROperationalBrowser.mm */,
 				5173A47229C0E2ED00F67F48 /* MTRFabricInfo_Internal.h */,
@@ -1213,6 +1216,7 @@
 				5ACDDD7A27CD129700EFD68A /* MTRClusterStateCacheContainer.h in Headers */,
 				5A6FEC9227B5669C00F25F42 /* MTRDeviceControllerOverXPC.h in Headers */,
 				5117DD3929A931AE00FFA1AA /* MTROperationalBrowser.h in Headers */,
+				51565CAC2A796B8200469F18 /* NSSetCATConversion.h in Headers */,
 				2C1B027B2641DB4E00780EF1 /* MTROperationalCredentialsDelegate.h in Headers */,
 				5173A47529C0E2ED00F67F48 /* MTRFabricInfo_Internal.h in Headers */,
 				3D843717294979230070D20A /* MTRClusters_Internal.h in Headers */,