1 //-----------------------------------------------------------------------------
2 // Copyright (C) 2017 Merlok
4 // This code is licensed to you under the terms of the GNU GPL, version 2 or,
5 // at your option, any later version. See the LICENSE.txt file for the text of
7 //-----------------------------------------------------------------------------
8 // Command line parser core commands
9 //-----------------------------------------------------------------------------
11 #include "cliparser.h"
14 #include <util.h> // color constants
15 #include <ui.h> // PrintAndLogEx
16 #include <ctype.h> // tolower
17 #include <inttypes.h> // PRIu64
20 # define ARRAYLEN(x) (sizeof(x)/sizeof((x)[0]))
24 // To default the color return s
25 #define _SectionTagColor_(s) _GREEN_(s)
26 #define _ExampleColor_(s) _YELLOW_(s)
27 #define _CommandColor_(s) _RED_(s)
28 #define _DescriptionColor_(s) _CYAN_(s)
29 #define _ArgColor_(s) s
30 #define _ArgHelpColor_(s) s
32 // Option width set to 30 to allow option descriptions to align. approx line 74
33 // Example width set to 50 to allow help descriptions to align. approx line 93
35 int CLIParserInit(CLIParserContext
**ctx
, const char *vprogramName
, const char *vprogramHint
, const char *vprogramHelp
) {
36 *ctx
= calloc(sizeof(CLIParserContext
), sizeof(uint8_t));
38 PrintAndLogEx(ERR
, "ERROR: Insufficient memory\n");
42 (*ctx
)->argtable
= NULL
;
43 (*ctx
)->argtableLen
= 0;
44 (*ctx
)->programName
= vprogramName
;
45 (*ctx
)->programHint
= vprogramHint
;
46 (*ctx
)->programHelp
= vprogramHelp
;
47 memset((*ctx
)->buf
, 0x00, sizeof((*ctx
)->buf
));
52 void CLIParserPrintHelp(CLIParserContext
*ctx
) {
53 if (ctx
->programHint
) {
54 PrintAndLogEx(NORMAL
, "\n"_DescriptionColor_("%s"), ctx
->programHint
);
57 PrintAndLogEx(NORMAL
, "\n"_SectionTagColor_("usage:"));
58 PrintAndLogEx(NORMAL
, " "_CommandColor_("%s")NOLF
, ctx
->programName
);
59 arg_print_syntax(stdout
, ctx
->argtable
, "\n\n");
61 PrintAndLogEx(NORMAL
, _SectionTagColor_("options:"));
62 arg_print_glossary(stdout
, ctx
->argtable
, " "_ArgColor_("%-30s")" "_ArgHelpColor_("%s")"\n");
63 PrintAndLogEx(NORMAL
, "");
65 if (ctx
->programHelp
) {
67 // allocate more then enough memory as we are splitting
68 char *s
= calloc(strlen(ctx
->programHelp
) + 1, sizeof(uint8_t));
70 PrintAndLogEx(FAILED
, "cannot allocate memory");
74 PrintAndLogEx(NORMAL
, _SectionTagColor_("examples/notes:"));
76 // pointer to split example from comment.
81 for (int i
= 0; i
<= strlen(ctx
->programHelp
); i
++) { // <= so to get string terminator.
83 s
[idx
++] = ctx
->programHelp
[i
];
85 if ((ctx
->programHelp
[i
] == '\n') || (ctx
->programHelp
[i
] == 0x00)) {
88 p2
= strstr(s
, "->"); // See if the example has a comment.
94 egWidth
= strlen(s
) + 5;
98 PrintAndLogEx(NORMAL
, " "_ExampleColor_("%-*s")" %s", egWidth
, s
, p2
);
100 PrintAndLogEx(NORMAL
, " "_ExampleColor_("%-*s"), egWidth
, s
);
107 PrintAndLogEx(NORMAL
, "");
112 int CLIParserParseArg(CLIParserContext
*ctx
, int argc
, char **argv
, void *vargtable
[], size_t vargtableLen
, bool allowEmptyExec
) {
115 ctx
->argtable
= vargtable
;
116 ctx
->argtableLen
= vargtableLen
;
118 /* verify the argtable[] entries were allocated sucessfully */
119 if (arg_nullcheck(ctx
->argtable
) != 0) {
120 /* NULL entries were detected, some allocations must have failed */
121 PrintAndLogEx(ERR
, "ERROR: Insufficient memory\n");
125 /* Parse the command line as defined by argtable[] */
126 nerrors
= arg_parse(argc
, argv
, ctx
->argtable
);
128 /* special case: '--help' takes precedence over error reporting */
129 if ((argc
< 2 && !allowEmptyExec
) || ((struct arg_lit
*)(ctx
->argtable
)[0])->count
> 0) { // help must be the first record
130 CLIParserPrintHelp(ctx
);
134 /* If the parser returned any errors then display them and exit */
136 /* Display the error details contained in the arg_end struct.*/
137 arg_print_errors(stdout
, ((struct arg_end
*)(ctx
->argtable
)[vargtableLen
- 1]), ctx
->programName
);
138 PrintAndLogEx(WARNING
, "Try " _YELLOW_("'%s --help'") " for more information.\n", ctx
->programName
);
153 #define isSpace(c)(c == ' ' || c == '\t')
155 int CLIParserParseString(CLIParserContext
*ctx
, const char *str
, void *vargtable
[], size_t vargtableLen
, bool allowEmptyExec
) {
156 return CLIParserParseStringEx(ctx
, str
, vargtable
, vargtableLen
, allowEmptyExec
, false);
159 int CLIParserParseStringEx(CLIParserContext
*ctx
, const char *str
, void *vargtable
[], size_t vargtableLen
, bool allowEmptyExec
, bool clueData
) {
161 char *argv
[MAX_INPUT_ARG_LENGTH
] = {NULL
};
163 int len
= strlen(str
);
165 memset(ctx
->buf
, 0x00, ARRAYLEN(ctx
->buf
));
167 char *bufptr
= ctx
->buf
;
168 char *bufptrend
= ctx
->buf
+ ARRAYLEN(ctx
->buf
) - 1;
169 char *spaceptr
= NULL
;
170 enum ParserState state
= PS_FIRST
;
172 argv
[argc
++] = bufptr
;
173 // param0 = program name + with 0x00
174 memcpy(ctx
->buf
, ctx
->programName
, strlen(ctx
->programName
) + 1);
176 bufptr
+= strlen(ctx
->programName
) + 1;
178 argv
[argc
++] = bufptr
;
182 for (int i
= 0; i
< len
; i
++) {
184 case PS_FIRST
: // first char
185 if (!clueData
|| str
[i
] == '-') { // first char before space is '-' - next element - option OR not "clueData" for not-option fields
192 argv
[argc
++] = bufptr
;
197 if (state
== PS_FIRST
)
203 if (isSpace(str
[i
])) {
211 if (isSpace(str
[i
])) {
216 argv
[argc
++] = bufptr
;
228 if (isSpace(str
[i
]) == false) {
234 if (bufptr
> bufptrend
) {
235 PrintAndLogEx(ERR
, "ERROR: Line too long\n");
241 return CLIParserParseArg(ctx
, argc
, argv
, vargtable
, vargtableLen
, allowEmptyExec
);
245 int CLIParamHexToBuf(struct arg_str
*argstr
, uint8_t *data
, int maxdatalen
, int *datalen
) {
249 uint8_t tmpstr
[MAX_INPUT_ARG_LENGTH
+ 1] = {0};
251 // concat all strings in argstr into tmpstr[]
252 int res
= CLIParamStrToBuf(argstr
, tmpstr
, sizeof(tmpstr
), &tmplen
);
253 if (res
|| (tmplen
== 0)) {
257 res
= param_gethex_to_eol((char *)tmpstr
, 0, data
, maxdatalen
, datalen
);
260 PrintAndLogEx(ERR
, "Parameter error: Invalid HEX value\n");
263 PrintAndLogEx(ERR
, "Parameter error: parameter too large\n");
266 PrintAndLogEx(ERR
, "Parameter error: Hex string must have EVEN number of digits\n");
272 int CLIParamBinToBuf(struct arg_str
*argstr
, uint8_t *data
, int maxdatalen
, int *datalen
) {
276 uint8_t tmpstr
[MAX_INPUT_ARG_LENGTH
+ 1] = {0};
278 // concat all strings in argstr into tmpstr[]
280 int res
= CLIParamStrToBuf(argstr
, tmpstr
, sizeof(tmpstr
), &tmplen
);
281 if (res
|| tmplen
== 0) {
285 res
= param_getbin_to_eol((char *)tmpstr
, 0, data
, maxdatalen
, datalen
);
288 PrintAndLogEx(ERR
, "Parameter error: Invalid BINARY value\n");
291 PrintAndLogEx(ERR
, "Parameter error: parameter too large\n");
297 int CLIParamStrToBuf(struct arg_str
*argstr
, uint8_t *data
, int maxdatalen
, int *datalen
) {
302 uint8_t tmpstr
[MAX_INPUT_ARG_LENGTH
+ 1] = {0};
305 for (int i
= 0; i
< argstr
->count
; i
++) {
307 int len
= strlen(argstr
->sval
[i
]);
309 if (len
> ((sizeof(tmpstr
) / 2) - ibuf
)) {
310 PrintAndLogEx(ERR
, "Parameter error: string too long (%i chars), expect MAX %zu chars\n", len
+ ibuf
, (sizeof(tmpstr
) / 2));
314 memcpy(&tmpstr
[ibuf
], argstr
->sval
[i
], len
);
319 ibuf
= MIN(ibuf
, (sizeof(tmpstr
) / 2));
325 if (ibuf
> maxdatalen
) {
326 PrintAndLogEx(ERR
, "Parameter error: string too long (%i chars), expected MAX %i chars\n", ibuf
, maxdatalen
);
330 memcpy(data
, tmpstr
, ibuf
+ 1);
335 int CLIGetOptionList(struct arg_str
*argstr
, const CLIParserOption
*option_array
, int *value
) {
336 char data
[200] = {0};
338 int res
= CLIParamStrToBuf(argstr
, (uint8_t *)data
, sizeof(data
), &datalen
);
342 // no data to check - we do not touch *value, just return
350 for (int i
= 0; (i
< CLI_MAX_OPTLIST_LEN
) && (option_array
[i
].text
!= NULL
); i
++) {
352 if (strcmp(option_array
[i
].text
, data
) == 0) {
353 *value
= option_array
[i
].code
;
357 if (strncmp(option_array
[i
].text
, data
, datalen
) == 0) {
358 val
= option_array
[i
].code
;
363 // check partial match
365 PrintAndLogEx(ERR
, "Parameter error: No similar option to `%s`. Valid options: %s\n", argstr
->sval
[0], argstr
->hdr
.datatype
);
369 PrintAndLogEx(ERR
, "Parameter error: Several options fit to `%s`. Valid options: %s\n", argstr
->sval
[0], argstr
->hdr
.datatype
);
377 const char *CLIGetOptionListStr(const CLIParserOption
*option_array
, int value
) {
378 static const char *errmsg
= "n/a";
380 for (int i
= 0; (i
< CLI_MAX_OPTLIST_LEN
) && (option_array
[i
].text
!= NULL
); i
++) {
381 if (option_array
[i
].code
== value
) {
382 return option_array
[i
].text
;
389 // hexstr -> u64, w optional len input and default value fallback.
392 // 3 = optional param - not set
393 uint64_t arg_get_u64_hexstr_def(CLIParserContext
*ctx
, uint8_t paramnum
, uint64_t def
) {
397 int res
= CLIParamHexToBuf(arg_get_str(ctx
, paramnum
), d
, sizeof(d
), &dlen
);
398 if (res
== 0 && dlen
> 0) {
399 for (int i
= 0; i
< dlen
; i
++) {
409 // hexstr -> u64, w optional len input and default value fallback.
412 // 2 = wrong len param, use default
413 // 3 = optional param, if fail, use default.
414 int arg_get_u64_hexstr_def_nlen(CLIParserContext
*ctx
, uint8_t paramnum
, uint64_t def
, uint64_t *out
, uint8_t nlen
, bool optional
) {
417 int res
= CLIParamHexToBuf(arg_get_str(ctx
, paramnum
), d
, sizeof(d
), &n
);
418 if (res
== 0 && n
== nlen
) {
420 for (uint8_t i
= 0; i
< n
; i
++) {
426 } else if (res
== 0 && n
) {
429 } else if (res
== 0 && n
== 0 && optional
) {
436 int arg_get_u32_hexstr_def(CLIParserContext
*ctx
, uint8_t paramnum
, uint32_t def
, uint32_t *out
) {
437 return arg_get_u32_hexstr_def_nlen(ctx
, paramnum
, def
, out
, 4, false);
440 int arg_get_u32_hexstr_def_nlen(CLIParserContext
*ctx
, uint8_t paramnum
, uint32_t def
, uint32_t *out
, uint8_t nlen
, bool optional
) {
443 int res
= CLIParamHexToBuf(arg_get_str(ctx
, paramnum
), d
, sizeof(d
), &n
);
444 if (res
== 0 && n
== nlen
) {
446 for (uint8_t i
= 0; i
< n
; i
++) {
452 } else if (res
== 0 && n
) {
455 } else if (res
== 0 && n
== 0 && optional
) {