| /* | 
 |  * | 
 |  *    Copyright (c) 2021 Project CHIP Authors | 
 |  * | 
 |  *    Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  *    you may not use this file except in compliance with the License. | 
 |  *    You may obtain a copy of the License at | 
 |  * | 
 |  *        http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  *    Unless required by applicable law or agreed to in writing, software | 
 |  *    distributed under the License is distributed on an "AS IS" BASIS, | 
 |  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  *    See the License for the specific language governing permissions and | 
 |  *    limitations under the License. | 
 |  */ | 
 |  | 
 | #include "MatterShell.h" | 
 | #include "streamer.h" | 
 | #include <lib/shell/Engine.h> | 
 | #include <lib/support/CHIPMem.h> | 
 | #include <platform/CHIPDeviceLayer.h> | 
 |  | 
 | #include <ctype.h> | 
 | #include <string.h> | 
 |  | 
 | using chip::FormatCHIPError; | 
 | using chip::Platform::MemoryAlloc; | 
 | using chip::Platform::MemoryFree; | 
 | using chip::Shell::Engine; | 
 | using chip::Shell::streamer_get; | 
 |  | 
 | namespace { | 
 |  | 
 | constexpr char kShellPrompt[] = "matterCli> "; | 
 |  | 
 | // To track carriage returns of Windows return cases of '\r\n' | 
 | bool haveCR = false; | 
 |  | 
 | void ReadLine(char * buffer, size_t max) | 
 | { | 
 |     size_t line_sz = 0; | 
 |     size_t read    = 0; | 
 |     bool done      = false; | 
 |  | 
 |     // Read in characters until we get a line ending or EOT. | 
 |     while ((line_sz < max) && !done) | 
 |     { | 
 |         // Stop reading if we've run out of space in the buffer (still need to null-terminate). | 
 |         if (line_sz >= max - 1u) | 
 |         { | 
 |             buffer[max - 1] = '\0'; | 
 |             break; | 
 |         } | 
 |  | 
 |         chip::WaitForShellActivity(); | 
 |         while (streamer_read(streamer_get(), buffer + read, 1) == 1) | 
 |         { | 
 |             // Count how many characters were read; usually one but could be copy/paste | 
 |             read++; | 
 |         } | 
 |         // Process all characters that were read until we run out or exceed max char limit | 
 |         while (line_sz < read && line_sz < max) | 
 |         { | 
 |             switch (buffer[line_sz]) | 
 |             { | 
 |             case '\r': | 
 |                 // Mac OS return case of '\r' or beginning of Windows return case '\r\n' | 
 |                 buffer[line_sz] = '\0'; | 
 |                 streamer_printf(streamer_get(), "\r\n"); | 
 |                 haveCR = true; | 
 |                 done   = true; | 
 |                 line_sz++; | 
 |                 break; | 
 |             case '\n': | 
 |                 // True if Windows return case of '\r\n' | 
 |                 if (haveCR) | 
 |                 { | 
 |                     // Do nothing - already taken care of with CR, return to loop and don't increment buffer | 
 |                     haveCR = false; | 
 |                     read--; | 
 |                 } | 
 |                 // Linux return case of just '\n' | 
 |                 else | 
 |                 { | 
 |                     buffer[line_sz] = '\0'; | 
 |                     streamer_printf(streamer_get(), "\r\n"); | 
 |                     done = true; | 
 |                     line_sz++; | 
 |                 } | 
 |                 break; | 
 |             case 0x7F: | 
 |                 // Do not accept backspace character (i.e. don't increment line_sz) and remove 1 additional character if it exists. | 
 |                 if (line_sz >= 1u) | 
 |                 { | 
 |                     // Delete backspace character + whatever came before it | 
 |                     streamer_printf(streamer_get(), "\b \b"); | 
 |                     line_sz--; | 
 |                     read--; | 
 |                 } | 
 |                 // Remove backspace character regardless | 
 |                 read--; | 
 |  | 
 |                 break; | 
 |             default: | 
 |                 if (isprint(static_cast<int>(buffer[line_sz])) || buffer[line_sz] == '\t') | 
 |                 { | 
 |                     streamer_printf(streamer_get(), "%c", buffer[line_sz]); | 
 |                 } | 
 |                 line_sz++; | 
 |                 break; | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | bool IsSeparator(char ch) | 
 | { | 
 |     return (ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'); | 
 | } | 
 |  | 
 | bool IsEscape(char ch) | 
 | { | 
 |     return (ch == '\\'); | 
 | } | 
 |  | 
 | bool IsEscapable(char ch) | 
 | { | 
 |     return IsSeparator(ch) || IsEscape(ch); | 
 | } | 
 |  | 
 | int TokenizeLine(char * buffer, char ** tokens, int max_tokens) | 
 | { | 
 |     size_t len = strlen(buffer); | 
 |     int cursor = 0; | 
 |     size_t i   = 0; | 
 |  | 
 |     // Strip leading spaces | 
 |     while (buffer[i] && buffer[i] == ' ') | 
 |     { | 
 |         i++; | 
 |     } | 
 |  | 
 |     if (len <= i) | 
 |     { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     // The first token starts at the beginning. | 
 |     tokens[cursor++] = &buffer[i]; | 
 |  | 
 |     for (; i < len && cursor < max_tokens; i++) | 
 |     { | 
 |         if (IsEscape(buffer[i]) && IsEscapable(buffer[i + 1])) | 
 |         { | 
 |             // include the null terminator: strlen(cmd) = strlen(cmd + 1) + 1 | 
 |             memmove(&buffer[i], &buffer[i + 1], strlen(&buffer[i])); | 
 |         } | 
 |         else if (IsSeparator(buffer[i])) | 
 |         { | 
 |             buffer[i] = 0; | 
 |             // Don't treat the previous character as a separator if this one is 0 | 
 |             // otherwise the trailing space will become a token | 
 |             if (!IsSeparator(buffer[i + 1]) && buffer[i + 1] != 0) | 
 |             { | 
 |                 tokens[cursor++] = &buffer[i + 1]; | 
 |             } | 
 |         } | 
 |     } | 
 |     // If for too many arguments, overwrite last entry with guard. | 
 |     if (cursor >= max_tokens) | 
 |     { | 
 |         cursor = max_tokens - 1; | 
 |     } | 
 |  | 
 |     tokens[cursor] = nullptr; | 
 |  | 
 |     return cursor; | 
 | } | 
 |  | 
 | void ProcessShellLine(intptr_t args) | 
 | { | 
 |     int argc; | 
 |     char * argv[CHIP_SHELL_MAX_TOKENS]; | 
 |  | 
 |     char * line = reinterpret_cast<char *>(args); | 
 |     argc        = TokenizeLine(line, argv, CHIP_SHELL_MAX_TOKENS); | 
 |  | 
 |     if (argc > 0) | 
 |     { | 
 |         CHIP_ERROR retval = Engine::Root().ExecCommand(argc, argv); | 
 |  | 
 |         if (retval != CHIP_NO_ERROR) | 
 |         { | 
 |             char errorStr[160]; | 
 |             bool errorStrFound = FormatCHIPError(errorStr, sizeof(errorStr), retval); | 
 |             if (!errorStrFound) | 
 |             { | 
 |                 errorStr[0] = 0; | 
 |             } | 
 |             streamer_printf(streamer_get(), "Error %s: %s\r\n", argv[0], errorStr); | 
 |         } | 
 |         else | 
 |         { | 
 |             streamer_printf(streamer_get(), "Done\r\n", argv[0]); | 
 |         } | 
 |     } | 
 |     MemoryFree(line); | 
 |     streamer_printf(streamer_get(), kShellPrompt); | 
 | } | 
 |  | 
 | } // namespace | 
 |  | 
 | namespace chip { | 
 | namespace Shell { | 
 |  | 
 | void Engine::RunMainLoop() | 
 | { | 
 |     streamer_printf(streamer_get(), kShellPrompt); | 
 |  | 
 |     while (mRunning) | 
 |     { | 
 |         char * line = static_cast<char *>(Platform::MemoryAlloc(CHIP_SHELL_MAX_LINE_SIZE)); | 
 |         ReadLine(line, CHIP_SHELL_MAX_LINE_SIZE); | 
 | #if CONFIG_DEVICE_LAYER | 
 |         DeviceLayer::PlatformMgr().ScheduleWork(ProcessShellLine, reinterpret_cast<intptr_t>(line)); | 
 | #else | 
 |         ProcessShellLine(reinterpret_cast<intptr_t>(line)); | 
 | #endif | 
 |     } | 
 | } | 
 |  | 
 | } // namespace Shell | 
 | } // namespace chip |