/** * \file * * * \brief Channel protocol parser and commands. * * This file contains the channel protocol parser and * the definition of the protocol commands. Commands are defined * in a "CmdTemplate" type array, containing: * - the name of the command, * - the arguments it expects to receive, * - the output values, * - the name of the function implementing the command. * * The arguments and results are passed to command function * using an union: the element of the union to use for each * argument is determined by format strings present in the * CmdTemplate table. * * \author Bernie Innocenti * \author Stefano Fedrigo * \author Giovanni Bajo * * */ #include "parser.h" #include "cfg/cfg_parser.h" #include #include #include // atol(), NULL #include // strchr(), strcmp() /// Hashtable hook to extract the key from a command static const void* get_key_from_command(const void* cmd, uint8_t* length); /// Hashtable that handles the commands that can be executed DECLARE_HASHTABLE_STATIC(commands, CONFIG_MAX_COMMANDS_NUMBER, get_key_from_command); /** * \brief Tokenize one word at a time from a text. * * This function is similar to strtok, but does not use any implicit * context, nor it does modify the input buffer in any form. * The word is returned as a STL-like [begin,end) range. * * To extract the first word, make both begin and end point at the * start of the text, and call the function. Then, subsequent * calls will return the following words (assuming the begin/end * variable are not modified between calls). * * \param begin Will contain the index of the first character of the word * \param end Will contain the index of the character after the last * character of the word * * \return True if a word was extracted, false if we got to the end * of the string without extracting any word. */ static bool get_word(const char **begin, const char **end) { const char *cur = *end; while ((*cur == ' ' || *cur == '\t') && *cur) ++cur; *begin = cur; while ((*cur != ' ' && *cur != '\t') && *cur) ++cur; *end = cur; return (*end != *begin); } /** * \brief Command arguments parser. * * Using the format pointed by the argument fmt * parses the input string filling the array argv * with input parameters of the correct type. * * \param fmt Parameters format string. * \param input Input string. * \param argv Array filled with parameters. * * \return False in case of errors, otherwise true. */ static bool parseArgs(const char *fmt, const char *input, parms argv[]) { const char *begin = input, *end = input; while (*fmt) { // Extract the argument if (!get_word(&begin, &end)) return false; switch (*fmt) { case 'd': (*argv++).l = atol(begin); break; case 's': (*argv++).s = begin; break; default: ASSERT2(0, "Unknown format for argument"); return false; } ++fmt; } /* check if there are remaining args */ if (get_word(&begin, &end)) return false; return true; } /// Hook provided by the parser for matching of command names (TAB completion) for readline const char* parser_rl_match(UNUSED_ARG(void *,dummy), const char *word, int word_len) { HashIterator cur; HashIterator end = ht_iter_end(&commands); const char *found = NULL; for (cur = ht_iter_begin(&commands); !ht_iter_cmp(cur, end); cur = ht_iter_next(cur)) { const struct CmdTemplate* cmdp = (const struct CmdTemplate*)ht_iter_get(cur); if (strncmp(cmdp->name, word, word_len) == 0) { // If there was another matching word, it means that we have a multiple // match: then return NULL. if (found) return NULL; found = cmdp->name; } } return found; } #if CONFIG_ENABLE_COMPAT_BEHAVIOUR bool parser_get_cmd_id(const char* line, unsigned long* ID) { const char *begin = line, *end = line; char *end2; // The first word is the ID if (!get_word(&begin, &end)) return false; *ID = strtoul(begin, &end2, 10); if (end2 != end) return false; return true; } #endif /** * Find the template for the command contained in the text line. * The template can be used to tokenize the command and interpret * it. * * This function can be used to find out which command is contained * in a given text line without parsing all the parameters and * executing it. * * \param input Text line to be processed (ASCIIZ) * * \return The command template associated with the command contained * in the line, or NULL if the command is invalid. */ const struct CmdTemplate* parser_get_cmd_template(const char *input) { const char *begin = input, *end = input; #if CONFIG_ENABLE_COMPAT_BEHAVIOUR // Skip the ID, and get the command if (!get_word(&begin, &end)) return NULL; #endif if (!get_word(&begin, &end)) return NULL; return (const struct CmdTemplate*)ht_find(&commands, begin, end-begin); } static const char *skip_to_params(const char *input, const struct CmdTemplate *cmdp) { const char *begin = input, *end = input; #if CONFIG_ENABLE_COMPAT_BEHAVIOUR // Skip the ID, and get the command if (!get_word(&begin, &end)) return NULL; #endif if (!get_word(&begin, &end)) return NULL; ASSERT2(strlen(cmdp->name) == (size_t)(end-begin), "Invalid command template specified"); ASSERT2(!strncmp(begin, cmdp->name, end-begin), "Invalid command template specified"); return end; } /** * Extract the arguments for the command contained in the text line. * * The first argument will always be the command name, so the actual arguments * will start at index 1. * * \param input Text line to be processed (ASCIIZ) * \param cmdp Command template for this line * \param args Will contain the extracted parameters * * \return True if everything OK, false in case of parsing error. */ bool parser_get_cmd_arguments(const char* input, const struct CmdTemplate* cmdp, parms args[CONFIG_PARSER_MAX_ARGS]) { input = skip_to_params(input, cmdp); if (!input) return false; args[0].s = cmdp->name; if (!parseArgs(cmdp->arg_fmt, input, args + 1)) return false; return true; } static const void* get_key_from_command(const void* cmd, uint8_t* length) { const struct CmdTemplate* c = cmd; *length = strlen(c->name); return c->name; } /** * \brief Command input handler. * * Process the input, calling the requested command (if found). * * \param input Text line to be processed (ASCIIZ) * * \return true if everything is OK, false in case of errors */ bool parser_process_line(const char* input) { const struct CmdTemplate *cmdp; parms args[CONFIG_PARSER_MAX_ARGS]; cmdp = parser_get_cmd_template(input); if (!cmdp) return false; if (!parser_get_cmd_arguments(input, cmdp, args)) return false; if (!parser_execute_cmd(cmdp, args)) return false; return true; } /** * Register a new command into the parser * * \param cmd Command template describing the command * \return true if registration was successful, false otherwise */ bool parser_register_cmd(const struct CmdTemplate* cmd) { return ht_insert(&commands, cmd); } void parser_init(void) { // Initialize the hashtable used to store the command description ht_init(&commands); }