From b68b5ee9d7f5a1ba06e37bd9b523d918bb8c761d Mon Sep 17 00:00:00 2001 From: Vivien Nicolas Date: Mon, 23 Jan 2023 17:49:55 +0100 Subject: [PATCH] [chip-tool] Add support for commands/attributes arguments formatted as json (#24386) --- .../chip-tool/commands/common/Commands.cpp | 170 +++++++++++++++++- examples/chip-tool/commands/common/Commands.h | 6 +- .../commands/common/CustomStringPrefix.h | 16 ++ .../interactive/InteractiveCommands.cpp | 30 +--- .../interactive/InteractiveCommands.mm | 28 +-- 5 files changed, 187 insertions(+), 63 deletions(-) diff --git a/examples/chip-tool/commands/common/Commands.cpp b/examples/chip-tool/commands/common/Commands.cpp index 4e9c508f0fb4f8..1b22c681deabda 100644 --- a/examples/chip-tool/commands/common/Commands.cpp +++ b/examples/chip-tool/commands/common/Commands.cpp @@ -21,11 +21,59 @@ #include "Command.h" #include +#include +#include #include +#include #include #include +#include "../clusters/JsonParser.h" + +namespace { + +char kInteractiveModeName[] = ""; +constexpr size_t kInteractiveModeArgumentsMaxLength = 32; +constexpr const char * kOptionalArgumentPrefix = "--"; +constexpr const char * kJsonClusterKey = "cluster"; +constexpr const char * kJsonCommandKey = "command"; +constexpr const char * kJsonCommandSpecifierKey = "command_specifier"; +constexpr const char * kJsonArgumentsKey = "arguments"; + +std::vector GetArgumentsFromJson(Command * command, Json::Value & value, bool optional) +{ + std::vector args; + for (size_t i = 0; i < command->GetArgumentsCount(); i++) + { + auto argName = command->GetArgumentName(i); + for (auto const & memberName : value.getMemberNames()) + { + if (strcasecmp(argName, memberName.c_str()) != 0) + { + continue; + } + + if (command->GetArgumentIsOptional(i) != optional) + { + continue; + } + + if (optional) + { + args.push_back(std::string(kOptionalArgumentPrefix) + argName); + } + + auto argValue = value[memberName].asString(); + args.push_back(std::move(argValue)); + break; + } + } + return args; +}; + +} // namespace + void Commands::Register(const char * clusterName, commands_list commandsList) { for (auto & command : commandsList) @@ -55,15 +103,38 @@ int Commands::Run(int argc, char ** argv) return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE; } -int Commands::RunInteractive(int argc, char ** argv) +int Commands::RunInteractive(const char * command) { - CHIP_ERROR err = RunCommand(argc, argv, true); - if (err == CHIP_NO_ERROR) + std::vector arguments; + VerifyOrReturnValue(DecodeArgumentsFromInteractiveMode(command, arguments), EXIT_FAILURE); + + if (arguments.size() > (kInteractiveModeArgumentsMaxLength - 1 /* for interactive mode name */)) + { + ChipLogError(chipTool, "Too many arguments. Ignoring."); + arguments.resize(kInteractiveModeArgumentsMaxLength - 1); + } + + int argc = 0; + char * argv[kInteractiveModeArgumentsMaxLength] = {}; + argv[argc++] = kInteractiveModeName; + + for (auto & arg : arguments) + { + argv[argc] = new char[arg.size() + 1]; + strcpy(argv[argc++], arg.c_str()); + } + + auto err = RunCommand(argc, argv, true); + + // Do not delete arg[0] + for (auto i = 1; i < argc; i++) { - return EXIT_SUCCESS; + delete[] argv[i]; } - ChipLogError(chipTool, "Run command failure: %s", chip::ErrorStr(err)); - return EXIT_FAILURE; + + VerifyOrReturnValue(CHIP_NO_ERROR == err, EXIT_FAILURE, ChipLogError(chipTool, "Run command failure: %s", chip::ErrorStr(err))); + + return EXIT_SUCCESS; } CHIP_ERROR Commands::RunCommand(int argc, char ** argv, bool interactive) @@ -368,3 +439,90 @@ void Commands::ShowCommand(std::string executable, std::string clusterName, Comm fprintf(stderr, "%s\n", description.c_str()); } } + +bool Commands::DecodeArgumentsFromInteractiveMode(const char * command, std::vector & args) +{ + // Remote clients may not know the ordering of arguments, so instead of a strict ordering arguments can + // be passed in as a json payload encoded in base64 and are reordered on the fly. + return IsJsonString(command) ? DecodeArgumentsFromBase64EncodedJson(command, args) + : DecodeArgumentsFromStringStream(command, args); +} + +bool Commands::DecodeArgumentsFromBase64EncodedJson(const char * json, std::vector & args) +{ + Json::Value jsonValue; + bool parsed = JsonParser::ParseCustomArgument(json, json + kJsonStringPrefixLen, jsonValue); + VerifyOrReturnValue(parsed, false, ChipLogError(chipTool, "Error while parsing json.")); + VerifyOrReturnValue(jsonValue.isObject(), false, ChipLogError(chipTool, "Unexpected json type.")); + VerifyOrReturnValue(jsonValue.isMember(kJsonClusterKey), false, + ChipLogError(chipTool, "'%s' key not found in json.", kJsonClusterKey)); + VerifyOrReturnValue(jsonValue.isMember(kJsonCommandKey), false, + ChipLogError(chipTool, "'%s' key not found in json.", kJsonCommandKey)); + VerifyOrReturnValue(jsonValue.isMember(kJsonArgumentsKey), false, + ChipLogError(chipTool, "'%s' key not found in json.", kJsonArgumentsKey)); + VerifyOrReturnValue(IsBase64String(jsonValue[kJsonArgumentsKey].asString().c_str()), false, + ChipLogError(chipTool, "'arguments' is not a base64 string.")); + + auto clusterName = jsonValue[kJsonClusterKey].asString(); + auto commandName = jsonValue[kJsonCommandKey].asString(); + auto arguments = jsonValue[kJsonArgumentsKey].asString(); + + auto cluster = GetCluster(clusterName); + VerifyOrReturnValue(cluster != mClusters.end(), false, + ChipLogError(chipTool, "Cluster '%s' is not supported.", clusterName.c_str())); + + auto command = GetCommand(cluster->second, commandName); + + if (jsonValue.isMember(kJsonCommandSpecifierKey) && IsGlobalCommand(commandName)) + { + auto commandSpecifierName = jsonValue[kJsonCommandSpecifierKey].asString(); + command = GetGlobalCommand(cluster->second, commandName, commandSpecifierName); + } + VerifyOrReturnValue(nullptr != command, false, ChipLogError(chipTool, "Unknown command.")); + + auto encodedData = arguments.c_str(); + encodedData += kBase64StringPrefixLen; + + size_t encodedDataSize = strlen(encodedData); + size_t expectedMaxDecodedSize = BASE64_MAX_DECODED_LEN(encodedDataSize); + + chip::Platform::ScopedMemoryBuffer decodedData; + VerifyOrReturnValue(decodedData.Calloc(expectedMaxDecodedSize + 1 /* for null */), false); + + size_t decodedDataSize = chip::Base64Decode(encodedData, static_cast(encodedDataSize), decodedData.Get()); + VerifyOrReturnValue(decodedDataSize != 0, false, ChipLogError(chipTool, "Error while decoding base64 data.")); + + decodedData.Get()[decodedDataSize] = '\0'; + + Json::Value jsonArguments; + bool parsedArguments = JsonParser::ParseCustomArgument(encodedData, chip::Uint8::to_char(decodedData.Get()), jsonArguments); + VerifyOrReturnValue(parsedArguments, false, ChipLogError(chipTool, "Error while parsing json.")); + VerifyOrReturnValue(jsonArguments.isObject(), false, ChipLogError(chipTool, "Unexpected json type, expects and object.")); + + auto mandatoryArguments = GetArgumentsFromJson(command, jsonArguments, false /* addOptional */); + auto optionalArguments = GetArgumentsFromJson(command, jsonArguments, true /* addOptional */); + + args.push_back(std::move(clusterName)); + args.push_back(std::move(commandName)); + if (jsonValue.isMember(kJsonCommandSpecifierKey)) + { + auto commandSpecifierName = jsonValue[kJsonCommandSpecifierKey].asString(); + args.push_back(std::move(commandSpecifierName)); + } + args.insert(args.end(), mandatoryArguments.begin(), mandatoryArguments.end()); + args.insert(args.end(), optionalArguments.begin(), optionalArguments.end()); + + return true; +} + +bool Commands::DecodeArgumentsFromStringStream(const char * command, std::vector & args) +{ + std::string arg; + std::stringstream ss(command); + while (ss >> std::quoted(arg, '\'')) + { + args.push_back(std::move(arg)); + } + + return true; +} diff --git a/examples/chip-tool/commands/common/Commands.h b/examples/chip-tool/commands/common/Commands.h index 57475b1c027087..80f585931f65db 100644 --- a/examples/chip-tool/commands/common/Commands.h +++ b/examples/chip-tool/commands/common/Commands.h @@ -32,7 +32,7 @@ class Commands void Register(const char * clusterName, commands_list commandsList); int Run(int argc, char ** argv); - int RunInteractive(int argc, char ** argv); + int RunInteractive(const char * command); private: CHIP_ERROR RunCommand(int argc, char ** argv, bool interactive = false); @@ -50,6 +50,10 @@ class Commands void ShowClusterEvents(std::string executable, std::string clusterName, std::string commandName, CommandsVector & commands); void ShowCommand(std::string executable, std::string clusterName, Command * command); + bool DecodeArgumentsFromInteractiveMode(const char * command, std::vector & args); + bool DecodeArgumentsFromBase64EncodedJson(const char * encodedData, std::vector & args); + bool DecodeArgumentsFromStringStream(const char * command, std::vector & args); + std::map mClusters; #ifdef CONFIG_USE_LOCAL_STORAGE PersistentStorage mStorage; diff --git a/examples/chip-tool/commands/common/CustomStringPrefix.h b/examples/chip-tool/commands/common/CustomStringPrefix.h index a3133b3876b393..b761a358f5850c 100644 --- a/examples/chip-tool/commands/common/CustomStringPrefix.h +++ b/examples/chip-tool/commands/common/CustomStringPrefix.h @@ -18,12 +18,28 @@ #pragma once +static constexpr char kJsonStringPrefix[] = "json:"; +constexpr size_t kJsonStringPrefixLen = ArraySize(kJsonStringPrefix) - 1; // Don't count the null + +static constexpr char kBase64StringPrefix[] = "base64:"; +constexpr size_t kBase64StringPrefixLen = ArraySize(kBase64StringPrefix) - 1; // Don't count the null + 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 IsJsonString(const char * str) +{ + return strncmp(str, kJsonStringPrefix, kJsonStringPrefixLen) == 0; +} + +inline bool IsBase64String(const char * str) +{ + return strncmp(str, kBase64StringPrefix, kBase64StringPrefixLen) == 0; +} + inline bool IsHexString(const char * str) { return strncmp(str, kHexStringPrefix, kHexStringPrefixLen) == 0; diff --git a/examples/chip-tool/commands/interactive/InteractiveCommands.cpp b/examples/chip-tool/commands/interactive/InteractiveCommands.cpp index e887391a12031a..73b7017352a104 100644 --- a/examples/chip-tool/commands/interactive/InteractiveCommands.cpp +++ b/examples/chip-tool/commands/interactive/InteractiveCommands.cpp @@ -21,12 +21,8 @@ #include #include -#include -#include -char kInteractiveModeName[] = ""; constexpr const char * kInteractiveModePrompt = ">>> "; -constexpr uint8_t kInteractiveModeArgumentsMaxLength = 32; constexpr const char * kInteractiveModeHistoryFilePath = "/tmp/chip_tool_history"; constexpr const char * kInteractiveModeStopCommand = "quit()"; @@ -114,31 +110,7 @@ bool InteractiveCommand::ParseCommand(char * command) return false; } - char * args[kInteractiveModeArgumentsMaxLength]; - args[0] = kInteractiveModeName; - int argsCount = 1; - std::string arg; - - std::stringstream ss(command); - while (ss >> std::quoted(arg, '\'')) - { - if (argsCount == kInteractiveModeArgumentsMaxLength) - { - ChipLogError(chipTool, "Too many arguments. Ignoring."); - return true; - } - - char * carg = new char[arg.size() + 1]; - strcpy(carg, arg.c_str()); - args[argsCount++] = carg; - } - ClearLine(); - mHandler->RunInteractive(argsCount, args); - - // Do not delete arg[0] - while (--argsCount) - delete[] args[argsCount]; - + mHandler->RunInteractive(command); return true; } diff --git a/examples/darwin-framework-tool/commands/interactive/InteractiveCommands.mm b/examples/darwin-framework-tool/commands/interactive/InteractiveCommands.mm index a251d517e6b83e..4188da365574c5 100644 --- a/examples/darwin-framework-tool/commands/interactive/InteractiveCommands.mm +++ b/examples/darwin-framework-tool/commands/interactive/InteractiveCommands.mm @@ -21,12 +21,8 @@ #include #include -#include -#include -char kInteractiveModeName[] = ""; constexpr const char * kInteractiveModePrompt = ">>> "; -constexpr uint8_t kInteractiveModeArgumentsMaxLength = 32; constexpr const char * kInteractiveModeHistoryFilePath = "/tmp/darwin_framework_tool_history"; constexpr const char * kInteractiveModeStopCommand = "quit()"; @@ -144,29 +140,7 @@ el_status_t StopFunction() return NO; } - char * args[kInteractiveModeArgumentsMaxLength]; - args[0] = kInteractiveModeName; - int argsCount = 1; - std::string arg; - - std::stringstream ss(command); - while (ss >> std::quoted(arg, '\'')) { - if (argsCount == kInteractiveModeArgumentsMaxLength) { - ChipLogError(chipTool, "Too many arguments. Ignoring."); - return YES; - } - - char * carg = new char[arg.size() + 1]; - strcpy(carg, arg.c_str()); - args[argsCount++] = carg; - } - ClearLine(); - mHandler->RunInteractive(argsCount, args); - - // Do not delete arg[0] - while (--argsCount) - delete[] args[argsCount]; - + mHandler->RunInteractive(command); return YES; }