/** * @file cli_registry.cpp * @brief Command-line interface commands registry * * Here we have a data structure which holds all the dynamically-registered * command line interface action names & callback. This logic is invoked in * user context by the console thread - see consoleThreadThreadEntryPoint * * TODO: there is too much copy-paste here, this class needs some refactoring :) * * see testConsoleLogic() * * @date Nov 15, 2012 * @author Andrey Belomutskiy, (c) 2012-2018 */ #include #include #include #include "main.h" #include "cli_registry.h" #include "efilib.h" #if EFI_PROD_CODE #include "board_test.h" #endif #if ! EFI_UNIT_TEST #include "eficonsole.h" #endif /* ! EFI_UNIT_TEST */ // todo: support \t as well #define SPACE_CHAR ' ' static Logging * logging; static int consoleActionCount = 0; static TokenCallback consoleActions[CONSOLE_MAX_ACTIONS] CCM_OPTIONAL; #define SECURE_LINE_PREFIX "sec!" #define SECURE_LINE_PREFIX_LENGTH 4 void resetConsoleActions(void) { consoleActionCount = 0; } static void doAddAction(const char *token, action_type_e type, Void callback, void *param) { efiAssertVoid(consoleActionCount < CONSOLE_MAX_ACTIONS, "Too many console actions"); TokenCallback *current = &consoleActions[consoleActionCount++]; for (uint32_t i = 0; i< efiStrlen(token);i++) { char ch = token[i]; if (ch != mytolower(ch)) { firmwareError(CUSTOM_ERR_COMMAND_LOWER_CASE_EXPECTED, "lowerCase expected [%s]", token); } } current->token = token; current->parameterType = type; current->callback = callback; current->param = param; } void addConsoleActionP(const char *token, VoidPtr callback, void *param) { doAddAction(token, NO_PARAMETER_P, (Void) callback, param); } void addConsoleActionSSP(const char *token, VoidCharPtrCharPtrVoidPtr callback, void *param) { doAddAction(token, STRING2_PARAMETER_P, (Void) callback, param); } /** * @brief Register console action without parameters */ void addConsoleAction(const char *token, Void callback) { doAddAction(token, NO_PARAMETER, callback, NULL); } /** * @brief Register a console command with one Integer parameter */ void addConsoleActionI(const char *token, VoidInt callback) { doAddAction(token, ONE_PARAMETER, (Void) callback, NULL); } void addConsoleActionIP(const char *token, VoidIntVoidPtr callback, void *param) { doAddAction(token, ONE_PARAMETER_P, (Void) callback, param); } /** * @brief Register a console command with two Integer parameters */ void addConsoleActionII(const char *token, VoidIntInt callback) { doAddAction(token, TWO_INTS_PARAMETER, (Void) callback, NULL); } void addConsoleActionIIP(const char *token, VoidIntIntVoidPtr callback, void *param) { doAddAction(token, TWO_INTS_PARAMETER_P, (Void) callback, param); } void addConsoleActionS(const char *token, VoidCharPtr callback) { doAddAction(token, STRING_PARAMETER, (Void) callback, NULL); } void addConsoleActionSP(const char *token, VoidCharPtrVoidPtr callback, void *param) { doAddAction(token, STRING_PARAMETER_P, (Void) callback, param); } void addConsoleActionSS(const char *token, VoidCharPtrCharPtr callback) { doAddAction(token, STRING2_PARAMETER, (Void) callback, NULL); } void addConsoleActionSSS(const char *token, VoidCharPtrCharPtrCharPtr callback) { doAddAction(token, STRING3_PARAMETER, (Void) callback, NULL); } void addConsoleActionSSSSS(const char *token, VoidCharPtrCharPtrCharPtrCharPtrCharPtr callback) { doAddAction(token, STRING5_PARAMETER, (Void) callback, NULL); } void addConsoleActionF(const char *token, VoidFloat callback) { doAddAction(token, FLOAT_PARAMETER, (Void) callback, NULL); } void addConsoleActionFF(const char *token, VoidFloatFloat callback) { doAddAction(token, FLOAT_FLOAT_PARAMETER, (Void) callback, NULL); } void addConsoleActionFFP(const char *token, VoidFloatFloatVoidPtr callback, void *param) { doAddAction(token, FLOAT_FLOAT_PARAMETER_P, (Void) callback, param); } static int getParameterCount(action_type_e parameterType) { switch (parameterType) { case NO_PARAMETER: case NO_PARAMETER_P: return 0; case ONE_PARAMETER: case ONE_PARAMETER_P: case FLOAT_PARAMETER: case STRING_PARAMETER: return 1; case FLOAT_FLOAT_PARAMETER: case FLOAT_FLOAT_PARAMETER_P: case STRING2_PARAMETER: case STRING2_PARAMETER_P: case TWO_INTS_PARAMETER: case TWO_INTS_PARAMETER_P: return 2; case STRING3_PARAMETER: return 3; case STRING5_PARAMETER: return 5; default: return -1; } } /** * @brief This function prints out a list of all available commands */ void helpCommand(void) { #if EFI_PROD_CODE || defined(__DOXYGEN__) if (isBoardTestMode()) { printBoardTestState(); return; } #endif /* EFI_PROD_CODE */ #if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__) scheduleMsg(logging, "%d actions available", consoleActionCount); for (int i = 0; i < consoleActionCount; i++) { TokenCallback *current = &consoleActions[i]; scheduleMsg(logging, " %s: %d parameters", current->token, getParameterCount(current->parameterType)); } #endif scheduleMsg(logging, "For more visit http://rusefi.com/wiki/index.php?title=Manual:Software:dev_console_commands"); } /** * @brief This is just a test function */ static void echo(int value) { print("got value: %d\r\n", value); } char *unquote(char *line) { if (line[0] == '"') { int len = strlen(line); if (line[len - 1] == '"') { line[len - 1] = 0; return line + 1; } } return line; } int findEndOfToken(const char *line) { if (line[0] == '"') { /** * Looks like this is a quoted token */ int v = indexOf(line + 1, '"'); if (v == -1) { /** * Matching closing quote not found */ return -1; } /** * Skipping first quote and the symbol after closing quote */ return v + 2; } return indexOf(line, SPACE_CHAR); } #define REPLACE_SPACES_WITH_ZERO { \ while (parameter[spaceIndex + 1] == SPACE_CHAR) { \ parameter[spaceIndex++] = 0; \ } \ parameter[spaceIndex] = 0; \ } void handleActionWithParameter(TokenCallback *current, char *parameter) { while (parameter[0] == SPACE_CHAR) { parameter[0] = 0; parameter++; } switch (current->parameterType) { case STRING_PARAMETER: { VoidCharPtr callbackS = (VoidCharPtr) current->callback; (*callbackS)(parameter); return; } case STRING_PARAMETER_P: { VoidCharPtrVoidPtr callbackS = (VoidCharPtrVoidPtr) current->callback; (*callbackS)(parameter, current->param); return; } default: // todo: handle all cases explicitly break; } // todo: refactor this hell! if (current->parameterType == STRING2_PARAMETER || current->parameterType == STRING2_PARAMETER_P) { int spaceIndex = findEndOfToken(parameter); if (spaceIndex == -1) { return; } REPLACE_SPACES_WITH_ZERO; char * param0 = parameter; parameter += spaceIndex + 1; char * param1 = parameter; if (current->parameterType == STRING2_PARAMETER) { VoidCharPtrCharPtr callbackS = (VoidCharPtrCharPtr) current->callback; (*callbackS)(param0, param1); } else { VoidCharPtrCharPtrVoidPtr callbackS = (VoidCharPtrCharPtrVoidPtr) current->callback; (*callbackS)(param0, param1, current->param); } return; } if (current->parameterType == STRING3_PARAMETER) { int spaceIndex = findEndOfToken(parameter); if (spaceIndex == -1) { return; } REPLACE_SPACES_WITH_ZERO; char * param0 = parameter; parameter += spaceIndex + 1; spaceIndex = findEndOfToken(parameter); if (spaceIndex == -1) return; REPLACE_SPACES_WITH_ZERO; char * param1 = parameter; parameter += spaceIndex + 1; char * param2 = parameter; VoidCharPtrCharPtrCharPtr callbackS = (VoidCharPtrCharPtrCharPtr) current->callback; (*callbackS)(param0, param1, param2); return; } // todo: refactor this hell! if (current->parameterType == STRING5_PARAMETER) { int spaceIndex = findEndOfToken(parameter); if (spaceIndex == -1) { return; } REPLACE_SPACES_WITH_ZERO; char * param0 = parameter; parameter += spaceIndex + 1; spaceIndex = findEndOfToken(parameter); if (spaceIndex == -1) return; REPLACE_SPACES_WITH_ZERO; char * param1 = parameter; parameter += spaceIndex + 1; spaceIndex = findEndOfToken(parameter); if (spaceIndex == -1) return; REPLACE_SPACES_WITH_ZERO; char * param2 = parameter; parameter += spaceIndex + 1; spaceIndex = findEndOfToken(parameter); if (spaceIndex == -1) return; REPLACE_SPACES_WITH_ZERO; char * param3 = parameter; parameter += spaceIndex + 1; char * param4 = parameter; VoidCharPtrCharPtrCharPtrCharPtrCharPtr callbackS = (VoidCharPtrCharPtrCharPtrCharPtrCharPtr) current->callback; (*callbackS)(param0, param1, param2, param3, param4); return; } if (current->parameterType == TWO_INTS_PARAMETER) { int spaceIndex = findEndOfToken(parameter); if (spaceIndex == -1) return; REPLACE_SPACES_WITH_ZERO; int value1 = atoi(parameter); if (absI(value1) == ERROR_CODE) { #if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__) scheduleMsg(logging, "not an integer [%s]", parameter); #endif return; } parameter += spaceIndex + 1; int value2 = atoi(parameter); if (absI(value2) == ERROR_CODE) { #if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__) scheduleMsg(logging, "not an integer [%s]", parameter); #endif return; } VoidIntInt callbackS = (VoidIntInt) current->callback; (*callbackS)(value1, value2); return; } if (current->parameterType == FLOAT_PARAMETER) { float value = atoff(parameter); if (cisnan(value)) { print("invalid float [%s]\r\n", parameter); return; } VoidFloat callbackF = (VoidFloat) current->callback; // invoke callback function by reference (*callbackF)(value); return; } if (current->parameterType == FLOAT_FLOAT_PARAMETER || current->parameterType == FLOAT_FLOAT_PARAMETER_P) { int spaceIndex = findEndOfToken(parameter); if (spaceIndex == -1) return; REPLACE_SPACES_WITH_ZERO; float value1 = atoff(parameter); if (cisnan(value1)) { print("invalid float [%s]\r\n", parameter); return; } parameter += spaceIndex + 1; float value2 = atoff(parameter); if (cisnan(value2)) { print("invalid float [%s]\r\n", parameter); return; } if (current->parameterType == FLOAT_FLOAT_PARAMETER) { VoidFloatFloat callbackS = (VoidFloatFloat) current->callback; (*callbackS)(value1, value2); } else { VoidFloatFloatVoidPtr callbackS = (VoidFloatFloatVoidPtr) current->callback; (*callbackS)(value1, value2, current->param); } return; } int value = atoi(parameter); if (absI(value) == ERROR_CODE) { print("invalid integer [%s]\r\n", parameter); return; } if (current->parameterType == ONE_PARAMETER_P) { VoidIntVoidPtr callback1 = (VoidIntVoidPtr) current->callback; // invoke callback function by reference (*callback1)(value, current->param); } else { VoidInt callback1 = (VoidInt) current->callback; // invoke callback function by reference (*callback1)(value); } } /** * @return Number of space-separated tokens in the string */ int tokenLength(const char *msgp) { int result = 0; while (*msgp) { char ch = *msgp++; if (ch == SPACE_CHAR) { break; } result++; } return result; } void initConsoleLogic(Logging *sharedLogger) { logging = sharedLogger; // resetConsoleActions(); addConsoleAction("help", helpCommand); addConsoleActionI("echo", echo); } /** * @return NULL if input line validation failed, reference to line payload if validation succeeded. * @see sendOutConfirmation() for command confirmation processing. */ char *validateSecureLine(char *line) { if (line == NULL) return NULL; if (strncmp(SECURE_LINE_PREFIX, line, SECURE_LINE_PREFIX_LENGTH) == 0) { // COM protocol looses bytes, this is a super-naive error detection // print("Got secure mode request header [%s]\r\n", line); line += SECURE_LINE_PREFIX_LENGTH; // print("Got secure mode request command [%s]\r\n", line); char *divider = line; while (*divider != '!') { if (*divider == '\0') { print("Divider not found [%s]\r\n", line); return NULL; } divider++; } *divider++ = 0; // replacing divider symbol with zero int expectedLength = atoi(line); line = divider; int actualLength = strlen(line); if (expectedLength != actualLength) { print("Error detected: expected %d but got %d in [%s]\r\n", expectedLength, actualLength, line); return NULL; } } return line; } static char confirmation[200]; static char handleBuffer[200]; static bool handleConsoleLineInternal(const char *commandLine, int lineLength) { strncpy(handleBuffer, commandLine, sizeof(handleBuffer) - 1); handleBuffer[sizeof(handleBuffer) - 1] = 0; // we want this to be null-terminated for sure char *line = handleBuffer; int firstTokenLength = tokenLength(line); // print("processing [%s] with %d actions\r\n", line, consoleActionCount); if (firstTokenLength == lineLength) { // no-param actions are processed here for (int i = 0; i < consoleActionCount; i++) { TokenCallback *current = &consoleActions[i]; if (strEqual(line, current->token)) { if (current->parameterType == NO_PARAMETER) { (*current->callback)(); } else if (current->parameterType == NO_PARAMETER_P) { VoidPtr cb = (VoidPtr) current->callback; (*cb)(current->param); } return true; } } } else { char *ptr = line + firstTokenLength; ptr[0] = 0; // change space into line end ptr++; // start from next symbol for (int i = 0; i < consoleActionCount; i++) { TokenCallback *current = &consoleActions[i]; if (strEqual(line, current->token)) { handleActionWithParameter(current, ptr); return true; } } } return false; } #if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__) static void sendOutConfirmation(const char *command, int length) { scheduleMsg(logging, "%s%d", command, length); } #endif /** * @brief This function takes care of one command line once we have it */ void handleConsoleLine(char *line) { line = validateSecureLine(line); if (line == NULL) return; // error detected int lineLength = strlen(line); if (lineLength > 100) { // todo: better max size logic // todo: better reaction to excessive line print("Long line?\r\n"); return; } strcpy(confirmation, "confirmation_"); strcat(confirmation, line); strcat(confirmation, ":"); #if EFI_PROD_CODE || EFI_SIMULATOR sendOutConfirmation(confirmation, lineLength); #endif #if EFI_SIMULATOR || defined(__DOXYGEN__) printf("handleConsoleLine [%s]\r\n", line); #endif /* EFI_SIMULATOR */ bool isKnownComman = handleConsoleLineInternal(line, lineLength); if (!isKnownComman) { scheduleMsg(logging, "unknown [%s]", line); helpCommand(); } }