// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. /*--------------- Includes --------------*/ #include #include #include #include "Cli.h" /*-- Symbolic Constant Macros (defines) --*/ #define MAX_LINE_HISTORY 2 #define MAX_MENUS 100 /*-------------- Typedefs ----------------*/ typedef enum _CLI_ESC_CTRL_T { CLI_ESC_NONE, CLI_ESC_PENDING1, CLI_ESC_PENDING2, CLI_ESC_UP } CLI_ESC_CTRL_T; typedef struct _CLI_COMMAND_LIST { CLI_COMMAND_T Cmd; struct _CLI_COMMAND_LIST *Next; } CLI_COMMAND_LIST; /*-- Declarations (Statics and globals) --*/ static char *Prompt = CLI_MAIN_MENU; static CLI_COMMAND_LIST *RegisteredCmds = NULL; static bool ShellOpen = false; /*--------- Function Prototypes ----------*/ void Help(int argc, char **argv); bool StrHasUpper(const char *pszStr); /*-------------- Functions ---------------*/ /** * Converts a string to a UINT32 value * * @param str * Pointer to the string to conver * * @return * TRUE The data was correctly formated and converted * FALSE The data MAY have been converted but it was incorrectly formated * */ bool CliGetStrVal(const char *str, uint32_t *pOut) { size_t len = strlen(str); // Default to decimal argument. *pOut = 0; int32_t base = 10; uint32_t start = 0; if (len == 0) return false; // If find '0x' or '0X', change to hexadecimal argument. if (len > 1 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { start = 2; base = 16; } *pOut = (uint32_t)strtoul(&str[start], NULL, base); return true; } /** * function to extract binary data from space separated string * * @param pSrc Pointer to the string source * @param pDst Pointer to where the binary data will be placed * @param Size Size of the Destination pointed to by pDst * * @return * UINT8 Numb of bytes converted */ uint8_t CliGetBin(char *pSrc, uint8_t *pDst, uint32_t Size) { uint8_t Index = 0; uint32_t Value; // Go through the input string and parse to binary data while ((*pSrc != 0) && (Index < Size)) { // skip white spaces while ((*pSrc < 48) && (*pSrc != 0)) { pSrc++; } if (*pSrc != 0) { CliGetStrVal((const char *)pSrc, &Value); pDst[Index++] = Value & 0xFF; // move pointer beyond value while ((*pSrc > 47) && (*pSrc != 0)) { pSrc++; } } } return Index; } /** * This function detects if an escape character * is entered. * * @param Ch Character to include for esc check * * @return * CLI_ESC_NONE No characters detected * CLI_ESC_PENDING1 First stage of escape character detected * CLI_ESC_PENDING2 Second stage of escape character detected * CLI_ESC_UP - Up arrow detected */ static CLI_ESC_CTRL_T CliEsc(char Ch) { static CLI_ESC_CTRL_T EscChar = CLI_ESC_NONE; if (Ch == 0x1B) { EscChar = CLI_ESC_PENDING1; } else if ((EscChar == CLI_ESC_PENDING1) && (Ch == '[')) { EscChar = CLI_ESC_PENDING2; } else if (EscChar == CLI_ESC_PENDING2 && Ch == 'A') { EscChar = CLI_ESC_UP; } else { EscChar = CLI_ESC_NONE; } return EscChar; } /** * Get Line from serial stream * * @param pBuffer Pointer to the buffer the will hold the line * * @param Length The size of the buffer handed in * * @return * uint16_t number of bytes received * */ static uint16_t CliGetLine(char *pBuffer, uint8_t Length) { char Ch; uint16_t NumChar = 0; static char Line[MAX_LINE_HISTORY][MAX_LINE_LENGTH]; static uint8_t LineCount; static uint8_t LinePosition; CLI_ESC_CTRL_T EscCtrl; while (NumChar < Length) { Ch = (char)getchar(); // Check for LF or CR. if (Ch == '\n' || Ch == '\r') { pBuffer[NumChar] = 0; // Exit loop break; } // Check for full cmd buffer. if (NumChar >= Length - 1) { continue; } // Check for ESC sequence: [ 1 3 ~ EscCtrl = CliEsc(Ch); if (EscCtrl != CLI_ESC_NONE) { if ((EscCtrl == CLI_ESC_UP) && (LineCount > 0)) { // Last command // go back 1 cmd position if (LinePosition != 0) { LinePosition--; } else { LinePosition = LineCount - 1; } // backspace current number of characters while (NumChar > 0) { NumChar--; } // Copy last command to buffer NumChar = (uint16_t)strlen(Line[LinePosition]); if (NumChar > Length) { NumChar = Length; } memcpy(pBuffer, Line[LinePosition], NumChar); } continue; } // Check for backspace or del character. else if ((Ch == '\b') || (Ch == 0x7F)) { if (NumChar > 0) { NumChar--; } } // Check for uppercase character. else if (Ch >= 'A' && Ch <= 'Z') { pBuffer[NumChar++] = Ch - 'A' + 'a'; } // Check for printable character. else { pBuffer[NumChar++] = Ch; } } if (NumChar > 0) { // Copy to buffer history memcpy(Line[LinePosition], pBuffer, strlen(pBuffer)); LinePosition++; if (LinePosition > LineCount) { LineCount = LinePosition; } if (LinePosition >= MAX_LINE_HISTORY) { LinePosition = 0; // loop through available buffer } } return NumChar; } /** * This utility function will parse the input line by separating * the arguments by NULLs. * * @param pLine Pointer to the string to parse * * @param Argv Pointer to the pointer array for arguments * * @param Length Maximum number of arguments to parse * * @return * int Number of arguments found * */ static int ParseLine(char *pLine, char **Argv, uint8_t Length) { int Argc; char Ch; bool StringVar = false; for (Argc = 0; Argc < Length;) { // Skip whitespace. while ((Ch = *pLine) != 0) { if (Ch == '"') { StringVar = true; } if (Ch != ' ' && Ch != '\t' && Ch != '"') { break; } pLine++; } if (Ch == 0) break; // Record start of current argument. Argv[Argc] = pLine; Argc++; // Skip up to next whitespace. while ((Ch = *pLine) != 0) { if (StringVar) { if (Ch == '"') { StringVar = false; break; } } else { if (Ch == ' ' || Ch == '\t') break; } pLine++; } // Terminate argument string. if (Ch != 0) { *pLine++ = 0; } } return Argc; } /** * Shell loop for processing commands. * */ static void CliShell(void) { char Buffer[MAX_LINE_LENGTH]; static int MyArgc; static char *MyArgv[MAX_ARGS]; ShellOpen = true; printf("%s>", Prompt); while (ShellOpen) { CliGetLine(Buffer, MAX_LINE_LENGTH); MyArgc = ParseLine(Buffer, MyArgv, MAX_ARGS); // Parse and execute command. CliExecute(MyArgc, &MyArgv[0]); } } /** * Exit command for shell * */ static void CliExit(void) { ShellOpen = false; } /** * This function is execute the input from the user * * @param Argc Number of variables handed in * * @param Argv Pointer to variable array * * @return * CLI_SUCCESS If executed properly * CLI_ERROR If error was detected * */ int CliExecute(int Argc, char **Argv) { CLI_COMMAND_LIST *pCmdEntry = RegisteredCmds; CLI_COMMAND_T *pCmd; CLI_STATUS Status = CLI_SUCCESS; // Search for command. printf("\n"); // check for help if ((Argc == 0) || (strcmp(Argv[0], "?") == 0)) { // Print help information Help(Argc, &Argv[0]); printf("%s>", Prompt); return (int)Status; } // check for shell if (strcmp(Argv[0], "shell") == 0) { // Go into command line shell CliShell(); return (int)Status; } // check for exit if (strcmp(Argv[0], "exit") == 0) { // Exit shell if in it CliExit(); return (int)Status; } // check for main menu if (strcmp(Argv[0], "..") == 0) { // Go to main menu Prompt = CLI_MAIN_MENU; printf("%s>", Prompt); return (int)Status; } // Find command while (pCmdEntry != NULL) { pCmd = &(pCmdEntry->Cmd); if ((strcmp(Argv[0], pCmd->Menu) == 0) && (Argc == 1)) { // Change prompts and menu level Prompt = pCmd->Menu; break; } if ((strcmp(Prompt, pCmd->Menu) == 0) && (strcmp(Argv[0], pCmd->Command) == 0)) { Status = pCmd->Routine(Argc, &Argv[0]); if (Status == CLI_SUCCESS) { printf("Ok\n"); } else { printf("ERROR\n"); } break; } else if ((Argc > 1) && (strcmp(Argv[0], pCmd->Menu) == 0) && (strcmp(Argv[1], pCmd->Command) == 0)) { Status = pCmd->Routine(Argc - 1, &Argv[1]); if (Status == CLI_SUCCESS) { printf("Ok\n"); } else { printf("ERROR\n"); } break; } pCmdEntry = pCmdEntry->Next; }; if (pCmdEntry == NULL) { // No commands found to match printf("'%s' Invalid command. Type ? for help.\n", Argv[0]); Status = CLI_ERROR; } printf("%s>", Prompt); return (int)Status; } /** * This function is used to display help information * * @param Argc Number of variables handed in * * @param Argv Pointer to variable array * */ void Help(int Argc, char **Argv) { CLI_COMMAND_LIST *pCmdEntry = RegisteredCmds; CLI_COMMAND_T *pCmd; uint32_t y; char *Menus[MAX_MENUS] = { NULL }; // Display menu or usage information while (pCmdEntry != NULL) { pCmd = &(pCmdEntry->Cmd); // Extract list of menus for (y = 0; y < MAX_MENUS; y++) { if (Menus[y] == NULL) { Menus[y] = pCmd->Menu; break; } else if (strcmp(Menus[y], pCmd->Menu) == 0) { break; } } if (Argc == 1) { // List all command at current level if (strcmp(Prompt, pCmd->Menu) == 0) { printf("%s", pCmd->Command); printf("%s", &" "[strlen(pCmd->Command) & 0xF]); printf("%s\n", pCmd->Description); } } else if (Argc == 2) { // Display help on item at current level if ((strcmp(Prompt, pCmd->Menu) == 0) && (strcmp(Argv[1], pCmd->Command) == 0) && (pCmd->Usage != NULL)) { printf("%s\n", pCmd->Usage); break; } } else if (Argc == 3) { // Display sub menu item help if ((strcmp(Argv[1], pCmd->Menu) == 0) && (strcmp(Argv[2], pCmd->Command) == 0) && (pCmd->Usage != NULL)) { printf("%s\n", pCmd->Usage); break; } } pCmdEntry = pCmdEntry->Next; if (pCmdEntry == NULL) { // Print out default commands in menu list if (strncmp(Prompt, CLI_MAIN_MENU, sizeof(CLI_MAIN_MENU)) != 0) { printf(".. Goto Main Menu\n"); } else { // print out available sub-menus for (y = 0; y < MAX_MENUS; y++) { if ((Menus[y] != 0) && (*Menus[y] != 0)) { printf("%s", Menus[y]); printf("%s", &" "[strlen(Menus[y]) & 0xF]); printf("Menu\n"); } } } printf("? Display menu or usage information\n"); break; } } } /** * Dynamic registration API for CLI commands * * @param pMenu String pointer for menu syntax * * @param pSyntax String pointer for command syntax * * @param Routine Function pointer associated with the command * * @param pDescription String pointer describing the command * * @param pUsage Usage string * */ void CliRegister(char *pMenu, const char *pSyntax, CLI_STATUS (*Routine)(int argc, char **argv), const char *pDescription, const char *pUsage) { assert((pSyntax != NULL) && (Routine != NULL) && (pMenu != NULL)); CLI_COMMAND_LIST *pCmdEntry = RegisteredCmds; CLI_COMMAND_LIST *pNewEntry; assert(!StrHasUpper(pMenu)); assert(!StrHasUpper(pSyntax)); pNewEntry = malloc(sizeof(CLI_COMMAND_LIST)); assert(pNewEntry != NULL); pNewEntry->Cmd.Menu = pMenu; pNewEntry->Cmd.Command = pSyntax; pNewEntry->Cmd.Routine = Routine; pNewEntry->Cmd.Description = pDescription; pNewEntry->Cmd.Usage = pUsage; pNewEntry->Next = NULL; // Fist command? if (RegisteredCmds == NULL) { RegisteredCmds = pNewEntry; } else { // Find last command while (pCmdEntry->Next != NULL) { pCmdEntry = pCmdEntry->Next; } pCmdEntry->Next = pNewEntry; } } /** * Dynamic registration API for CLI commands * * @param pTable Pointer to the array of commands to register with * the command engine. * */ void CliRegisterTable(CLI_COMMAND_T *pTable) { while (pTable->Command != NULL) { CliRegister(pTable->Menu, pTable->Command, pTable->Routine, pTable->Description, pTable->Usage); pTable++; } } /** * Method for prompting information from the user * * @param pPrompt The prompt string that will be displayed to the * user when this function is called. If NULL, no * prompt will be displayed. * * @param pBuffer Pointer to the location used to hold * the input string * * @param Length Size of buffer handed in * * @return * uint16_t Number of bytes that were placed in pBuffer */ uint16_t CliRead(char *pPrompt, char *pBuffer, uint8_t Length) { if (pPrompt != NULL) { printf("%s", pPrompt); } return CliGetLine(pBuffer, Length); } /** * Method to display the usage string of the function associated with the * command syntax * * @param Routine Function pointer to the function associated * with the command syntax. * */ void CliDisplayUsage(CLI_STATUS (*Routine)(int argc, char **argv)) { CLI_COMMAND_LIST *pCmdEntry = RegisteredCmds; while (pCmdEntry != NULL) { if ((pCmdEntry->Cmd.Routine == Routine) && (pCmdEntry->Cmd.Usage != NULL)) { printf("%s\n", pCmdEntry->Cmd.Usage); break; } pCmdEntry = pCmdEntry->Next; } } /** * Check if a string has any upper case charater * * @param pszStr pointer to a NULL terminated string * * @return * true if an upper case character is found in the string * false if no upper case character is found in the string */ bool StrHasUpper(const char *pszStr) { while (pszStr && *pszStr) { if (isupper(*pszStr++)) { return true; } } return false; }