From 8c4a1c5270fa22e13090bce64633ec5d0287cbf3 Mon Sep 17 00:00:00 2001
From: Vivien Nicolas <vnicolas@apple.com>
Date: Tue, 22 Nov 2022 14:17:18 +0100
Subject: [PATCH] [chip-tool] Add support for hex:/str: prefixed OCTET_STRING
 for complex arguments instead of always assuming this is an hex string

For compatibility reason the default behavior of assuming this is an hex string is preserved.
---
 .../commands/clusters/ComplexArgument.h       | 56 ++++++++++++++++---
 .../chip-tool/commands/clusters/JsonParser.h  |  2 +
 .../chip-tool/commands/common/Command.cpp     | 20 +++----
 .../commands/common/CustomStringPrefix.h      | 35 ++++++++++++
 4 files changed, 96 insertions(+), 17 deletions(-)
 create mode 100644 examples/chip-tool/commands/common/CustomStringPrefix.h

diff --git a/examples/chip-tool/commands/clusters/ComplexArgument.h b/examples/chip-tool/commands/clusters/ComplexArgument.h
index 8cb23a0b661797..2af80e5b3eef3e 100644
--- a/examples/chip-tool/commands/clusters/ComplexArgument.h
+++ b/examples/chip-tool/commands/clusters/ComplexArgument.h
@@ -16,6 +16,21 @@
  *
  */
 
+/**
+ * This file allocate/free memory using the chip platform abstractions
+ * (Platform::MemoryCalloc and Platform::MemoryFree) for hosting a subset of the
+ * data model internal types until they are consumed by the DataModel::Encode machinery:
+ *   - chip::app:DataModel::List<T>
+ *   - chip::ByteSpan
+ *   - chip::CharSpan
+ *
+ * Memory allocation happens during the 'Setup' phase, while memory deallocation happens
+ * during the 'Finalize' phase.
+ *
+ * The 'Finalize' phase during the destructor phase, and if needed, 'Finalize' will call
+ * the 'Finalize' phase of its descendant.
+ */
+
 #pragma once
 
 #include <app-common/zap-generated/cluster-objects.h>
@@ -173,17 +188,44 @@ class ComplexArgumentParser
             return CHIP_ERROR_INVALID_ARGUMENT;
         }
 
-        if (strlen(value.asCString()) % 2 != 0)
+        auto str         = value.asString();
+        auto size        = str.size();
+        uint8_t * buffer = nullptr;
+
+        if (IsStrString(str.c_str()))
         {
-            ChipLogError(chipTool, "Error while encoding %s as an octet string: Odd number of characters.", label);
-            return CHIP_ERROR_INVALID_STRING_LENGTH;
+            // Skip the prefix
+            str.erase(0, kStrStringPrefixLen);
+            size = str.size();
+
+            buffer = static_cast<uint8_t *>(chip::Platform::MemoryCalloc(size, sizeof(uint8_t)));
+            memcpy(buffer, str.c_str(), size);
         }
+        else
+        {
+            if (IsHexString(str.c_str()))
+            {
+                // Skip the prefix
+                str.erase(0, kHexStringPrefixLen);
+                size = str.size();
+            }
+
+            if (size % 2 != 0)
+            {
+                ChipLogError(chipTool, "Error while encoding %s as a hex string: Odd number of characters.", label);
+                return CHIP_ERROR_INVALID_STRING_LENGTH;
+            }
 
-        size_t size       = strlen(value.asCString());
-        auto buffer       = static_cast<uint8_t *>(chip::Platform::MemoryCalloc(size / 2, sizeof(uint8_t)));
-        size_t octetCount = chip::Encoding::HexToBytes(value.asCString(), size, buffer, size / 2);
+            buffer = static_cast<uint8_t *>(chip::Platform::MemoryCalloc(size / 2, sizeof(uint8_t)));
+            size   = chip::Encoding::HexToBytes(str.c_str(), size, buffer, size / 2);
+            if (size == 0)
+            {
+                ChipLogError(chipTool, "Error while encoding %s as a hex string.", label);
+                return CHIP_ERROR_INTERNAL;
+            }
+        }
 
-        request = chip::ByteSpan(buffer, octetCount);
+        request = chip::ByteSpan(buffer, size);
         return CHIP_NO_ERROR;
     }
 
diff --git a/examples/chip-tool/commands/clusters/JsonParser.h b/examples/chip-tool/commands/clusters/JsonParser.h
index 2b74aee6fffb7c..adb1375bd880d1 100644
--- a/examples/chip-tool/commands/clusters/JsonParser.h
+++ b/examples/chip-tool/commands/clusters/JsonParser.h
@@ -18,6 +18,8 @@
 
 #pragma once
 
+#include "../common/CustomStringPrefix.h"
+
 #include <json/json.h>
 #include <lib/core/Optional.h>
 
diff --git a/examples/chip-tool/commands/common/Command.cpp b/examples/chip-tool/commands/common/Command.cpp
index 0c5777e825df3b..c4161b6ac59908 100644
--- a/examples/chip-tool/commands/common/Command.cpp
+++ b/examples/chip-tool/commands/common/Command.cpp
@@ -17,6 +17,8 @@
  */
 
 #include "Command.h"
+#include "CustomStringPrefix.h"
+#include "platform/PlatformManager.h"
 
 #include <functional>
 #include <netdb.h>
@@ -362,10 +364,9 @@ bool Command::InitArgument(size_t argIndex, char * argValue)
             // We support two ways to pass an octet string argument.  If it happens
             // to be all-ASCII, you can just pass it in.  Otherwise you can pass in
             // "hex:" followed by the hex-encoded bytes.
-            size_t argLen                     = strlen(argValue);
-            static constexpr char hexPrefix[] = "hex:";
-            constexpr size_t prefixLen        = ArraySize(hexPrefix) - 1; // Don't count the null
-            if (strncmp(argValue, hexPrefix, prefixLen) == 0)
+            size_t argLen = strlen(argValue);
+
+            if (IsHexString(argValue))
             {
                 // Hex-encoded.  Decode it into a temporary buffer first, so if we
                 // run into errors we can do correct "argument is not valid" logging
@@ -381,7 +382,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue)
                     return false;
                 }
 
-                size_t octetCount = chip::Encoding::HexToBytes(argValue + prefixLen, argLen - prefixLen, buffer.Get(), argLen);
+                size_t octetCount =
+                    chip::Encoding::HexToBytes(argValue + kHexStringPrefixLen, argLen - kHexStringPrefixLen, buffer.Get(), argLen);
                 if (octetCount == 0)
                 {
                     return false;
@@ -393,13 +395,11 @@ bool Command::InitArgument(size_t argIndex, char * argValue)
             }
 
             // Just ASCII.  Check for the "str:" prefix.
-            static constexpr char strPrefix[] = "str:";
-            constexpr size_t strPrefixLen     = ArraySize(strPrefix) - 1; // Don't         count the null
-            if (strncmp(argValue, strPrefix, strPrefixLen) == 0)
+            if (IsStrString(argValue))
             {
                 // Skip the prefix
-                argValue += strPrefixLen;
-                argLen -= strPrefixLen;
+                argValue += kStrStringPrefixLen;
+                argLen -= kStrStringPrefixLen;
             }
             *value = chip::ByteSpan(chip::Uint8::from_char(argValue), argLen);
             return true;
diff --git a/examples/chip-tool/commands/common/CustomStringPrefix.h b/examples/chip-tool/commands/common/CustomStringPrefix.h
new file mode 100644
index 00000000000000..a3133b3876b393
--- /dev/null
+++ b/examples/chip-tool/commands/common/CustomStringPrefix.h
@@ -0,0 +1,35 @@
+/*
+ *   Copyright (c) 2022 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   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
+
+static constexpr char kHexStringPrefix[] = "hex:";
+constexpr size_t kHexStringPrefixLen     = ArraySize(kHexStringPrefix) - 1; // Don't count the null
+
+static constexpr char kStrStringPrefix[] = "str:";
+constexpr size_t kStrStringPrefixLen     = ArraySize(kStrStringPrefix) - 1; // Don't count the null
+
+inline bool IsHexString(const char * str)
+{
+    return strncmp(str, kHexStringPrefix, kHexStringPrefixLen) == 0;
+}
+
+inline bool IsStrString(const char * str)
+{
+    return strncmp(str, kStrStringPrefix, kStrStringPrefixLen) == 0;
+}