blob: 6a815f98e896ece4b0a7edd907b1bcfbbf592a36 [file] [log] [blame]
Vivien Nicolas4be198b2020-09-11 17:10:50 +02001/*
2 * Copyright (c) 2020 Project CHIP Authors
3 * All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +020019#include "Commands.h"
Vivien Nicolas4be198b2020-09-11 17:10:50 +020020
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +020021#include "Command.h"
Vivien Nicolas6b140c42020-10-13 16:22:28 +020022
23#include <algorithm>
Vivien Nicolasfc4281c2023-01-23 17:49:55 +010024#include <iomanip>
25#include <sstream>
Vivien Nicolas4be198b2020-09-11 17:10:50 +020026#include <string>
27
Vivien Nicolasfc4281c2023-01-23 17:49:55 +010028#include <lib/support/Base64.h>
Zang MingJie53dd5832021-09-03 03:05:16 +080029#include <lib/support/CHIPMem.h>
30#include <lib/support/CodeUtils.h>
Boris Zbarsky55607f42023-09-22 10:35:35 -040031#include <platform/CHIPDeviceConfig.h>
Boris Zbarsky8edb9f02023-09-13 07:49:30 -040032#include <platform/KeyValueStoreManager.h>
Sagar Dhawanae69dd72021-09-29 15:13:09 -070033
Vivien Nicolasfc4281c2023-01-23 17:49:55 +010034#include "../clusters/JsonParser.h"
35
36namespace {
37
38char kInteractiveModeName[] = "";
39constexpr size_t kInteractiveModeArgumentsMaxLength = 32;
Michael Spang547b5872023-12-05 09:25:54 -050040constexpr char kOptionalArgumentPrefix[] = "--";
41constexpr char kJsonClusterKey[] = "cluster";
42constexpr char kJsonCommandKey[] = "command";
43constexpr char kJsonCommandSpecifierKey[] = "command_specifier";
44constexpr char kJsonArgumentsKey[] = "arguments";
Vivien Nicolasfc4281c2023-01-23 17:49:55 +010045
Boris Zbarsky55607f42023-09-22 10:35:35 -040046#if !CHIP_DISABLE_PLATFORM_KVS
Boris Zbarsky8edb9f02023-09-13 07:49:30 -040047template <typename T>
48struct HasInitWithString
49{
50 template <typename U>
51 static constexpr auto check(U *) -> typename std::is_same<decltype(std::declval<U>().Init("")), CHIP_ERROR>::type;
52
53 template <typename>
54 static constexpr std::false_type check(...);
55
56 typedef decltype(check<std::remove_reference_t<T>>(nullptr)) type;
57
58public:
59 static constexpr bool value = type::value;
60};
61
62// Template so we can do conditional enabling
63template <typename T, std::enable_if_t<HasInitWithString<T>::value, int> = 0>
64static void UseStorageDirectory(T & storageManagerImpl, const char * storageDirectory)
65{
Boris Zbarsky8edb9f02023-09-13 07:49:30 -040066 std::string platformKVS = std::string(storageDirectory) + "/chip_tool_kvs";
67 storageManagerImpl.Init(platformKVS.c_str());
Boris Zbarsky8edb9f02023-09-13 07:49:30 -040068}
69
70template <typename T, std::enable_if_t<!HasInitWithString<T>::value, int> = 0>
71static void UseStorageDirectory(T & storageManagerImpl, const char * storageDirectory)
72{}
Boris Zbarsky55607f42023-09-22 10:35:35 -040073#endif // !CHIP_DISABLE_PLATFORM_KVS
Boris Zbarsky8edb9f02023-09-13 07:49:30 -040074
Vivien Nicolasa64797f2023-02-06 22:15:54 +010075bool GetArgumentsFromJson(Command * command, Json::Value & value, bool optional, std::vector<std::string> & outArgs)
Vivien Nicolasfc4281c2023-01-23 17:49:55 +010076{
Vivien Nicolasa64797f2023-02-06 22:15:54 +010077 auto memberNames = value.getMemberNames();
78
Vivien Nicolasfc4281c2023-01-23 17:49:55 +010079 std::vector<std::string> args;
80 for (size_t i = 0; i < command->GetArgumentsCount(); i++)
81 {
Vivien Nicolasa64797f2023-02-06 22:15:54 +010082 auto argName = command->GetArgumentName(i);
83 auto memberNamesIterator = memberNames.begin();
84 while (memberNamesIterator != memberNames.end())
Vivien Nicolasfc4281c2023-01-23 17:49:55 +010085 {
Vivien Nicolasa64797f2023-02-06 22:15:54 +010086 auto memberName = *memberNamesIterator;
Vivien Nicolasfc4281c2023-01-23 17:49:55 +010087 if (strcasecmp(argName, memberName.c_str()) != 0)
88 {
Vivien Nicolasa64797f2023-02-06 22:15:54 +010089 memberNamesIterator++;
Vivien Nicolasfc4281c2023-01-23 17:49:55 +010090 continue;
91 }
92
93 if (command->GetArgumentIsOptional(i) != optional)
94 {
Vivien Nicolasa64797f2023-02-06 22:15:54 +010095 memberNamesIterator = memberNames.erase(memberNamesIterator);
Vivien Nicolasfc4281c2023-01-23 17:49:55 +010096 continue;
97 }
98
99 if (optional)
100 {
101 args.push_back(std::string(kOptionalArgumentPrefix) + argName);
102 }
103
104 auto argValue = value[memberName].asString();
105 args.push_back(std::move(argValue));
Vivien Nicolasa64797f2023-02-06 22:15:54 +0100106 memberNamesIterator = memberNames.erase(memberNamesIterator);
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100107 break;
108 }
109 }
Vivien Nicolasa64797f2023-02-06 22:15:54 +0100110
111 if (memberNames.size())
112 {
113 auto memberName = memberNames.front();
114 ChipLogError(chipTool, "The argument \"\%s\" is not supported.", memberName.c_str());
115 return false;
116 }
117
118 outArgs = args;
119 return true;
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100120};
121
Boris Zbarskybdfee0e2023-02-02 20:36:44 -0500122// Check for arguments with a starting '"' but no ending '"': those
123// would indicate that people are using double-quoting, not single
124// quoting, on arguments with spaces.
125static void DetectAndLogMismatchedDoubleQuotes(int argc, char ** argv)
126{
127 for (int curArg = 0; curArg < argc; ++curArg)
128 {
129 char * arg = argv[curArg];
130 if (!arg)
131 {
132 continue;
133 }
134
135 auto len = strlen(arg);
136 if (len == 0)
137 {
138 continue;
139 }
140
141 if (arg[0] == '"' && arg[len - 1] != '"')
142 {
143 ChipLogError(chipTool,
144 "Mismatched '\"' detected in argument: '%s'. Use single quotes to delimit arguments with spaces "
145 "in them: 'x y', not \"x y\".",
146 arg);
147 }
148 }
149}
150
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100151} // namespace
152
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400153void Commands::Register(const char * commandSetName, commands_list commandsList, const char * helpText, bool isCluster)
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200154{
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400155 VerifyOrDieWithMsg(isCluster || helpText != nullptr, chipTool, "Non-cluster command sets must have help text");
156 mCommandSets[commandSetName].isCluster = isCluster;
157 mCommandSets[commandSetName].helpText = helpText;
Vivien Nicolas010a4972020-10-02 22:27:18 +0200158 for (auto & command : commandsList)
159 {
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400160 mCommandSets[commandSetName].commands.push_back(std::move(command));
Vivien Nicolas010a4972020-10-02 22:27:18 +0200161 }
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200162}
Vivien Nicolas4be198b2020-09-11 17:10:50 +0200163
Boris Zbarsky1571f742021-07-16 18:24:05 -0400164int Commands::Run(int argc, char ** argv)
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200165{
Vivien Nicolasf3826252020-12-01 18:27:45 +0100166 CHIP_ERROR err = CHIP_NO_ERROR;
Pankaj Garg7b000572021-08-13 20:14:29 -0700167
Vivien Nicolasf3826252020-12-01 18:27:45 +0100168 err = chip::Platform::MemoryInit();
169 VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(Controller, "Init Memory failure: %s", chip::ErrorStr(err)));
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200170
Vivien Nicolasc47353f2022-06-15 16:09:50 +0200171#ifdef CONFIG_USE_LOCAL_STORAGE
Jerry Johns6cf91db2021-06-14 11:53:53 -0700172 err = mStorage.Init();
Vivien Nicolasf3826252020-12-01 18:27:45 +0100173 VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(Controller, "Init Storage failure: %s", chip::ErrorStr(err)));
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200174
Jerry Johns6cf91db2021-06-14 11:53:53 -0700175 chip::Logging::SetLogFilter(mStorage.GetLoggingLevel());
Vivien Nicolasc47353f2022-06-15 16:09:50 +0200176#endif // CONFIG_USE_LOCAL_STORAGE
Boris Zbarsky1571f742021-07-16 18:24:05 -0400177
Vivien Nicolas6afd51d2021-12-15 15:18:59 +0100178 err = RunCommand(argc, argv);
Vivien Nicolasb86d23e2021-10-14 18:12:05 +0200179 VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(chipTool, "Run command failure: %s", chip::ErrorStr(err)));
Jerry Johns972bff52021-06-24 13:41:50 -0700180
Vivien Nicolasf3826252020-12-01 18:27:45 +0100181exit:
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200182 return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE;
183}
184
Boris Zbarsky68abf762023-11-14 03:26:43 -0500185int Commands::RunInteractive(const char * command, const chip::Optional<char *> & storageDirectory, bool advertiseOperational)
Vivien Nicolas11ab5a32022-03-30 17:12:23 +0200186{
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100187 std::vector<std::string> arguments;
188 VerifyOrReturnValue(DecodeArgumentsFromInteractiveMode(command, arguments), EXIT_FAILURE);
189
190 if (arguments.size() > (kInteractiveModeArgumentsMaxLength - 1 /* for interactive mode name */))
Boris Zbarskyaae56d22022-06-15 20:35:05 -0400191 {
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100192 ChipLogError(chipTool, "Too many arguments. Ignoring.");
193 arguments.resize(kInteractiveModeArgumentsMaxLength - 1);
Boris Zbarskyaae56d22022-06-15 20:35:05 -0400194 }
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100195
196 int argc = 0;
197 char * argv[kInteractiveModeArgumentsMaxLength] = {};
198 argv[argc++] = kInteractiveModeName;
199
Vivien Nicolas6f182352023-01-25 17:10:37 +0100200 std::string commandStr;
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100201 for (auto & arg : arguments)
202 {
203 argv[argc] = new char[arg.size() + 1];
204 strcpy(argv[argc++], arg.c_str());
Vivien Nicolas6f182352023-01-25 17:10:37 +0100205 commandStr += arg;
206 commandStr += " ";
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100207 }
208
Vivien Nicolas6f182352023-01-25 17:10:37 +0100209 ChipLogProgress(chipTool, "Command: %s", commandStr.c_str());
Boris Zbarsky68abf762023-11-14 03:26:43 -0500210 auto err = RunCommand(argc, argv, true, storageDirectory, advertiseOperational);
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100211
212 // Do not delete arg[0]
213 for (auto i = 1; i < argc; i++)
214 {
215 delete[] argv[i];
216 }
217
Vivien Nicolas31da7202023-02-24 19:23:09 +0100218 return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE;
Vivien Nicolas11ab5a32022-03-30 17:12:23 +0200219}
220
Boris Zbarsky95acf3d2023-05-10 10:20:48 -0400221CHIP_ERROR Commands::RunCommand(int argc, char ** argv, bool interactive,
Boris Zbarsky68abf762023-11-14 03:26:43 -0500222 const chip::Optional<char *> & interactiveStorageDirectory, bool interactiveAdvertiseOperational)
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200223{
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200224 Command * command = nullptr;
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200225
Vivien Nicolas6afd51d2021-12-15 15:18:59 +0100226 if (argc <= 1)
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200227 {
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400228 ChipLogError(chipTool, "Missing cluster or command set name");
229 ShowCommandSets(argv[0]);
Vivien Nicolasb86d23e2021-10-14 18:12:05 +0200230 return CHIP_ERROR_INVALID_ARGUMENT;
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200231 }
232
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400233 auto commandSetIter = GetCommandSet(argv[1]);
234 if (commandSetIter == mCommandSets.end())
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200235 {
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400236 ChipLogError(chipTool, "Unknown cluster or command set: %s", argv[1]);
237 ShowCommandSets(argv[0]);
Vivien Nicolasb86d23e2021-10-14 18:12:05 +0200238 return CHIP_ERROR_INVALID_ARGUMENT;
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200239 }
240
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400241 auto & commandList = commandSetIter->second.commands;
242 auto * helpText = commandSetIter->second.helpText;
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400243
Vivien Nicolas6afd51d2021-12-15 15:18:59 +0100244 if (argc <= 2)
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200245 {
246 ChipLogError(chipTool, "Missing command name");
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400247 ShowCommandSet(argv[0], argv[1], commandList, helpText);
Vivien Nicolasb86d23e2021-10-14 18:12:05 +0200248 return CHIP_ERROR_INVALID_ARGUMENT;
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200249 }
250
Vivien Nicolasff8fe372023-01-12 17:04:07 +0100251 bool isGlobalCommand = IsGlobalCommand(argv[2]);
252 if (!isGlobalCommand)
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200253 {
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400254 command = GetCommand(commandList, argv[2]);
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200255 if (command == nullptr)
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200256 {
Vivien Nicolas6afd51d2021-12-15 15:18:59 +0100257 ChipLogError(chipTool, "Unknown command: %s", argv[2]);
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400258 ShowCommandSet(argv[0], argv[1], commandList, helpText);
Vivien Nicolasb86d23e2021-10-14 18:12:05 +0200259 return CHIP_ERROR_INVALID_ARGUMENT;
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200260 }
261 }
Vivien Nicolascd9bea72022-01-13 08:02:10 +0100262 else if (IsEventCommand(argv[2]))
263 {
264 if (argc <= 3)
265 {
266 ChipLogError(chipTool, "Missing event name");
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400267 ShowClusterEvents(argv[0], argv[1], argv[2], commandList);
Vivien Nicolascd9bea72022-01-13 08:02:10 +0100268 return CHIP_ERROR_INVALID_ARGUMENT;
269 }
270
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400271 command = GetGlobalCommand(commandList, argv[2], argv[3]);
Vivien Nicolascd9bea72022-01-13 08:02:10 +0100272 if (command == nullptr)
273 {
274 ChipLogError(chipTool, "Unknown event: %s", argv[3]);
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400275 ShowClusterEvents(argv[0], argv[1], argv[2], commandList);
Vivien Nicolascd9bea72022-01-13 08:02:10 +0100276 return CHIP_ERROR_INVALID_ARGUMENT;
277 }
278 }
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200279 else
280 {
Vivien Nicolas6afd51d2021-12-15 15:18:59 +0100281 if (argc <= 3)
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200282 {
283 ChipLogError(chipTool, "Missing attribute name");
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400284 ShowClusterAttributes(argv[0], argv[1], argv[2], commandList);
Vivien Nicolasb86d23e2021-10-14 18:12:05 +0200285 return CHIP_ERROR_INVALID_ARGUMENT;
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200286 }
Vivien Nicolas010a4972020-10-02 22:27:18 +0200287
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400288 command = GetGlobalCommand(commandList, argv[2], argv[3]);
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200289 if (command == nullptr)
290 {
Vivien Nicolas6afd51d2021-12-15 15:18:59 +0100291 ChipLogError(chipTool, "Unknown attribute: %s", argv[3]);
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400292 ShowClusterAttributes(argv[0], argv[1], argv[2], commandList);
Vivien Nicolasb86d23e2021-10-14 18:12:05 +0200293 return CHIP_ERROR_INVALID_ARGUMENT;
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200294 }
295 }
296
Vivien Nicolasff8fe372023-01-12 17:04:07 +0100297 int argumentsPosition = isGlobalCommand ? 4 : 3;
298 if (!command->InitArguments(argc - argumentsPosition, &argv[argumentsPosition]))
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200299 {
Boris Zbarskybdfee0e2023-02-02 20:36:44 -0500300 if (interactive)
301 {
302 DetectAndLogMismatchedDoubleQuotes(argc - argumentsPosition, &argv[argumentsPosition]);
303 }
Vivien Nicolas6afd51d2021-12-15 15:18:59 +0100304 ShowCommand(argv[0], argv[1], command);
Vivien Nicolasb86d23e2021-10-14 18:12:05 +0200305 return CHIP_ERROR_INVALID_ARGUMENT;
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200306 }
Vivien Nicolas84e1e9c2020-09-30 01:44:17 +0200307
Boris Zbarsky95acf3d2023-05-10 10:20:48 -0400308 if (interactive)
309 {
Boris Zbarsky68abf762023-11-14 03:26:43 -0500310 return command->RunAsInteractive(interactiveStorageDirectory, interactiveAdvertiseOperational);
Boris Zbarsky95acf3d2023-05-10 10:20:48 -0400311 }
312
313 // Now that the command is initialized, get our storage from it as needed
314 // and set up our loging level.
315#ifdef CONFIG_USE_LOCAL_STORAGE
316 CHIP_ERROR err = mStorage.Init(nullptr, command->GetStorageDirectory().ValueOr(nullptr));
317 if (err != CHIP_NO_ERROR)
318 {
319 ChipLogError(Controller, "Init Storage failure: %s", chip::ErrorStr(err));
320 return err;
321 }
322
323 chip::Logging::SetLogFilter(mStorage.GetLoggingLevel());
Boris Zbarsky8edb9f02023-09-13 07:49:30 -0400324
Boris Zbarsky55607f42023-09-22 10:35:35 -0400325#if !CHIP_DISABLE_PLATFORM_KVS
Boris Zbarsky8edb9f02023-09-13 07:49:30 -0400326 UseStorageDirectory(chip::DeviceLayer::PersistedStorage::KeyValueStoreMgrImpl(), mStorage.GetDirectory());
Boris Zbarsky55607f42023-09-22 10:35:35 -0400327#endif // !CHIP_DISABLE_PLATFORM_KVS
328
Boris Zbarsky95acf3d2023-05-10 10:20:48 -0400329#endif // CONFIG_USE_LOCAL_STORAGE
330
331 return command->Run();
Boris Zbarsky5ccb5912021-06-23 08:45:53 -0400332}
333
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400334Commands::CommandSetMap::iterator Commands::GetCommandSet(std::string commandSetName)
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200335{
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400336 for (auto & commandSet : mCommandSets)
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200337 {
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400338 std::string key(commandSet.first);
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200339 std::transform(key.begin(), key.end(), key.begin(), ::tolower);
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400340 if (key.compare(commandSetName) == 0)
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200341 {
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400342 return mCommandSets.find(commandSet.first);
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200343 }
344 }
345
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400346 return mCommandSets.end();
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200347}
348
349Command * Commands::GetCommand(CommandsVector & commands, std::string commandName)
350{
351 for (auto & command : commands)
352 {
353 if (commandName.compare(command->GetName()) == 0)
354 {
355 return command.get();
356 }
357 }
358
359 return nullptr;
360}
361
Vivien Nicolas6cd5a482020-10-30 21:04:24 +0100362Command * Commands::GetGlobalCommand(CommandsVector & commands, std::string commandName, std::string attributeName)
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200363{
364 for (auto & command : commands)
365 {
366 if (commandName.compare(command->GetName()) == 0 && attributeName.compare(command->GetAttribute()) == 0)
367 {
368 return command.get();
369 }
370 }
371
372 return nullptr;
373}
374
Vivien Nicolascd9bea72022-01-13 08:02:10 +0100375bool Commands::IsAttributeCommand(std::string commandName) const
Vivien Nicolas6cd5a482020-10-30 21:04:24 +0100376{
Vivien Nicolasfc414492023-02-02 21:09:29 +0100377 return commandName.compare("read") == 0 || commandName.compare("write") == 0 || commandName.compare("force-write") == 0 ||
378 commandName.compare("subscribe") == 0;
Vivien Nicolas6cd5a482020-10-30 21:04:24 +0100379}
380
Vivien Nicolascd9bea72022-01-13 08:02:10 +0100381bool Commands::IsEventCommand(std::string commandName) const
382{
Hrishikesh Dhayaguded24d5b02022-01-26 09:56:44 +0530383 return commandName.compare("read-event") == 0 || commandName.compare("subscribe-event") == 0;
Vivien Nicolascd9bea72022-01-13 08:02:10 +0100384}
385
386bool Commands::IsGlobalCommand(std::string commandName) const
387{
388 return IsAttributeCommand(commandName) || IsEventCommand(commandName);
389}
390
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400391void Commands::ShowCommandSetOverview(std::string commandSetName, const CommandSet & commandSet)
392{
393 std::transform(commandSetName.begin(), commandSetName.end(), commandSetName.begin(),
394 [](unsigned char c) { return std::tolower(c); });
395 fprintf(stderr, " | * %-82s|\n", commandSetName.c_str());
396 ShowHelpText(commandSet.helpText);
397}
398
399void Commands::ShowCommandSets(std::string executable)
Vivien Nicolas4be198b2020-09-11 17:10:50 +0200400{
Vivien Nicolas010a4972020-10-02 22:27:18 +0200401 fprintf(stderr, "Usage:\n");
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200402 fprintf(stderr, " %s cluster_name command_name [param1 param2 ...]\n", executable.c_str());
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400403 fprintf(stderr, "or:\n");
404 fprintf(stderr, " %s command_set_name command_name [param1 param2 ...]\n", executable.c_str());
Vivien Nicolas010a4972020-10-02 22:27:18 +0200405 fprintf(stderr, "\n");
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400406 // Table of clusters
Vivien Nicolas010a4972020-10-02 22:27:18 +0200407 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
408 fprintf(stderr, " | Clusters: |\n");
Vivien Nicolas010a4972020-10-02 22:27:18 +0200409 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400410 for (auto & commandSet : mCommandSets)
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200411 {
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400412 if (commandSet.second.isCluster)
413 {
414 ShowCommandSetOverview(commandSet.first, commandSet.second);
415 }
416 }
417 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
418 fprintf(stderr, "\n");
419
420 // Table of command sets
421 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
422 fprintf(stderr, " | Command sets: |\n");
423 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
424 for (auto & commandSet : mCommandSets)
425 {
426 if (!commandSet.second.isCluster)
427 {
428 ShowCommandSetOverview(commandSet.first, commandSet.second);
429 }
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200430 }
431 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
432}
433
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400434void Commands::ShowCommandSet(std::string executable, std::string commandSetName, CommandsVector & commands, const char * helpText)
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200435{
436 fprintf(stderr, "Usage:\n");
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400437 fprintf(stderr, " %s %s command_name [param1 param2 ...]\n", executable.c_str(), commandSetName.c_str());
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400438
439 if (helpText)
440 {
441 fprintf(stderr, "\n%s\n", helpText);
442 }
443
Vivien Nicolas656e4592020-10-07 03:41:02 +0200444 fprintf(stderr, "\n");
Vivien Nicolas010a4972020-10-02 22:27:18 +0200445 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200446 fprintf(stderr, " | Commands: |\n");
Vivien Nicolas010a4972020-10-02 22:27:18 +0200447 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
Hrishikesh Dhayaguded24d5b02022-01-26 09:56:44 +0530448 bool readCommand = false;
449 bool writeCommand = false;
Vivien Nicolasfc414492023-02-02 21:09:29 +0100450 bool writeOverrideCommand = false;
Hrishikesh Dhayaguded24d5b02022-01-26 09:56:44 +0530451 bool subscribeCommand = false;
452 bool readEventCommand = false;
453 bool subscribeEventCommand = false;
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200454 for (auto & command : commands)
Vivien Nicolas4be198b2020-09-11 17:10:50 +0200455 {
Vivien Nicolas6cd5a482020-10-30 21:04:24 +0100456 bool shouldPrint = true;
457
458 if (IsGlobalCommand(command->GetName()))
Vivien Nicolas4be198b2020-09-11 17:10:50 +0200459 {
Andrei Litvinb583b572022-03-24 16:53:00 -0400460 if (strcmp(command->GetName(), "read") == 0 && !readCommand)
Vivien Nicolas010a4972020-10-02 22:27:18 +0200461 {
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200462 readCommand = true;
Vivien Nicolas010a4972020-10-02 22:27:18 +0200463 }
Andrei Litvinb583b572022-03-24 16:53:00 -0400464 else if (strcmp(command->GetName(), "write") == 0 && !writeCommand)
Vivien Nicolas3e68ea52020-10-28 07:46:48 +0100465 {
Vivien Nicolas3e68ea52020-10-28 07:46:48 +0100466 writeCommand = true;
467 }
Vivien Nicolasfc414492023-02-02 21:09:29 +0100468 else if (strcmp(command->GetName(), "force-write") == 0 && !writeOverrideCommand)
469 {
470 writeOverrideCommand = true;
471 }
Andrei Litvinb583b572022-03-24 16:53:00 -0400472 else if (strcmp(command->GetName(), "subscribe") == 0 && !subscribeCommand)
Vivien Nicolas6cd5a482020-10-30 21:04:24 +0100473 {
Hrishikesh Dhayaguded24d5b02022-01-26 09:56:44 +0530474 subscribeCommand = true;
Vivien Nicolas6cd5a482020-10-30 21:04:24 +0100475 }
Andrei Litvinb583b572022-03-24 16:53:00 -0400476 else if (strcmp(command->GetName(), "read-event") == 0 && !readEventCommand)
Vivien Nicolascd9bea72022-01-13 08:02:10 +0100477 {
478 readEventCommand = true;
479 }
Andrei Litvinb583b572022-03-24 16:53:00 -0400480 else if (strcmp(command->GetName(), "subscribe-event") == 0 && !subscribeEventCommand)
Vivien Nicolasa82a9ea2022-01-13 16:40:14 +0100481 {
Hrishikesh Dhayaguded24d5b02022-01-26 09:56:44 +0530482 subscribeEventCommand = true;
Vivien Nicolasa82a9ea2022-01-13 16:40:14 +0100483 }
Vivien Nicolas6cd5a482020-10-30 21:04:24 +0100484 else
485 {
486 shouldPrint = false;
487 }
Vivien Nicolas3e68ea52020-10-28 07:46:48 +0100488 }
Vivien Nicolas6cd5a482020-10-30 21:04:24 +0100489
490 if (shouldPrint)
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200491 {
492 fprintf(stderr, " | * %-82s|\n", command->GetName());
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400493 ShowHelpText(command->GetHelpText());
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200494 }
Vivien Nicolas4be198b2020-09-11 17:10:50 +0200495 }
Vivien Nicolas010a4972020-10-02 22:27:18 +0200496 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
497}
498
Vivien Nicolas3e68ea52020-10-28 07:46:48 +0100499void Commands::ShowClusterAttributes(std::string executable, std::string clusterName, std::string commandName,
500 CommandsVector & commands)
Vivien Nicolas010a4972020-10-02 22:27:18 +0200501{
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200502 fprintf(stderr, "Usage:\n");
Vivien Nicolas3e68ea52020-10-28 07:46:48 +0100503 fprintf(stderr, " %s %s %s attribute-name [param1 param2 ...]\n", executable.c_str(), clusterName.c_str(),
504 commandName.c_str());
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200505 fprintf(stderr, "\n");
506 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
507 fprintf(stderr, " | Attributes: |\n");
508 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
509 for (auto & command : commands)
510 {
Vivien Nicolas3e68ea52020-10-28 07:46:48 +0100511 if (commandName.compare(command->GetName()) == 0)
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200512 {
513 fprintf(stderr, " | * %-82s|\n", command->GetAttribute());
514 }
515 }
Vivien Nicolas010a4972020-10-02 22:27:18 +0200516 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
517}
518
Vivien Nicolascd9bea72022-01-13 08:02:10 +0100519void Commands::ShowClusterEvents(std::string executable, std::string clusterName, std::string commandName,
520 CommandsVector & commands)
521{
522 fprintf(stderr, "Usage:\n");
523 fprintf(stderr, " %s %s %s event-name [param1 param2 ...]\n", executable.c_str(), clusterName.c_str(), commandName.c_str());
524 fprintf(stderr, "\n");
525 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
526 fprintf(stderr, " | Events: |\n");
527 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
528 for (auto & command : commands)
529 {
530 if (commandName.compare(command->GetName()) == 0)
531 {
532 fprintf(stderr, " | * %-82s|\n", command->GetEvent());
533 }
534 }
535 fprintf(stderr, " +-------------------------------------------------------------------------------------+\n");
536}
537
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200538void Commands::ShowCommand(std::string executable, std::string clusterName, Command * command)
Vivien Nicolas010a4972020-10-02 22:27:18 +0200539{
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200540 fprintf(stderr, "Usage:\n");
541
Andrei Litvin0c06ba52022-04-07 05:19:33 -1000542 std::string arguments;
Irene Siu (Apple)68da2eb2022-05-05 18:27:15 -0700543 std::string description;
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200544 arguments += command->GetName();
545
Vivien Nicolasff8fe372023-01-12 17:04:07 +0100546 if (command->GetReadOnlyGlobalCommandArgument())
547 {
548 arguments += ' ';
549 arguments += command->GetReadOnlyGlobalCommandArgument();
550 }
551
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200552 size_t argumentsCount = command->GetArgumentsCount();
553 for (size_t i = 0; i < argumentsCount; i++)
Vivien Nicolas010a4972020-10-02 22:27:18 +0200554 {
Irene Siu (Apple)68da2eb2022-05-05 18:27:15 -0700555 std::string arg;
Vivien Nicolas7ca6e282021-11-12 18:05:32 +0100556 bool isOptional = command->GetArgumentIsOptional(i);
557 if (isOptional)
558 {
Irene Siu (Apple)68da2eb2022-05-05 18:27:15 -0700559 arg += "[--";
Vivien Nicolas7ca6e282021-11-12 18:05:32 +0100560 }
Irene Siu (Apple)68da2eb2022-05-05 18:27:15 -0700561 arg += command->GetArgumentName(i);
Vivien Nicolas7ca6e282021-11-12 18:05:32 +0100562 if (isOptional)
563 {
Irene Siu (Apple)68da2eb2022-05-05 18:27:15 -0700564 arg += "]";
565 }
566 arguments += " ";
567 arguments += arg;
568
569 const char * argDescription = command->GetArgumentDescription(i);
570 if ((argDescription != nullptr) && (strlen(argDescription) > 0))
571 {
572 description += "\n";
573 description += arg;
574 description += ":\n ";
575 description += argDescription;
576 description += "\n";
Vivien Nicolas7ca6e282021-11-12 18:05:32 +0100577 }
Vivien Nicolas010a4972020-10-02 22:27:18 +0200578 }
Vivien Nicolas6b140c42020-10-13 16:22:28 +0200579 fprintf(stderr, " %s %s %s\n", executable.c_str(), clusterName.c_str(), arguments.c_str());
Irene Siu (Apple)68da2eb2022-05-05 18:27:15 -0700580
Boris Zbarsky5dade572022-08-09 23:07:03 -0400581 if (command->GetHelpText())
582 {
583 fprintf(stderr, "\n%s\n", command->GetHelpText());
584 }
585
Irene Siu (Apple)68da2eb2022-05-05 18:27:15 -0700586 if (description.size() > 0)
587 {
588 fprintf(stderr, "%s\n", description.c_str());
589 }
Vivien Nicolas4be198b2020-09-11 17:10:50 +0200590}
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100591
592bool Commands::DecodeArgumentsFromInteractiveMode(const char * command, std::vector<std::string> & args)
593{
594 // Remote clients may not know the ordering of arguments, so instead of a strict ordering arguments can
595 // be passed in as a json payload encoded in base64 and are reordered on the fly.
596 return IsJsonString(command) ? DecodeArgumentsFromBase64EncodedJson(command, args)
597 : DecodeArgumentsFromStringStream(command, args);
598}
599
600bool Commands::DecodeArgumentsFromBase64EncodedJson(const char * json, std::vector<std::string> & args)
601{
602 Json::Value jsonValue;
603 bool parsed = JsonParser::ParseCustomArgument(json, json + kJsonStringPrefixLen, jsonValue);
604 VerifyOrReturnValue(parsed, false, ChipLogError(chipTool, "Error while parsing json."));
605 VerifyOrReturnValue(jsonValue.isObject(), false, ChipLogError(chipTool, "Unexpected json type."));
606 VerifyOrReturnValue(jsonValue.isMember(kJsonClusterKey), false,
607 ChipLogError(chipTool, "'%s' key not found in json.", kJsonClusterKey));
608 VerifyOrReturnValue(jsonValue.isMember(kJsonCommandKey), false,
609 ChipLogError(chipTool, "'%s' key not found in json.", kJsonCommandKey));
610 VerifyOrReturnValue(jsonValue.isMember(kJsonArgumentsKey), false,
611 ChipLogError(chipTool, "'%s' key not found in json.", kJsonArgumentsKey));
612 VerifyOrReturnValue(IsBase64String(jsonValue[kJsonArgumentsKey].asString().c_str()), false,
613 ChipLogError(chipTool, "'arguments' is not a base64 string."));
614
615 auto clusterName = jsonValue[kJsonClusterKey].asString();
616 auto commandName = jsonValue[kJsonCommandKey].asString();
617 auto arguments = jsonValue[kJsonArgumentsKey].asString();
618
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400619 auto clusterIter = GetCommandSet(clusterName);
620 VerifyOrReturnValue(clusterIter != mCommandSets.end(), false,
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100621 ChipLogError(chipTool, "Cluster '%s' is not supported.", clusterName.c_str()));
622
Boris Zbarsky6f8d6022023-07-11 16:36:26 -0400623 auto & commandList = clusterIter->second.commands;
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400624
625 auto command = GetCommand(commandList, commandName);
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100626
627 if (jsonValue.isMember(kJsonCommandSpecifierKey) && IsGlobalCommand(commandName))
628 {
629 auto commandSpecifierName = jsonValue[kJsonCommandSpecifierKey].asString();
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400630 command = GetGlobalCommand(commandList, commandName, commandSpecifierName);
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100631 }
632 VerifyOrReturnValue(nullptr != command, false, ChipLogError(chipTool, "Unknown command."));
633
634 auto encodedData = arguments.c_str();
635 encodedData += kBase64StringPrefixLen;
636
637 size_t encodedDataSize = strlen(encodedData);
638 size_t expectedMaxDecodedSize = BASE64_MAX_DECODED_LEN(encodedDataSize);
639
640 chip::Platform::ScopedMemoryBuffer<uint8_t> decodedData;
641 VerifyOrReturnValue(decodedData.Calloc(expectedMaxDecodedSize + 1 /* for null */), false);
642
643 size_t decodedDataSize = chip::Base64Decode(encodedData, static_cast<uint16_t>(encodedDataSize), decodedData.Get());
644 VerifyOrReturnValue(decodedDataSize != 0, false, ChipLogError(chipTool, "Error while decoding base64 data."));
645
646 decodedData.Get()[decodedDataSize] = '\0';
647
648 Json::Value jsonArguments;
649 bool parsedArguments = JsonParser::ParseCustomArgument(encodedData, chip::Uint8::to_char(decodedData.Get()), jsonArguments);
650 VerifyOrReturnValue(parsedArguments, false, ChipLogError(chipTool, "Error while parsing json."));
651 VerifyOrReturnValue(jsonArguments.isObject(), false, ChipLogError(chipTool, "Unexpected json type, expects and object."));
652
Vivien Nicolasa64797f2023-02-06 22:15:54 +0100653 std::vector<std::string> mandatoryArguments;
654 std::vector<std::string> optionalArguments;
655 VerifyOrReturnValue(GetArgumentsFromJson(command, jsonArguments, false /* addOptional */, mandatoryArguments), false);
656 VerifyOrReturnValue(GetArgumentsFromJson(command, jsonArguments, true /* addOptional */, optionalArguments), false);
Vivien Nicolasfc4281c2023-01-23 17:49:55 +0100657
658 args.push_back(std::move(clusterName));
659 args.push_back(std::move(commandName));
660 if (jsonValue.isMember(kJsonCommandSpecifierKey))
661 {
662 auto commandSpecifierName = jsonValue[kJsonCommandSpecifierKey].asString();
663 args.push_back(std::move(commandSpecifierName));
664 }
665 args.insert(args.end(), mandatoryArguments.begin(), mandatoryArguments.end());
666 args.insert(args.end(), optionalArguments.begin(), optionalArguments.end());
667
668 return true;
669}
670
671bool Commands::DecodeArgumentsFromStringStream(const char * command, std::vector<std::string> & args)
672{
673 std::string arg;
674 std::stringstream ss(command);
675 while (ss >> std::quoted(arg, '\''))
676 {
677 args.push_back(std::move(arg));
678 }
679
680 return true;
681}
Boris Zbarskyc8e453c2023-05-05 16:22:09 -0400682
683void Commands::ShowHelpText(const char * helpText)
684{
685 if (helpText == nullptr)
686 {
687 return;
688 }
689
690 // We leave 82 chars for command/cluster names. The help text starts
691 // two chars further to the right, so there are 80 chars left
692 // for it.
693 if (strlen(helpText) > 80)
694 {
695 // Add "..." at the end to indicate truncation, and only
696 // show the first 77 chars, since that's what will fit.
697 fprintf(stderr, " | - %.77s...|\n", helpText);
698 }
699 else
700 {
701 fprintf(stderr, " | - %-80s|\n", helpText);
702 }
703}