Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[chip-tool] Add support for unordered commands/attributes arguments formatted a… #24386

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}