Skip to content

Commit

Permalink
[chip-tool] Add support for commands/attributes arguments formatted a…
Browse files Browse the repository at this point in the history
…s json (#24386)
  • Loading branch information
vivien-apple authored and pull[bot] committed May 7, 2024
1 parent 8b3450d commit 2054209
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 63 deletions.
170 changes: 164 additions & 6 deletions examples/chip-tool/commands/common/Commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,59 @@
#include "Command.h"

#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>

#include <lib/support/Base64.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>

#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<std::string> GetArgumentsFromJson(Command * command, Json::Value & value, bool optional)
{
std::vector<std::string> 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)
Expand Down Expand Up @@ -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<std::string> 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)
Expand Down Expand Up @@ -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<std::string> & 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<std::string> & 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<uint8_t> decodedData;
VerifyOrReturnValue(decodedData.Calloc(expectedMaxDecodedSize + 1 /* for null */), false);

size_t decodedDataSize = chip::Base64Decode(encodedData, static_cast<uint16_t>(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<std::string> & args)
{
std::string arg;
std::stringstream ss(command);
while (ss >> std::quoted(arg, '\''))
{
args.push_back(std::move(arg));
}

return true;
}
6 changes: 5 additions & 1 deletion examples/chip-tool/commands/common/Commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<std::string> & args);
bool DecodeArgumentsFromBase64EncodedJson(const char * encodedData, std::vector<std::string> & args);
bool DecodeArgumentsFromStringStream(const char * command, std::vector<std::string> & args);

std::map<std::string, CommandsVector> mClusters;
#ifdef CONFIG_USE_LOCAL_STORAGE
PersistentStorage mStorage;
Expand Down
16 changes: 16 additions & 0 deletions examples/chip-tool/commands/common/CustomStringPrefix.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
30 changes: 1 addition & 29 deletions examples/chip-tool/commands/interactive/InteractiveCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,8 @@
#include <platform/logging/LogV.h>

#include <editline.h>
#include <iomanip>
#include <sstream>

char kInteractiveModeName[] = "";
constexpr const char * kInteractiveModePrompt = ">>> ";
constexpr uint8_t kInteractiveModeArgumentsMaxLength = 32;
constexpr const char * kInteractiveModeHistoryFilePath = "/tmp/chip_tool_history";
constexpr const char * kInteractiveModeStopCommand = "quit()";

Expand Down Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,8 @@
#include <platform/logging/LogV.h>

#include <editline.h>
#include <iomanip>
#include <sstream>

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()";

Expand Down Expand Up @@ -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;
}

0 comments on commit 2054209

Please sign in to comment.