Browse Source

Merge branch 'release/V0.80.0000' of shimatta/shellmatta into master

shimatta 10 months ago
parent
commit
de7d2b360f
74 changed files with 9714 additions and 2071 deletions
  1. 4 1
      .gitignore
  2. 42 0
      .vscode/launch.json
  3. 3 9
      .vscode/settings.json
  4. 16 0
      .vscode/tasks.json
  5. 12 7
      README.md
  6. 353 38
      api/shellmatta.h
  7. 97 0
      cfg/cppcheck/cppcheck_suppressions.xml
  8. 575 271
      cfg/doxygen/doxyfile
  9. 15 0
      doc/shellmatta.dox
  10. 84 0
      doc/shellmatta_auth.dox
  11. 152 0
      doc/shellmatta_transport_layer.dox
  12. 102 0
      doc/shellmatta_ymodem.dox
  13. 110 13
      example/main.c
  14. 159 63
      makefile
  15. 0 0
      python_driver/__init__.py
  16. 265 0
      python_driver/shellmatta_transport.py
  17. 115 0
      python_driver/shellmatta_transport_serial.py
  18. 460 330
      src/shellmatta.c
  19. 634 0
      src/shellmatta_auth.c
  20. 38 0
      src/shellmatta_auth.h
  21. 31 7
      src/shellmatta_autocomplete.c
  22. 1 1
      src/shellmatta_autocomplete.h
  23. 235 0
      src/shellmatta_crc.c
  24. 27 0
      src/shellmatta_crc.h
  25. 1 1
      src/shellmatta_escape.c
  26. 1 1
      src/shellmatta_escape.h
  27. 16 3
      src/shellmatta_history.c
  28. 2 1
      src/shellmatta_history.h
  29. 3 3
      src/shellmatta_opt.c
  30. 1 1
      src/shellmatta_opt.h
  31. 527 0
      src/shellmatta_transport.c
  32. 87 0
      src/shellmatta_transport.h
  33. 120 22
      src/shellmatta_utils.c
  34. 49 29
      src/shellmatta_utils.h
  35. 531 0
      src/shellmatta_ymodem.c
  36. 33 0
      src/shellmatta_ymodem.h
  37. 2282 1171
      test/framework/catch.hpp
  38. 31 38
      test/integrationtest/test_integration.cpp
  39. 3 3
      test/integrationtest/test_integration_busy.cpp
  40. 5 5
      test/integrationtest/test_integration_continue.cpp
  41. 11 11
      test/integrationtest/test_integration_help.cpp
  42. 4 4
      test/integrationtest/test_integration_history.cpp
  43. 3 3
      test/integrationtest/test_integration_opt.cpp
  44. 3 3
      test/integrationtest/test_integration_optLong.cpp
  45. 226 0
      test/integrationtest/test_integration_ymodem.cpp
  46. 847 0
      test/integrationtest_auth/test_integration_auth.cpp
  47. 11 0
      test/integrationtest_auth/test_main.cpp
  48. 506 0
      test/integrationtest_transport/test_integration_transport.cpp
  49. 11 0
      test/integrationtest_transport/test_main.cpp
  50. 16 3
      test/unittest/shellmatta/test_shellmatta_doInit.cpp
  51. 14 2
      test/unittest/shellmatta_autocomplete/test_autocomplete_run.cpp
  52. 35 0
      test/unittest/shellmatta_crc/test_crc16Fast.cpp
  53. 35 0
      test/unittest/shellmatta_crc/test_crc16Slow.cpp
  54. 35 0
      test/unittest/shellmatta_crc/test_crc32Fast.cpp
  55. 35 0
      test/unittest/shellmatta_crc/test_crc32Slow.cpp
  56. 280 0
      test/unittest/shellmatta_crc/test_crc_data.h
  57. 15 1
      test/unittest/shellmatta_escape/test_escape_processArrowKeys.cpp
  58. 16 4
      test/unittest/shellmatta_history/test_appendHistoryByte.cpp
  59. 14 0
      test/unittest/shellmatta_opt/test_opt_findNextHunk.cpp
  60. 14 0
      test/unittest/shellmatta_opt/test_opt_peekNextHunk.cpp
  61. 14 0
      test/unittest/shellmatta_utils/test_utils_clearInput.cpp
  62. 14 0
      test/unittest/shellmatta_utils/test_utils_eraseLine.cpp
  63. 14 2
      test/unittest/shellmatta_utils/test_utils_forwardCursor.cpp
  64. 14 0
      test/unittest/shellmatta_utils/test_utils_insertChars.cpp
  65. 14 0
      test/unittest/shellmatta_utils/test_utils_removeChars.cpp
  66. 14 0
      test/unittest/shellmatta_utils/test_utils_restoreCursorPos.cpp
  67. 14 0
      test/unittest/shellmatta_utils/test_utils_rewindCursor.cpp
  68. 14 0
      test/unittest/shellmatta_utils/test_utils_saveCursorPos.cpp
  69. 170 0
      test/unittest/shellmatta_utils/test_utils_shellAsciiToUInt32.cpp
  70. 29 15
      test/unittest/shellmatta_utils/test_utils_shellItoa.cpp
  71. 14 0
      test/unittest/shellmatta_utils/test_utils_terminateInput.cpp
  72. 18 4
      test/unittest/shellmatta_utils/test_utils_writeEcho.cpp
  73. 18 0
      test/unittest/shellmatta_ymodem/test_ymodem.cpp
  74. 0 1
      test/unittest/test_main.cpp

+ 4 - 1
.gitignore

@@ -1,2 +1,5 @@
 output
 output
-output
+**__pycache__**
+.cache
+compile_commands.json
+python_driver/shellmatta_transport.py

+ 42 - 0
.vscode/launch.json

@@ -66,6 +66,48 @@
             ],
             ],
             "preLaunchTask": "make integrationtest",
             "preLaunchTask": "make integrationtest",
             "miDebuggerPath": "/usr/bin/gdb"
             "miDebuggerPath": "/usr/bin/gdb"
+        },
+        {
+            "name": "debug integrationtest_auth",
+            "type": "cppdbg",
+            "request": "launch",
+            "program": "${workspaceFolder}/output/test/integrationtest_auth/integrationtest_auth",
+            "args": [],
+            "stopAtEntry": false,
+            "cwd": "${workspaceFolder}",
+            "environment": [],
+            "externalConsole": false,
+            "MIMode": "gdb",
+            "setupCommands": [
+                {
+                    "description": "Enable pretty-printing for gdb",
+                    "text": "-enable-pretty-printing",
+                    "ignoreFailures": true
+                }
+            ],
+            "preLaunchTask": "make integrationtest_auth",
+            "miDebuggerPath": "/usr/bin/gdb"
+        },
+        {
+            "name": "debug integrationtest_transport",
+            "type": "cppdbg",
+            "request": "launch",
+            "program": "${workspaceFolder}/output/test/integrationtest_transport/integrationtest_transport",
+            "args": [],
+            "stopAtEntry": false,
+            "cwd": "${workspaceFolder}",
+            "environment": [],
+            "externalConsole": false,
+            "MIMode": "gdb",
+            "setupCommands": [
+                {
+                    "description": "Enable pretty-printing for gdb",
+                    "text": "-enable-pretty-printing",
+                    "ignoreFailures": true
+                }
+            ],
+            "preLaunchTask": "make integrationtest_transport",
+            "miDebuggerPath": "/usr/bin/gdb"
         }
         }
     ]
     ]
 }
 }

+ 3 - 9
.vscode/settings.json

@@ -1,13 +1,7 @@
 {
 {
     "files.associations": {
     "files.associations": {
-        "random": "cpp",
-        "*.tcc": "cpp",
-        "string": "cpp",
-        "vector": "cpp",
-        "fstream": "cpp",
-        "limits": "cpp",
         "sstream": "cpp",
         "sstream": "cpp",
-        "utility": "cpp",
-        "algorithm": "cpp"
-    }
+        "regex": "cpp"
+    },
+    "C_Cpp.default.defines": ["SHELLMATTA_AUTH", "SHELLMATTA_TRANSPORT"]
 }
 }

+ 16 - 0
.vscode/tasks.json

@@ -38,6 +38,22 @@
                 "$gcc"
                 "$gcc"
             ]
             ]
         },
         },
+        {
+            "label": "make integrationtest_auth",
+            "type": "shell",
+            "command": "make integrationtest_auth",
+            "problemMatcher": [
+                "$gcc"
+            ]
+        },
+        {
+            "label": "make integrationtest_transport",
+            "type": "shell",
+            "command": "make integrationtest_transport",
+            "problemMatcher": [
+                "$gcc"
+            ]
+        },
         {
         {
             "label": "make test",
             "label": "make test",
             "type": "shell",
             "type": "shell",

+ 12 - 7
README.md

@@ -35,6 +35,7 @@ The `shellmatta` piled up some features over time:
 2. auto complete
 2. auto complete
 3. heredoc like interface to pass multiline data
 3. heredoc like interface to pass multiline data
 4. option parser (getopt like)
 4. option parser (getopt like)
+5. simple authentication mechanism
 
 
 ## Documentation
 ## Documentation
 
 
@@ -140,13 +141,17 @@ int main(void)
 
 
 There are some defines you can use to change the behaviour of the shellmatta:
 There are some defines you can use to change the behaviour of the shellmatta:
 
 
-| Define                     | Description                                    |
-| -------------------------- | ---------------------------------------------- |
-| SHELLMATTA_STRIP_PRINTF    | removes stdio dependencies to reduce footprint |
-| SHELLMATTA_HELP_COMMAND    | string to overwrite the help command name      |
-| SHELLMATTA_HELP_ALIAS      | string to overwrite the help command alias     |
-| SHELLMATTA_HELP_HELP_TEXT  | string to overwrite the help command help      |
-| SHELLMATTA_HELP_USAGE_TEXT | string to overwrite the help command usage     |
+| Define                                 | Description                                    |
+| -------------------------------------- | ---------------------------------------------- |
+| SHELLMATTA_STRIP_PRINTF                | removes stdio dependencies to reduce footprint |
+| SHELLMATTA_HELP_COMMAND                | string to overwrite the help command name      |
+| SHELLMATTA_HELP_ALIAS                  | string to overwrite the help command alias     |
+| SHELLMATTA_HELP_HELP_TEXT              | string to overwrite the help command help      |
+| SHELLMATTA_HELP_USAGE_TEXT             | string to overwrite the help command usage     |
+| SHELLMATTA_AUTHENTICATION              | if defined this enables the authentication     |
+| SHELLMATTA_TRANSPORT                   | if defined this enables the transport layer    |
+| SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP     | disables lookup for transport layer crc32      |
+| SHELLMATTA_YMODEM_CRC_NO_LOOKUP        | disables lookup for ymodem crc16               |
 
 
 ## Example
 ## Example
 
 

+ 353 - 38
api/shellmatta.h

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -25,6 +25,21 @@
 
 
 /* global defines */
 /* global defines */
 
 
+
+/**
+ * Define the printf format specifier for all GCC versions > 3.3
+ * This will let the compiler know that shellmatta_printf() is a function taking printf-like format specifiers.
+ */
+#ifndef SHELLMATTA_ATTR_FORMAT
+#   if ((__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)))
+#       define SHELLMATTA_ATTR_FORMAT(fmt, args) __attribute__((__format__(__printf__, fmt, args)))
+#   else
+#       define SHELLMATTA_ATTR_FORMAT(fmt, args)
+#   endif
+#else
+#   define SHELLMATTA_ATTR_FORMAT(fmt, args)
+#endif
+
 /**
 /**
  * @brief definition of a shellmatta handle
  * @brief definition of a shellmatta handle
  */
  */
@@ -101,6 +116,66 @@ typedef shellmatta_retCode_t (*shellmatta_cmdFct_t)(const shellmatta_handle_t
  */
  */
 typedef shellmatta_retCode_t (*shellmatta_write_t)(const char* data, uint32_t length);
 typedef shellmatta_retCode_t (*shellmatta_write_t)(const char* data, uint32_t length);
 
 
+#ifdef SHELLMATTA_AUTHENTICATION
+/**
+ * @brief user role matrix
+ */
+typedef struct
+{
+    uint32_t    userId;     /**< id of the user (!= 0)                              */
+    bool        superuser;  /**< allow the user to access all commands              */
+    const char  *username;  /**< name of the user role                              */
+    const char  *password;  /**< password of the user role or NULL (custom auth)    */
+} shellmatta_auth_user_t;
+
+/**
+ * @brief typedefinition of one line of the authentication table
+ */
+typedef struct
+{
+    const char*    cmd;           /**< command to grand access to               */
+    const uint32_t *userIds;      /**< list of user ids with access to the cmd  */
+    const uint32_t userIdslength; /**< length of the user list                  */
+} shellmatta_auth_perm_t;
+
+/**
+ * @brief login states
+ */
+typedef enum
+{
+    SHELLMATTA_AUTH_IDLE,       /**< authentication system waits    */
+    SHELLMATTA_AUTH_USERNAME,   /**< input of username              */
+    SHELLMATTA_AUTH_PASSWORD    /**< input of password              */
+} shellmatta_auth_state_t;
+
+/**
+ * @brief log actions - passed to the log function
+ */
+typedef enum
+{
+    SHELLMATTA_AUTH_EVENT_NONE,         /**< no event (init value)      */
+    SHELLMATTA_AUTH_EVENT_LOGIN,        /**< successful login event     */
+    SHELLMATTA_AUTH_EVENT_LOGIN_FAILED, /**< failed login event         */
+    SHELLMATTA_AUTH_EVENT_LOGOUT,       /**< succesful logout event     */
+} shellmatta_auth_log_event_t;
+
+/**
+ * @brief custom shellmatta authentication method
+ * @param[in]   userId      user id to log in (name of the user role)
+ * @param[in]   password    password for the login
+ * @return      userId if password was correct - otherwise 0
+ */
+typedef shellmatta_retCode_t (*shellmatta_auth_check_t)(const uint32_t userId, const char* password);
+
+/**
+ * @brief shellmatta authentication log method - will be called whenever a login attempt is done
+ * @param[in]   userId  userId to be logged in (0 on unknown user)
+ * @param[in]   event   event type to be logged (e.g. successful login)
+ */
+typedef void (*shellmatta_auth_log_t)(const uint32_t userId, shellmatta_auth_log_event_t event);
+
+#endif
+
 /**
 /**
  * @brief structure of one shellmatta command
  * @brief structure of one shellmatta command
  */
  */
@@ -111,50 +186,240 @@ typedef struct shellmatta_cmd
     char                    *helpText;  /**< help text to print in "help" command   */
     char                    *helpText;  /**< help text to print in "help" command   */
     char                    *usageText; /**< usage text - printed on "help cmd"     */
     char                    *usageText; /**< usage text - printed on "help cmd"     */
     shellmatta_cmdFct_t     cmdFct;     /**< pointer to the cmd callack function    */
     shellmatta_cmdFct_t     cmdFct;     /**< pointer to the cmd callack function    */
+#ifdef SHELLMATTA_AUTHENTICATION
+    shellmatta_auth_perm_t  *authLink;  /**< internally used - pointer to perm list */
+#endif
     struct shellmatta_cmd   *next;      /**< pointer to next command or NULL        */
     struct shellmatta_cmd   *next;      /**< pointer to next command or NULL        */
 } shellmatta_cmd_t;
 } shellmatta_cmd_t;
 
 
+/**
+ * @brief shellmatta ymodem cancel callback definition
+ * @param[in]   handle      pointer to the instance which is calling the callback
+ */
+typedef void (*shellmatta_ymodem_cancel_t)(const shellmatta_handle_t handle);
+
+/**
+ * @brief shellmatta ymodem header receive callback definition
+ * @param[in]   handle      pointer to the instance which is calling the callback
+ * @param[in]   fileSize    file size of the file to be received
+ * @param[in]   fileName    file name of the file to be received
+ */
+typedef void (*shellmatta_ymodem_recvHeader_t)(const shellmatta_handle_t    handle,
+                                               uint32_t                     fileSize,
+                                               char*                        fileName);
+
+/**
+ * @brief shellmatta ymodem packet receive callback definition
+ * @param[in]   handle      pointer to the instance which is calling the callback
+ * @param[in]   data        received data
+ * @param[in]   packetSize  size of the data in the packet
+ * @param[in]   packetNum   number of the received packet
+ */
+typedef void (*shellmatta_ymodem_recvPacket_t)(const shellmatta_handle_t    handle,
+                                               uint8_t                      *data,
+                                               uint32_t                     packetSize,
+                                               uint32_t                     packetNum);
+
+/**
+ * @brief shellmatta ymodem transmission complete callback definition
+ * @param[in]   handle      pointer to the instance which is calling the callback
+ * @param[in]   result      #SHELLMATTA_OK
+ *                          #SHELLMATTA_ERROR - missing data
+ */
+typedef void (*shellmatta_ymodem_complete_t)(const shellmatta_handle_t      handle,
+                                             shellmatta_retCode_t           result);
+
+/**
+ * @brief state enumeration for ymodem receive state machine
+ */
+typedef enum
+{
+    SHELLMATTA_YMODEM_INACTIVE,         /**< YModem module not initialised  */
+    SHELLMATTA_YMODEM_WAIT_FOR_START,   /**< waiting for start of header    */
+    SHELLMATTA_YMODEM_RECEIVE_HEADER,   /**< reading header data            */
+    SHELLMATTA_YMODEM_RECEIVE_DATA,     /**< reading payload                */
+    SHELLMATTA_YMODEM_RECEIVE_CRC,      /**< reading crc                    */
+    SHELLMATTA_YMODEM_WAIT_FOR_END      /**< wait until EOTs stop           */
+} shellmatta_ymodem_state_t;
+
+/** @brief packet structure that holds several information about its content */
+typedef struct
+{
+    uint16_t size;          /**< size of the packet (128 or 1024)     */
+    uint8_t packetNumber;   /**< packet number in this packet         */
+    uint8_t* packetData;    /**< pointer to the data of this packet   */
+    uint16_t crc;           /**< crc checksum in this packet          */
+} shellmatta_ymodem_packet_t;
+
+/**
+ * @brief definition of shellmatta ymodem instance
+ */
+typedef struct
+{
+    shellmatta_ymodem_state_t state;                            /**< current state of the ymodem module             */
+    uint32_t byteCounter;                                       /**< internal counter for processing input data     */
+    uint32_t packetCounter;                                     /**< counter of received packets                    */
+    uint32_t totalBytesReceived;                                /**< counter of received bytes                      */
+    uint32_t fileSize;                                          /**< size of the file received in packet 0          */
+    bool pauseRequested;                                        /**< pause requested from the application           */
+    uint32_t pollCyclesLeft;                                    /**< number of poll cycles left before finish       */
+    uint32_t cancelCounter;                                     /**< counter to count the amount of cancels         */
+    shellmatta_ymodem_packet_t packet;                          /**< currently processed packet                     */
+    shellmatta_ymodem_cancel_t cancelCallback;                  /**< callback to pass cancel events                 */
+    shellmatta_ymodem_recvHeader_t recvHeaderCallback;          /**< callback to pass received header data          */
+    shellmatta_ymodem_recvPacket_t recvPacketCallback;          /**< callback to pass received data                 */
+    shellmatta_ymodem_complete_t transmissionCompleteCallback;  /**< callback to be called on a complete transfer   */
+} shellmatta_ymodem_t;
+
+#ifdef SHELLMATTA_TRANSPORT
+
+/**
+ * @brief definition of shellmatta transport layer states
+ */
+typedef enum
+{
+    SHELLMATTA_TRANSPORT_STATE_WAIT = 0u    ,   /**< wait for start of header   */
+    SHELLMATTA_TRANSPORT_STATE_GET_HEADER   ,   /**< read in header             */
+    SHELLMATTA_TRANSPORT_STATE_GET_PAYLOAD  ,   /**< read in payload            */
+    SHELLMATTA_TRANSPORT_STATE_GET_CRC      ,   /**< read crc and process data  */
+} shellmatta_transport_state_t;
+
+
+/** @brief max length of a plain data payload */
+#define SHELLMATTA_TRANPORT_PAYLOAD_MAXLENGTH           ((uint8_t)(255))
+
+/** @brief size of the shellmatta transport uuid */
+#define SHELLMATTA_TRANPORT_UUID_LENGTH                 ((uint8_t)(16))
+
+/**
+ * @brief shellmatta transport crc function definition for custom crcs
+ * @param[in]   data        data to calculate the crc of
+ * @param[in]   size        size of the data in bytes
+ */
+typedef uint32_t (*shellmatta_transport_crc_t)(const char* data, const uint32_t size);
+
+/**
+ * @brief structure of one shellmatta transport header
+ */
+typedef struct __attribute__((__packed__))
+{
+    uint8_t startOfHeader;      /**< start of header field                  */
+    uint8_t protocolVersion;    /**< protocol version of the packet         */
+    uint8_t packetType;         /**< type of the packet                     */
+    uint8_t payloadLength;      /**< length of the payload                  */
+    uint8_t source;             /**< source of the packet                   */
+    uint8_t destination;        /**< destination of the packet              */
+    uint8_t sequenceH2S;        /**< sequence counter host to shellmatta    */
+    uint8_t sequenceS2H;        /**< sequence counter shellmatta to host    */
+} shellmatta_transport_header_t;
+
+/**
+ * @brief structure of one shellmatta transport packet
+ */
+typedef struct __attribute__((__packed__))
+{
+    shellmatta_transport_header_t   header;                                         /**< header of the packet   */
+    char                            payload[SHELLMATTA_TRANPORT_PAYLOAD_MAXLENGTH]; /**< payload of the packet  */
+    uint32_t                        crc;                                            /**< crc of the packet      */
+} shellmatta_transport_packet_t;
+
+/**
+ * @brief structure of one shellmatta transport layer instance
+ */
+typedef struct
+{
+    shellmatta_transport_state_t    state;              /**< current state of the transport layer reception */
+    bool                            active;             /**< is transport layer communication active        */
+    bool                            disableAutoFlush;   /**< enforce manual flushing                        */
+    bool                            mandatory;          /**< is the transport layer enforced                */
+    bool                            suspendOptional;    /**< forces the transport layer to not run optional */
+    uint8_t                         sequenceH2S;        /**< sequence counter host to shellmatta            */
+    uint8_t                         sequenceS2H;        /**< sequenc counter shellmatta to host             */
+    uint32_t                        headerIndex;        /**< read index of the header                       */
+    uint32_t                        payloadIndex;       /**< read index of the payload                      */
+    uint32_t                        crcIndex;           /**< read index of the checksum                     */
+    uint8_t                         hostBufferSize;     /**< buffersize of the host                         */
+    uint8_t                         address;            /**< address of the shellmatta                      */
+    uint8_t                         uuid[SHELLMATTA_TRANPORT_UUID_LENGTH]; /**< uuid if the shellmatta      */
+    shellmatta_transport_packet_t   inPacket;           /**< buffer for the received packets                */
+    shellmatta_transport_packet_t   outPacket;          /**< buffer for the sent packets                    */
+    shellmatta_write_t              writeFct;           /**< shellmatta write function                      */
+    shellmatta_transport_crc_t      customCrcFct;       /**< use this function to calculate crcs            */
+} shellmatta_transport_layer_t;
+
+#endif
+
 /**
 /**
  * @brief structure of one shellmatta instance
  * @brief structure of one shellmatta instance
  */
  */
 typedef struct
 typedef struct
 {
 {
-    uint32_t            magic;              /**< magic number to check if initialized   */
-    char                *buffer;            /**< input buffer                           */
-    uint32_t            bufferSize;         /**< size of the input buffer               */
-    uint32_t            inputCount;         /**< offset of the current write operation  */
-    uint32_t            byteCounter;        /**< counter used to loop over input data   */
-    uint32_t            lastNewlineIdx;     /**< index of the lest newline              */
-    uint32_t            cursor;             /**< offset where the cursor is at          */
-    uint32_t            stdinIdx;           /**< start index of stdin in buffer         */
-    uint32_t            stdinLength;        /**< length of the stdin data               */
-    char                *historyBuffer;     /**< buffer to store the last commands      */
-    uint32_t            historyBufferSize;  /**< size of the history buffer             */
-    uint32_t            historyStart;       /**< index of the oldest stored command     */
-    uint32_t            historyEnd;         /**< index of the newest stored command     */
-    uint32_t            historyRead;        /**< index of the current search            */
-    bool                historyReadUp;      /**< flag to show the last history dir      */
-    uint32_t            tabCounter;         /**< counts the tabulator key presses       */
-    uint32_t            escapeCounter;      /**< counts the characters of an escape seq */
-    char                escapeChars[4u];    /**< buffer to save the escape characters   */
-    uint32_t            hereStartIdx;       /**< heredoc start of "<<"                  */
-    uint32_t            hereDelimiterIdx;   /**< heredoc delimiter index in input       */
-    uint32_t            hereLength;         /**< length of the heredoc delimiter        */
-    bool                echoEnabled;        /**< if true the input is printed           */
-    bool                dirty;              /**< dirty flag to show changes             */
-    const char          *prompt;            /**< prompt is printed after every command  */
-    char                delimiter;          /**< delimiter (return) to terminate a cmd  */
-    shellmatta_mode_t   mode;               /**< mode of the shell                      */
-    shellmatta_write_t  write;              /**< pointer to write function              */
-    shellmatta_cmd_t    helpCmd;            /**< help command structure                 */
-    shellmatta_cmd_t    *cmdList;           /**< pointer to the first command           */
-    shellmatta_cmd_t    *continuousCmd;     /**< command to be called continuously      */
-    shellmatta_cmd_t    *busyCmd;           /**< command to be polled (busy mode)       */
-    bool                cmdListIsConst;     /**< true if the #cmdList was passed during
-                                                 initialization                         */
-    shellmatta_opt_t    optionParser;       /**< option parser sructure                 */
+    uint32_t                        magic;              /**< magic number to check if initialized   */
+    char                            *buffer;            /**< input buffer                           */
+    uint32_t                        bufferSize;         /**< size of the input buffer               */
+    uint32_t                        inputCount;         /**< offset of the current write operation  */
+    uint32_t                        byteCounter;        /**< counter used to loop over input data   */
+    uint32_t                        lastNewlineIdx;     /**< index of the lest newline              */
+    uint32_t                        cursor;             /**< offset where the cursor is at          */
+    uint32_t                        stdinIdx;           /**< start index of stdin in buffer         */
+    uint32_t                        stdinLength;        /**< length of the stdin data               */
+    char                            *historyBuffer;     /**< buffer to store the last commands      */
+    uint32_t                        historyBufferSize;  /**< size of the history buffer             */
+    uint32_t                        historyStart;       /**< index of the oldest stored command     */
+    uint32_t                        historyEnd;         /**< index of the newest stored command     */
+    uint32_t                        historyRead;        /**< index of the current search            */
+    bool                            historyReadUp;      /**< flag to show the last history dir      */
+    uint32_t                        tabCounter;         /**< counts the tabulator key presses       */
+    uint32_t                        escapeCounter;      /**< counts the characters of an escape seq */
+    char                            escapeChars[4u];    /**< buffer to save the escape characters   */
+    uint32_t                        hereStartIdx;       /**< heredoc start of "<<"                  */
+    uint32_t                        hereDelimiterIdx;   /**< heredoc delimiter index in input       */
+    uint32_t                        hereLength;         /**< length of the heredoc delimiter        */
+    bool                            echoEnabled;        /**< if true the input is printed           */
+    bool                            dirty;              /**< dirty flag to show changes             */
+    const char                      *prompt;            /**< prompt is printed after every command  */
+    char                            delimiter;          /**< delimiter (return) to terminate a cmd  */
+    shellmatta_mode_t               mode;               /**< mode of the shell                      */
+    shellmatta_write_t              write;              /**< pointer to write function              */
+    shellmatta_cmd_t                helpCmd;            /**< help command structure                 */
+    shellmatta_cmd_t                *cmdList;           /**< pointer to the first command           */
+    shellmatta_cmd_t                *continuousCmd;     /**< command to be called continuously      */
+    shellmatta_cmd_t                *busyCmd;           /**< command to be polled (busy mode)       */
+    bool                            cmdListIsConst;     /**< true if the #cmdList was passed during
+                                                             initialization                         */
+    shellmatta_opt_t                optionParser;       /**< option parser sructure                 */
+    shellmatta_ymodem_t             ymodem;             /**< ymodem instance                        */
+#ifdef SHELLMATTA_AUTHENTICATION
+    shellmatta_auth_state_t         loginState;         /**< state variable of the login cmd        */
+    shellmatta_cmd_t                loginCmd;           /**< login command structure                */
+    shellmatta_cmd_t                logoutCmd;          /**< logout command structure               */
+    uint32_t                        userId;             /**< user ID of the current session         */
+    uint32_t                        tmpUserId;          /**< remporary user ID during input         */
+    shellmatta_auth_user_t          *userPointer;       /**< pointer to the user in the user list   */
+    shellmatta_auth_user_t          *userList;          /**< user list                              */
+    uint32_t                        userListLength;     /**< length of the user list                */
+    shellmatta_auth_perm_t          *permList;          /**< permission list                        */
+    uint32_t                        permListLength;     /**< length of the permission list          */
+    shellmatta_auth_check_t         checkFct;           /**< custom credential check function       */
+    shellmatta_auth_log_t           logFct;             /**< auth log function                      */
+#endif
+#ifdef SHELLMATTA_TRANSPORT
+    uint32_t                        transportBusyMark;  /**< transport processing position during
+                                                             busy cmd execution                     */
+    shellmatta_transport_layer_t    transportLayer;     /**< transport layer instance               */
+#endif
 } shellmatta_instance_t;
 } shellmatta_instance_t;
 
 
+/**
+ * @brief helper macro for the send function
+ */
+#ifdef SHELLMATTA_TRANSPORT
+#define SHELLMATTA_WRITE(data, length)  inst->transportLayer.active == true ?                                                                   \
+                                        shellmatta_transport_write((shellmatta_transport_layer_t*)&inst->transportLayer, (data), (length)) :    \
+                                        inst->write((data), (length))
+#else
+#define SHELLMATTA_WRITE(data, length)  inst->write((data), (length))
+#endif
 
 
 shellmatta_retCode_t shellmatta_doInit( shellmatta_instance_t   *inst,
 shellmatta_retCode_t shellmatta_doInit( shellmatta_instance_t   *inst,
                                         shellmatta_handle_t     *handle,
                                         shellmatta_handle_t     *handle,
@@ -172,8 +437,8 @@ shellmatta_retCode_t shellmatta_resetShell( shellmatta_handle_t handle,
 shellmatta_retCode_t shellmatta_addCmd(     shellmatta_handle_t handle,
 shellmatta_retCode_t shellmatta_addCmd(     shellmatta_handle_t handle,
                                             shellmatta_cmd_t    *cmd);
                                             shellmatta_cmd_t    *cmd);
 
 
-shellmatta_retCode_t shellmatta_removeCmd(  shellmatta_handle_t handle,
-                                            shellmatta_cmd_t    *cmd);
+shellmatta_retCode_t shellmatta_removeCmd(  shellmatta_handle_t     handle,
+                                            const shellmatta_cmd_t  *cmd);
 
 
 shellmatta_retCode_t shellmatta_configure(  shellmatta_handle_t handle,
 shellmatta_retCode_t shellmatta_configure(  shellmatta_handle_t handle,
                                             shellmatta_mode_t   mode,
                                             shellmatta_mode_t   mode,
@@ -204,12 +469,62 @@ shellmatta_retCode_t shellmatta_opt_long(   shellmatta_handle_t         handle,
                                             char                        **argument,
                                             char                        **argument,
                                             uint32_t                    *argLen);
                                             uint32_t                    *argLen);
 
 
+#ifdef SHELLMATTA_TRANSPORT
+
+shellmatta_retCode_t shellmatta_transport_configure(shellmatta_handle_t         handle,
+                                                    bool                        mandatory,
+                                                    bool                        disableAutoFlush,
+                                                    uint8_t                     uuid[SHELLMATTA_TRANPORT_UUID_LENGTH],
+                                                    shellmatta_transport_crc_t  customCrcFct);
+
+shellmatta_retCode_t shellmatta_transport_reset(shellmatta_handle_t handle);
+
+shellmatta_retCode_t shellmatta_transport_flush(shellmatta_handle_t handle);
+
+#endif
+
 #ifndef SHELLMATTA_STRIP_PRINTF
 #ifndef SHELLMATTA_STRIP_PRINTF
 shellmatta_retCode_t shellmatta_printf(     shellmatta_handle_t handle,
 shellmatta_retCode_t shellmatta_printf(     shellmatta_handle_t handle,
                                             const char          *fmt,
                                             const char          *fmt,
-                                            ...);
+                                            ...)
+                                            SHELLMATTA_ATTR_FORMAT(2, 3);
 #endif
 #endif
 
 
+#ifdef SHELLMATTA_AUTHENTICATION
+
+shellmatta_retCode_t shellmatta_auth_init(                  shellmatta_handle_t     handle,
+                                                            shellmatta_auth_user_t  *userList,
+                                                            uint32_t                userListLength,
+                                                            shellmatta_auth_perm_t  *permList,
+                                                            uint32_t                permListLength,
+                                                            bool                    customLogin,
+                                                            shellmatta_auth_check_t checkFct,
+                                                            shellmatta_auth_log_t   logFct);
+shellmatta_retCode_t shellmatta_auth_login(                 shellmatta_handle_t     handle,
+                                                            uint32_t                userId);
+shellmatta_retCode_t shellmatta_auth_logout(                shellmatta_handle_t     handle);
+uint32_t             shellmatta_auth_getLoggedInUserId(     shellmatta_handle_t     handle);
+shellmatta_retCode_t shellmatta_auth_getLoggedInUserName(   shellmatta_handle_t     handle,
+                                                            char                    *data,
+                                                            uint32_t                *length);
+shellmatta_retCode_t shellmatta_auth_chpasswd(              shellmatta_handle_t     handle,
+                                                            const char              *username,
+                                                            const char              *password);
+
+#endif
+
+shellmatta_retCode_t shellmatta_ymodem_init(    shellmatta_handle_t                 handle,
+                                                uint8_t*                            recvBuffer,
+                                                shellmatta_ymodem_cancel_t          cancelCallback,
+                                                shellmatta_ymodem_recvHeader_t      recvHeaderCallback,
+                                                shellmatta_ymodem_recvPacket_t      recvPacketCallback,
+                                                shellmatta_ymodem_complete_t        transmissionCompleteCallback);
+
+shellmatta_retCode_t shellmatta_ymodem_pause(   shellmatta_handle_t                 handle);
+shellmatta_retCode_t shellmatta_ymodem_resume(  shellmatta_handle_t                 handle);
+shellmatta_retCode_t shellmatta_ymodem_cancel(  shellmatta_handle_t                 handle,
+                                                bool                                doCancel);
+
 #endif
 #endif
 
 
 /** @} */
 /** @} */

+ 97 - 0
cfg/cppcheck/cppcheck_suppressions.xml

@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<suppressions>
+    <suppress>
+        <id>missingIncludeSystem</id>
+    </suppress>
+    <suppress>
+        <id>variableScope</id>
+        <!--Intentionally
+        not limiting the variable scope as suggested - declaring variables at
+            the top of each function.-->
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_doInit</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_resetShell</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_removeCmd</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_configure</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_processData</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_printf</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_write</symbolName>
+    </suppress>
+    <suppress>
+        <id>constVariablePointer</id>
+        <fileName>src/shellmatta.c</fileName>
+        <lineNumber>780</lineNumber>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_opt.c</fileName>
+        <symbolName>shellmatta_opt</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_auth.c</fileName>
+        <symbolName>shellmatta_auth_init</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_auth.c</fileName>
+        <symbolName>shellmatta_auth_getLoggedInUserId</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_auth.c</fileName>
+        <symbolName>shellmatta_auth_getLoggedInUserName</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_auth.c</fileName>
+        <symbolName>shellmatta_auth_chpasswd</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_ymodem.c</fileName>
+        <symbolName>shellmatta_ymodem_init</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_ymodem.c</fileName>
+        <symbolName>shellmatta_ymodem_pause</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_ymodem.c</fileName>
+        <symbolName>shellmatta_ymodem_resume</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_ymodem.c</fileName>
+        <symbolName>shellmatta_ymodem_cancel</symbolName>
+    </suppress>
+</suppressions>

File diff suppressed because it is too large
+ 575 - 271
cfg/doxygen/doxyfile


+ 15 - 0
doc/shellmatta.dox

@@ -24,6 +24,10 @@
             App -> Shellmatta: shellmatta_addCmd(command)
             App -> Shellmatta: shellmatta_addCmd(command)
         end
         end
 
 
+        alt Authentication enabled
+            App -> Shellmatta: shellmatta_auth_init()
+        end
+
         loop until finished
         loop until finished
             IO -> Shellmatta: shellmatta_processData(data)
             IO -> Shellmatta: shellmatta_processData(data)
             Shellmatta -> App: call command function
             Shellmatta -> App: call command function
@@ -32,4 +36,15 @@
         end
         end
     @enduml
     @enduml
 
 
+
+    @section shellmatta_options Shellmatta options
+
+    The Shellmatta comes with some features which can be opted in:
+
+    @subpage shellmatta_auth
+
+    @subpage shellmatta_transport_layer
+
+    @subpage shellmatta_ymodem
+
 */
 */

+ 84 - 0
doc/shellmatta_auth.dox

@@ -0,0 +1,84 @@
+/**
+
+    @page shellmatta_auth Shellmatta Authentication
+
+    The shellmatta comes with a simple authentication mechanism.
+    It can be used to hide certain (or all) commands from users without
+    permission.
+
+    The permissions can be set per command.
+
+    To enable the shellmatta auth module you have to include the file
+    shellmatta_auth.c into your build and set the define
+    ``SHELLMATTA_AUTHENTICATION``.
+
+    Unfortunately the structure of each command has to be altered to include
+    the additional information required by the auth module.
+    Please add another NULL to the initializers of every command of type
+    #shellmatta_cmd_t.
+
+
+        shellmatta_cmd_t exampleCmd = { "example",
+                                        "e",
+                                        "example command",
+                                        "example [options]\n"
+                                        "\t-v, --version - print the version of the command",
+                                        exampleCmdFct,
+                                        NULL,
+                                        NULL};
+
+    After initializing the shellmatta instance you have to setup users with
+    username and password.
+    By enabling the user to be superuser you grant this user access to all
+    commands without the need of setting a permission list.
+
+        shellmatta_auth_user_t userList[] = {
+            {1, true, "root", "rootpw"},
+            {2, false, "shimatta", "12345678"},
+            {3, false, "not_shimatta", "87654321"}
+        };
+
+    Every command can get a permission matrix - the perm lists can be reused for
+    multiple users with the same permissions.
+    When no entry is found for a command in the permission list the command
+    defaults to be public.
+
+    It is also possible to use the userID 0 to hide a command when logged in.
+
+        uint32_t exampleCmdPerms[] = {2};
+        shellmatta_auth_perm_t permList[] = {
+            {"exampleCmd", exampleCmdPerms, sizeof(exampleCmdPerms)/sizeof(exampleCmdPerms[0])}
+        };
+
+    Now call the #shellmatta_auth_init method and pass the user and permissions
+    lists.
+    It is possible to register optional callbacks for a custom password check
+    and a log function which is called on every authentication event.
+
+        shellmatta_auth_init(handle, userList, 3, permList, 1, false, NULL, NULL);
+
+
+    @section shellmatta_auth_custom_login Custom login
+
+    By default the shellmatta uses plain text passwords.
+    This of course is not state of the art and usually highly insecure.
+
+    As most of the fancy password hashing methods are platform dependant none of
+    those is included to keep up the compatibility with as many platforms as
+    possible (sacrificing security).
+
+    To overcome this limitation you can register your own function to check the
+    credentials.
+
+    Just implement a function of type #shellmatta_auth_check_t and pass it to
+    the #shellmatta_auth_init method during initialization.
+
+        shellmatta_retCode_t custom_auth_check(const uint32_t userId, const char* password) {
+            /‌/ Check if the passed userID matches the passed password.
+            if (password_matches()) {
+                return SHELLMATTA_OK;
+            }
+            return SHELLMATTA_ERROR;
+        }
+
+*/

+ 152 - 0
doc/shellmatta_transport_layer.dox

@@ -0,0 +1,152 @@
+/**
+
+    @page shellmatta_transport_layer Shellmatta Transport Layer
+
+    To be able to use the shellmatta directly on an unsecured interface like
+    raw UART a simple connectionless transport layer has been implemented.
+
+    The transport layer is optional and can be removed during compile time as
+    well as deactivated during runtime.
+
+    To enable it during compile time add the define ``SHELLMATTA_TRANSPORT``.
+
+
+    The transport layer intends to be used in machine to machine interfaces.
+
+    @warning    As the transport layer is connectionless there is no way to
+                determine wether a packet is received properly.
+                To check if all packets have been received by the shellmatta
+                the sequence counters included in any packet can be used.
+
+                The packet counter will be incremented on every packet which
+                is received with a valid CRC and address.
+                Unknown packets or packets with wrong payload lengths are
+                ignored - but the sequence counter will still be incremented.
+
+    @section shellmatta_transport_layer_sequence Basic sequence
+
+    @startuml
+        loop until command is sent
+            Host -> Shellmatta: send packet
+            Shellmatta -> Shellmatta: validate packet
+        end
+
+        loop until response is sent
+            Shellmatta -> Host: send return packet
+            Host -> Host: validate packet
+        end
+    @enduml
+
+
+    @section shellmatta_transport_layer_protocol Protocol
+
+
+    | Offset | Length        | Description                                 |
+    |--------|---------------|---------------------------------------------|
+    |    0   |     1         | Start of header (\\SOH)                     |
+    |    1   |     1         | Protocol version                            |
+    |    2   |     1         | Packet type                                 |
+    |    3   |     1         | Payload Length                              |
+    |    4   |     1         | Source                                      |
+    |    5   |     1         | Destination                                 |
+    |    6   |     1         | Sequence counter host to shellmatta         |
+    |    7   |     1         | Sequence counter shellmatta to host         |
+    |    8   | L1 = 0 .. 255 | Payload                                     |
+    |8 + L1  |     4         | CRC32 of header + payload without CRC32     |
+
+
+    @subsection shellmatta_transport_layer_protocol_packet_Types Packet types
+
+    The type codes are divided into request and respond codes using the MSB.
+    The MSB indicates a response.
+
+    | type | Description                    | Payload length |  Payload               |
+    |------|--------------------------------|----------------|------------------------|
+    | 0x00 | Plain Data                     | 0 .. 255       | User data              |
+    | 0x01 | Request Sequence counters      | 0              | -                      |
+    | 0x81 | Respond Sequence counters      | 0              | -                      |
+    | 0x02 | Request and set max buffersize | 1              | Hosts buffersize       |
+    | 0x82 | Respond max buffersize         | 1              | Shellmattas buffersize |
+    | 0x03 | Search device by unique ID     | 32             | UUID Range 2x 16 byte  |
+    | 0x83 | Respond to search              | 16             | UUID of Shellmatta     |
+    | 0x04 | Set address                    | 16 + 1         | UUID + new address     |
+    | 0x84 | Respond to set address         | 16             | UUID of Shellmatta     |
+
+
+    @section shellmatta_transport_layer_sequence_counters Sequence counters
+
+    The sequence counters are included in the header of every packet to enable
+    the host to check for any dropped packets.
+    This is a quite nasty workaround as the host has no chance to determine
+    which packet has been dropped - but better than nothing.
+
+    If no response is received from the shellmatta the sequence counters can be
+    requested explicitly.
+
+    The sequence counter host to shellmatta will increment on every successfully
+    received packet.
+
+    The sequence counter shellmatta to host will increment on every packet the
+    shellmatta sends to the host.
+
+
+    @section shellmatta_transport_layer_buffersize Buffer sizes
+
+    The shellmatta always uses the maximum buffersize of
+    #SHELLMATTA_TRANPORT_PAYLOAD_MAXLENGTH.
+    If the host has a smaller receive buffer this can be set using the 0x02
+    packet.
+
+    A host should use this packet to request the shellmatta buffersize.
+    This can be changed in the future.
+
+
+    @section shellmatta_transport_layer_addressing Addressing and Search
+
+    The shellmatta comes with a primitive multidrop implementation to address
+    and find multiple shellmatta transport layer enabled devices on a bus.
+
+    Every command comes with a source and destination address.
+    0 is the broadcast address an received everytime.
+    1 is reserved for the MASTER/HOST. The Shellmatta will always send responses
+    to this address - no matter what the source of the telegram was.
+
+    The packet response is sent to the source from the request packet.
+
+    To assign a new address to a shellmatta transport layer enabled device
+    there is a possibility to search for devices on the bus.
+
+    Every shellmatta which enables this feature requires a 16 byte UUID to be
+    specified which is unique on the bus.
+    The Host can then search for shellmatta nodes using a range of UUIDs
+    using command 0x03.
+
+    Every shellmatta instance within this range will respond.
+    If more than one shellmatta is in the specified range this will lead to
+    collisions on the bus. When the host detects collisions it shall repeat
+    the search requests with different ranges until only one shellmatta
+    responds.
+
+    Once the host has found a single shellmatta in one range it can assign a
+    unique address using command 0x04.
+
+    The shellmatta can now be accessed using this address.
+
+
+    @section shellmatta_transport_layer_crc CRC calculation
+
+    The shellmatta transport layer uses the CRC32 algorithm to secure the
+    transmission (0x04c11db7).
+
+    By default the shellmatta crc implementation uses a lookup table to
+    calculate the crc in a performant way.
+
+    This uses quite a lot of read only memory on the device. If the memory
+    consumption is an Issue you can switch to a slower implementation without
+    the lookup table. This will increase CPU load quite a bit, but saves nearly
+    1K of read only memory.
+
+    To enable the lookup table less CRC just define
+    ``SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP`` during compilation.
+
+*/

+ 102 - 0
doc/shellmatta_ymodem.dox

@@ -0,0 +1,102 @@
+/**
+
+    @page shellmatta_ymodem Shellmatta ymodem
+
+    To be able to pass binary files to the shellmatta there is a ymodem module
+    implemented.
+    The ymodem reception can be started from a shellmatta command and handles
+    the handshaking as well as the data transmission.
+
+    As the shellmatta does not use dynamic memory every packet is passed
+    directly to the application using a callback.
+    Only the currently received packet is stored in the shellmatta internally.
+
+    In order to transmit the start characters to the sender you need to poll the
+    shellmatta_processData() function cyclically even if no new data has been
+    received.
+    Do this in 0.1s - 1s periods (depending on the latency you are after).
+
+    When the transmission is complete the shellmatta requires to be polled
+    #YMODEM_POLL_NUMBER times. This ensures a short wait time to flush all
+    unwanted bytes from the transmitter.
+
+    The shellmatta_ymodem_init() function will return and the command it was
+    called from will not be executed again. The rest of the ymodem processing
+    will be handled by the shellmatta. The data will be passed using the
+    callbacks registered during the start.
+
+    To cancel the ymodem (e.g. timeout) just call shellmatta_ymodem_cancel()
+
+
+    @section shellmatta_ymodem_sequence Basic sequence
+
+    @startuml
+        Application -> Shellmatta: shellmatta_ymodem_init()
+        Shellmatta -> Host: send C to start transmission
+        Shellmatta -> Application: return
+        loop until ymodem transmission starts (or is canceled)
+            Application -> Shellmatta: shellmatta_processData(null)
+            Shellmatta -> Host: send C to start transmission
+        end
+        Application -> Shellmatta: shellmatta_processData(first packet)
+        Shellmatta -> Application: packetHeader(fileSize, FileName) callback
+        loop until shellmatta transmission is complete
+            Host -> Shellmatta: Data transmission
+            Shellmatta -> Application: packetRcv() callback
+        end
+        Shellmatta -> Application: transmissionComplete() callback
+    @enduml
+
+    While the ymodem is active no command can be executed.
+    When the ymodem is canceled from the Host the data received afterwards will
+    be processed as usual.
+
+    @warning    Ymodem cannot cooperate with optional transport layer.
+                When the transport layer is enabled but not mandatory it is
+                forced to stay in the mode it was when the ymodem transfer
+                starts.
+                When the transport layer was active it has to keep active.
+                If it is inactive it cannot be activated during the ymodem
+                transfer.
+
+
+    @section shellmatta_ymodem_internal_buffer Internal buffer
+
+    To save some memory you can use the shellmatta ymodem module using the
+    internal buffer which usually is used to cache normal input before
+    processing it.
+
+    Just set the recvBuffer to NULL in #shellmatta_ymodem_init calls.
+
+
+    @warning    Ensure that your shellmattas buffer is at least
+                #YMODEM_PACKET_SIZE_1K large to fit the ymodem packet.
+                This is checked during initialization.
+
+
+    @section shellmatta_ymodem_paused_mode Paused mode
+
+    When the application requires time to process the currently received packet
+    the application can call #shellmatta_ymodem_pause from the callbacks.
+
+    This will prevent the shellmatta from sending the ACK to the sender.
+
+    When the processing is done just call #shellmatta_ymodem_resume.
+
+    @section shellmatta_ymodem_crc CRC calculation
+
+    The shellmatta ymodem uses the CRC16 algorithm to secure the
+    transmission (0x1021).
+
+    By default the shellmatta crc implementation uses a lookup table to
+    calculate the crc in a performant way.
+
+    This uses quite a lot of read only memory on the device. If the memory
+    consumption is an Issue you can switch to a slower implementation without
+    the lookup table. This will increase CPU load quite a bit, but saves nearly
+    0.5K of read only memory.
+
+    To enable the lookup table less CRC just define
+    ``SHELLMATTA_YMODEM_CRC_NO_LOOKUP`` during compilation.
+
+*/

+ 110 - 13
example/main.c

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -31,7 +31,7 @@ static shellmatta_retCode_t doSomething(shellmatta_handle_t handle, const char *
     shellmatta_printf(handle, "%s - length: %u", arguments, length);
     shellmatta_printf(handle, "%s - length: %u", arguments, length);
     return SHELLMATTA_OK;
     return SHELLMATTA_OK;
 }
 }
-shellmatta_cmd_t doSomethingCmd = {"doSomething", "do", "Function does something", "use me, please", doSomething, NULL};
+shellmatta_cmd_t doSomethingCmd = {"doSomething", "do", "Function does something", "use me, please", doSomething, NULL, NULL};
 
 
 static shellmatta_retCode_t doSome(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 static shellmatta_retCode_t doSome(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
 {
@@ -45,7 +45,7 @@ static shellmatta_retCode_t doSome(shellmatta_handle_t handle, const char *argum
 
 
     return SHELLMATTA_OK;
     return SHELLMATTA_OK;
 }
 }
-shellmatta_cmd_t doSomeCmd = {"adoSome2", "adof2", "Function does something", "use me, please", doSome, NULL};
+shellmatta_cmd_t doSomeCmd = {"adoSome2", "adof2", "Function does something", "use me, please", doSome, NULL, NULL};
 
 
 static shellmatta_retCode_t removeCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 static shellmatta_retCode_t removeCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
 {
@@ -58,7 +58,7 @@ static shellmatta_retCode_t removeCmdFct(shellmatta_handle_t handle, const char
 
 
     return SHELLMATTA_OK;
     return SHELLMATTA_OK;
 }
 }
-shellmatta_cmd_t removeCommand = {"remove", "r", "Function removes a command", "", removeCmdFct, NULL};
+shellmatta_cmd_t removeCommand = {"remove", "r", "Function removes a command", "", removeCmdFct, NULL, NULL};
 
 
 
 
 static shellmatta_retCode_t quit(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 static shellmatta_retCode_t quit(shellmatta_handle_t handle, const char *arguments, uint32_t length)
@@ -71,7 +71,7 @@ static shellmatta_retCode_t quit(shellmatta_handle_t handle, const char *argumen
 
 
     return SHELLMATTA_OK;
     return SHELLMATTA_OK;
 }
 }
-shellmatta_cmd_t quitCommand = {"quit", "q", "Function quits the shell", "", quit, NULL};
+shellmatta_cmd_t quitCommand = {"quit", "q", "Function quits the shell", "", quit, NULL, NULL};
 
 
 static shellmatta_retCode_t empty(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 static shellmatta_retCode_t empty(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
 {
@@ -83,7 +83,7 @@ static shellmatta_retCode_t empty(shellmatta_handle_t handle, const char *argume
 
 
     return SHELLMATTA_OK;
     return SHELLMATTA_OK;
 }
 }
-shellmatta_cmd_t emptyCommand = {"empty", NULL, NULL, NULL, empty, NULL};
+shellmatta_cmd_t emptyCommand = {"empty", NULL, NULL, NULL, empty, NULL, NULL};
 
 
 static shellmatta_retCode_t reset(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 static shellmatta_retCode_t reset(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
 {
@@ -95,7 +95,7 @@ static shellmatta_retCode_t reset(shellmatta_handle_t handle, const char *argume
     uint32_t argLen;
     uint32_t argLen;
     bool printPrompt = false;
     bool printPrompt = false;
 
 
-    static const shellmatta_opt_long_t options[] = 
+    static const shellmatta_opt_long_t options[] =
     {
     {
         {"prompt",  'p',    SHELLMATTA_OPT_ARG_REQUIRED},
         {"prompt",  'p',    SHELLMATTA_OPT_ARG_REQUIRED},
         {NULL,      '\0',   SHELLMATTA_OPT_ARG_NONE}
         {NULL,      '\0',   SHELLMATTA_OPT_ARG_NONE}
@@ -127,7 +127,7 @@ static shellmatta_retCode_t reset(shellmatta_handle_t handle, const char *argume
 
 
     return SHELLMATTA_OK;
     return SHELLMATTA_OK;
 }
 }
-shellmatta_cmd_t resetCommand = {"reset", NULL, "resets the shellmatta instance", "reset [--prompt true/false]", reset, NULL};
+shellmatta_cmd_t resetCommand = {"reset", NULL, "resets the shellmatta instance", "reset [--prompt true/false]", reset, NULL, NULL};
 
 
 static shellmatta_retCode_t continuous(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 static shellmatta_retCode_t continuous(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
 {
@@ -151,7 +151,7 @@ static shellmatta_retCode_t continuous(shellmatta_handle_t handle, const char *a
     }
     }
     return ret;
     return ret;
 }
 }
-shellmatta_cmd_t continuousCommand = {"continuous", "cont", "prints continously all input bytes", "continuous", continuous, NULL};
+shellmatta_cmd_t continuousCommand = {"continuous", "cont", "prints continously all input bytes", "continuous", continuous, NULL, NULL};
 
 
 static shellmatta_retCode_t busy(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 static shellmatta_retCode_t busy(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
 {
@@ -173,7 +173,76 @@ static shellmatta_retCode_t busy(shellmatta_handle_t handle, const char *argumen
 
 
     return ret;
     return ret;
 }
 }
-shellmatta_cmd_t busyCommand = {"busy", NULL, NULL, NULL, busy, NULL};
+shellmatta_cmd_t busyCommand = {"busy", NULL, NULL, NULL, busy, NULL, NULL};
+
+shellmatta_retCode_t ret = SHELLMATTA_OK;
+
+void ymodemCallbackCancel(shellmatta_handle_t handle) {
+
+    (void)handle;
+    printf("Received cancel callback\n");
+
+    return;
+}
+
+void ymodemCallbackReceiveHeader(shellmatta_handle_t handle, uint32_t fileSize, char* fileName) {
+
+    (void)handle;
+    printf("Received Packet Header: %u %s\n", fileSize, fileName);
+
+    return;
+}
+
+void ymodemCallbackReceivePacket(shellmatta_handle_t handle, uint8_t *data, uint32_t packetSize, uint32_t packetNum)
+{
+
+    uint32_t i;
+
+    (void)handle;
+
+    printf("Received Packet: %u %u\n", packetNum, packetSize);
+
+    for(i = 0u; i < packetSize; i ++)
+    {
+        printf("0x%02x ", data[i]);
+
+        if(15u == (i % 16))
+        {
+            printf("\n");
+        }
+    }
+    printf("\n");
+
+    return;
+}
+
+void ymodemCallbackTransmissionComplete(shellmatta_handle_t handle, shellmatta_retCode_t result) {
+
+    (void)handle;
+
+    ret = SHELLMATTA_OK;
+    printf("\nTransmission complete: %s\n", result == SHELLMATTA_OK ? "success" : "error");
+    return;
+}
+
+
+uint8_t ymodemBuffer[1024u] = {0};
+
+static shellmatta_retCode_t ymodem(shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    (void)arguments;
+    (void)length;
+    shellmatta_printf(handle, "Starting ymodem session\r\n");
+    shellmatta_ymodem_init(handle,
+                      ymodemBuffer,
+                      ymodemCallbackCancel,
+                      ymodemCallbackReceiveHeader,
+                      ymodemCallbackReceivePacket,
+                      ymodemCallbackTransmissionComplete);
+
+    return SHELLMATTA_OK;
+}
+shellmatta_cmd_t ymodemCommand = {"ymodem", NULL, NULL, NULL, ymodem, NULL, NULL};
 
 
 
 
 shellmatta_retCode_t writeFct(const char* data, uint32_t length)
 shellmatta_retCode_t writeFct(const char* data, uint32_t length)
@@ -203,6 +272,8 @@ int main(int argc, char **argv)
         return f;
         return f;
     }
     }
 
 
+    printf("Starting Shellmatta\n");
+
     shellmatta_doInit(  &instance,
     shellmatta_doInit(  &instance,
                         &handle,
                         &handle,
                         buffer,
                         buffer,
@@ -220,19 +291,45 @@ int main(int argc, char **argv)
     shellmatta_addCmd(handle, &resetCommand);
     shellmatta_addCmd(handle, &resetCommand);
     shellmatta_addCmd(handle, &continuousCommand);
     shellmatta_addCmd(handle, &continuousCommand);
     shellmatta_addCmd(handle, &busyCommand);
     shellmatta_addCmd(handle, &busyCommand);
+    shellmatta_addCmd(handle, &ymodemCommand);
+
+
+    shellmatta_auth_user_t userList[] = {
+        {1, false, "shimatta", "12345678"},
+        {2, false, "not_shimatta", "87654321"}
+    };
+
+    uint32_t doSomeCmdPerms[] = {1, 2};
+    uint32_t removeCommandPerms[] = {1};
+    shellmatta_auth_perm_t permList[] = {
+        {"adoSome2", doSomeCmdPerms, sizeof(doSomeCmdPerms)/sizeof(doSomeCmdPerms[0])},
+        {"remove", removeCommandPerms, sizeof(removeCommandPerms)/sizeof(removeCommandPerms[0])}
+    };
 
 
+    shellmatta_auth_init(handle, userList, 2, permList, 2, false, NULL, NULL);
+
+    int i = 0;
     while(exitRequest == false)
     while(exitRequest == false)
     {
     {
         char c;
         char c;
         shellmatta_retCode_t ret;
         shellmatta_retCode_t ret;
+        int flags = fcntl(f, F_GETFL, 0);
+        fcntl(f, F_SETFL, flags | O_NONBLOCK);
         int res = 0;
         int res = 0;
         res = read (f, &c, 1);
         res = read (f, &c, 1);
-
-        fprintf(stdout, "0x%02x \n", c);
-        fflush(stdout);
+        if (res == -1)
+        {
+            i = (i + 1) % 10000;
+            usleep(1);
+            if (i != 0)
+            {
+                continue;
+            }
+        }
 
 
         do
         do
         {
         {
+            res = res > 0 ? res : 0;
             ret = shellmatta_processData(handle, &c, res);
             ret = shellmatta_processData(handle, &c, res);
             if(SHELLMATTA_BUSY == ret)
             if(SHELLMATTA_BUSY == ret)
             {
             {

+ 159 - 63
makefile

@@ -1,15 +1,26 @@
-# 
-# Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
-# 
+#
+# Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+#
 # This Source Code Form is subject to the terms of the Mozilla Public
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at https://mozilla.org/MPL/2.0/.
 # file, You can obtain one at https://mozilla.org/MPL/2.0/.
-# 
+#
 
 
-OBJ_DIR := output/
-INTEGRATIONTEST_CPP_OBJ_DIR := $(OBJ_DIR)test/integrationtest/
-INTEGRATIONTEST_C_OBJ_DIR := $(INTEGRATIONTEST_CPP_OBJ_DIR)
-UNITTEST_OBJ_DIR := $(OBJ_DIR)test/unittest/
+OBJ_DIR                                 := output/
+OBJ_DIR_EXAMPLE                         := $(OBJ_DIR)example_obj/
+INTEGRATIONTEST_CPP_OBJ_DIR             := $(OBJ_DIR)test/integrationtest/
+INTEGRATIONTEST_AUTH_CPP_OBJ_DIR        := $(OBJ_DIR)test/integrationtest_auth/
+INTEGRATIONTEST_C_OBJ_DIR               := $(INTEGRATIONTEST_CPP_OBJ_DIR)
+INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT   := $(OBJ_DIR)test/integrationtest_transport/
+INTEGRATIONTEST_C_OBJ_DIR_TRANSPORT     := $(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT)
+INTEGRATIONTEST_AUTH_C_OBJ_DIR          := $(INTEGRATIONTEST_AUTH_CPP_OBJ_DIR)
+UNITTEST_OBJ_DIR                        := $(OBJ_DIR)test/unittest/
+
+# helper define to run tests from a list
+define \n
+
+
+endef
 
 
 CC  := gcc
 CC  := gcc
 CPP := g++
 CPP := g++
@@ -20,7 +31,30 @@ SOURCES :=  src/shellmatta.c                \
             src/shellmatta_history.c        \
             src/shellmatta_history.c        \
             src/shellmatta_utils.c          \
             src/shellmatta_utils.c          \
             src/shellmatta_escape.c         \
             src/shellmatta_escape.c         \
-            src/shellmatta_opt.c
+            src/shellmatta_opt.c            \
+            src/shellmatta_ymodem.c         \
+            src/shellmatta_crc.c
+
+
+SOURCES_TRANPORT_LAYER  :=  $(SOURCES)                      \
+                            src/shellmatta_transport.c
+AUTH_SOURCES     := $(SOURCES) src/shellmatta_auth.c
+EXAMPLE_SOURCES  := example/main.c
+EXAMPLE_SOURCES  += $(filter-out $(EXAMPLE_SOURCES),$(AUTH_SOURCES))
+EXAMPLE_SOURCES  += $(filter-out $(EXAMPLE_SOURCES),$(SOURCES_TRANPORT_LAYER))
+EXAMPLE_COBJ     := $(patsubst %.c,$(OBJ_DIR_EXAMPLE)%.o,$(EXAMPLE_SOURCES))
+
+CPPCHECK_SOURCES := $(AUTH_SOURCES)                         \
+                    src/shellmatta_transport.c              \
+                    src/shellmatta_crc.c
+CPPCHECK_OPTIONS := --enable=all                                            \
+                    --check-level=exhaustive                                \
+                    --template=gcc                                          \
+                    --cppcheck-build-dir=output/cppcheck                    \
+                    --suppress-xml=cfg/cppcheck/cppcheck_suppressions.xml   \
+                    -DSHELLMATTA_AUTHENTICATION                             \
+                    -Iapi                                                   \
+                    $(CPPCHECK_SOURCES)
 
 
 INCLUDES    := api .
 INCLUDES    := api .
 
 
@@ -29,6 +63,7 @@ UNITTEST_SOURCES := test/unittest/test_main.cpp
                     test/unittest/shellmatta_opt/test_opt_peekNextHunk.cpp              \
                     test/unittest/shellmatta_opt/test_opt_peekNextHunk.cpp              \
                     test/unittest/shellmatta_utils/test_utils_writeEcho.cpp             \
                     test/unittest/shellmatta_utils/test_utils_writeEcho.cpp             \
                     test/unittest/shellmatta_utils/test_utils_shellItoa.cpp             \
                     test/unittest/shellmatta_utils/test_utils_shellItoa.cpp             \
+                    test/unittest/shellmatta_utils/test_utils_shellAsciiToUInt32.cpp    \
                     test/unittest/shellmatta_utils/test_utils_saveCursorPos.cpp         \
                     test/unittest/shellmatta_utils/test_utils_saveCursorPos.cpp         \
                     test/unittest/shellmatta_utils/test_utils_restoreCursorPos.cpp      \
                     test/unittest/shellmatta_utils/test_utils_restoreCursorPos.cpp      \
                     test/unittest/shellmatta_utils/test_utils_eraseLine.cpp             \
                     test/unittest/shellmatta_utils/test_utils_eraseLine.cpp             \
@@ -41,7 +76,12 @@ UNITTEST_SOURCES := test/unittest/test_main.cpp
                     test/unittest/shellmatta_autocomplete/test_autocomplete_run.cpp     \
                     test/unittest/shellmatta_autocomplete/test_autocomplete_run.cpp     \
                     test/unittest/shellmatta_escape/test_escape_processArrowKeys.cpp    \
                     test/unittest/shellmatta_escape/test_escape_processArrowKeys.cpp    \
                     test/unittest/shellmatta_history/test_appendHistoryByte.cpp         \
                     test/unittest/shellmatta_history/test_appendHistoryByte.cpp         \
-                    test/unittest/shellmatta/test_shellmatta_doInit.cpp
+                    test/unittest/shellmatta/test_shellmatta_doInit.cpp                 \
+                    test/unittest/shellmatta_crc/test_crc32Slow.cpp                     \
+                    test/unittest/shellmatta_crc/test_crc32Fast.cpp                     \
+                    test/unittest/shellmatta_crc/test_crc16Slow.cpp                     \
+                    test/unittest/shellmatta_crc/test_crc16Fast.cpp                     \
+                    test/unittest/shellmatta_ymodem/test_ymodem.cpp
 
 
 INTEGRATIONTEST_SOURCES :=  test/integrationtest/test_main.cpp                  \
 INTEGRATIONTEST_SOURCES :=  test/integrationtest/test_main.cpp                  \
                             test/integrationtest/test_integration.cpp           \
                             test/integrationtest/test_integration.cpp           \
@@ -50,32 +90,61 @@ INTEGRATIONTEST_SOURCES :=  test/integrationtest/test_main.cpp
                             test/integrationtest/test_integration_continue.cpp  \
                             test/integrationtest/test_integration_continue.cpp  \
                             test/integrationtest/test_integration_busy.cpp      \
                             test/integrationtest/test_integration_busy.cpp      \
                             test/integrationtest/test_integration_history.cpp   \
                             test/integrationtest/test_integration_history.cpp   \
-                            test/integrationtest/test_integration_help.cpp
+                            test/integrationtest/test_integration_help.cpp      \
+                            test/integrationtest/test_integration_ymodem.cpp
+
+INTEGRATIONTEST_TRANSPORT_SOURCES  :=  $(INTEGRATIONTEST_SOURCES)                           \
+                                       test/integrationtest_transport/test_integration_transport.cpp
+INTEGRATIONTEST_AUTH_SOURCES := test/integrationtest_auth/test_main.cpp                 \
+                                test/integrationtest_auth/test_integration_auth.cpp
 
 
 UNITTEST_CPPOBJ  := $(patsubst %.cpp,$(UNITTEST_OBJ_DIR)%.o,$(UNITTEST_SOURCES))
 UNITTEST_CPPOBJ  := $(patsubst %.cpp,$(UNITTEST_OBJ_DIR)%.o,$(UNITTEST_SOURCES))
 
 
 INTEGRATIONTEST_CPPOBJ  :=  $(patsubst %.cpp,$(INTEGRATIONTEST_CPP_OBJ_DIR)%.o,$(INTEGRATIONTEST_SOURCES))
 INTEGRATIONTEST_CPPOBJ  :=  $(patsubst %.cpp,$(INTEGRATIONTEST_CPP_OBJ_DIR)%.o,$(INTEGRATIONTEST_SOURCES))
 INTEGRATIONTEST_COBJ    :=  $(patsubst %.c,$(INTEGRATIONTEST_CPP_OBJ_DIR)%.o,$(SOURCES))
 INTEGRATIONTEST_COBJ    :=  $(patsubst %.c,$(INTEGRATIONTEST_CPP_OBJ_DIR)%.o,$(SOURCES))
 
 
-CFLAGS      := $(INCLUDES:%=-I%) -g -Wall -Werror -Wextra -pedantic -DSHELLMATTA_HELP_ALIAS=\(char*\)\"?\"
-TESTFLAGS   := $(CFLAGS) -fprofile-arcs -ftest-coverage
-TESTLFLAGS  := -fprofile-arcs -Wl,--allow-multiple-definition
-
-DEPEND      = -MT $@ -MF "$(@:%.o=%.d)" -MG -MM
-
-COBJ    := $(patsubst %.c,$(OBJ_DIR)%.o,$(SOURCES))
-
-EXAMPLE_SOURCES := example/main.c
-EXAMPLE_COBJ     := $(patsubst %.c,$(OBJ_DIR)%.o,$(EXAMPLE_SOURCES))
-
-EXAMPLE_TARGET  := $(OBJ_DIR)example/example
-
-UNITTEST_TARGET     := $(OBJ_DIR)test/unittest/unittest
-
-INTEGRATIONTEST_TARGET     := $(OBJ_DIR)test/integrationtest/integrationtest
-
-OBJ     := $(COBJ) $(EXAMPLE_COBJ) $(UNITTEST_CPPOBJ) $(INTEGRATIONTEST_CPPOBJ) $(INTEGRATIONTEST_COBJ)
-DEPS    := $(OBJ:%.o=%.d)
+INTEGRATIONTEST_TRANSPORT_CPPOBJ    :=  $(patsubst %.cpp,$(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT)%.o,$(INTEGRATIONTEST_TRANSPORT_SOURCES))
+INTEGRATIONTEST_TRANSPORT_COBJ     	:=  $(patsubst %.c,$(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT)%.o,$(SOURCES_TRANPORT_LAYER))
+INTEGRATIONTEST_AUTH_CPPOBJ :=  $(patsubst %.cpp,$(INTEGRATIONTEST_AUTH_CPP_OBJ_DIR)%.o,$(INTEGRATIONTEST_AUTH_SOURCES))
+INTEGRATIONTEST_AUTH_COBJ   :=  $(patsubst %.c,$(INTEGRATIONTEST_AUTH_CPP_OBJ_DIR)%.o,$(AUTH_SOURCES))
+
+
+CFLAGS                  := $(INCLUDES:%=-I%) -g -Wall -Werror -Wextra -pedantic
+CFLAGS                  += -DSHELLMATTA_HELP_ALIAS=\(char*\)\"?\"
+CFLAGS_EXAMPLE          := $(CFLAGS) -DSHELLMATTA_AUTHENTICATION -DSHELLMATTA_TRANSPORT
+TESTFLAGS               := $(CFLAGS) -fprofile-arcs -ftest-coverage
+TESTFLAGS_AUTH          := $(CFLAGS) -DSHELLMATTA_AUTHENTICATION -fprofile-arcs -ftest-coverage
+TESTLFLAGS              := -fprofile-arcs -Wl,--allow-multiple-definition
+TESTFLAGS_TRANSPORT     := $(TESTFLAGS) -DSHELLMATTA_TRANSPORT
+TESTLFLAGS_TRANSPORT    := $(TESTLFLAGS)
+DEPEND                  = -MT $@ -MF "$(@:%.o=%.d)" -MG -MM
+
+
+COBJ := $(patsubst %.c,$(OBJ_DIR)%.o,$(SOURCES))
+
+EXAMPLE_TARGET                      := $(OBJ_DIR)example/example
+UNITTEST_TARGET                     := $(OBJ_DIR)test/unittest/unittest
+INTEGRATIONTEST_TARGET              := $(OBJ_DIR)test/integrationtest/integrationtest
+INTEGRATIONTEST_TARGET_AUTH         := $(OBJ_DIR)test/integrationtest_auth/integrationtest_auth
+INTEGRATIONTEST_TARGET_TRANSPORT    := $(OBJ_DIR)test/integrationtest_transport/integrationtest_transport
+
+TEST_RUN_TARGETS := unittest integrationtest integrationtest_auth integrationtest_transport
+# ensure the tests are running one after another when general test target is called
+ifneq ($(filter test,$(MAKECMDGOALS)),)
+TEST_RUN_TARGETS_HELPER := $(TEST_RUN_TARGETS)
+endif
+TEST_TARGETS := $(UNITTEST_TARGET) $(INTEGRATIONTEST_TARGET) $(INTEGRATIONTEST_TARGET_AUTH) $(INTEGRATIONTEST_TARGET_TRANSPORT)
+
+OBJ     :=  $(COBJ)                             \
+            $(EXAMPLE_COBJ)                     \
+            $(UNITTEST_CPPOBJ)                  \
+            $(INTEGRATIONTEST_CPPOBJ)           \
+            $(INTEGRATIONTEST_COBJ)             \
+            $(INTEGRATIONTEST_AUTH_CPPOBJ)      \
+            $(INTEGRATIONTEST_AUTH_COBJ)        \
+            $(INTEGRATIONTEST_TRANSPORT_CPPOBJ) \
+            $(INTEGRATIONTEST_TRANSPORT_COBJ)
+DEPS    :=  $(OBJ:%.o=%.d)
 
 
 export
 export
 
 
@@ -85,39 +154,38 @@ help:
 	@echo Shellmatta help
 	@echo Shellmatta help
 	@echo -----------------------------------------------
 	@echo -----------------------------------------------
 	@echo test      - run all tests
 	@echo test      - run all tests
-	@echo cppcheck  - run static code analysis (cppcheck)
+	@echo cppcheck  - run static code analysis \(cppcheck\)
 	@echo example   - build example
 	@echo example   - build example
 	@echo -----------------------------------------------
 	@echo -----------------------------------------------
 
 
-test: unittest integrationtest
+test: $(TEST_RUN_TARGETS)
+	- @mkdir -p output/test/report
+# run the test alltogether to get coverage from all combined tests
+#	remove coverage from former run
+	@-find . -name "*.gcda" -type f -delete
+	$(foreach TEST_RUN_TARGET,$(TEST_RUN_TARGETS),./$(OBJ_DIR)test/$(TEST_RUN_TARGET)/$(TEST_RUN_TARGET)${\n})
+
+#	remove report from former run
+	-rm -rf $(OBJ_DIR)test/report/*
+	gcovr --html-details --output $(OBJ_DIR)test/report/report.html -f src -f api -d
 
 
 cppcheck:
 cppcheck:
 	- @mkdir -p output/cppcheck/html
 	- @mkdir -p output/cppcheck/html
-	cppcheck --enable=all --template=gcc --cppcheck-build-dir=output/cppcheck $(SOURCES)
-	cppcheck --enable=all --template=gcc --cppcheck-build-dir=output/cppcheck $(SOURCES) --xml 2>output/cppcheck/cppcheck.xml
+	cppcheck $(CPPCHECK_OPTIONS)
+	cppcheck $(CPPCHECK_OPTIONS) --xml 2>output/cppcheck/cppcheck.xml
 	cppcheck-htmlreport --file=output/cppcheck/cppcheck.xml --title="Shellmatta" --report-dir=output/cppcheck/html
 	cppcheck-htmlreport --file=output/cppcheck/cppcheck.xml --title="Shellmatta" --report-dir=output/cppcheck/html
 
 
-unittest: $(UNITTEST_TARGET)
-	- @mkdir -p output/test/unittest/report
-	@echo running test:
+
+$(TEST_RUN_TARGETS): $(TEST_TARGETS) $(filter-out $@,$(TEST_RUN_TARGETS_HELPER))
+	- @mkdir -p output/test/$@/report
+	@echo running test $@:
 #	remove coverage from former run
 #	remove coverage from former run
 	@-find . -name "*.gcda" -type f -delete
 	@-find . -name "*.gcda" -type f -delete
-	-$(UNITTEST_TARGET)
+	-$(OBJ_DIR)test/$@/$@
 
 
 #	remove report from former run
 #	remove report from former run
-	-rm -rf $(OBJ_DIR)test/unittest/report/*
-	gcovr --html-details --output $(OBJ_DIR)test/unittest/report/report.html output/test/unittest -f src -f api -d
-
-integrationtest: $(INTEGRATIONTEST_TARGET)
-	- @mkdir -p output/test/integrationtest/report
-	@echo running test:
-#	remove coverage from former run
-#	@-find . -name "*.gcda" -type f -delete
-	-$(INTEGRATIONTEST_TARGET)
-
-	#	remove report from former run
-#	-rm -rf $(OBJ_DIR)test/unittest/report/*
-	gcovr --html-details --output $(OBJ_DIR)test/integrationtest/report/report.html output/test/integrationtest -f src -f api -d
+	-rm -rf $(OBJ_DIR)test/$@/report/*
+	gcovr --html-details --output $(OBJ_DIR)test/$@/report/report.html output/test/$@ -f src -f api -d
 
 
 example: $(EXAMPLE_TARGET)
 example: $(EXAMPLE_TARGET)
 	@echo building example
 	@echo building example
@@ -126,54 +194,82 @@ doc:
 	- @mkdir -p output/doc/html
 	- @mkdir -p output/doc/html
 	- @mkdir -p output/doc/latex
 	- @mkdir -p output/doc/latex
 	doxygen cfg/doxygen/doxyfile
 	doxygen cfg/doxygen/doxyfile
-	
+
 clean:
 clean:
 	- rm -rf $(OBJ_DIR)
 	- rm -rf $(OBJ_DIR)
 
 
-$(EXAMPLE_TARGET): $(COBJ) $(EXAMPLE_COBJ)
+$(EXAMPLE_TARGET): $(EXAMPLE_COBJ)
 	- @mkdir -p $(@D)
 	- @mkdir -p $(@D)
 	$(CC) $(LFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
 	$(CC) $(LFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
 
 
 $(UNITTEST_TARGET): $(UNITTEST_CPPOBJ)
 $(UNITTEST_TARGET): $(UNITTEST_CPPOBJ)
 	- @mkdir -p $(@D)
 	- @mkdir -p $(@D)
 	$(CPP) $(TESTLFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
 	$(CPP) $(TESTLFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
-	
+
 $(INTEGRATIONTEST_TARGET): $(INTEGRATIONTEST_CPPOBJ) $(INTEGRATIONTEST_COBJ)
 $(INTEGRATIONTEST_TARGET): $(INTEGRATIONTEST_CPPOBJ) $(INTEGRATIONTEST_COBJ)
 	- @mkdir -p $(@D)
 	- @mkdir -p $(@D)
 	$(CPP) $(TESTLFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
 	$(CPP) $(TESTLFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
+	
+$(INTEGRATIONTEST_TARGET_TRANSPORT): $(INTEGRATIONTEST_TRANSPORT_CPPOBJ) $(INTEGRATIONTEST_TRANSPORT_COBJ)
+	- @mkdir -p $(@D)
+	$(CPP) $(TESTLFLAGS_TRANSPORT) $(LIB_PATH) -o $@ $^ $(LIBS)
+
+$(INTEGRATIONTEST_TARGET_AUTH): $(INTEGRATIONTEST_AUTH_CPPOBJ) $(INTEGRATIONTEST_AUTH_COBJ)
+	- @mkdir -p $(@D)
+	$(CPP) $(TESTLFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
 
 
 $(TARGET): $(OBJ)
 $(TARGET): $(OBJ)
 	- @mkdir -p $(@D)
 	- @mkdir -p $(@D)
 	$(CC) $(LFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
 	$(CC) $(LFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
-	
+
 $(COBJ):
 $(COBJ):
 	- @mkdir -p $(@D)
 	- @mkdir -p $(@D)
-	@$(CC) -c $(CFLAGS)  $(DEPEND) -o $@  $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
+	@$(CC) -c $(CFLAGS) $(DEPEND) -o $(@:%.o=%.d) $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
 	$(CC) -c $(CFLAGS) -o $@  $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
 	$(CC) -c $(CFLAGS) -o $@  $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
 
 
 $(EXAMPLE_COBJ):
 $(EXAMPLE_COBJ):
 	- @mkdir -p $(@D)
 	- @mkdir -p $(@D)
-	@$(CC) -c $(CFLAGS) $(DEPEND) -o $@  $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
-	$(CC) -c $(CFLAGS) -o $@  $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
+	@$(CC) -c $(CFLAGS_EXAMPLE) $(DEPEND) -o $(@:%.o=%.d) $(subst $(OBJ_DIR_EXAMPLE), ,$(@:%.o=%.c))
+	$(CC) -c $(CFLAGS_EXAMPLE) -o $@  $(subst $(OBJ_DIR_EXAMPLE), ,$(@:%.o=%.c))
 
 
 $(UNITTEST_CPPOBJ):
 $(UNITTEST_CPPOBJ):
 	- @mkdir -p $(@D)
 	- @mkdir -p $(@D)
-	@$(CPP) -c $(TESTFLAGS) $(DEPEND) -o $@  $(subst $(UNITTEST_OBJ_DIR), ,$(@:%.o=%.cpp))
-	$(CPP) -c $(TESTFLAGS) -o $@  $(subst $(UNITTEST_OBJ_DIR), ,$(@:%.o=%.cpp))
+	@$(CPP) -c $(TESTFLAGS) $(DEPEND) -o $(@:%.o=%.d) $(subst $(UNITTEST_OBJ_DIR), ,$(@:%.o=%.cpp))
+	$(CPP) -c $(TESTFLAGS) -o $@ $(subst $(UNITTEST_OBJ_DIR), ,$(@:%.o=%.cpp))
 
 
 $(INTEGRATIONTEST_CPPOBJ):
 $(INTEGRATIONTEST_CPPOBJ):
 	- @mkdir -p $(@D)
 	- @mkdir -p $(@D)
-	@$(CPP) -c $(TESTFLAGS) $(DEPEND) -o $@  $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
+	@$(CPP) -c $(TESTFLAGS) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
 	$(CPP) -c $(TESTFLAGS) -o $@  $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
 	$(CPP) -c $(TESTFLAGS) -o $@  $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
 
 
 $(INTEGRATIONTEST_COBJ):
 $(INTEGRATIONTEST_COBJ):
 	- @mkdir -p $(@D)
 	- @mkdir -p $(@D)
-	@$(CC) -c $(TESTFLAGS) $(DEPEND) -o $@  $(subst $(INTEGRATIONTEST_C_OBJ_DIR), ,$(@:%.o=%.c))
+	@$(CC) -c $(TESTFLAGS) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_C_OBJ_DIR), ,$(@:%.o=%.c))
 	$(CC) -c $(TESTFLAGS) -o $@  $(subst $(INTEGRATIONTEST_C_OBJ_DIR), ,$(@:%.o=%.c))
 	$(CC) -c $(TESTFLAGS) -o $@  $(subst $(INTEGRATIONTEST_C_OBJ_DIR), ,$(@:%.o=%.c))
 
 
+$(INTEGRATIONTEST_TRANSPORT_CPPOBJ):
+	- @mkdir -p $(@D)
+	@$(CPP) -c $(TESTFLAGS_TRANSPORT) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT), ,$(@:%.o=%.cpp))
+	$(CPP) -c $(TESTFLAGS_TRANSPORT) -o $@  $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT), ,$(@:%.o=%.cpp))
+
+$(INTEGRATIONTEST_TRANSPORT_COBJ):
+	- @mkdir -p $(@D)
+	@$(CC) -c $(TESTFLAGS_TRANSPORT) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_C_OBJ_DIR_TRANSPORT), ,$(@:%.o=%.c))
+	$(CC) -c $(TESTFLAGS_TRANSPORT) -o $@  $(subst $(INTEGRATIONTEST_C_OBJ_DIR_TRANSPORT), ,$(@:%.o=%.c))
+
+$(INTEGRATIONTEST_AUTH_CPPOBJ):
+	- @mkdir -p $(@D)
+	@$(CPP) -c $(TESTFLAGS_AUTH) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_AUTH_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
+	$(CPP) -c $(TESTFLAGS_AUTH) -o $@  $(subst $(INTEGRATIONTEST_AUTH_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
+
+$(INTEGRATIONTEST_AUTH_COBJ):
+	- @mkdir -p $(@D)
+	$(CC) -c $(TESTFLAGS_AUTH) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_AUTH_C_OBJ_DIR), ,$(@:%.o=%.c))
+	$(CC) -c $(TESTFLAGS_AUTH) -o $@  $(subst $(INTEGRATIONTEST_AUTH_C_OBJ_DIR), ,$(@:%.o=%.c))
+
 %.o: %.cpp
 %.o: %.cpp
 	- @mkdir -p $(@D)
 	- @mkdir -p $(@D)
-	@$(CPP) -c $(CFLAGS) $(DEPEND) -o $@  $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
+	@$(CPP) -c $(CFLAGS) $(DEPEND) -o $(@:%.o=%.d) $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
 	$(CPP) -c $(CFLAGS) -o $@  $<
 	$(CPP) -c $(CFLAGS) -o $@  $<
 
 
 -include $(DEPS)
 -include $(DEPS)

+ 0 - 0
python_driver/__init__.py


+ 265 - 0
python_driver/shellmatta_transport.py

@@ -0,0 +1,265 @@
+""" Python module to handle the shellmatta transport layer """
+
+from crccheck.crc import Crc32
+import struct
+
+class ShellmattaTransport():
+    """ Class to handle the shellmatta transport layer """
+
+    class Packet():
+        """ A shellmatta packet structure """
+
+        HEADER_LENGTH = 8
+        MAX_PAYLOAD_LENGTH = 255
+        CRC_LENGTH = 4
+
+        SOH                         = 1
+        PROTOCOL_VERSION            = 1
+
+        TYPE_DATA                   = 0
+        TYPE_SEQ_CNT_REQUEST        = 1
+        TYPE_SEQ_CNT_RESPOND        = 129
+        TYPE_MAX_BUFFERSIZE_REQUEST = 2
+        TYPE_MAX_BUFFERSIZE_RESPOND = 130
+        TYPE_SEARCH_DEVICE_REQUEST  = 3
+        TYPE_SEARCH_DEVICE_RESPOND  = 131
+        TYPE_SET_ADDRESS_REQUEST    = 4
+        TYPE_SET_ADDRESS_RESPOND    = 132
+
+        def __init__(self,
+                     start_of_header,
+                     protocol_version,
+                     packet_type,
+                     payload_length,
+                     source,
+                     destination,
+                     sequence_h2s,
+                     sequence_s2h,
+                     payload=b'',
+                     crc=0,
+                     crc_fct=Crc32.calc):
+            """ creates a shellmatta transport packet based on the passed data """
+
+            self.start_of_header    = start_of_header
+            self.protocol_version   = protocol_version
+            self.packet_type        = packet_type
+            self.payload_length     = payload_length
+            self.source             = source
+            self.destination        = destination
+            self.sequence_h2s       = sequence_h2s
+            self.sequence_s2h       = sequence_s2h
+            self.payload            = payload
+            self.crc                = crc
+            self.crc_fct            = crc_fct
+
+        @classmethod
+        def from_header_data(cls, header_data):
+            """ create an empty packet based on raw header data """
+
+            if not header_data or len(header_data) != cls.HEADER_LENGTH:
+                raise ValueError("A shellmatta transport packet needs 8 data bytes as a header.")
+
+            data = struct.unpack('BBBBBBBB', header_data)
+            return cls(*data)
+
+        def set_payload(self, payload):
+            """ sets/replaces the complete payload of the packet """
+
+            if len(payload) > self.MAX_PAYLOAD_LENGTH:
+                raise ValueError("Payload size exceeds limit!")
+            self.payload = payload
+            self.payload_length = len(self.payload)
+
+        def append_payload(self, payload):
+            """ append passed payload to the packet """
+
+            if len(payload) + self.payload_length > self.MAX_PAYLOAD_LENGTH:
+                raise ValueError("Payload size exceeds limit!")
+            self.payload += payload
+            self.payload_length = len(self.payload)
+
+        def calc_crc(self):
+            """ Calculates the crc checksum """
+
+            return self.crc_fct(bytes(self)[:-4])
+
+        def secure(self):
+            """ Calculates the crc checksum """
+
+            self.crc = self.calc_crc()
+
+        def verify(self, crc):
+            """ Checks the packet agains the passed crc """
+
+            return crc == self.calc_crc()
+
+        def __bytes__(self):
+            """ Create binary representation of the packet """
+
+            # pack header
+            raw_buffer = struct.pack('BBBBBBBB',
+                                     self.start_of_header,
+                                     self.protocol_version,
+                                     self.packet_type,
+                                     self.payload_length,
+                                     self.source,
+                                     self.destination,
+                                     self.sequence_h2s,
+                                     self.sequence_s2h)
+            raw_buffer += self.payload
+            raw_buffer += self.crc.to_bytes(4, 'big')
+            return raw_buffer
+
+    def __init__(self, com_obj, mandatory=False, custom_crc=Crc32.calc):
+        self.com_obj = com_obj
+        self.mandatory = mandatory
+        self.custom_crc = custom_crc
+        self.sequence_counter_h2s = 0
+        self.sequence_counter_s2h = 0
+        self.received_raw_buffer = b''
+        self.received_packet = None
+        self.received_buffer = b''
+
+    def __send(self, packet_type, data, destination=0):
+        """ Sends data to the shellmatta - splitting at max size
+
+        Args:
+            packet_type (int): type of packet to send
+            data (bytestring): string of data to send
+        """
+        while len(data) > 0:
+            packet = self.Packet(self.Packet.SOH,
+                                self.Packet.PROTOCOL_VERSION,
+                                packet_type,
+                                min(len(data), self.Packet.MAX_PAYLOAD_LENGTH),
+                                0,
+                                destination,
+                                self.sequence_counter_h2s,
+                                self.sequence_counter_s2h,
+                                data[:self.Packet.MAX_PAYLOAD_LENGTH],
+                                crc_fct=self.custom_crc)
+            self.sequence_counter_h2s += 1
+            packet.secure()
+            self.com_obj.write(bytes(packet))
+            data = data[self.Packet.MAX_PAYLOAD_LENGTH:]
+
+    def __peek_com(self, size):
+        """ wraps the read method to be able to peek data - leave data in buffer
+
+        Args:
+            size(integer) : number of bytes to peek"""
+        if len(self.received_raw_buffer) < size:
+            received_data = self.com_obj.read(size - len(self.received_raw_buffer))
+            self.received_raw_buffer += received_data
+
+            if len(self.received_raw_buffer) < size:
+                raise TimeoutError("No response from Shellmatta")
+
+        # return the requested data from the buffer
+        data = self.received_raw_buffer[:size]
+
+        return data
+
+    def __read_com(self, size):
+        """ wraps the read method - removes read data from the bufffer
+
+        Args:
+            size(integer) : number of bytes to read"""
+
+        # return the requested data from the buffer
+        data = self.__peek_com(size)
+        self.received_raw_buffer = self.received_raw_buffer[size:]
+
+        return data
+
+    def __process_reception(self):
+        """ try to read a complete telegram from the shellmatta """
+
+        data = self.__peek_com(1)
+        success = False
+
+        # start parsing transport layer telegram
+        if int(data[0]) == self.Packet.SOH:
+
+            # process the header
+            data = self.__peek_com(self.Packet.HEADER_LENGTH)
+            packet = self.Packet.from_header_data(data)
+
+            # read complete packet
+            packet_size = self.Packet.HEADER_LENGTH + packet.payload_length + self.Packet.CRC_LENGTH
+            packet_data = self.__peek_com(packet_size)
+            packet.set_payload(packet_data[self.Packet.HEADER_LENGTH:-self.Packet.CRC_LENGTH])
+
+            # verify crc
+            crc = int.from_bytes(packet_data[-self.Packet.CRC_LENGTH:], 'big', signed=False)
+            success = packet.verify(crc)
+
+            if success:
+
+                # remove the packet from the raw buffer
+                self.__read_com(packet_size)
+
+                if packet.packet_type == packet.TYPE_DATA:
+                    self.received_buffer += packet.payload
+
+        # process invalid bytes
+        if not success:
+            if not self.mandatory:
+                # append the received SOH directly to the buffer
+                self.received_buffer += self.__read_com(1)
+            else:
+                # throw away the SOH byte
+                self.__read_com(1)
+
+    def write(self, data):
+        """ Send data using the transport layer
+
+        Args:
+            data (bytes): data to send to shellmatta
+        """
+
+        if not isinstance(data, bytes):
+            raise ValueError("data must be od type bytes")
+        self.__send(self.Packet.TYPE_DATA, data)
+
+    def write_manual(self, data):
+        """Send data as if it was written by manual input. Will not use transport layer protocol.
+
+        Args:
+            data (string): String to send to shellmatta
+        """
+        if not isinstance(data, bytes):
+            raise ValueError("data must be od type bytes")
+        self.com_obj.write(data)
+
+    def read(self, size=1):
+        """ Reads size bytes from the shellmatta transport layer
+
+        Args:
+            size (integer): number of bytes to read
+        """
+        try:
+            while len(self.received_buffer) < size:
+                self.__process_reception()
+        except TimeoutError:
+            pass
+
+        data = self.received_buffer[:size]
+        self.received_buffer = self.received_buffer[size:]
+
+        return data
+
+    def reset(self):
+        """ resets all internal states and flush the counterpart """
+        self.sequence_counter_h2s = 0
+        self.sequence_counter_s2h = 0
+        self.received_raw_buffer = b''
+        self.received_packet = None
+        self.received_buffer = b''
+        # flush the buffer and send one cancel
+        self.com_obj.write(b'\x00' * (self.Packet.MAX_PAYLOAD_LENGTH + 12))
+        self.write(b'\x03')
+
+    def close(self):
+        """ Close port """
+        self.reset()

+ 115 - 0
python_driver/shellmatta_transport_serial.py

@@ -0,0 +1,115 @@
+""" Wrapper arount a shellmatta with transport layer to send commands and receive the response """
+
+import time
+import serial
+import argparse
+from shellmatta_transport import ShellmattaTransport
+
+class ShellmattaSerial():
+    """ Helper class to communicate with a shellmatta enabled device """
+
+
+    def __init__(self, com_port, baudrate=115200, prompt="->", timeout=1, transport_layer_mandatory=False):
+        """ create the transport layer instance using the passed com port """
+        self.prompt = prompt
+        self.com = com_port
+        self.com_port = serial.Serial(com_port, baudrate=baudrate, timeout=timeout)
+
+        self.transport = ShellmattaTransport(self.com_port, transport_layer_mandatory)
+
+    def send_command(self, command):
+        """ Send command and wait for the prompt in the response. """
+
+        if isinstance(command, str):
+            send_data = str.encode(command)
+        else:
+            send_data = command
+
+        retries = 3
+
+        while retries:
+            send_data = send_data + b"\r"
+            self.transport.write(send_data)
+            data = b''
+
+            while True:
+                data += self.transport.read()
+
+                if data.endswith(str.encode(self.prompt)):
+                    # return received string without echo and prompt
+                    data = data[:-len(str.encode(self.prompt))]
+                    if data.startswith(send_data):
+
+                        data = data[len(send_data):]
+
+                    break
+
+            # todo implement proper retries / sequence checking
+            if b"crc error\r\n" not in data:
+                break
+
+            retries -= 1
+
+        return data
+
+    def send_command_only(self, command):
+        """ Send command without waiting for response. """
+
+        send_string = command + "\r"
+        self.transport.write(str.encode(send_string))
+
+    def send_raw(self, data):
+        """ Send a passed bytes string without any mercy. """
+
+        self.transport.write_manual(data)
+
+    def reset_communication(self):
+        """ Clears all internal buffers, throwing away all data. """
+        self.transport.reset()
+        self.com_port.flush()
+
+        self.com_port.reset_input_buffer()
+        self.com_port.reset_output_buffer()
+
+    def close_serial(self):
+        """ Closes the serial port """
+        self.com_port.close()
+
+    def set_baudrate(self, baud):
+        """ Set the baudrate of the serial port """
+        self.com_port.baudrate = baud
+        self.com_port.close()
+        time.sleep(0.5)
+
+        self.com_port = serial.Serial(self.com, baudrate=baud, timeout=self.com_port.timeout)
+        self.transport.close()
+        self.transport = ShellmattaTransport(self.com_port,
+                                             False,
+                                             None)
+
+# start interactive mode if not used as a module
+if __name__ == "__main__":
+
+    # setup an argument parser
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-c", "--com-port", dest="com_port", help="Com port to use.", required=True)
+    parser.add_argument("-b", "--baudrate", dest="baudrate", help="Baudrate to use.", type=int, default=115200)
+    parser.add_argument("-p", "--prompt",   dest="prompt",   help="Prompt text of the shellmatta instance.", default="->")
+    parser.add_argument("-t", "--timeout",  dest="timeout",  help="Timeout on the com port.", type=float, default=0.1)
+    parser.add_argument("-m", "--mandatory", dest="mandatory", help="Force transport layer usage.", default=True)
+    args = parser.parse_args()
+
+    # get our own shellmatta serial instance
+    shellmatta = ShellmattaSerial(args.com_port,
+                                  args.baudrate,
+                                  args.prompt,
+                                  args.timeout,
+                                  args.mandatory)
+
+    shellmatta.reset_communication()
+
+    # process user input
+    print("Shellmatta Transport Serial wrapper - type commands as you like:")
+    while True:
+        command = input()
+        print(shellmatta.send_command(command))

+ 460 - 330
src/shellmatta.c

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -23,11 +23,395 @@
 #include "shellmatta_utils.h"
 #include "shellmatta_utils.h"
 #include "shellmatta_escape.h"
 #include "shellmatta_escape.h"
 #include "shellmatta_opt.h"
 #include "shellmatta_opt.h"
+#include "shellmatta_ymodem.h"
+#ifdef SHELLMATTA_TRANSPORT
+#include "shellmatta_transport.h"
+#endif
+#ifdef SHELLMATTA_AUTHENTICATION
+#include "shellmatta_auth.h"
+#endif
 #include <stddef.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <string.h>
 #include <string.h>
 #include <stdarg.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdio.h>
 
 
+/**
+ * @brief       processes the passed amount of data - called from the transport layer
+ * @param[in]   handle  shellmatta instance handle
+ * @param[in]   data    pointer to input data to process
+ * @param[in]   size    length of input data to process
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_USE_FAULT
+ */
+static shellmatta_retCode_t shellmatta_processDataInt(shellmatta_handle_t     handle,
+                                                      char                    *data,
+                                                      uint32_t                 size)
+{
+    shellmatta_cmd_t            *cmd;
+    uint8_t                     cmdExecuted = 0u;
+    uint32_t                    cmdLen;
+    char                        *tempString;
+
+    shellmatta_retCode_t        ret = SHELLMATTA_OK;
+    shellmatta_retCode_t        cmdRet;
+    shellmatta_instance_t       *inst = (shellmatta_instance_t*)handle;
+
+    /** -# in busy mode - keep calling this command */
+    if(NULL != inst->busyCmd)
+    {
+        /** -# just call the function until it is not busy anymore */
+        (void)shellmatta_opt_reInit(inst);
+        ret = inst->busyCmd->cmdFct(handle, inst->buffer, inst->inputCount);
+
+        if(SHELLMATTA_BUSY == ret)
+        {
+            /** -# do nothing - still busy */
+        }
+        else if(SHELLMATTA_CONTINUE == ret)
+        {
+            inst->continuousCmd = inst->busyCmd;
+            inst->busyCmd       = NULL;
+        }
+        else
+        {
+            utils_terminateInput(inst);
+        }
+    }
+    /** -# poll shellmatta ymodem to send out the request to the sender */
+    if((0u == size) && (SHELLMATTA_YMODEM_INACTIVE != inst->ymodem.state))
+    {
+        (void)shellmatta_ymodem_poll(handle);
+    }
+    /** -# call continuous function even if there is no data */
+    else if((0u == size) && (NULL != inst->continuousCmd))
+    {
+        /** -# just call the function without any new data */
+        inst->stdinLength               = 0u;
+        inst->buffer[inst->stdinIdx]    = '\0';
+        (void)shellmatta_opt_reInit(inst);
+        ret = inst->continuousCmd->cmdFct(handle, inst->buffer, inst->inputCount);
+
+        if(SHELLMATTA_CONTINUE == ret)
+        {
+            /** -# do nothing just continue */
+        }
+        else if(SHELLMATTA_BUSY == ret)
+        {
+            inst->busyCmd       = inst->continuousCmd;
+            inst->continuousCmd = NULL;
+        }
+        else
+        {
+            utils_terminateInput(inst);
+        }
+    }
+    else
+    {
+        /* nothing to do here - continue parsing the command */
+    }
+
+    /** -# process byte wise */
+    for (; (inst->byteCounter < size) && (NULL == inst->busyCmd); inst->byteCounter++)
+    {
+
+        /** -# handle ymodem when a session is active */
+        if (inst->ymodem.state != SHELLMATTA_YMODEM_INACTIVE)
+        {
+            ret = shellmatta_ymodem_processByte(handle, data[inst->byteCounter]);
+        }
+        /** -# in continuous mode - pass data directly to the command */
+        else if(NULL != inst->continuousCmd)
+        {
+            /** -# copy data and call command function */
+            inst->buffer[inst->stdinIdx]        = data[inst->byteCounter];
+            inst->buffer[inst->stdinIdx + 1u]   = '\0';
+            inst->stdinLength                   = 1u;
+            (void)shellmatta_opt_reInit(inst);
+            ret = inst->continuousCmd->cmdFct(handle, inst->buffer, inst->inputCount);
+            
+            /** -# check if continuous mode is canceled or interrupted by busy mode */
+            if(SHELLMATTA_BUSY == ret)
+            {
+                inst->busyCmd 	= inst->continuousCmd;
+                inst->continuousCmd = NULL;
+            }
+            else if(('\x03' == data[inst->byteCounter]))
+            {
+                /** -# cancel continue session */
+                utils_terminateInput(inst);
+
+                ret = SHELLMATTA_OK;
+            }
+            else if(SHELLMATTA_CONTINUE == ret)
+            {
+                /** -# do nothing - continue */
+            }
+            else
+            {
+                utils_terminateInput(inst);
+            }
+        }
+        /** -# handle escape sequences */
+        else if(inst->escapeCounter != 0u)
+        {
+            escape_handleSequence(inst, data[inst->byteCounter]);
+        }
+        /** -# handle delimiter as start of processing the command */
+        else if (inst->delimiter == data[inst->byteCounter])
+        {
+            if(0u == inst->hereLength)
+            {
+                /**
+                     *  @dot
+                     *  digraph heredocParser {
+                     *      start -> wait [ label="<< in first line - store delimiter" ];
+                     *      wait -> wait [ label="delimiter not detected" ];
+                     *      wait -> end [ label="delimiter found - remove all heredoc stuff and send the input to the command" ];
+                     *  }
+                     *  @enddot */
+
+                /** -# check for heredoc - add string delimiter to stop strstr from searching too far */
+                inst->buffer[inst->inputCount] = '\0';
+                tempString = strstr(inst->buffer, "<<");
+                if(NULL != tempString)
+                {
+                    /*' -# check if length of heredoc delimiter is valid */
+                    if(inst->inputCount > ((uint32_t)(tempString - inst->buffer) + 2u))
+                    {
+                        inst->hereStartIdx      = (uint32_t)(tempString - inst->buffer);
+                        inst->hereDelimiterIdx  = inst->hereStartIdx + 2u;
+                        while((inst->hereDelimiterIdx < inst->inputCount)
+                                && (    ('\0' == inst->buffer[inst->hereDelimiterIdx])
+                                    ||  (' '  == inst->buffer[inst->hereDelimiterIdx])))
+                        {
+                            inst->hereDelimiterIdx ++;
+                        }
+
+                        inst->hereLength = inst->inputCount - inst->hereDelimiterIdx;
+
+                        inst->dirty = true;
+                        utils_insertChars(inst, &data[inst->byteCounter], 1u);
+                        inst->lastNewlineIdx = inst->inputCount;
+                    }
+                    else
+                    {
+                        inst->hereLength    = 0u;
+
+                        /** -# store the current command and reset the history buffer */
+                        inst->dirty = true;
+                        history_storeCmd(inst);
+                        history_reset(inst);
+                    }
+                }
+                else
+                {
+                    /** -# store the current command and reset the history buffer */
+                    inst->dirty = true;
+                    history_storeCmd(inst);
+                    history_reset(inst);
+                }
+            }
+            else
+            {
+                tempString  = &inst->buffer[inst->lastNewlineIdx];
+                cmdLen      = inst->inputCount - inst->lastNewlineIdx;
+
+                /** -# skip newline characters before comparison */
+                while(('\n' == *tempString) || ('\r' == *tempString))
+                {
+                    tempString ++;
+                    cmdLen --;
+                }
+
+                if(     (inst->hereLength == cmdLen)
+                    &&  (0  == strncmp( &inst->buffer[inst->hereDelimiterIdx],
+                                        tempString,
+                                        inst->hereLength)))
+                {
+                    /** -# store the current command and reset the history buffer */
+                    inst->dirty = true;
+                    history_storeCmd(inst);
+                    history_reset(inst);
+
+                    /** -# process heredoc as stdin like input  */
+                    /** -# find start of heredoc data           */
+                    inst->stdinIdx = inst->hereDelimiterIdx + inst->hereLength;
+                    while(      ('\n' == inst->buffer[inst->stdinIdx])
+                            ||  ('\r' == inst->buffer[inst->stdinIdx]))
+                    {
+                        inst->stdinIdx ++;
+                    }
+                    /** -# calculate length and terminate stdin string */
+                    if(inst->stdinIdx < inst->lastNewlineIdx)
+                    {
+                        inst->stdinLength = inst->lastNewlineIdx - inst->stdinIdx;
+                        inst->buffer[inst->lastNewlineIdx] = '\0';
+                    }
+                    else
+                    {
+                        inst->stdinLength = 0u;
+                    }
+
+                    /** -# calculate length and terminate argument string */
+                    inst->inputCount = inst->hereStartIdx;
+                    inst->buffer[inst->hereStartIdx] = '\0';
+
+                    /** -# terminate heredoc session */
+                    inst->hereLength = 0u;
+                }
+                else
+                {
+                    /** -# the party goes on - just print the delimiter and store the position */
+                    inst->lastNewlineIdx = inst->inputCount;
+                    utils_insertChars(inst, &data[inst->byteCounter], 1u);
+                }
+            }
+
+            if(0u == inst->hereLength)
+            {
+                cmd = inst->cmdList;
+
+                /** -# determine the cmd len (chars until first space or \0 is found */
+                cmdLen = 0u;
+                while(      (cmdLen <   inst->inputCount)
+                        &&  (' '    !=  inst->buffer[cmdLen])
+                        &&  ('\r'   !=  inst->buffer[cmdLen])
+                        &&  ('\n'   !=  inst->buffer[cmdLen])
+                        &&  ('\0'   !=  inst->buffer[cmdLen]))
+                {
+                    cmdLen ++;
+                }
+
+                /** -# search for a matching command */
+                while (NULL != cmd)
+                {
+                    /** -# compare command and alias string and length */
+                    if (    ((cmdLen    == strlen(cmd->cmd))
+                            && (0       == strncmp(inst->buffer, cmd->cmd, cmdLen)))
+                        || ((NULL != cmd->cmdAlias)
+                            && (cmdLen  == strlen(cmd->cmdAlias))
+                            && (0       == strncmp(inst->buffer, cmd->cmdAlias, cmdLen))))
+                    {
+                        utils_writeEcho(inst, "\r\n", 2u);
+                        shellmatta_opt_init(inst, cmdLen + 1u);
+
+#ifdef SHELLMATTA_AUTHENTICATION
+                        cmdRet = SHELLMATTA_OK;
+                        if (SHELLMATTA_OK == shellmatta_auth_is_cmd_permitted(inst, cmd))
+                        {
+                            cmdExecuted = 1u;
+                            cmdRet = cmd->cmdFct(handle, inst->buffer, inst->inputCount);
+                        }
+
+#else
+                        cmdExecuted = 1u;
+                        cmdRet = cmd->cmdFct(handle, inst->buffer, inst->inputCount);
+#endif
+
+                        switch(cmdRet)
+                        {
+                            case SHELLMATTA_CONTINUE:
+                                /** -# initialize stdin buffer and continuous cmd */
+                                inst->stdinIdx      = inst->bufferSize - 2u;
+                                inst->stdinLength   = 0u;
+                                inst->continuousCmd = cmd;
+                                ret                 = cmdRet;
+                                break;
+                            
+                            case SHELLMATTA_BUSY:
+                                inst->busyCmd   = cmd;
+                                ret             = cmdRet;
+                                break;
+
+                            default:
+                                /* nothing to do - everything ok */
+                                break;
+                        }
+                        cmd = NULL;
+                    }
+                    else
+                    {
+                        cmd = cmd->next;
+                    }
+                }
+
+                if ((0u == cmdExecuted) && (inst->inputCount > 0))
+                {
+                    if (inst->echoEnabled)
+                    {
+                        SHELLMATTA_WRITE("\r\nCommand: ", 11u);
+                        SHELLMATTA_WRITE(inst->buffer, inst->inputCount);
+                        SHELLMATTA_WRITE(" not found", 10u);
+                    }
+                    else
+                    {
+                        SHELLMATTA_WRITE("\r\nCommand not found!", 20u);
+                    }
+                }
+
+                /** -# terminate this session if no continuous mode is requested */
+                if(     (NULL == inst->continuousCmd)
+                    &&  (NULL == inst->busyCmd))
+                {
+                    utils_terminateInput(inst);
+                }
+            }
+        }
+        /** -# ignore newline as first character (to be compatible to
+         * terminals sending newline after return */
+        else if((0u == inst->inputCount) && ('\n' == data[inst->byteCounter]))
+        {
+            /* do nothing */
+        }
+        /** -# check for tabulator key - auto complete */
+        else if('\t' == data[inst->byteCounter])
+        {
+            inst->dirty = true;
+            autocomplete_run(inst);
+        }
+        /** -# check for cancel -
+         *      terminate current input and print prompt again */
+        else if('\x03' == data[inst->byteCounter])
+        {
+            inst->dirty = false;
+            history_reset(inst);
+            utils_terminateInput(inst);
+        }
+        /** -# check for backspace */
+        else if(    ('\b'   == data[inst->byteCounter])
+                ||  ('\x7f' == data[inst->byteCounter]))
+        {
+            inst->dirty = true;
+            utils_removeChars(inst, 1u, true);
+        }
+        /** -# check for start of escape sequence */
+        else if('\x1b' == data[inst->byteCounter])
+        {
+            inst->escapeCounter = 1u;
+        }
+        else
+        {
+            inst->dirty = true;
+            utils_insertChars(inst, &data[inst->byteCounter], 1u);
+        }
+
+        /** -# reset tab counter on not a tab */
+        if ('\t' != data[inst->byteCounter])
+        {
+            inst->tabCounter = 0u;
+        }
+    }
+
+    /** -# initialize the byte buffer if processing of the input is finished */
+    if(ret != SHELLMATTA_BUSY)
+    {
+        inst->byteCounter = 0u;
+    }
+
+    return ret;
+}
+
 /**
 /**
  * @}
  * @}
  * @addtogroup shellmatta_api
  * @addtogroup shellmatta_api
@@ -71,50 +455,51 @@ shellmatta_retCode_t shellmatta_doInit(
         &&  (NULL != writeFct)
         &&  (NULL != writeFct)
         &&  ((NULL != historyBuffer) || (0u == historyBufferSize)))
         &&  ((NULL != historyBuffer) || (0u == historyBufferSize)))
     {
     {
+        /** -# clear the shellmatta instance */
+        memset((void *)inst, 0, sizeof(shellmatta_instance_t));
+
         /** -# copy all provided buffers into the shellmatta instance */
         /** -# copy all provided buffers into the shellmatta instance */
         inst->buffer                = buffer;
         inst->buffer                = buffer;
         inst->bufferSize            = bufferSize;
         inst->bufferSize            = bufferSize;
-        inst->inputCount            = 0u;
-        inst->byteCounter           = 0u;
-        inst->lastNewlineIdx        = 0u;
-        inst->cursor                = 0u;
-        inst->stdinIdx              = 0u;
-        inst->stdinLength           = 0u;
         inst->historyBuffer         = historyBuffer;
         inst->historyBuffer         = historyBuffer;
         inst->historyBufferSize     = historyBufferSize;
         inst->historyBufferSize     = historyBufferSize;
-        inst->historyStart          = 0u;
-        inst->historyEnd            = 0u;
-        inst->historyRead           = 0u;
         inst->historyReadUp         = true;
         inst->historyReadUp         = true;
         inst->write                 = writeFct;
         inst->write                 = writeFct;
         inst->prompt                = prompt;
         inst->prompt                = prompt;
         inst->echoEnabled           = true;
         inst->echoEnabled           = true;
         inst->dirty                 = false;
         inst->dirty                 = false;
-        inst->tabCounter            = 0u;
-        inst->escapeCounter         = 0u;
-        inst->hereStartIdx          = 0u;
-        inst->hereDelimiterIdx      = 0u;
-        inst->hereLength            = 0u;
         inst->delimiter             = '\r';
         inst->delimiter             = '\r';
         inst->mode                  = SHELLMATTA_MODE_INSERT;
         inst->mode                  = SHELLMATTA_MODE_INSERT;
         inst->cmdList               = &(inst->helpCmd);
         inst->cmdList               = &(inst->helpCmd);
-        inst->continuousCmd         = NULL;
-        inst->busyCmd               = NULL;
-        inst->cmdListIsConst        = false;
         shellmatta_opt_init(inst, 0u);
         shellmatta_opt_init(inst, 0u);
 
 
         /** -# copy the help command structure to this instance */
         /** -# copy the help command structure to this instance */
         memcpy(&(inst->helpCmd), &helpCmd, sizeof(shellmatta_cmd_t));
         memcpy(&(inst->helpCmd), &helpCmd, sizeof(shellmatta_cmd_t));
+#ifdef SHELLMATTA_AUTHENTICATION
+        /** -# copy the auth commands to the instance */
+        memcpy(&(inst->loginCmd), &shellmatta_auth_loginCmd, sizeof(shellmatta_cmd_t));
+        memcpy(&(inst->logoutCmd), &shellmatta_auth_logoutCmd, sizeof(shellmatta_cmd_t));
+#endif
 
 
         if(NULL != cmdList)
         if(NULL != cmdList)
         {
         {
+#ifndef SHELLMATTA_AUTHENTICATION
             inst->helpCmd.next = (shellmatta_cmd_t *) cmdList;
             inst->helpCmd.next = (shellmatta_cmd_t *) cmdList;
+#else
+            inst->logoutCmd.next = (shellmatta_cmd_t *) cmdList;
+#endif
             inst->cmdListIsConst = true;
             inst->cmdListIsConst = true;
         }
         }
 
 
         inst->magic = SHELLMATTA_MAGIC;
         inst->magic = SHELLMATTA_MAGIC;
         *handle     = (shellmatta_handle_t)inst;
         *handle     = (shellmatta_handle_t)inst;
 
 
+#ifdef SHELLMATTA_TRANSPORT
+        /* init transport layer */
+        inst->transportBusyMark = 0u;
+        shellmatta_transport_init(&inst->transportLayer, inst->write);
+#endif
+
         /** -# print the first prompt */
         /** -# print the first prompt */
         utils_terminateInput(inst);
         utils_terminateInput(inst);
     }
     }
@@ -132,7 +517,7 @@ shellmatta_retCode_t shellmatta_doInit(
  * It resets all internal states - the buffers are left as they are - they will be overwritten.
  * It resets all internal states - the buffers are left as they are - they will be overwritten.
  * The history buffer is deleted as well.
  * The history buffer is deleted as well.
  */
  */
-shellmatta_retCode_t shellmatta_resetShell( shellmatta_handle_t handle, bool printPrompt)
+shellmatta_retCode_t shellmatta_resetShell(shellmatta_handle_t handle, bool printPrompt)
 {
 {
     shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
     shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
     shellmatta_retCode_t ret = SHELLMATTA_OK;
     shellmatta_retCode_t ret = SHELLMATTA_OK;
@@ -159,8 +544,14 @@ shellmatta_retCode_t shellmatta_resetShell( shellmatta_handle_t handle, bool pri
         inst->hereStartIdx          = 0u;
         inst->hereStartIdx          = 0u;
         inst->hereDelimiterIdx      = 0u;
         inst->hereDelimiterIdx      = 0u;
         inst->hereLength            = 0u;
         inst->hereLength            = 0u;
+        inst->ymodem.state          = SHELLMATTA_YMODEM_INACTIVE;
         shellmatta_opt_init(inst, 0u);
         shellmatta_opt_init(inst, 0u);
 
 
+#ifdef SHELLMATTA_AUTHENTICATION
+        inst->userId = 0u;
+        inst->userPointer = NULL;
+#endif
+
         if(true == printPrompt)
         if(true == printPrompt)
         {
         {
             /** -# print a prompt if requested */
             /** -# print a prompt if requested */
@@ -252,6 +643,22 @@ shellmatta_retCode_t shellmatta_addCmd(shellmatta_handle_t handle, shellmatta_cm
                 tempCmd = tempCmd->next;
                 tempCmd = tempCmd->next;
             }
             }
         }
         }
+
+#ifdef SHELLMATTA_AUTHENTICATION
+        /** -# append permissions to added command if any */
+        if (inst->permList)
+        {
+            /** -# Search for command in perm list */
+            for (uint32_t i = 0u; i < inst->permListLength; i++)
+            {
+                if (0 == strcmp(cmd->cmd, inst->permList[i].cmd))
+                {
+                    cmd->authLink = &inst->permList[i];
+                    break;
+                }
+            }
+        }
+#endif
     }
     }
     else
     else
     {
     {
@@ -268,7 +675,7 @@ shellmatta_retCode_t shellmatta_addCmd(shellmatta_handle_t handle, shellmatta_cm
  * @return      errorcode   #SHELLMATTA_OK
  * @return      errorcode   #SHELLMATTA_OK
  *                          #SHELLMATTA_USE_FAULT (param err)
  *                          #SHELLMATTA_USE_FAULT (param err)
  */
  */
-shellmatta_retCode_t shellmatta_removeCmd(shellmatta_handle_t handle, shellmatta_cmd_t *cmd)
+shellmatta_retCode_t shellmatta_removeCmd(shellmatta_handle_t handle, const shellmatta_cmd_t *cmd)
 {
 {
     shellmatta_instance_t   *inst       = (shellmatta_instance_t*)handle;
     shellmatta_instance_t   *inst       = (shellmatta_instance_t*)handle;
     shellmatta_cmd_t       *prevCmd;
     shellmatta_cmd_t       *prevCmd;
@@ -369,345 +776,68 @@ shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t     handle,
                                             char                    *data,
                                             char                    *data,
                                             uint32_t                 size)
                                             uint32_t                 size)
 {
 {
-    shellmatta_cmd_t        *cmd;
-    uint8_t                 cmdExecuted = 0u;
-    uint32_t                cmdLen;
-    char                    *tempString;
-
-    shellmatta_retCode_t    ret = SHELLMATTA_OK;
-    shellmatta_retCode_t    cmdRet;
+    shellmatta_retCode_t    ret   = SHELLMATTA_OK;
     shellmatta_instance_t   *inst = (shellmatta_instance_t*)handle;
     shellmatta_instance_t   *inst = (shellmatta_instance_t*)handle;
+#ifdef SHELLMATTA_TRANSPORT
+    char                    *tmpData;
+    uint32_t                tmpSize = 0;
+    uint32_t                i;
+    bool                    processingDone = false;
+#endif
 
 
     /** -# check parameters for plausibility  */
     /** -# check parameters for plausibility  */
     if(     (NULL               != inst)
     if(     (NULL               != inst)
         &&  (SHELLMATTA_MAGIC   == inst->magic))
         &&  (SHELLMATTA_MAGIC   == inst->magic))
     {
     {
-        /** -# in busy mode - keep calling this command */
-        if(NULL != inst->busyCmd)
+#ifdef SHELLMATTA_TRANSPORT
+        for (i = inst->transportBusyMark; i < size; i ++)
         {
         {
-            /** -# just call the function until it is not busy anymore */
-            (void)shellmatta_opt_reInit(inst);
-            ret = inst->busyCmd->cmdFct(handle, inst->buffer, inst->inputCount);
-
-            if(SHELLMATTA_BUSY == ret)
-            {
-                /** -# do nothing - still busy */
-            }
-            else if(SHELLMATTA_CONTINUE == ret)
-            {
-                inst->continuousCmd = inst->busyCmd;
-                inst->busyCmd       = NULL;
-            }
-            else
+            ret = shellmatta_transport_process(&inst->transportLayer, data[i], &tmpData, &tmpSize);
+            if (SHELLMATTA_OK == ret)
             {
             {
-                utils_terminateInput(inst);
-            }
-        }
-        /** -# call continuous function even if there is no data */
-        else if((0u == size) && (NULL != inst->continuousCmd))
-        {
-            /** -# just call the function without any new data */
-            inst->stdinLength               = 0u;
-            inst->buffer[inst->stdinIdx]    = '\0';
-            (void)shellmatta_opt_reInit(inst);
-            ret = inst->continuousCmd->cmdFct(handle, inst->buffer, inst->inputCount);
+                ret = shellmatta_processDataInt(handle, tmpData, tmpSize);
+                processingDone = true;
 
 
-            if(SHELLMATTA_CONTINUE == ret)
-            {
-                /** -# do nothing just continue */
-            }
-            else if(SHELLMATTA_BUSY == ret)
-            {
-                inst->busyCmd       = inst->continuousCmd;
-                inst->continuousCmd = NULL;
-            }
-            else
-            {
-                utils_terminateInput(inst);
-            }
-        }
-        else
-        {
-            /* nothing to do here - continue parsing the command */
-        }
-
-        /** -# process byte wise */
-        for (; (inst->byteCounter < size) && (NULL == inst->busyCmd); inst->byteCounter++)
-        {
-            /** -# in continuous mode - pass data directly to the command */
-            if(NULL != inst->continuousCmd)
-            {
-                /** -# copy data and call command function */
-                inst->buffer[inst->stdinIdx]        = data[inst->byteCounter];
-                inst->buffer[inst->stdinIdx + 1u]   = '\0';
-                inst->stdinLength                   = 1u;
-                (void)shellmatta_opt_reInit(inst);
-                ret = inst->continuousCmd->cmdFct(handle, inst->buffer, inst->inputCount);
-                
-                /** -# check if continuous mode is canceled or interrupted by busy mode */
-                if(SHELLMATTA_BUSY == ret)
-                {
-                    inst->busyCmd 	= inst->continuousCmd;
-                    inst->continuousCmd = NULL;
-                }
-                else if(('\x03' == data[inst->byteCounter]))
+                if (SHELLMATTA_BUSY == ret)
                 {
                 {
-                    /** -# cancel continue session */
-                    utils_terminateInput(inst);
-                    ret = SHELLMATTA_OK;
-                }
-                else if(SHELLMATTA_CONTINUE == ret)
-                {
-                    /** -# do nothing - continue */
+                    inst->transportBusyMark = i;
+                    break;
                 }
                 }
                 else
                 else
                 {
                 {
-                    utils_terminateInput(inst);
+                    inst->transportBusyMark = 0u;
                 }
                 }
             }
             }
-            /** -# handle escape sequences */
-            else if(inst->escapeCounter != 0u)
+            else if (SHELLMATTA_ERROR == ret)
             {
             {
-                escape_handleSequence(inst, data[inst->byteCounter]);
-            }
-            /** -# handle delimiter as start of processing the command */
-            else if (inst->delimiter == data[inst->byteCounter])
-            {
-                if(0u == inst->hereLength)
-                {
-                    /**
-                      *  @dot
-                      *  digraph heredocParser {
-                      *      start -> wait [ label="<< in first line - store delimiter" ];
-                      *      wait -> wait [ label="delimiter not detected" ];
-                      *      wait -> end [ label="delimiter found - remove all heredoc stuff and send the input to the command" ];
-                      *  }
-                      *  @enddot */
-
-                    /** -# check for heredoc - add string delimiter to stop strstr from searching too far */
-                    inst->buffer[inst->inputCount] = '\0';
-                    tempString = strstr(inst->buffer, "<<");
-                    if(NULL != tempString)
-                    {
-                        /*' -# check if length of heredoc delimiter is valid */
-                        if(inst->inputCount > ((uint32_t)(tempString - inst->buffer) + 2u))
-                        {
-                            inst->hereStartIdx      = (uint32_t)(tempString - inst->buffer);
-                            inst->hereDelimiterIdx  = inst->hereStartIdx + 2u;
-                            while((inst->hereDelimiterIdx < inst->inputCount)
-                                    && (    ('\0' == inst->buffer[inst->hereDelimiterIdx])
-                                        ||  (' '  == inst->buffer[inst->hereDelimiterIdx])))
-                            {
-                                inst->hereDelimiterIdx ++;
-                            }
-
-                            inst->hereLength = inst->inputCount - inst->hereDelimiterIdx;
-
-                            inst->dirty = true;
-                            utils_insertChars(inst, &data[inst->byteCounter], 1u);
-                            inst->lastNewlineIdx = inst->inputCount;
-                        }
-                        else
-                        {
-                            inst->hereLength    = 0u;
-
-                            /** -# store the current command and reset the history buffer */
-                            inst->dirty = true;
-                            history_storeCmd(inst);
-                            history_reset(inst);
-                        }
-                    }
-                    else
-                    {
-                        /** -# store the current command and reset the history buffer */
-                        inst->dirty = true;
-                        history_storeCmd(inst);
-                        history_reset(inst);
-                    }
-                }
-                else
-                {
-                    tempString  = &inst->buffer[inst->lastNewlineIdx];
-                    cmdLen      = inst->inputCount - inst->lastNewlineIdx;
-
-                    /** -# skip newline characters before comparison */
-                    while(('\n' == *tempString) || ('\r' == *tempString))
-                    {
-                        tempString ++;
-                        cmdLen --;
-                    }
-
-                    if(     (inst->hereLength == cmdLen)
-                        &&  (0  == strncmp( &inst->buffer[inst->hereDelimiterIdx],
-                                            tempString,
-                                            inst->hereLength)))
-                    {
-                        /** -# store the current command and reset the history buffer */
-                        inst->dirty = true;
-                        history_storeCmd(inst);
-                        history_reset(inst);
-
-                        /** -# process heredoc as stdin like input  */
-                        /** -# find start of heredoc data           */
-                        inst->stdinIdx = inst->hereDelimiterIdx + inst->hereLength;
-                        while(      ('\n' == inst->buffer[inst->stdinIdx])
-                                ||  ('\r' == inst->buffer[inst->stdinIdx]))
-                        {
-                            inst->stdinIdx ++;
-                        }
-                        /** -# calculate length and terminate stdin string */
-                        if(inst->stdinIdx < inst->lastNewlineIdx)
-                        {
-                            inst->stdinLength = inst->lastNewlineIdx - inst->stdinIdx;
-                            inst->buffer[inst->lastNewlineIdx] = '\0';
-                        }
-                        else
-                        {
-                            inst->stdinLength = 0u;
-                        }
-
-                        /** -# calculate length and terminate argument string */
-                        inst->inputCount = inst->hereStartIdx;
-                        inst->buffer[inst->hereStartIdx] = '\0';
-
-                        /** -# terminate heredoc session */
-                        inst->hereLength = 0u;
-                    }
-                    else
-                    {
-                        /** -# the party goes on - just print the delimiter and store the position */
-                        inst->lastNewlineIdx = inst->inputCount;
-                        utils_insertChars(inst, &data[inst->byteCounter], 1u);
-                    }
-                }
-
-                if(0u == inst->hereLength)
-                {
-                    cmd = inst->cmdList;
-
-                    /** -# determine the cmd len (chars until first space or \0 is found */
-                    cmdLen = 0u;
-                    while(      (cmdLen <   inst->inputCount)
-                            &&  (' '    !=  inst->buffer[cmdLen])
-                            &&  ('\r'   !=  inst->buffer[cmdLen])
-                            &&  ('\n'   !=  inst->buffer[cmdLen])
-                            &&  ('\0'   !=  inst->buffer[cmdLen]))
-                    {
-                        cmdLen ++;
-                    }
-
-                    /** -# search for a matching command */
-                    while (NULL != cmd)
-                    {
-                        /** -# compare command and alias string and length */
-                        if (    ((cmdLen    == strlen(cmd->cmd))
-                                && (0       == strncmp(inst->buffer, cmd->cmd, cmdLen)))
-                            || ((NULL != cmd->cmdAlias)
-                                && (cmdLen  == strlen(cmd->cmdAlias))
-                                && (0       == strncmp(inst->buffer, cmd->cmdAlias, cmdLen))))
-                        {
-                            utils_writeEcho(inst, "\r\n", 2u);
-                            shellmatta_opt_init(inst, cmdLen + 1u);
-                            cmdExecuted = 1u;
-                            cmdRet = cmd->cmdFct(handle, inst->buffer, inst->inputCount);
-
-                            switch(cmdRet)
-                            {
-                                case SHELLMATTA_CONTINUE:
-                                    /** -# initialize stdin buffer and continuous cmd */
-                                    inst->stdinIdx      = inst->inputCount + 1u;
-                                    inst->stdinLength   = 0u;
-                                    inst->continuousCmd = cmd;
-                                    ret                 = cmdRet;
-                                    break;
-                                
-                                case SHELLMATTA_BUSY:
-                                    inst->busyCmd   = cmd;
-                                    ret             = cmdRet;
-                                    break;
-
-                                default:
-                                    /* nothing to do - everything ok */
-                                    break;
-                            }
-                            cmd = NULL;
-                        }
-                        else
-                        {
-                            cmd = cmd->next;
-                        }
-                    }
-
-                    if ((0u == cmdExecuted) && (inst->inputCount > 0))
-                    {
-                        inst->write("\r\nCommand: ", 11u);
-                        inst->write(inst->buffer, inst->inputCount);
-                        inst->write(" not found", 10u);
-                    }
-
-                    /** -# terminate this session if no continuous mode is requested */
-                    if(     (NULL == inst->continuousCmd)
-                        &&  (NULL == inst->busyCmd))
-                    {
-                        utils_terminateInput(inst);
-                    }
-                }
-            }
-            /** -# ignore newline as first character (to be compatible to
-             * terminals sending newline after return */
-            else if((0u == inst->inputCount) && ('\n' == data[inst->byteCounter]))
-            {
-                /* do nothing */
-            }
-            /** -# check for tabulator key - auto complete */
-            else if('\t' == data[inst->byteCounter])
-            {
-                inst->dirty = true;
-                autocomplete_run(inst);
-            }
-            /** -# check for cancel -
-             *      terminate current input and print prompt again */
-            else if('\x03' == data[inst->byteCounter])
-            {
-                inst->dirty = false;
-                history_reset(inst);
+                utils_writeEcho(inst, "crc error\r\n", 11);
                 utils_terminateInput(inst);
                 utils_terminateInput(inst);
             }
             }
-            /** -# check for backspace */
-            else if(    ('\b'   == data[inst->byteCounter])
-                    ||  ('\x7f' == data[inst->byteCounter]))
-            {
-                inst->dirty = true;
-                utils_removeChars(inst, 1u, true);
-            }
-            /** -# check for start of escape sequence */
-            else if('\x1b' == data[inst->byteCounter])
-            {
-                inst->escapeCounter = 1u;
-            }
             else
             else
             {
             {
-                inst->dirty = true;
-                utils_insertChars(inst, &data[inst->byteCounter], 1u);
+                /* nothing to do - transport layer busy */
             }
             }
+        }
 
 
-            /** -# reset tab counter on not a tab */
-            if ('\t' != data[inst->byteCounter])
-            {
-                inst->tabCounter = 0u;
-            }
+        /*! -# call the internal processing at least once - for continued and busy commands */
+        if (true != processingDone)
+        {
+            ret = shellmatta_processDataInt(handle, tmpData, 0);
         }
         }
 
 
-        /** -# initialize the byte buffer if processing of the input is finished */
-        if(ret != SHELLMATTA_BUSY)
+        if (false == inst->transportLayer.disableAutoFlush)
         {
         {
-            inst->byteCounter = 0u;
+            (void)shellmatta_transport_flush(handle);
         }
         }
+#else
+        ret = shellmatta_processDataInt(handle, data, size);
+#endif
     }
     }
     else
     else
     {
     {
         ret = SHELLMATTA_USE_FAULT;
         ret = SHELLMATTA_USE_FAULT;
     }
     }
+
     return ret;
     return ret;
 }
 }
 
 
@@ -730,7 +860,7 @@ shellmatta_retCode_t shellmatta_write(  shellmatta_handle_t handle,
         &&  (SHELLMATTA_MAGIC   == inst->magic))
         &&  (SHELLMATTA_MAGIC   == inst->magic))
     {
     {
         /** -# pass the data to the write function of the instance  */
         /** -# pass the data to the write function of the instance  */
-        ret = inst->write(data, length);
+        ret = SHELLMATTA_WRITE(data, length);
     }
     }
 
 
     return ret;
     return ret;
@@ -806,7 +936,7 @@ shellmatta_retCode_t shellmatta_printf( shellmatta_handle_t handle,
         }
         }
         else
         else
         {
         {
-            inst->write(outputBuffer, length);
+            SHELLMATTA_WRITE(outputBuffer, length);
         }
         }
     }
     }
     else
     else

+ 634 - 0
src/shellmatta_auth.c

@@ -0,0 +1,634 @@
+/*
+ * Copyright (c) 2022 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    shellmatta_auth.c
+ * @brief   simple authentication method
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+/**
+ * @addtogroup shellmatta_auth
+ * @{
+ */
+
+#include "shellmatta.h"
+#include "shellmatta_auth.h"
+#include "shellmatta_history.h"
+#include "shellmatta_utils.h"
+#include "shellmatta_escape.h"
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+
+/**
+ * @brief       wraps the input to the login command and mimics the editability of the standard shell input
+ * @param[in, out]  inst    shellmatta instance to work with
+ * @param[in]       data    one byte of data the user put in
+ * @param[in]       hide    true: do not print anything tho the output (e.g. password input)
+ * @return      #SHELLMATTA_BUSY    => reading in data
+ *              #SHELLMATTA_OK      => delimiter (e.g. enter) detected
+ */
+static shellmatta_retCode_t inputWrapper(shellmatta_instance_t *inst, char data, bool hide)
+{
+    shellmatta_retCode_t ret = SHELLMATTA_BUSY;
+    bool echoEnabledBackup = inst->echoEnabled;
+
+    /** -# disable echo when hidden */
+    inst->echoEnabled &= !hide;
+
+    /** -# handle escape sequences */
+    if(inst->escapeCounter != 0u)
+    {
+        escape_handleSequence(inst, data);
+    }
+    else if (inst->delimiter == data)
+    {
+        ret = SHELLMATTA_OK;
+    }
+        /** -# ignore newline as first character (to be compatible to
+     * terminals sending newline after return */
+    else if((0u == inst->inputCount) && ('\n' == data))
+    {
+        /* do nothing */
+    }
+    /** -# check for backspace */
+    else if(    ('\b'   == data)
+            ||  ('\x7f' == data))
+    {
+        utils_removeChars(inst, 1u, true);
+    }
+    /** -# check for start of escape sequence */
+    else if('\x1b' == data)
+    {
+        inst->escapeCounter = 1u;
+    }
+    else
+    {
+        utils_insertChars(inst, &data, 1u);
+    }
+
+    inst->echoEnabled = echoEnabledBackup;
+
+    return ret;
+}
+
+/**
+ * @brief       searches the user list for a passed username and returns the userId
+ * @param[in]   inst        shellmatta instance to work with
+ * @param[in]   username    username to search for - pointer to string
+ * @return      userId or 0 if user was not found
+ */
+static uint32_t getUserIdFromName(shellmatta_instance_t *inst, const char *username)
+{
+    uint32_t    userId  = 0u;
+    uint32_t    i;
+
+    for (i = 0u; i < inst->userListLength; i++)
+    {
+        /** -# search for user */
+        if (0 == strcmp(inst->userList[i].username, username))
+        {
+            userId = inst->userList[i].userId;
+            break;
+        }
+    }
+
+    return userId;
+}
+
+/**
+ * @brief       checks the passed password and logs the user into the instance on success
+ * @param[in, out]  handle      shellmatta handle to work with
+ * @param[in]       userId      userId to check the password for
+ * @param[in]       password    password to check for - pointer to string
+ */
+static void checkPassword(shellmatta_handle_t handle, uint32_t userId, char *password)
+{
+    shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
+    uint32_t                approvedUserId  = 0u;
+    uint32_t                i;
+
+    if (NULL != inst->checkFct)
+    {
+        approvedUserId = inst->checkFct(userId, password) == SHELLMATTA_OK ? userId : 0u;
+    }
+    else
+    {
+        for (i = 0u; i < inst->userListLength; i++)
+        {
+            /** -# search for user */
+            if (inst->userList[i].userId == userId)
+            {
+                /** -# check password */
+                if (0 == strcmp(inst->userList[i].password, password))
+                {
+                    approvedUserId = inst->userList[i].userId;
+                }
+                break;
+            }
+        }
+    }
+
+    /** -# call log function - only for unsuccessful logins - successful logins are handled by #shellmatta_auth_login */
+    if ((inst->logFct) && (0u == approvedUserId))
+    {
+        inst->logFct(userId, SHELLMATTA_AUTH_EVENT_LOGIN_FAILED);
+    }
+
+    /** -# print login result */
+    if (0 == approvedUserId)
+    {
+        SHELLMATTA_WRITE("username or password is wrong\r\n", 31);
+    }
+    else
+    {
+        SHELLMATTA_WRITE("login successful\r\n", 18);
+        (void)shellmatta_auth_login(handle, approvedUserId);
+    }
+}
+
+/**
+ * @brief       handles the login based on username and password
+ * @param[in]   handle      handle  shellmatta instance handle
+ * @param[in]   arguments   arguments containing a command name or alias
+ * @param[in]   length      length of the arguments
+ * @return      #SHELLMATTA_OK
+ *              #SHELLMATTA_CONTINUE (waiting for input)
+  */
+static shellmatta_retCode_t loginCmdFct(const shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
+    shellmatta_retCode_t    ret     = SHELLMATTA_OK;
+    char option;
+    char *argument;
+    uint32_t argLen;
+    char *username = NULL;
+    char *password = NULL;
+
+    static const shellmatta_opt_long_t options[] =
+    {
+        {"username" , 'u',  SHELLMATTA_OPT_ARG_REQUIRED},
+        {"password" , 'p',  SHELLMATTA_OPT_ARG_REQUIRED},
+        {NULL       , '\0', SHELLMATTA_OPT_ARG_NONE}
+    };
+
+    /** -# handle the login state machine */
+    switch(inst->loginState)
+    {
+        /** -# init the login procedure - use directly passed credentials if any */
+        case SHELLMATTA_AUTH_IDLE:
+            ret = shellmatta_opt_long(handle, options, &option, &argument, &argLen);
+            while(SHELLMATTA_OK == ret)
+            {
+                switch(option)
+                {
+                    case 'u':
+                        if(NULL != argument)
+                        {
+                            username = argument;
+                        }
+                        break;
+                    case 'p':
+                        if(NULL != argument)
+                        {
+                            password = argument;
+                        }
+                        break;
+                    default:
+                        SHELLMATTA_WRITE("Unknown option\r\n", 16);
+                        return SHELLMATTA_USE_FAULT;
+                        break;
+                }
+                ret = shellmatta_opt_long(handle, options, &option, &argument, &argLen);
+            }
+
+            if ((NULL != username) && (NULL != password))
+            {
+                inst->tmpUserId = getUserIdFromName(inst, username);
+                checkPassword(handle, inst->tmpUserId, password);
+            }
+            /** -# user passed username and start interactive password input */
+            else if(NULL != username)
+            {
+                inst->inputCount = 0u;
+                inst->cursor = 0u;
+
+                inst->tmpUserId = getUserIdFromName(handle, username);
+                SHELLMATTA_WRITE("\r\nenter password:\r\n", 19);
+                inst->loginState = SHELLMATTA_AUTH_PASSWORD;
+                ret = SHELLMATTA_CONTINUE;
+            }
+            else if (NULL != password)
+            {
+                SHELLMATTA_WRITE("Missing username\r\n", 18);
+                ret = SHELLMATTA_USE_FAULT;
+            }
+            else
+            {
+                /** -# no credentials are passed with the command - start the interactive input */
+                inst->inputCount = 0u;
+                inst->cursor = 0u;
+                SHELLMATTA_WRITE("enter username:\r\n", 17);
+                inst->loginState = SHELLMATTA_AUTH_USERNAME;
+                ret = SHELLMATTA_CONTINUE;
+            }
+            break;
+
+        case SHELLMATTA_AUTH_USERNAME:
+            /** -# read in password characters to the instance buffer */
+            (void)shellmatta_read(handle, &argument, &argLen);
+            if ((1 == argLen) && (SHELLMATTA_OK == inputWrapper(inst, argument[0], false)))
+            {
+                /** -# store user id */
+                inst->buffer[inst->inputCount] = '\0';
+                inst->tmpUserId = getUserIdFromName(handle, inst->buffer);
+
+                /** -# reinitialize input */
+                inst->inputCount = 0u;
+                inst->cursor = 0u;
+
+                SHELLMATTA_WRITE("\r\nenter password:\r\n", 19);
+                inst->loginState = SHELLMATTA_AUTH_PASSWORD;
+            }
+            ret = SHELLMATTA_CONTINUE;
+            break;
+
+        case SHELLMATTA_AUTH_PASSWORD:
+            /** -# read in password characters to the instance buffer - hiding output */
+            (void)shellmatta_read(handle, &argument, &argLen);
+            if ((1 == argLen) && (SHELLMATTA_OK == inputWrapper(inst, argument[0], true)))
+            {
+                /** -# check input username and password */
+                inst->buffer[inst->inputCount] = '\0';
+                SHELLMATTA_WRITE("\r\n", 2);
+                checkPassword(handle, inst->tmpUserId, inst->buffer);
+                inst->loginState = SHELLMATTA_AUTH_IDLE;
+            }
+            else
+            {
+                ret = SHELLMATTA_CONTINUE;
+            }
+            break;
+        default:
+            break;
+    }
+
+    (void)arguments;
+    (void)length;
+
+    return ret;
+}
+const shellmatta_cmd_t shellmatta_auth_loginCmd = {"login"
+                                                 , "li"
+                                                 , "Login command"
+                                                 , "login interactively or use -u <username> -p <password>"
+                                                 , loginCmdFct
+                                                 , NULL
+                                                 , NULL};
+
+
+/**
+ * @brief       handles the logout of the current session
+ * @param[in]   handle      handle  shellmatta instance handle
+ * @param[in]   arguments   arguments containing a command name or alias
+ * @param[in]   length      length of the arguments
+ * @return      #SHELLMATTA_OK
+ */
+static shellmatta_retCode_t logoutCmdFct(const shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
+
+    /** -# log the logout event */
+    if (inst->logFct)
+    {
+        inst->logFct(inst->userId, SHELLMATTA_AUTH_EVENT_LOGOUT);
+    }
+
+    shellmatta_auth_logout(handle);
+    SHELLMATTA_WRITE("good bye\r\n", 10);
+
+    (void)arguments;
+    (void)length;
+
+    return SHELLMATTA_OK;
+}
+const shellmatta_cmd_t shellmatta_auth_logoutCmd = {"logout"
+                                                  , "lo"
+                                                  , "Logout command"
+                                                  , "logs out the currently logged in user"
+                                                  , logoutCmdFct
+                                                  , NULL
+                                                  , NULL};
+
+/**
+ * @brief       initializes the shellmatta authentication system
+ * @param[in]   handle          shellmatta instance handle
+ * @param[in]   userList        list to specify all users pointer to a list of type shellmatta_user_t
+ * @param[in]   userListLength  length of the user list
+ * @param[in]   permList        list to specify the command permissions pointer to a list of type shellmatta_perm_t
+ * @param[in]   permListLength  length of the perm list
+ * @param[in]   customLogin     true: no internal login command is provided
+ * @param[in]   checkFct        pointer to custom credential check function of type shellmatta_check_t
+ * @param[in]   logFct          pointer to login log function of type shellmatta_auth_log_t
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_DUPLICATE (login/logout commands already existed)
+ *                          #SHELLMATTA_USE_FAULT (param err)
+ */
+shellmatta_retCode_t shellmatta_auth_init(shellmatta_handle_t       handle,
+                                          shellmatta_auth_user_t    *userList,
+                                          uint32_t                  userListLength,
+                                          shellmatta_auth_perm_t    *permList,
+                                          uint32_t                  permListLength,
+                                          bool                      customLogin,
+                                          shellmatta_auth_check_t   checkFct,
+                                          shellmatta_auth_log_t     logFct)
+{
+    shellmatta_retCode_t    ret     = SHELLMATTA_OK;
+    shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
+    uint32_t                i;
+    shellmatta_cmd_t        *cmd;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic)
+        &&  (NULL               != userList)
+        &&  (0                  != userListLength)
+        &&  (NULL               != permList)
+        &&  (0                  != permListLength))
+    {
+        inst->loginState = SHELLMATTA_AUTH_IDLE;
+        inst->userId = 0u;
+        inst->tmpUserId = 0u;
+        inst->userPointer = NULL;
+        inst->userList = userList;
+        inst->userListLength = userListLength;
+        inst->permList = permList;
+        inst->permListLength = permListLength;
+        SHELLMATTA_RET(ret, shellmatta_addCmd(handle, &inst->logoutCmd));
+        if(!customLogin)
+        {
+            SHELLMATTA_RET(ret, shellmatta_addCmd(handle, &inst->loginCmd));
+        }
+        inst->checkFct  = checkFct;
+        inst->logFct    = logFct;
+
+        /** -# assign links to the perm list to each command */
+        cmd = inst->cmdList;
+
+        /** -# search for a matching command */
+        while (NULL != cmd)
+        {
+            /** -# Search for command in perm list */
+            for (i = 0u; i < permListLength; i++)
+            {
+                if (0 == strcmp(cmd->cmd, permList[i].cmd))
+                {
+                    cmd->authLink = &permList[i];
+                    break;
+                }
+            }
+
+            cmd = cmd->next;
+        }
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+
+    return ret;
+}
+
+/**
+ * @brief       logs a passed user id into the shellmatta instance
+ * @param[in]   handle      shellmatta instance handle
+ * @param[in]   userId      userId to login
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_ERROR (user does not exist)
+ *                          #SHELLMATTA_USE_FAULT (param err)
+ */
+shellmatta_retCode_t shellmatta_auth_login(shellmatta_handle_t handle, uint32_t userId)
+{
+    shellmatta_retCode_t    ret     = SHELLMATTA_OK;
+    shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
+    uint32_t                i;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic))
+    {
+        /** -# log passed user id into the instance */
+        inst->userId = userId;
+        inst->userPointer = NULL;
+        if (0 != userId)
+        {
+            /** -# set user pointer to print the name in the prompt */
+            for (i = 0u; i < inst->userListLength; i++)
+            {
+                if (inst->userList[i].userId == userId)
+                {
+                    inst->userPointer = &inst->userList[i];
+                    break;
+                }
+            }
+        }
+
+        /** -# check if the user did exist */
+        if (NULL == inst->userPointer)
+        {
+            ret = SHELLMATTA_ERROR;
+        }
+
+        /** -# call log function */
+        if (inst->logFct)
+        {
+            inst->logFct(userId,
+                         NULL != inst->userPointer ?                \
+                                 SHELLMATTA_AUTH_EVENT_LOGIN :      \
+                                 SHELLMATTA_AUTH_EVENT_LOGIN_FAILED);
+        }
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+    return ret;
+}
+
+/**
+ * @brief       logs the currently logged in user out of the instance
+ * @param[in]   handle      shellmatta instance handle
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_USE_FAULT (param err)
+ */
+shellmatta_retCode_t shellmatta_auth_logout(shellmatta_handle_t handle)
+{
+    shellmatta_retCode_t    ret     = SHELLMATTA_OK;
+    shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic))
+    {
+        inst->userId = 0u;
+        inst->userPointer = NULL;
+
+        /** -# clear the history buffer */
+        (void)history_clear(handle);
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+    return ret;
+}
+
+/**
+ * @brief       returns the currently logged in userId
+ * @param[in]   handle      shellmatta instance handle
+ * @return      userId or 0 if no user is logged in
+ */
+uint32_t shellmatta_auth_getLoggedInUserId(shellmatta_handle_t handle)
+{
+    uint32_t                    userId  = 0u;
+    const shellmatta_instance_t *inst   = (shellmatta_instance_t*)handle;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic))
+    {
+        userId = inst->userId;
+    }
+
+    return userId;
+}
+
+/**
+ * @brief           copies the username into the passed buffer
+ * @param[in]       handle      shellmatta instance handle
+ * @param[out]      data        pointer to buffer to write the username into
+ * @param[in, out]  length      in - length of the passed buffer, out - actual stringlength of the username
+ * @return          errorcode   #SHELLMATTA_OK
+ *                              #SHELLMATTA_ERROR (passed buffer is too small)
+ *                              #SHELLMATTA_USE_FAULT (param err)
+ */
+shellmatta_retCode_t shellmatta_auth_getLoggedInUserName(shellmatta_handle_t handle, char *data, uint32_t *length)
+{
+    shellmatta_retCode_t    ret     = SHELLMATTA_OK;
+    shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
+    size_t                  userNameLength;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic)
+        &&  (data               != NULL)
+        &&  (length             != NULL))
+    {
+        if (NULL != inst->userPointer)
+        {
+            userNameLength = strlen(inst->userPointer->username);
+
+            if (userNameLength < *length)
+            {
+                (void)strcpy(data, inst->userPointer->username);
+                *length = userNameLength;
+            }
+            else
+            {
+                ret = SHELLMATTA_ERROR;
+            }
+        }
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+    return ret;
+}
+
+/**
+ * @brief           changes the password of the passed username
+ * @param[in]       handle      shellmatta instance handle
+ * @param[in]       username    username to change the password of
+ * @param[in]       password    password to be set for the user
+ * @return          errorcode   #SHELLMATTA_OK
+ *                              #SHELLMATTA_ERROR (user not found)
+ *                              #SHELLMATTA_USE_FAULT (param err)
+ */
+shellmatta_retCode_t shellmatta_auth_chpasswd(shellmatta_handle_t handle, const char *username, const char *password)
+{
+    shellmatta_retCode_t    ret     = SHELLMATTA_ERROR;
+    shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
+    uint32_t                i;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic)
+        &&  (username           != NULL)
+        &&  (password           != NULL))
+    {
+        for (i = 0u; i < inst->userListLength; i++)
+        {
+            /** -# search for user */
+            if (0 == strcmp(inst->userList[i].username, username))
+            {
+                inst->userList[i].password = password;
+                return SHELLMATTA_OK;
+            }
+        }
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+    return ret;
+}
+
+/**
+ * @brief       checks if a command is accessible by the logged in user
+ * @param[in]   inst    shellmatta instance to work with
+ * @param[in]   cmd     command to check permissions for
+ * @return      errorcode   #SHELLMATTA_OK      => permitted
+ *                          #SHELLMATTA_ERROR   => not permitted
+ */
+shellmatta_retCode_t shellmatta_auth_is_cmd_permitted(const shellmatta_instance_t *inst, shellmatta_cmd_t *cmd)
+{
+    shellmatta_retCode_t    ret     = SHELLMATTA_ERROR;
+    shellmatta_auth_perm_t  *permList;
+    uint32_t                i;
+
+    /**! -# commands without an authentication set are public */
+    if (NULL == cmd->authLink)
+    {
+        ret = SHELLMATTA_OK;
+    }
+    /**! -# allow superuser to access all commands */
+    else if ((NULL != inst->userPointer) && (true == inst->userPointer->superuser))
+    {
+        ret = SHELLMATTA_OK;
+    }
+    else
+    {
+        permList = cmd->authLink;
+        for (i = 0u; i < permList->userIdslength; i++)
+        {
+            if (inst->userId == permList->userIds[i])
+            {
+                ret = SHELLMATTA_OK;
+                break;
+            }
+        }
+    }
+
+    return ret;
+}
+
+/**
+ * @}
+ */

+ 38 - 0
src/shellmatta_auth.h

@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    shellmatta_auth.c
+ * @brief   simple authentication method
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+/**
+ * @addtogroup shellmatta_auth
+ * @{
+ */
+#ifndef _SHELLMATTA_AUTH_H_
+#define _SHELLMATTA_AUTH_H_
+
+#include "shellmatta.h"
+#include <stdint.h>
+
+/** @brief login command to enable a user to login */
+extern const shellmatta_cmd_t shellmatta_auth_loginCmd;
+
+/** @brief logout command */
+extern const shellmatta_cmd_t shellmatta_auth_logoutCmd;
+
+
+shellmatta_retCode_t shellmatta_auth_is_cmd_permitted(const shellmatta_instance_t   *inst,
+                                                      shellmatta_cmd_t              *cmd);
+
+#endif
+
+/** @} */
+

+ 31 - 7
src/shellmatta_autocomplete.c

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -20,6 +20,9 @@
 #include "shellmatta.h"
 #include "shellmatta.h"
 #include "shellmatta_autocomplete.h"
 #include "shellmatta_autocomplete.h"
 #include "shellmatta_utils.h"
 #include "shellmatta_utils.h"
+#ifdef SHELLMATTA_AUTHENTICATION
+#include "shellmatta_auth.h"
+#endif
 #include <stdint.h>
 #include <stdint.h>
 #include <string.h>
 #include <string.h>
 
 
@@ -50,6 +53,13 @@ void autocomplete_run(shellmatta_instance_t *inst)
         /** -# loop through all registered commands */
         /** -# loop through all registered commands */
         while (NULL != cmd)
         while (NULL != cmd)
         {
         {
+#ifdef SHELLMATTA_AUTHENTICATION
+            if (SHELLMATTA_OK != shellmatta_auth_is_cmd_permitted(inst, cmd))
+            {
+                cmd = cmd->next;
+                continue;
+            }
+#endif
             /** -# check if command matches the input */
             /** -# check if command matches the input */
             if(    (strlen(cmd->cmd) >= inst->cursor)
             if(    (strlen(cmd->cmd) >= inst->cursor)
                 && (0 == strncmp(cmd->cmd, inst->buffer, inst->cursor)))
                 && (0 == strncmp(cmd->cmd, inst->buffer, inst->cursor)))
@@ -57,11 +67,11 @@ void autocomplete_run(shellmatta_instance_t *inst)
                 /** -# add newline on first find */
                 /** -# add newline on first find */
                 if(0u == printedLen)
                 if(0u == printedLen)
                 {
                 {
-                    inst->write("\r\n", 2u);
+                    SHELLMATTA_WRITE("\r\n", 2u);
                 }
                 }
-                inst->write(cmd->cmd, strlen(cmd->cmd));
+                SHELLMATTA_WRITE(cmd->cmd, strlen(cmd->cmd));
                 printedLen += strlen(cmd->cmd);
                 printedLen += strlen(cmd->cmd);
-                inst->write("    ", 4u);
+                SHELLMATTA_WRITE("    ", 4u);
                 printedLen += 4u;
                 printedLen += 4u;
             }
             }
             /** -# check if command alias matches the input */
             /** -# check if command alias matches the input */
@@ -72,11 +82,11 @@ void autocomplete_run(shellmatta_instance_t *inst)
                 /** -# add newline on first find */
                 /** -# add newline on first find */
                 if(0u == printedLen)
                 if(0u == printedLen)
                 {
                 {
-                    inst->write("\r\n", 2u);
+                    SHELLMATTA_WRITE("\r\n", 2u);
                 }
                 }
-                inst->write(cmd->cmdAlias, strlen(cmd->cmdAlias));
+                SHELLMATTA_WRITE(cmd->cmdAlias, strlen(cmd->cmdAlias));
                 printedLen += strlen(cmd->cmdAlias);
                 printedLen += strlen(cmd->cmdAlias);
-                inst->write("    ", 4u);
+                SHELLMATTA_WRITE("    ", 4u);
                 printedLen += 4u;
                 printedLen += 4u;
             }
             }
             cmd = cmd->next;
             cmd = cmd->next;
@@ -85,6 +95,13 @@ void autocomplete_run(shellmatta_instance_t *inst)
         if(printedLen > 0u)
         if(printedLen > 0u)
         {
         {
             utils_writeEcho(inst, "\r\n", 2u);
             utils_writeEcho(inst, "\r\n", 2u);
+#ifdef SHELLMATTA_AUTHENTICATION
+            if (NULL != inst->userPointer)
+            {
+                utils_writeEcho(inst, inst->userPointer->username, strlen(inst->userPointer->username));
+                utils_writeEcho(inst, "@", 1);
+            }
+#endif
             utils_writeEcho(inst, inst->prompt, strlen(inst->prompt));
             utils_writeEcho(inst, inst->prompt, strlen(inst->prompt));
             utils_writeEcho(inst, inst->buffer, inst->inputCount);
             utils_writeEcho(inst, inst->buffer, inst->inputCount);
             tempCursor = inst->cursor;
             tempCursor = inst->cursor;
@@ -98,6 +115,13 @@ void autocomplete_run(shellmatta_instance_t *inst)
         /** -# loop through all registered commands */
         /** -# loop through all registered commands */
         while (NULL != cmd)
         while (NULL != cmd)
         {
         {
+#ifdef SHELLMATTA_AUTHENTICATION
+            if (SHELLMATTA_OK != shellmatta_auth_is_cmd_permitted(inst, cmd))
+            {
+                cmd = cmd->next;
+                continue;
+            }
+#endif
             /** -# check if command matches the input */
             /** -# check if command matches the input */
             if(    (strlen(cmd->cmd) >= inst->cursor)
             if(    (strlen(cmd->cmd) >= inst->cursor)
                 && (0 == strncmp(cmd->cmd, inst->buffer, inst->cursor)))
                 && (0 == strncmp(cmd->cmd, inst->buffer, inst->cursor)))

+ 1 - 1
src/shellmatta_autocomplete.h

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this

+ 235 - 0
src/shellmatta_crc.c

@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    shellmatta_crc.c
+ * @brief   cyclic redundancy check functions of shellmatta
+ * @author  Simon Fischer <fischer.simon.1991@gmail.com>
+ */
+
+#include "shellmatta_crc.h"
+
+#ifdef SHELLMATTA_TRANSPORT
+
+#ifndef SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP
+/** \brief CRC Lookup table for 0x04c11db7 reflected */
+uint32_t crc32Table[] = {
+    0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
+    0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
+    0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
+    0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
+    0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
+    0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
+    0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
+    0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
+    0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
+    0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
+    0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
+    0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
+    0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
+    0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
+    0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
+    0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
+    0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
+    0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
+    0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
+    0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
+    0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
+    0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
+    0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
+    0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
+    0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
+    0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
+    0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
+    0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
+    0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
+    0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
+    0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
+    0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+};
+
+/**
+ * @brief       Computes the crc32-checksum of a buffer. O(n)
+ * @param[in]   data            pointer to data buffer
+ * @param[in]   size            amount of bytes to be processed
+ * @param[in]   lookupTable     pointer to uint32_t lookup table for crc computation
+ * @return      crc             calculated crc32 value
+ */
+static uint32_t crc32Fast(const char* data, uint32_t size, const uint32_t* lookupTable)
+{
+    uint32_t i;
+    uint32_t crcTemp = 0xffffffffu;
+
+    for (i = 0u; i < size; i++)
+    {
+        uint8_t index = data[i] ^ (crcTemp & 0xffu);
+        crcTemp = lookupTable[index] ^ (crcTemp >> 8u);
+    }
+
+    return ~crcTemp;
+}
+
+#else
+/**
+ * @brief       Reverses bits of a value.
+ * @param[in]   x       input value
+ * @param[in]   size    amount of bits used in value
+ * @return      reversed data
+ */
+static uint32_t reverse(uint32_t x, uint32_t bits)
+{
+    x = ((x & 0x55555555u) << 1u)  | ((x & 0xAAAAAAAAu) >> 1u);
+    x = ((x & 0x33333333u) << 2u)  | ((x & 0xCCCCCCCCu) >> 2u);
+    x = ((x & 0x0F0F0F0Fu) << 4u)  | ((x & 0xF0F0F0F0u) >> 4u);
+    x = ((x & 0x00FF00FFu) << 8u)  | ((x & 0xFF00FF00u) >> 8u);
+    x = ((x & 0x0000FFFFu) << 16u) | ((x & 0xFFFF0000u) >> 16u);
+    return x >> (32u - bits);
+}
+
+/**
+ * @brief       Computes the crc32-checksum of a buffer. O(8n)
+ * @param[in]   data            pointer to data buffer
+ * @param[in]   size            amount of bytes to be processed
+ * @return      crc             calculated crc32 value
+ */
+static uint32_t crc32Slow(const char* data, uint32_t size)
+{
+    uint32_t i;
+    uint8_t j;
+    uint32_t polynom = reverse(CRC32_POLYNOM, 32u);
+    uint8_t pivotByte;
+
+    /* start with 0xffffffffu */
+    uint32_t crcTemp = 0xffffffffu;
+
+    for (i = 0u; i < size; i++)
+    {
+        pivotByte = data[i];
+        for (j = 0u; j < BITS_PER_BYTE; j++)
+        {
+            if ((crcTemp & 1u) != (pivotByte & 1u))
+            {
+                crcTemp = (crcTemp >> 1u) ^ polynom;
+            }
+            else
+            {
+                crcTemp >>= 1u;
+            }
+            pivotByte >>= 1u;
+        }
+    }
+
+    /* final xor */
+    crcTemp ^= 0xffffffffu;
+
+    return crcTemp;
+}
+
+#endif /* SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP */
+
+/**
+ * @brief       Computes the crc32-checksum of a buffer.
+ * @param[in]   data            pointer to data buffer
+ * @param[in]   size            amount of bytes to be processed
+ * @return      crc             calculated crc32 value
+ */
+uint32_t crc32Calc(const char* data, uint32_t size)
+{
+    #ifdef  SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP
+        return crc32Slow(data, size);
+    #else
+        return crc32Fast(data, size, crc32Table);
+    #endif
+}
+
+#endif /* SHELLMATTA_TRANSPORT */
+
+#ifndef SHELLMATTA_YMODEM_CRC_NO_LOOKUP
+/** \brief CRC lookup table for 0x1021 non-reflected */
+uint16_t crc16Table[] = {
+    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
+    0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
+    0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
+    0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
+    0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
+    0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
+    0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
+    0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
+    0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
+    0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
+    0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+    0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
+    0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
+    0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
+    0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
+    0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
+};
+
+/**
+ * @brief       Computes the crc16-checksum of a buffer. O(n)
+ * @param[in]   data            pointer to data buffer
+ * @param[in]   size            amount of bytes to be processed
+ * @param[in]   lookupTable     pointer to uint16_t lookup table for crc computation
+ * @return      crc             calculated crc16 value
+*/
+uint16_t crc16Fast(const char* data, uint32_t size, const uint16_t* lookupTable)
+{
+    uint32_t counter;
+    uint16_t crc = 0;
+    for(counter = 0; counter < size; counter++)
+        crc = (crc<<8) ^ lookupTable[((crc>>8) ^ *(char *)data++)&0x00FF];
+    return crc;
+}
+
+#else
+
+/**
+ * @brief       Computes the crc16-checksum of a buffer. O(8n)
+ * @param[in]   data                pointer to data buffer
+ * @param[in]   size                amount of bytes to be process
+ * @return      crc                 calculated crc16 value
+ * @note        This code is a modified version of the code presented at http://wiki.synchro.net/ref:xmodem
+*/
+uint16_t crc16Slow(const char* data, uint32_t size)
+{
+    uint16_t crc = 0;
+    uint32_t i, j;
+
+    for (i = 0; i < size; i++) {
+        crc = crc ^ (data[i] << BITS_PER_BYTE);
+        for (j = 0; j < BITS_PER_BYTE; ++j)
+        {
+            if (crc & 0x8000)
+            {
+                crc = (crc << 1) ^ CRC16_XMODEM_POLYNOM;
+            }
+            else
+            {
+                crc = crc << 1;
+            }
+        }
+    }
+    return (crc & 0xFFFF);
+}
+
+#endif /* SHELLMATTA_YMODEM_CRC_NO_LOOKUP */
+
+/**
+ * @brief       Computes the crc16-checksum of a buffer.
+ * @param[in]   data            pointer to data buffer
+ * @param[in]   size            amount of bytes to be processed
+ * @return      crc             calculated crc16 value
+*/
+uint16_t crc16Calc(const char* data, uint32_t size)
+{
+    #ifdef SHELLMATTA_YMODEM_CRC_NO_LOOKUP
+        return crc16Slow(data, size);
+    #else
+        return crc16Fast(data, size, crc16Table);
+    #endif /* SHELLMATTA_YMODEM_CRC_NO_LOOKUP */
+}

+ 27 - 0
src/shellmatta_crc.h

@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    shellmatta_crc.h
+ * @brief   cyclic redundancy check functions of shellmatta
+ * @author  Simon Fischer <fischer.simon.1991@gmail.com>
+ */
+
+#ifndef _SHELLMATTA_CRC_H_
+#define _SHELLMATTA_CRC_H_
+
+#include <stdint.h>
+
+#define CRC32_POLYNOM           0x04c11db7u     /**< crc-32 ethernet 802.3                          */
+#define CRC16_XMODEM_POLYNOM    0x1021u         /**< crc16 xmodem                                   */
+#define BITS_PER_BYTE           ((uint8_t)8)    /**< amount of bits per byte; to avoid magic number */
+
+uint32_t crc32Calc(const char* data, uint32_t size);
+uint16_t crc16Calc(const char* data, uint32_t size);
+
+#endif /* _SHELLMATTA_CRC_H_ */

+ 1 - 1
src/shellmatta_escape.c

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this

+ 1 - 1
src/shellmatta_escape.h

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this

+ 16 - 3
src/shellmatta_history.c

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -16,7 +16,7 @@
  * @addtogroup shellmatta_history
  * @addtogroup shellmatta_history
  * @{
  * @{
  */
  */
-
+#include <string.h>
 #include "shellmatta_history.h"
 #include "shellmatta_history.h"
 #include "shellmatta.h"
 #include "shellmatta.h"
 #include "shellmatta_utils.h"
 #include "shellmatta_utils.h"
@@ -89,7 +89,7 @@ static bool getHistoryByte(shellmatta_instance_t *inst, char *byte)
  * @param[in]   inst    pointer to a shellmatta instance
  * @param[in]   inst    pointer to a shellmatta instance
  * @return      true:   current command is identical to the last one in the history buffer
  * @return      true:   current command is identical to the last one in the history buffer
  */
  */
-static bool compareLastCommand(shellmatta_instance_t *inst)
+static bool compareLastCommand(const shellmatta_instance_t *inst)
 {
 {
     bool        ret = false;
     bool        ret = false;
     uint32_t    i;
     uint32_t    i;
@@ -286,6 +286,19 @@ void history_reset(shellmatta_instance_t *inst)
     inst->historyReadUp = true;
     inst->historyReadUp = true;
 }
 }
 
 
+/**
+ * @brief       clears the history buffer
+ * @param[in]   inst    pointer to a shellmatta instance
+ */
+void history_clear(shellmatta_instance_t *inst)
+{
+    inst->historyStart      = 0u;
+    inst->historyEnd        = 0u;
+    inst->historyRead       = 0u;
+    inst->historyReadUp     = true;
+    memset(inst->historyBuffer, 0, inst->historyBufferSize);
+}
+
 /**
 /**
  * @}
  * @}
  */
  */

+ 2 - 1
src/shellmatta_history.h

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -26,6 +26,7 @@ bool history_navigate(shellmatta_instance_t *inst, int32_t cnt);
 void history_storeCmd(shellmatta_instance_t *inst);
 void history_storeCmd(shellmatta_instance_t *inst);
 void history_restoreCmd(shellmatta_instance_t *inst);
 void history_restoreCmd(shellmatta_instance_t *inst);
 void history_reset(shellmatta_instance_t *inst);
 void history_reset(shellmatta_instance_t *inst);
+void history_clear(shellmatta_instance_t *inst);
 
 
 #endif
 #endif
 
 

+ 3 - 3
src/shellmatta_opt.c

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -129,7 +129,7 @@ static shellmatta_retCode_t parseShortOpt(  shellmatta_instance_t       *inst,
                                             shellmatta_opt_argtype_t    *argtype)
                                             shellmatta_opt_argtype_t    *argtype)
 {
 {
     shellmatta_retCode_t ret = SHELLMATTA_ERROR;
     shellmatta_retCode_t ret = SHELLMATTA_ERROR;
-    char *buffer = &inst->buffer[inst->optionParser.offset];
+    const char *buffer = &inst->buffer[inst->optionParser.offset];
     uint32_t i;
     uint32_t i;
 
 
     /** -# check for correct syntax */
     /** -# check for correct syntax */
@@ -191,7 +191,7 @@ static shellmatta_retCode_t parseLongOpt(   shellmatta_instance_t       *inst,
                                             shellmatta_opt_argtype_t    *argtype)
                                             shellmatta_opt_argtype_t    *argtype)
 {
 {
     shellmatta_retCode_t ret = SHELLMATTA_ERROR;
     shellmatta_retCode_t ret = SHELLMATTA_ERROR;
-    char *buffer = &inst->buffer[inst->optionParser.offset];
+    const char *buffer = &inst->buffer[inst->optionParser.offset];
     uint32_t i;
     uint32_t i;
 
 
     /** -# check for correct syntax for short options */
     /** -# check for correct syntax for short options */

+ 1 - 1
src/shellmatta_opt.h

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this

+ 527 - 0
src/shellmatta_transport.c

@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    shellmatta_transport.c
+ * @brief   transport layer functions of shellmatta
+ * @authors Simon Fischer <dev@s-fischer.net> Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+/**
+ * @addtogroup shellmatta_transport
+ * @{
+ */
+
+#ifdef SHELLMATTA_TRANSPORT
+#include "shellmatta_transport.h"
+#include "shellmatta.h"
+#include "shellmatta_crc.h"
+#include "shellmatta_utils.h"
+#include <string.h>
+
+
+/**
+ * @brief   create a secured packet and pass it to the shellmatta write function
+ *
+ * @note    The packetType, payloadLength and payload have to be preset!
+ *
+ * @param[in, out]  transportLayer  transport layer instance to work on
+ * @param[in]       packet          packet to send
+ * @return          errorcode
+ */
+static shellmatta_retCode_t shellmatta_transport_send(shellmatta_transport_layer_t *transportLayer,
+                                                      shellmatta_transport_packet_t *packet)
+{
+    uint32_t crc;
+    char *rawPacket = (char*)packet;
+    shellmatta_transport_header_t *header = &packet->header;
+
+    /** -# fill header */
+    header->startOfHeader   = SHELLMATTA_TRANSPORT_START_OF_HEADER;
+    header->protocolVersion = SHELLMATTA_TRANSPORT_PROTOCOL_VERSION;
+    header->source          = transportLayer->address;
+    header->destination     = SHELLMATTA_TRANSPORT_MASTER_ADDRESS;
+    header->sequenceH2S     = transportLayer->sequenceH2S;
+    header->sequenceS2H     = ++transportLayer->sequenceS2H;
+
+    crc = crc32Calc((char*) packet, SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength);
+
+    /** -# append crc to end of payload */
+    rawPacket[SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength] = (uint8_t)(crc >> 24u);
+    rawPacket[SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength + 1u] = (uint8_t)(crc >> 16u);
+    rawPacket[SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength + 2u] = (uint8_t)(crc >> 8u);
+    rawPacket[SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength + 3u] = (uint8_t)(crc);
+
+    /** -# use original write function to send full buffer */
+    return transportLayer->writeFct((char*) rawPacket,
+                                    SHELLMATTA_TRANSPORT_LENGTH_STATIC +
+                                    header->payloadLength);
+}
+
+/**
+ * @brief   check a search packet and return if our UUID is in the search range
+ * @param[in, out]  transportLayer  transport layer instance to work on
+ * @param[in]       packet          search packet
+ * @return          true - UUID in passed range
+ */
+static bool shellmatta_transport_search(shellmatta_transport_layer_t *transportLayer,
+                                                        shellmatta_transport_packet_t *packet)
+{
+    bool match = true;
+    bool sure = false;
+    uint32_t i;
+    uint8_t *uuid1;
+    uint8_t *uuid2;
+
+    uuid1 = (uint8_t *)&packet->payload[0u];
+    uuid2 = (uint8_t *)&packet->payload[SHELLMATTA_TRANPORT_UUID_LENGTH];
+
+    /** -# compare UUID with range */
+    for(i = 0u; i < (SHELLMATTA_TRANPORT_UUID_LENGTH - 1u); i ++)
+    {
+        /** -# stop match if we are outside of the range with the current digit */
+        if((transportLayer->uuid[i] < uuid1[i]) || (transportLayer->uuid[i] > uuid2[i]))
+        {
+            match = false;
+        }
+        /** -# stop comparison when we are definitely in the range (lower than the top limit and inside) */
+        else if(transportLayer->uuid[i] < uuid2[i])
+        {
+            sure = true;
+            break;
+        }
+    }
+
+    /** -# explicitly check lowest digit - this is not inclusive to the lower limit */
+    if((false == sure) && ((transportLayer->uuid[i] <= uuid1[i]) || (transportLayer->uuid[i] > uuid2[i])))
+    {
+        match = false;
+    }
+
+    return match;
+}
+
+/**
+ * @brief           Initializes the transportLayerInst
+ * @param[in, out]  transportLayer  transport layer instance to work on
+ * @param[in]       writeFct        function pointer to output function
+ * @return          errorcode       #SHELLMATTA_OK
+ */
+shellmatta_retCode_t shellmatta_transport_init( shellmatta_transport_layer_t    *transportLayer,
+                                                shellmatta_write_t              writeFct)
+{
+    /** -# clear instance and store write function */
+    memset(transportLayer, 0u, sizeof(shellmatta_transport_layer_t));
+    transportLayer->hostBufferSize = SHELLMATTA_TRANPORT_PAYLOAD_MAXLENGTH;
+    transportLayer->writeFct = writeFct;
+
+    return SHELLMATTA_OK;
+}
+
+/**
+ * @brief           Configures the transport layer
+ * @param[in, out]  handle              shellmatta handle of the instance
+ * @param[in]       mandatory           enforce using the transport layer
+ * @param[in]       disableAutoFlush    enforce manual flushing of the output packet
+ * @param[in]       uuid                UUID for multidrop search (default NULL)
+ * @param[in]       customCrcFct        use a custom crc generation (default NULL)
+ * @return          errorcode
+ */
+shellmatta_retCode_t shellmatta_transport_configure(shellmatta_handle_t         handle,
+                                                    bool                        mandatory,
+                                                    bool                        disableAutoFlush,
+                                                    uint8_t                     uuid[SHELLMATTA_TRANPORT_UUID_LENGTH],
+                                                    shellmatta_transport_crc_t  customCrcFct)
+{
+    shellmatta_retCode_t    ret     = SHELLMATTA_OK;
+    shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic))
+    {
+        shellmatta_transport_layer_t *transportLayer = &inst->transportLayer;
+
+        transportLayer->mandatory           = mandatory;
+        transportLayer->disableAutoFlush    = disableAutoFlush;
+        transportLayer->customCrcFct        = customCrcFct;
+
+        /** -# set the transport layer active when configured as mandatory */
+        if(true == mandatory)
+        {
+            transportLayer->active = true;
+        }
+
+        if(NULL != uuid)
+        {
+            (void)memcpy(transportLayer->uuid, uuid, SHELLMATTA_TRANPORT_UUID_LENGTH);
+        }
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+
+    return ret;
+}
+
+/**
+ * @brief           Resets all values of tranportLayerInst except for sequence counters
+ * @param[in, out]  handle      shellmatta handle of the instance
+ * @return          errorcode
+ */
+shellmatta_retCode_t shellmatta_transport_reset(shellmatta_handle_t handle)
+{
+    shellmatta_retCode_t    ret     = SHELLMATTA_OK;
+    shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic))
+    {
+        /** -# set transport layer back to initial state */
+        shellmatta_transport_layer_t *transportLayer = &inst->transportLayer;
+
+        transportLayer->state = SHELLMATTA_TRANSPORT_STATE_WAIT;
+        transportLayer->active = transportLayer->mandatory;
+        transportLayer->headerIndex = 0u;
+        transportLayer->payloadIndex = 0u;
+        transportLayer->crcIndex = 0u;
+        memset(&transportLayer->inPacket, 0, sizeof(transportLayer->inPacket));
+        memset(&transportLayer->outPacket, 0, sizeof(transportLayer->outPacket));
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+
+    return ret;
+}
+
+/**
+ * @brief           processes the passed amount of data
+ * @param[in, out]  transportLayer  transport layer instance to work on
+ * @param[in]       byte            byte to process
+ * @param[out]      data            payload of processed packet - or manual mode
+ * @param[out]      length          length of processed packet
+ * @return          errorcode       #SHELLMATTA_OK      data is returned\n
+ *                                  #SHELLMATTA_BUSY    packet reception ongoing\n
+ *                                  #SHELLMATTA_ERROR   in case of crc error
+ */
+shellmatta_retCode_t shellmatta_transport_process(shellmatta_transport_layer_t   *transportLayer,
+                                                  char                           byte,
+                                                  char                           **data,
+                                                  uint32_t                       *length)
+{
+    shellmatta_retCode_t ret = SHELLMATTA_BUSY;
+    char *rawPacket = (char*)&transportLayer->inPacket;
+    shellmatta_transport_header_t *header = &transportLayer->inPacket.header;
+    shellmatta_transport_packet_int_t intPacket;
+    uint32_t refCrc;
+
+    switch (transportLayer->state)
+    {
+    /** -# look for start of header */
+    case SHELLMATTA_TRANSPORT_STATE_WAIT:
+        /** -# if start of header is found, continue transport layer fsm */
+        /** -# respect suspendedOptional when there is no active session */
+        if((SHELLMATTA_TRANSPORT_START_OF_HEADER == byte) &&
+           !((false == transportLayer->active) && (true == transportLayer->suspendOptional)))
+        {
+            memset(&transportLayer->inPacket, 0, sizeof(transportLayer->inPacket));
+            transportLayer->headerIndex = 1u;
+            transportLayer->payloadIndex = 0u;
+            transportLayer->crcIndex = 0u;
+            header->startOfHeader = byte;
+            transportLayer->state = SHELLMATTA_TRANSPORT_STATE_GET_HEADER;
+        }
+        else if((true == transportLayer->mandatory))
+        {
+            /** -# if transport layer is mandatory - throw away unrecognized bytes */
+            *data = transportLayer->inPacket.payload;
+            *length = 0;
+            transportLayer->active = true;
+            ret = SHELLMATTA_OK;
+        }
+        else
+        {
+            /** -# otherwise just pass the payload as is */
+            transportLayer->inPacket.payload[0] = byte;
+            *data = transportLayer->inPacket.payload;
+            *length = 1;
+            transportLayer->active = false;
+            ret = SHELLMATTA_OK;
+        }
+        break;
+
+    case SHELLMATTA_TRANSPORT_STATE_GET_HEADER:
+
+        rawPacket[transportLayer->headerIndex] = byte;
+        transportLayer->headerIndex ++;
+
+        if(transportLayer->headerIndex == SHELLMATTA_TRANSPORT_LENGTH_HEADER)
+        {
+            /** -# check protocol version and addressing*/
+            if((SHELLMATTA_TRANSPORT_PROTOCOL_VERSION != header->protocolVersion) ||
+              ((SHELLMATTA_TRANSPORT_BROADCAST_ADDRESS != header->destination) &&
+              (transportLayer->address != header->destination)))
+            {
+                transportLayer->state = SHELLMATTA_TRANSPORT_STATE_WAIT;
+            }
+            else if(0u != header->payloadLength)
+            {
+                transportLayer->state = SHELLMATTA_TRANSPORT_STATE_GET_PAYLOAD;
+            }
+            else
+            {
+                transportLayer->state = SHELLMATTA_TRANSPORT_STATE_GET_CRC;
+            }
+        }
+
+        break;
+
+    /** -# read payload for previously read length of payload */
+    case SHELLMATTA_TRANSPORT_STATE_GET_PAYLOAD:
+
+        transportLayer->inPacket.payload[transportLayer->payloadIndex] = byte;
+        transportLayer->payloadIndex ++;
+
+        if (transportLayer->payloadIndex >= header->payloadLength)
+        {
+            transportLayer->state = SHELLMATTA_TRANSPORT_STATE_GET_CRC;
+        }
+        break;
+
+    /** -# read crc32 for four bytes */
+    case SHELLMATTA_TRANSPORT_STATE_GET_CRC:
+
+        transportLayer->crcIndex ++;
+        transportLayer->inPacket.crc |= ((uint8_t)byte << ((SHELLMATTA_TRANSPORT_LENGTH_CRC - transportLayer->crcIndex) * 8u));
+
+        if (transportLayer->crcIndex >= SHELLMATTA_TRANSPORT_LENGTH_CRC)
+        {
+            refCrc = SHELLMATTA_TRANSPORT_CALC_CRC(transportLayer,
+                                                   rawPacket,
+                                                   SHELLMATTA_TRANSPORT_LENGTH_HEADER + header->payloadLength);
+
+            /** -# if crc is correct, further handling of data depends on type of packet */
+            if (transportLayer->inPacket.crc == refCrc)
+            {
+                transportLayer->active = true;
+
+                /* crc is correct */
+                transportLayer->sequenceH2S++;
+
+                switch (header->packetType)
+                {
+                case SHELLMATTA_TRANSPORT_PACKET_DATA:
+                    /** -# return pointer to payload and length */
+                    *data = (char*)&transportLayer->inPacket.payload;
+                    *length = header->payloadLength;
+                    ret = SHELLMATTA_OK;
+                    break;
+
+                case SHELLMATTA_TRANSPORT_PACKET_SEQ_CNT_REQUEST:
+                    if(transportLayer->inPacket.header.payloadLength == 0u)
+                    {
+                        /** -# send out packet with no payload */
+                        intPacket.header.packetType = SHELLMATTA_TRANSPORT_PACKET_SEQ_CNT_RESPOND;
+                        intPacket.header.payloadLength = 0u;
+                        (void)shellmatta_transport_send(transportLayer, (shellmatta_transport_packet_t *)&intPacket);
+                    }
+                    break;
+
+                case SHELLMATTA_TRANSPORT_PACKET_SEQ_CNT_RESPOND:
+                    /** -# ignore #SHELLMATTA_TRANSPORT_PACKET_SEQ_CNT_RESPOND - nothing to do */
+                    break;
+
+                case SHELLMATTA_TRANSPORT_PACKET_MAX_BUFFERSIZE_REQUEST:
+
+                    if(transportLayer->inPacket.header.payloadLength == 1u)
+                    {
+                        /** -# store the hosts buffersize */
+                        transportLayer->hostBufferSize = (uint8_t)transportLayer->inPacket.payload[0];
+
+                        /** -# respont with our own buffer size */
+                        intPacket.header.packetType = SHELLMATTA_TRANSPORT_PACKET_MAX_BUFFERSIZE_RESPOND;
+                        intPacket.header.payloadLength = 1u;
+                        intPacket.payload[0] = SHELLMATTA_TRANPORT_PAYLOAD_MAXLENGTH;
+                        (void)shellmatta_transport_send(transportLayer, (shellmatta_transport_packet_t *)&intPacket);
+                    }
+                    break;
+
+                case SHELLMATTA_TRANSPORT_PACKET_MAX_BUFFERSIZE_RESPOND:
+                    /** -# ignore #SHELLMATTA_TRANSPORT_PACKET_MAX_BUFFERSIZE_RESPOND - nothing to do */
+                    break;
+
+                case SHELLMATTA_TRANSPORT_PACKET_SEARCH_DEVICE_REQUEST:
+                    /** -# check if our own uuid is inside the passed range */
+                    if((transportLayer->inPacket.header.payloadLength == SHELLMATTA_TRANPORT_UUID_LENGTH * 2u) &&
+                       (true == shellmatta_transport_search(transportLayer, &transportLayer->inPacket)))
+                    {
+                        intPacket.header.packetType = SHELLMATTA_TRANSPORT_PACKET_SEARCH_DEVICE_RESPOND;
+                        intPacket.header.payloadLength = SHELLMATTA_TRANPORT_UUID_LENGTH;
+                        (void)memcpy(intPacket.payload, transportLayer->uuid, SHELLMATTA_TRANPORT_UUID_LENGTH);
+                        (void)shellmatta_transport_send(transportLayer, (shellmatta_transport_packet_t *)&intPacket);
+                    }
+                    break;
+
+                case SHELLMATTA_TRANSPORT_PACKET_SEARCH_DEVICE_RESPOND:
+                    /** -# ignore #SHELLMATTA_TRANSPORT_PACKET_SEARCH_DEVICE_RESPOND - nothing to do */
+                    break;
+
+                case SHELLMATTA_TRANSPORT_PACKET_SET_ADDRESS_REQUEST:
+
+                    /** -# use the passed address if the passed UUID matches our own */
+                    if((transportLayer->inPacket.header.payloadLength == SHELLMATTA_TRANPORT_UUID_LENGTH + 1u) &&
+                        0 == memcmp(transportLayer->uuid,
+                                   transportLayer->inPacket.payload,
+                                   SHELLMATTA_TRANPORT_UUID_LENGTH))
+                    {
+                        transportLayer->address = transportLayer->inPacket.payload[SHELLMATTA_TRANPORT_UUID_LENGTH];
+                        if(SHELLMATTA_TRANSPORT_MASTER_ADDRESS == transportLayer->address)
+                        {
+                            transportLayer->address = 0u;
+                        }
+                        else
+                        {
+                            /** -# respond to the address set command */
+                            intPacket.header.packetType = SHELLMATTA_TRANSPORT_PACKET_SET_ADDRESS_RESPOND;
+                            intPacket.header.payloadLength = SHELLMATTA_TRANPORT_UUID_LENGTH;
+                            (void)memcpy(intPacket.payload, transportLayer->uuid, SHELLMATTA_TRANPORT_UUID_LENGTH);
+                            (void)shellmatta_transport_send(transportLayer,
+                                                            (shellmatta_transport_packet_t *)&intPacket);
+                        }
+
+                    }
+                    
+                    break;
+
+                case SHELLMATTA_TRANSPORT_PACKET_SET_ADDRESS_RESPOND:
+                    /** -# ignore #SHELLMATTA_TRANSPORT_PACKET_SET_ADDRESS_RESPOND - nothing to do */
+                    break;
+
+                default:
+                    /** -# ignore unknown packets */
+                    break;
+                }
+            }
+            else
+            {
+                /** -#  return error if crc is incorrect */
+                ret = SHELLMATTA_ERROR;
+            }
+
+            /** -# reset state machine */
+            transportLayer->state = SHELLMATTA_TRANSPORT_STATE_WAIT;
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    return ret;
+}
+
+/**
+ * @brief       Wrapper function for the write-function of shellmatta handle
+ *
+ * This function is used to transmit data with the tranport layer protocol.\n
+ * The input data is copied into a new array along with the header and crc32.\n
+ * The resulting buffer is the forwarded to the original write function.\n
+ *
+ * @note        If length of data exceeds UINT8_MAX, data is sent in multiple packets.\n
+ *
+ * @param[in, out]  transportLayer  transport layer instance to work on
+ * @param[in]       data            pointer to input data to process
+ * @param[in]       length          length of data to process
+ * @return          errorcode       #SHELLMATTA_OK
+ */
+shellmatta_retCode_t shellmatta_transport_write(shellmatta_transport_layer_t *transportLayer,
+                                                const char* data,
+                                                uint32_t length)
+{
+    shellmatta_retCode_t ret = SHELLMATTA_OK;
+    uint32_t outPayloadLength = length;
+    shellmatta_transport_packet_t *packet = &transportLayer->outPacket;
+    shellmatta_transport_header_t *header = &transportLayer->outPacket.header;
+
+    /** -# handle data with length bigger than max length */
+    uint32_t processedLength = 0u;
+    uint32_t piledUpPayload;
+    uint32_t splitLength;
+    uint32_t restOfPayload;
+
+    /** -# foot-controlled loop to send packets without payload length */
+    do
+    {
+        piledUpPayload = header->payloadLength;
+
+        /* compute length of next payload split */
+        restOfPayload = (outPayloadLength - processedLength);
+        if (restOfPayload > (transportLayer->hostBufferSize - piledUpPayload))
+        {
+            splitLength = transportLayer->hostBufferSize - piledUpPayload;
+        }
+        else
+        {
+            splitLength = restOfPayload;
+        }
+
+        (void)memcpy(&packet->payload[piledUpPayload], &data[processedLength], splitLength);
+        header->payloadLength += splitLength;
+        processedLength += splitLength;
+
+        if(header->payloadLength >= transportLayer->hostBufferSize)
+        {
+            /** -# packet is full - send */
+            header->packetType = SHELLMATTA_TRANSPORT_PACKET_DATA;
+            ret = shellmatta_transport_send(transportLayer, packet);
+            header->payloadLength = 0u;
+        }
+    }
+    while (processedLength < outPayloadLength);
+    return ret;
+}
+
+/**
+ * @brief           Send out piled up payload
+ *
+ * @param[in, out]  handle      shellmatta handle of the instance
+ * @return          errorcode
+ */
+shellmatta_retCode_t shellmatta_transport_flush(shellmatta_handle_t handle)
+{
+    shellmatta_retCode_t    ret = SHELLMATTA_OK;
+    shellmatta_instance_t   *inst = (shellmatta_instance_t*)handle;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic))
+    {
+
+        shellmatta_transport_packet_t *packet = &inst->transportLayer.outPacket;
+
+        if(0 != packet->header.payloadLength)
+        {
+            packet->header.packetType = SHELLMATTA_TRANSPORT_PACKET_DATA;
+            ret = shellmatta_transport_send(&inst->transportLayer, packet);
+            packet->header.payloadLength = 0u;
+        }
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+
+    return ret;
+}
+#endif
+
+/** @} */

+ 87 - 0
src/shellmatta_transport.h

@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    shellmatta_transport.h
+ * @brief   transport layer functions of shellmatta
+ * @authors Simon Fischer <dev@s-fischer.net> Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+/**
+ * @addtogroup shellmatta_transport
+ * @{
+ */
+
+#ifndef _SHELLMATTA_TRANSPORT_H_
+#define _SHELLMATTA_TRANSPORT_H_
+
+#include "shellmatta.h"
+
+/** @brief value of start-of-header character */
+#define SHELLMATTA_TRANSPORT_START_OF_HEADER    0x01u
+/** @brief currently supported protocol version */
+#define SHELLMATTA_TRANSPORT_PROTOCOL_VERSION   0x01u
+/** @brief broadcast address */
+#define SHELLMATTA_TRANSPORT_BROADCAST_ADDRESS  0u
+/** @brief master address */
+#define SHELLMATTA_TRANSPORT_MASTER_ADDRESS     1u
+
+/* header field length defines */
+/** @brief length of header */
+#define SHELLMATTA_TRANSPORT_LENGTH_HEADER      ((uint8_t)(8))
+/** @brief maximum length of shellmatta internal packages (e.g. sequence counter packets) */
+#define SHELLMATTA_TRANSPORT_LENGTH_PAYLOAD_INT ((uint8_t)(32))
+/** @brief length of crc32 */
+#define SHELLMATTA_TRANSPORT_LENGTH_CRC         ((uint8_t)(4))
+/** @brief length of crc32 */
+#define SHELLMATTA_TRANSPORT_LENGTH_STATIC      (SHELLMATTA_TRANSPORT_LENGTH_HEADER + SHELLMATTA_TRANSPORT_LENGTH_CRC)
+
+/** @brief helper macro for CRC calculation */
+#define SHELLMATTA_TRANSPORT_CALC_CRC(transporLayer, data, size)    NULL != (transportLayer)->customCrcFct ?         \
+                                                                    (transportLayer)->customCrcFct((data), (size)) : \
+                                                                    crc32Calc((data), (size));
+
+/** @brief packet definition with reduced payload (for internal packets) */
+typedef struct __attribute__((__packed__))
+{
+    shellmatta_transport_header_t   header;                 /**< header of the packet   */
+    char payload[SHELLMATTA_TRANSPORT_LENGTH_PAYLOAD_INT];  /**< payload of the packet  */
+    uint32_t                        crc;                    /**< checksum of the packet */
+} shellmatta_transport_packet_int_t;
+
+
+/**
+ * @brief definitions of shellmatta transport layer packet types
+ */
+
+#define SHELLMATTA_TRANSPORT_PACKET_DATA                    0x00u   /**< packet type to send plain data                                  */
+#define SHELLMATTA_TRANSPORT_PACKET_SEQ_CNT_REQUEST         0x01u   /**< packet type to request sequence counters                        */
+#define SHELLMATTA_TRANSPORT_PACKET_SEQ_CNT_RESPOND         0x81u   /**< packet type to respond with sequence counters                   */
+#define SHELLMATTA_TRANSPORT_PACKET_MAX_BUFFERSIZE_REQUEST  0x02u   /**< packet type to set and request max buffersize                   */
+#define SHELLMATTA_TRANSPORT_PACKET_MAX_BUFFERSIZE_RESPOND  0x82u   /**< packet type to respond with max buffersize                      */
+#define SHELLMATTA_TRANSPORT_PACKET_SEARCH_DEVICE_REQUEST   0x03u   /**< UNUSED: packet type to request search for a device by unique id */
+#define SHELLMATTA_TRANSPORT_PACKET_SEARCH_DEVICE_RESPOND   0x83u   /**< UNUSED: packet type to respond with search results              */
+#define SHELLMATTA_TRANSPORT_PACKET_SET_ADDRESS_REQUEST     0x04u   /**< UNUSED: packet type to set and request an address               */
+#define SHELLMATTA_TRANSPORT_PACKET_SET_ADDRESS_RESPOND     0x84u   /**< UNUSED: packet type to respond with a set address               */
+
+
+shellmatta_retCode_t shellmatta_transport_init( shellmatta_transport_layer_t    *transportLayer,
+                                                shellmatta_write_t              writeFct);
+
+shellmatta_retCode_t shellmatta_transport_process(shellmatta_transport_layer_t   *transportLayer,
+                                                  char                           byte,
+                                                  char                           **data,
+                                                  uint32_t                       *length);
+
+shellmatta_retCode_t shellmatta_transport_write(shellmatta_transport_layer_t *transportLayer,
+                                                const char* data,
+                                                uint32_t length);
+
+#endif /* _SHELLMATTA_TRANSPORT_H_ */
+
+/** @} */

+ 120 - 22
src/shellmatta_utils.c

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -19,6 +19,9 @@
 
 
 #include "shellmatta_utils.h"
 #include "shellmatta_utils.h"
 #include "shellmatta.h"
 #include "shellmatta.h"
+#ifdef SHELLMATTA_AUTHENTICATION
+#include "shellmatta_auth.h"
+#endif
 #include <string.h>
 #include <string.h>
 
 
 /**
 /**
@@ -34,7 +37,7 @@ void utils_writeEcho(   shellmatta_instance_t   *inst,
 {
 {
     if(true == inst->echoEnabled)
     if(true == inst->echoEnabled)
     {
     {
-        inst->write(data, length);
+        SHELLMATTA_WRITE(data, length);
     }
     }
 }
 }
 
 
@@ -85,6 +88,69 @@ uint32_t utils_shellItoa(int32_t value, char *buffer, uint32_t base)
     return bufferIdx;
     return bufferIdx;
 }
 }
 
 
+/**
+ * @brief       turns a string of digits into an unsigned 32-bit integer
+ * @param[in]   str     pointer to the string
+ * @param[out]  result  the value of the string as unsigned 32-bit integer
+ * @param[in]   base    base of the conversion
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_USE_FAULT (param err)
+ *                          #SHELLMATTA_ERROR (invalid characters or overflow)
+*/
+shellmatta_retCode_t utils_shellAsciiToUInt32(char* str, uint32_t *result, uint32_t base)
+{
+    shellmatta_retCode_t ret = SHELLMATTA_OK;
+    uint32_t val;
+
+    if((NULL != str) && (NULL != result) && (base <= 16u))
+    {
+        *result = 0u;
+
+        while(*str && (' ' != *str) && (SHELLMATTA_OK == ret))
+        {
+            /** -# convert character to value */
+            if((*str >= '0') && (*str <= '9'))
+            {
+                val = ((uint32_t)*str - '0');
+            }
+            else if((*str >= 'A') && (*str <= 'F'))
+            {
+                val = ((uint32_t)*str - 'A') + 10u;
+            }
+            else if((*str >= 'a') && (*str <= 'f'))
+            {
+                val = ((uint32_t)*str - 'a') + 10u;
+            }
+            else
+            {
+                ret = SHELLMATTA_ERROR;
+            }
+
+            /** -# return error in case the value is outside of the expected base */
+            if(val > (base - 1u))
+            {
+                ret = SHELLMATTA_ERROR;
+            }
+            else
+            {
+                /** -# check for overflow */
+                if((UINT32_MAX - val) / base < *result)
+                {
+                    ret = SHELLMATTA_ERROR;
+                }
+                *result = (*result * base) + val;
+            }
+            str ++;
+        }
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+
+    return ret;
+}
+
 /**
 /**
  * @brief       tells the terminal to save the current cursor position
  * @brief       tells the terminal to save the current cursor position
  * @param[in]   inst    pointer to shellmatta instance
  * @param[in]   inst    pointer to shellmatta instance
@@ -164,7 +230,7 @@ void utils_forwardCursor(shellmatta_instance_t *inst, uint32_t length)
  * @param[in]   length  length of the data to be inserted
  * @param[in]   length  length of the data to be inserted
  */
  */
 void utils_insertChars( shellmatta_instance_t   *inst,
 void utils_insertChars( shellmatta_instance_t   *inst,
-                        char                    *data,
+                        const char              *data,
                         uint32_t                 length)
                         uint32_t                 length)
 {
 {
     uint32_t tmpLength = length;
     uint32_t tmpLength = length;
@@ -189,6 +255,7 @@ void utils_insertChars( shellmatta_instance_t   *inst,
 
 
             /** -# store and print the new chars */
             /** -# store and print the new chars */
             memcpy(&(inst->buffer[inst->cursor]), data, tmpLength);
             memcpy(&(inst->buffer[inst->cursor]), data, tmpLength);
+
             utils_writeEcho(inst, data, tmpLength);
             utils_writeEcho(inst, data, tmpLength);
 
 
             /** -# print the other chars and restore the cursor to this position */
             /** -# print the other chars and restore the cursor to this position */
@@ -284,27 +351,27 @@ static shellmatta_retCode_t printUsage(const shellmatta_instance_t *inst, const
     shellmatta_retCode_t ret = SHELLMATTA_OK;
     shellmatta_retCode_t ret = SHELLMATTA_OK;
 
 
     /** -# write the command and alias if configured */
     /** -# write the command and alias if configured */
-    SHELLMATTA_RET(ret, inst->write("Help for command: ", 18u));
-    SHELLMATTA_RET(ret, inst->write(cmd->cmd, strlen(cmd->cmd)));
+    SHELLMATTA_RET(ret, SHELLMATTA_WRITE("Help for command: ", 18u));
+    SHELLMATTA_RET(ret, SHELLMATTA_WRITE(cmd->cmd, strlen(cmd->cmd)));
     if((NULL != cmd->cmdAlias) && (0u != strlen(cmd->cmdAlias)))
     if((NULL != cmd->cmdAlias) && (0u != strlen(cmd->cmdAlias)))
     {
     {
-        SHELLMATTA_RET(ret, inst->write(" (", 2u));
-        SHELLMATTA_RET(ret, inst->write(cmd->cmdAlias, strlen(cmd->cmdAlias)));
-        SHELLMATTA_RET(ret, inst->write(")", 1u));
+        SHELLMATTA_RET(ret, SHELLMATTA_WRITE(" (", 2u));
+        SHELLMATTA_RET(ret, SHELLMATTA_WRITE(cmd->cmdAlias, strlen(cmd->cmdAlias)));
+        SHELLMATTA_RET(ret, SHELLMATTA_WRITE(")", 1u));
     }
     }
     /** -# write the help text if configured */
     /** -# write the help text if configured */
     if((NULL != cmd->helpText) && (0u != strlen(cmd->helpText)))
     if((NULL != cmd->helpText) && (0u != strlen(cmd->helpText)))
     {
     {
-        SHELLMATTA_RET(ret, inst->write("\r\n\r\n", 4u));
-        SHELLMATTA_RET(ret, inst->write(cmd->helpText, strlen(cmd->helpText)));
+        SHELLMATTA_RET(ret, SHELLMATTA_WRITE("\r\n\r\n", 4u));
+        SHELLMATTA_RET(ret, SHELLMATTA_WRITE(cmd->helpText, strlen(cmd->helpText)));
     }
     }
     /** -# write the usage text if configured */
     /** -# write the usage text if configured */
     if((NULL != cmd->usageText) && (0u != strlen(cmd->usageText)))
     if((NULL != cmd->usageText) && (0u != strlen(cmd->usageText)))
     {
     {
-        SHELLMATTA_RET(ret, inst->write("\r\n\r\nUsage: \r\n", 13u));
-        SHELLMATTA_RET(ret, inst->write(cmd->usageText, strlen(cmd->usageText)));
+        SHELLMATTA_RET(ret, SHELLMATTA_WRITE("\r\n\r\nUsage: \r\n", 13u));
+        SHELLMATTA_RET(ret, SHELLMATTA_WRITE(cmd->usageText, strlen(cmd->usageText)));
     }
     }
-    SHELLMATTA_RET(ret, inst->write("\r\n", 2u));
+    SHELLMATTA_RET(ret, SHELLMATTA_WRITE("\r\n", 2u));
 
 
     return ret;
     return ret;
 }
 }
@@ -385,6 +452,13 @@ static shellmatta_retCode_t helpCmdFct(const shellmatta_handle_t handle, const c
         cmd = inst->cmdList;
         cmd = inst->cmdList;
         while(NULL != cmd)
         while(NULL != cmd)
         {
         {
+#ifdef SHELLMATTA_AUTHENTICATION
+            if (SHELLMATTA_OK != shellmatta_auth_is_cmd_permitted(inst, cmd))
+            {
+                cmd = cmd->next;
+                continue;
+            }
+#endif
             maxCmdLen           = SHELLMATTA_MAX(maxCmdLen,         strlen(cmd->cmd));
             maxCmdLen           = SHELLMATTA_MAX(maxCmdLen,         strlen(cmd->cmd));
             if(NULL != cmd->cmdAlias)
             if(NULL != cmd->cmdAlias)
             {
             {
@@ -397,33 +471,40 @@ static shellmatta_retCode_t helpCmdFct(const shellmatta_handle_t handle, const c
         cmd = inst->cmdList;
         cmd = inst->cmdList;
         while(NULL != cmd)
         while(NULL != cmd)
         {
         {
+#ifdef SHELLMATTA_AUTHENTICATION
+            if (SHELLMATTA_OK != shellmatta_auth_is_cmd_permitted(inst, cmd))
+            {
+                cmd = cmd->next;
+                continue;
+            }
+#endif
             /** -# determine the length of each field to add padding */
             /** -# determine the length of each field to add padding */
             cmdLen       = strlen(cmd->cmd);
             cmdLen       = strlen(cmd->cmd);
             cmdAliasLen  = (NULL != cmd->cmdAlias) ? strlen(cmd->cmdAlias) : 0u;
             cmdAliasLen  = (NULL != cmd->cmdAlias) ? strlen(cmd->cmdAlias) : 0u;
 
 
-            SHELLMATTA_RET(ret, inst->write(cmd->cmd, strlen(cmd->cmd)));
+            SHELLMATTA_RET(ret, SHELLMATTA_WRITE(cmd->cmd, strlen(cmd->cmd)));
             tabCnt = (maxCmdLen - cmdLen) + 2u;
             tabCnt = (maxCmdLen - cmdLen) + 2u;
 
 
             /** -# add padding if there is anything to be printed afterwards */
             /** -# add padding if there is anything to be printed afterwards */
             if(    ((NULL != cmd->cmdAlias) && (0u != strlen(cmd->cmdAlias)))
             if(    ((NULL != cmd->cmdAlias) && (0u != strlen(cmd->cmdAlias)))
                 || ((NULL != cmd->helpText) && (0u != strlen(cmd->helpText))))
                 || ((NULL != cmd->helpText) && (0u != strlen(cmd->helpText))))
             {
             {
-                SHELLMATTA_PRINT_BUFFER(tabBuffer, tabCnt, inst->write);
+                SHELLMATTA_PRINT_BUFFER(tabBuffer, tabCnt);
             }
             }
 
 
             if((NULL != cmd->cmdAlias) && (0u != strlen(cmd->cmdAlias)))
             if((NULL != cmd->cmdAlias) && (0u != strlen(cmd->cmdAlias)))
             {
             {
-                SHELLMATTA_RET(ret, inst->write(cmd->cmdAlias, cmdAliasLen));
+                SHELLMATTA_RET(ret, SHELLMATTA_WRITE(cmd->cmdAlias, cmdAliasLen));
             }
             }
             tabCnt = (maxCmdAliasLen - cmdAliasLen) + 2u;
             tabCnt = (maxCmdAliasLen - cmdAliasLen) + 2u;
 
 
             if((NULL != cmd->helpText) && (0u != strlen(cmd->helpText)))
             if((NULL != cmd->helpText) && (0u != strlen(cmd->helpText)))
             {
             {
-                SHELLMATTA_PRINT_BUFFER(tabBuffer, tabCnt, inst->write);
-                SHELLMATTA_RET(ret, inst->write(cmd->helpText, strlen(cmd->helpText)));
+                SHELLMATTA_PRINT_BUFFER(tabBuffer, tabCnt);
+                SHELLMATTA_RET(ret, SHELLMATTA_WRITE(cmd->helpText, strlen(cmd->helpText)));
             }
             }
 
 
-            SHELLMATTA_RET(ret, inst->write("\r\n", 2u));
+            SHELLMATTA_RET(ret, SHELLMATTA_WRITE("\r\n", 2u));
 
 
             cmd = cmd->next;
             cmd = cmd->next;
         }
         }
@@ -436,7 +517,11 @@ const shellmatta_cmd_t helpCmd = {SHELLMATTA_HELP_COMMAND
                                 , SHELLMATTA_HELP_HELP_TEXT
                                 , SHELLMATTA_HELP_HELP_TEXT
                                 , SHELLMATTA_HELP_USAGE_TEXT
                                 , SHELLMATTA_HELP_USAGE_TEXT
                                 , helpCmdFct
                                 , helpCmdFct
-                                , NULL};
+                                , NULL
+#ifdef SHELLMATTA_AUTHENTICATION
+                                , NULL
+#endif
+                                };
 
 
 /**
 /**
  * @brief       terminates an input and prints the prompt again
  * @brief       terminates an input and prints the prompt again
@@ -452,8 +537,21 @@ void utils_terminateInput(shellmatta_instance_t *inst)
     inst->stdinLength       = 0u;
     inst->stdinLength       = 0u;
     inst->continuousCmd     = NULL;
     inst->continuousCmd     = NULL;
     inst->busyCmd           = NULL;
     inst->busyCmd           = NULL;
-    inst->write("\r\n", 2u);
-    inst->write(inst->prompt, strlen(inst->prompt));
+
+    /** -# print prompt and username when no ymodem session is running */
+    if(SHELLMATTA_YMODEM_INACTIVE == inst->ymodem.state)
+    {
+        SHELLMATTA_WRITE("\r\n", 2u);
+#ifdef SHELLMATTA_AUTHENTICATION
+        inst->loginState        = SHELLMATTA_AUTH_IDLE;
+        if (NULL != inst->userPointer)
+        {
+            SHELLMATTA_WRITE(inst->userPointer->username, strlen(inst->userPointer->username));
+            SHELLMATTA_WRITE("@", 1);
+        }
+#endif
+        SHELLMATTA_WRITE(inst->prompt, strlen(inst->prompt));
+    }
 }
 }
 
 
 /**
 /**

+ 49 - 29
src/shellmatta_utils.h

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -16,9 +16,13 @@
  * @addtogroup shellmatta_utils
  * @addtogroup shellmatta_utils
  * @{
  * @{
  */
  */
+
 #ifndef _SHELLMATTA_UTILS_H_
 #ifndef _SHELLMATTA_UTILS_H_
 #define _SHELLMATTA_UTILS_H_
 #define _SHELLMATTA_UTILS_H_
 
 
+#ifdef SHELLMATTA_TRANSPORT
+#include "shellmatta_transport.h"
+#endif
 #include "shellmatta.h"
 #include "shellmatta.h"
 #include <stdint.h>
 #include <stdint.h>
 
 
@@ -47,17 +51,16 @@
  * @brief       calls fct with cnt bytes from buffer (to print cnt same bytes)
  * @brief       calls fct with cnt bytes from buffer (to print cnt same bytes)
  * @param[in]   buffer  buffer to send (shall contain the same char)
  * @param[in]   buffer  buffer to send (shall contain the same char)
  * @param[in]   cnt     count of bytes to send
  * @param[in]   cnt     count of bytes to send
- * @param[in]   fct     write function
  */
  */
-#define SHELLMATTA_PRINT_BUFFER(buffer,cnt,fct) \
-    while((cnt) > sizeof((buffer)))             \
-    {                                           \
-        (cnt) -= sizeof((buffer));              \
-        (fct)((buffer), sizeof((buffer)));      \
-    }                                           \
-    if((cnt) != 0u)                             \
-    {                                           \
-        (fct)((buffer), (cnt));                 \
+#define SHELLMATTA_PRINT_BUFFER(buffer,cnt)             \
+    while((cnt) > sizeof((buffer)))                     \
+    {                                                   \
+        (cnt) -= sizeof((buffer));                      \
+        SHELLMATTA_WRITE((buffer), sizeof((buffer)));   \
+    }                                                   \
+    if((cnt) != 0u)                                     \
+    {                                                   \
+        SHELLMATTA_WRITE((buffer), (cnt));              \
     }
     }
 
 
 /** @brief help command which prints all shellmatta commands as table */
 /** @brief help command which prints all shellmatta commands as table */
@@ -103,25 +106,42 @@ extern const shellmatta_cmd_t helpCmd;
  *  @}
  *  @}
  */
  */
 
 
-void utils_writeEcho(   shellmatta_instance_t   *inst,
-                        const char              *data,
-                        uint32_t                length);
-uint32_t utils_shellItoa(int32_t value, char *buffer, uint32_t base);
-void utils_saveCursorPos(shellmatta_instance_t *inst);
-void utils_restoreCursorPos(shellmatta_instance_t *inst);
-void utils_eraseLine(shellmatta_instance_t *inst);
-void utils_rewindCursor(shellmatta_instance_t *inst, uint32_t length);
-void utils_forwardCursor(shellmatta_instance_t *inst, uint32_t length);
-void utils_insertChars( shellmatta_instance_t   *inst,
-                        char                    *data,
-                        uint32_t                 length);
-void utils_removeChars( shellmatta_instance_t   *inst,
-                        uint32_t                 length,
-                        bool                     backspace);
-void utils_clearInput(shellmatta_instance_t *inst);
-void utils_terminateInput(shellmatta_instance_t *inst);
+void utils_writeEcho(               shellmatta_instance_t   *inst,
+                                    const char              *data,
+                                    uint32_t                length);
+
+uint32_t utils_shellItoa(           int32_t value, 
+                                    char *buffer, 
+                                    uint32_t base);
+
+shellmatta_retCode_t utils_shellAsciiToUInt32(  char* str,
+                                                uint32_t *result,
+                                                uint32_t base);
+
+void utils_saveCursorPos(           shellmatta_instance_t *inst);
+
+void utils_restoreCursorPos(        shellmatta_instance_t *inst);
+
+void utils_eraseLine(               shellmatta_instance_t *inst);
+
+void utils_rewindCursor(            shellmatta_instance_t *inst, 
+                                    uint32_t length);
+
+void utils_forwardCursor(           shellmatta_instance_t *inst, 
+                                    uint32_t length);
+
+void utils_insertChars(             shellmatta_instance_t   *inst,
+                                    const char              *data,
+                                    uint32_t                 length);
+
+void utils_removeChars(             shellmatta_instance_t   *inst,
+                                    uint32_t                 length,
+                                    bool                     backspace);
+
+void utils_clearInput(              shellmatta_instance_t *inst);
+
+void utils_terminateInput(          shellmatta_instance_t *inst);
 
 
 #endif
 #endif
 
 
 /** @} */
 /** @} */
-

+ 531 - 0
src/shellmatta_ymodem.c

@@ -0,0 +1,531 @@
+/*
+ * Copyright (c) 2023 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    shellmatta_ymodem.c
+ * @brief   ymodem functions of shellmatta
+ * @authors Simon Fischer <dev@s-fischer.net> Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+/**
+ * @addtogroup shellmatta_ymodem
+ * @{
+ */
+
+#include "shellmatta_ymodem.h"
+#include "shellmatta_crc.h"
+#include "shellmatta_utils.h"
+#include <stddef.h>
+#include <string.h>
+
+/** @brief symbols needed for ymodem protocol */
+typedef enum {
+    YMODEM_NULL    = 0x00, /**< NULL-terminator                 */
+    YMODEM_SOH     = 0x01, /**< Start of header                 */
+    YMODEM_STX     = 0x02, /**< Start of header for 1k packet   */
+    YMODEM_EOT     = 0x04, /**< End of transmission             */
+    YMODEM_ACK     = 0x06, /**< Acknowledge                     */
+    YMODEM_NAK     = 0x15, /**< Negative acknowledge            */
+    YMODEM_CA      = 0x18, /**< Cancel                          */
+    YMODEM_SPACE   = 0x20, /**< Space                           */
+    YMODEM_CRC     = 0x43  /**< 'C' to indicate CRC type        */
+} YMODEM_SYMBOLS;
+
+#define YMODEM_PACKET_SIZE          128u    /**< default packet size of ymodem transmission     */
+#define YMODEM_PACKET_SIZE_1K       1024u   /**< extended packet size of ymodem transmission    */
+#define YMODEM_CRC_SIZE             2u      /**< CRC size of the ymodem packet                  */
+#define YMODEM_POLL_NUMBER          2u      /**< Number of polls to be received                 */
+
+/**
+ * @brief               forwards the given character to write-function without formatting
+ * @param[in]   handle  shellmatta handle of the instance
+ * @param[in]   c       character to be sent
+*/
+static void shellmatta_ymodem_control(shellmatta_handle_t handle, const uint8_t c)
+{
+    shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
+    SHELLMATTA_WRITE((char*)&c, 1);
+}
+
+/**
+ * @brief                       reset function for the ymodem module
+ * @param[in, out]  handle      shellmatta instance handle
+ * @param[in]       doCancel    flag to execute the cancel-callback
+*/
+static void shellmatta_ymodem_reset(shellmatta_handle_t handle, bool doCancel)
+{
+    shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
+
+    /** -# call cancel callback function */
+    if (doCancel)
+    {
+        /* send cancel symbol */
+        shellmatta_ymodem_control(handle, YMODEM_CA);
+        if (NULL != inst->ymodem.cancelCallback)
+        {
+            inst->ymodem.cancelCallback(handle);
+        }
+    }
+    /** -# reset instance variables */
+    inst->ymodem.state = SHELLMATTA_YMODEM_INACTIVE;
+    inst->ymodem.byteCounter = 0u;
+    inst->ymodem.packetCounter = 0u;
+    inst->ymodem.totalBytesReceived = 0u;
+    inst->ymodem.fileSize = 0u;
+    inst->ymodem.pauseRequested = false;
+    inst->ymodem.pollCyclesLeft = 0u;
+    inst->ymodem.cancelCounter = 0u;
+
+#ifdef SHELLMATTA_TRANSPORT
+    /** .-# reenable transport layer optional mode */
+    inst->transportLayer.suspendOptional = false;
+#endif
+
+    (void)memset((void *)&inst->ymodem.packet, 0, sizeof(shellmatta_ymodem_packet_t));
+}
+
+/**
+ * @brief                   Processes a completely received ymodem packet
+ * @param[in, out]  handle  shellmatta instance handle
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_ERROR (invalid file size)
+*/
+shellmatta_retCode_t processPacket(shellmatta_handle_t handle)
+{
+    shellmatta_retCode_t ret = SHELLMATTA_OK;
+    shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
+    char *fileName;
+    uint32_t packetSize;
+
+    /** -# read filesize and name from first packet - ignore rest */
+    if(0u == inst->ymodem.packetCounter)
+    {
+        fileName = (char*)inst->ymodem.packet.packetData;
+
+        ret = utils_shellAsciiToUInt32((char*)&inst->ymodem.packet.packetData[strlen(fileName) + 1u],
+                                       &inst->ymodem.fileSize,
+                                       10u);
+
+        /** -# pass filename and size to the callback */
+        inst->ymodem.recvHeaderCallback(handle, inst->ymodem.fileSize, fileName);
+    }
+    else
+    {
+        /** -# calculate packet size - when it is the last packet this is limited by the file size */
+        if((0u != inst->ymodem.fileSize) &&
+           (inst->ymodem.totalBytesReceived + inst->ymodem.packet.size) > inst->ymodem.fileSize)
+        {
+            packetSize = inst->ymodem.fileSize % inst->ymodem.packet.size;
+        }
+        else
+        {
+            packetSize = inst->ymodem.packet.size;
+        }
+
+        /** -# pass data to the application using the callback */
+        inst->ymodem.recvPacketCallback(handle, inst->ymodem.packet.packetData, packetSize, inst->ymodem.packetCounter);
+    }
+
+    return ret;
+}
+
+/**
+ * @brief                   Processes incoming bytes using the ymodem state machine
+ * @param[in, out]  handle  shellmatta instance handle
+ * @param[in]       byte    received byte
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_ERROR (error during packet processing)
+*/
+static shellmatta_retCode_t ymodem_stateMachine(shellmatta_handle_t handle, uint8_t byte)
+{
+    shellmatta_retCode_t ret = SHELLMATTA_OK;
+    shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
+    uint16_t computedCrc;
+
+    switch(inst->ymodem.state)
+    {
+        case SHELLMATTA_YMODEM_INACTIVE:
+            /** -# skip state machine if inactive */
+            break;
+        case SHELLMATTA_YMODEM_WAIT_FOR_START:
+            /** -# Wait for start character */
+            inst->ymodem.byteCounter = 0u;
+            switch(byte)
+            {
+                case YMODEM_SOH:
+                    inst->ymodem.packet.size = YMODEM_PACKET_SIZE;
+                    inst->ymodem.state = SHELLMATTA_YMODEM_RECEIVE_HEADER;
+                    break;
+                case YMODEM_STX:
+                    inst->ymodem.packet.size = YMODEM_PACKET_SIZE_1K;
+                    inst->ymodem.state = SHELLMATTA_YMODEM_RECEIVE_HEADER;
+                    break;
+                case YMODEM_EOT:
+                    /** -# ACK the successful file reception */
+                    shellmatta_ymodem_control(handle, YMODEM_ACK);
+                    shellmatta_ymodem_control(handle, YMODEM_CRC);
+
+                    /** -# handle additional EOTs in WAIT FOR END state */
+                    inst->ymodem.state = SHELLMATTA_YMODEM_WAIT_FOR_END;
+                    inst->ymodem.pollCyclesLeft = YMODEM_POLL_NUMBER;
+                    break;
+                default:
+                    /** -# ignore unexpected characters on start */
+                    break;
+            }
+            break;
+        case SHELLMATTA_YMODEM_RECEIVE_HEADER:
+            if(0u == inst->ymodem.byteCounter)
+            {
+                inst->ymodem.packet.packetNumber = byte;
+                inst->ymodem.byteCounter ++;
+            }
+            else
+            {
+                if(((0xffu - byte) != inst->ymodem.packet.packetNumber) ||
+                  (inst->ymodem.packetCounter % 256u != inst->ymodem.packet.packetNumber))
+                {
+                    /** -# return error on packet number mismatch or on unexpected packet numbers */
+                    inst->ymodem.state = SHELLMATTA_YMODEM_WAIT_FOR_START;
+                    shellmatta_ymodem_control(handle, YMODEM_NAK);
+                    ret = SHELLMATTA_ERROR;
+                }
+                else
+                {
+                    /** -# start receiving the payload */
+                    inst->ymodem.state = SHELLMATTA_YMODEM_RECEIVE_DATA;
+                    inst->ymodem.byteCounter = 0u;
+                }
+            }
+            break;
+        case SHELLMATTA_YMODEM_RECEIVE_DATA:
+            inst->ymodem.packet.packetData[inst->ymodem.byteCounter] = byte;
+            inst->ymodem.byteCounter ++;
+
+            /** -# load payload until the packet is full */
+            if(inst->ymodem.byteCounter >= inst->ymodem.packet.size)
+            {
+                inst->ymodem.state = SHELLMATTA_YMODEM_RECEIVE_CRC;
+                inst->ymodem.byteCounter = 0u;
+                inst->ymodem.packet.crc = 0u;
+            }
+
+            break;
+        case SHELLMATTA_YMODEM_RECEIVE_CRC:
+            inst->ymodem.byteCounter ++;
+            inst->ymodem.packet.crc |= (uint16_t)byte << (8u * (YMODEM_CRC_SIZE - inst->ymodem.byteCounter));
+
+            if(inst->ymodem.byteCounter >= YMODEM_CRC_SIZE)
+            {
+                inst->ymodem.state = SHELLMATTA_YMODEM_WAIT_FOR_START;
+
+                /** -# check CRC */
+                computedCrc = crc16Calc((const char*)inst->ymodem.packet.packetData,
+                                        inst->ymodem.packet.size);
+                if(computedCrc != inst->ymodem.packet.crc)
+                {
+                    shellmatta_ymodem_control(handle, YMODEM_NAK);
+                    ret = SHELLMATTA_ERROR;
+                }
+                else
+                {
+                    ret = processPacket(handle);
+                    if(SHELLMATTA_OK == ret)
+                    {
+                        if(0u != inst->ymodem.packetCounter)
+                        {
+                            /** -# Calculate the total bytes received */
+                            inst->ymodem.totalBytesReceived += inst->ymodem.packet.size;
+                        }
+                        if(true != inst->ymodem.pauseRequested)
+                        {
+                            /** -# ACK the successful packet reception */
+                            shellmatta_ymodem_control(handle, YMODEM_ACK);
+                            if(0u == inst->ymodem.packetCounter)
+                            {
+                                /** -# send addional CRC flag after packet 0 */
+                                shellmatta_ymodem_control(handle, YMODEM_CRC);
+                            }
+                        }
+                        inst->ymodem.packetCounter ++;
+                    }
+                }
+            }
+            break;
+        case SHELLMATTA_YMODEM_WAIT_FOR_END:
+
+            inst->ymodem.pollCyclesLeft = YMODEM_POLL_NUMBER;
+            if(YMODEM_EOT ==  byte)
+            {
+                /** -# ACK the successful file reception */
+                shellmatta_ymodem_control(handle, YMODEM_ACK);
+                shellmatta_ymodem_control(handle, YMODEM_CRC);
+            }
+            break;
+        default:
+            /** -# unexpected state - should never happen */
+            break;
+    }
+
+    return ret;
+}
+
+/**
+ * @brief                   Processes incoming bytes - checking for cancel requests and calling the state machine
+ * @param[in, out]  handle  shellmatta instance handle
+ * @param[in]       byte    received byte
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_ERROR (error during packet processing)
+*/
+shellmatta_retCode_t shellmatta_ymodem_processByte(shellmatta_handle_t handle, char byte)
+{
+    shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
+    shellmatta_retCode_t ret = SHELLMATTA_OK;
+
+    /** -# check if session is cancelled - accept only if ymodem is between packets */
+    if((('\x03' == byte) || (YMODEM_CA == byte)) && (inst->ymodem.state == SHELLMATTA_YMODEM_WAIT_FOR_START))
+    {
+        inst->ymodem.cancelCounter ++;
+
+        /** -# cancel after at least 2 cancel characters */
+        if(inst->ymodem.cancelCounter >= 2u)
+        {
+            /** -# explicitly reset ymodem with cancel, if it was active */
+            shellmatta_ymodem_reset(handle, true);
+
+            utils_terminateInput(inst);
+
+            ret = SHELLMATTA_ERROR;
+        }
+    }
+    else
+    {
+        inst->ymodem.cancelCounter = 0u;
+        ret = ymodem_stateMachine(handle, (uint8_t)byte);
+    }
+
+    return ret;
+}
+
+/**
+ * @brief                   Processes a poll from the application without data - shall be calles every second when
+ *                          no data is received
+ * @param[in, out]  handle  shellmatta instance handle
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_ERROR (error during packet processing)
+*/
+shellmatta_retCode_t shellmatta_ymodem_poll(shellmatta_handle_t handle)
+{
+    shellmatta_retCode_t ret = SHELLMATTA_ERROR;
+    shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
+
+    switch (inst->ymodem.state)
+    {
+    case SHELLMATTA_YMODEM_WAIT_FOR_START:
+        /** -# send ymodem symbol to start transmission */
+        shellmatta_ymodem_control(handle, YMODEM_CRC);
+        ret = SHELLMATTA_OK;
+        break;
+    case SHELLMATTA_YMODEM_WAIT_FOR_END:
+        if(inst->ymodem.pollCyclesLeft > 1u)
+        {
+            inst->ymodem.pollCyclesLeft --;
+        }
+        else
+        {
+            ret = SHELLMATTA_OK;
+            /** -# check if the received data matches the file size */
+            if((0u != inst->ymodem.fileSize) &&
+                ((inst->ymodem.totalBytesReceived < inst->ymodem.fileSize) ||
+                ((inst->ymodem.totalBytesReceived - inst->ymodem.fileSize) >= YMODEM_PACKET_SIZE_1K)))
+            {
+                ret = SHELLMATTA_ERROR;
+            }
+
+#ifdef SHELLMATTA_TRANSPORT
+            /** .-# reenable transport layer optional mode */
+            inst->transportLayer.suspendOptional = false;
+#endif
+
+            shellmatta_ymodem_reset(handle, false);
+            inst->ymodem.transmissionCompleteCallback(handle, ret);
+            (void)utils_terminateInput(inst);
+        }
+        break;
+    default:
+        /* nothing to do */
+        break;
+    }
+
+    return ret;
+}
+
+/**
+ * @brief                                           Initialise the ymodem prior to actually receiving data
+ * @param[in, out]  handle                          shellmatta instance handle
+ * @param[in]       recvBuffer                      pointer to the buffer to save the received payload in
+ * @param[in]       cancelCallback                  callback function for transmission being canceled
+ * @param[in]       recvHeaderCallback              callback function for header received
+ * @param[in]       recvPacketCallback              callback function for data packet received
+ * @param[in]       transmissionCompleteCallback    callback function for transmission complete
+ * @return          errorcode   #SHELLMATTA_OK
+ *                              #SHELLMATTA_USE_FAULT (param err)
+ * @note            Forces the transport layer in a fixed state when not mandatory
+*/
+shellmatta_retCode_t shellmatta_ymodem_init(shellmatta_handle_t             handle,
+                                            uint8_t*                        recvBuffer,
+                                            shellmatta_ymodem_cancel_t      cancelCallback,
+                                            shellmatta_ymodem_recvHeader_t  recvHeaderCallback,
+                                            shellmatta_ymodem_recvPacket_t  recvPacketCallback,
+                                            shellmatta_ymodem_complete_t    transmissionCompleteCallback)
+{
+    shellmatta_retCode_t ret = SHELLMATTA_OK;
+    shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic)
+        &&  (NULL               != cancelCallback)
+        &&  (NULL               != recvHeaderCallback)
+        &&  (NULL               != recvPacketCallback)
+        &&  (NULL               != transmissionCompleteCallback))
+    {
+
+        (void)memset((void *)&inst->ymodem, 0, sizeof(shellmatta_ymodem_t));
+
+        /** -# use instances buffer if no buffer is given */
+        if(NULL == recvBuffer)
+        {
+            if(inst->bufferSize < YMODEM_PACKET_SIZE_1K)
+            {
+                /** -# return use fault if buffer is too small */
+                ret = SHELLMATTA_USE_FAULT;
+            }
+            else
+            {
+                inst->ymodem.packet.packetData = (uint8_t*)inst->buffer;
+            }
+        }
+        else {
+            /** -# store buffer */
+            inst->ymodem.packet.packetData = recvBuffer;
+        }
+
+        /** -# init callbacks */
+        inst->ymodem.cancelCallback = cancelCallback;
+        inst->ymodem.recvHeaderCallback = recvHeaderCallback;
+        inst->ymodem.recvPacketCallback = recvPacketCallback;
+        inst->ymodem.transmissionCompleteCallback = transmissionCompleteCallback;
+
+        inst->ymodem.state = SHELLMATTA_YMODEM_WAIT_FOR_START;
+
+#ifdef SHELLMATTA_TRANSPORT
+        /** -# suspend the transport layer being optional while ymodem is running */
+        inst->transportLayer.suspendOptional = true;
+#endif
+
+        /** -# send initial ymodem symbol to start transmission */
+        shellmatta_ymodem_control(handle, YMODEM_CRC);
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+
+    return ret;
+}
+
+/**
+ * @brief                   pauses the shellmatta reception until #shellmatta_ymodem_resume resume is called
+ * @param[in, out]  handle  shellmatta instance handle
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_USE_FAULT (param err)
+ * @note                    This can be used when an application needs processing time before accepting the next packet
+*/
+shellmatta_retCode_t shellmatta_ymodem_pause(shellmatta_handle_t handle)
+{
+    shellmatta_retCode_t ret = SHELLMATTA_OK;
+    shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic))
+    {
+        inst->ymodem.pauseRequested = true;
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+
+    return ret;
+}
+
+/**
+ * @brief                   Resume the ymodem module
+ * @param[in, out]  handle  shellmatta instance handle
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_USE_FAULT (param err)
+ * @note                    This can be used when an application needs processing time before accepting the next packet
+*/
+shellmatta_retCode_t shellmatta_ymodem_resume(shellmatta_handle_t handle)
+{
+    shellmatta_retCode_t ret = SHELLMATTA_OK;
+    shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic))
+    {
+        /** -# ACK the successful packet reception */
+        shellmatta_ymodem_control(handle, YMODEM_ACK);
+        if(1u == inst->ymodem.packetCounter)
+        {
+            /** -# send addional CRC flag after packet 0 */
+            shellmatta_ymodem_control(handle, YMODEM_CRC);
+        }
+        inst->ymodem.pauseRequested = false;
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+
+    return ret;
+}
+
+/**
+ * @brief                   Resets the ymodem module
+ * @param[in, out]  handle  shellmatta instance handle
+ * @param[in]   doCancel    Set this flag to execute the cancel-callback function within the ymodem-reset function
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_USE_FAULT (param err)
+ * @note                    call this function to cancel an ongoing transmission
+*/
+shellmatta_retCode_t shellmatta_ymodem_cancel(shellmatta_handle_t handle, bool doCancel)
+{
+    shellmatta_retCode_t        ret = SHELLMATTA_OK;
+    const shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic))
+    {
+        shellmatta_ymodem_reset(handle, doCancel);
+        /* clear any possible leftover inputs */
+        utils_clearInput((shellmatta_instance_t*)handle);
+    }
+    else
+    {
+        ret = SHELLMATTA_USE_FAULT;
+    }
+
+    return ret;
+}
+
+/** @} */

+ 33 - 0
src/shellmatta_ymodem.h

@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2023 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    shellmatta_ymodem.h
+ * @brief   ymodem functions of shellmatta
+ * @authors Simon Fischer <dev@s-fischer.net> Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+/**
+ * @addtogroup shellmatta_transport
+ * @{
+ */
+
+#ifndef _SHELLMATTA_YMODEM_H_
+#define _SHELLMATTA_YMODEM_H_
+
+#include <stdint.h>
+#include "shellmatta.h"
+
+shellmatta_retCode_t shellmatta_ymodem_processByte( shellmatta_handle_t handle,
+                                                    char                byte);
+
+shellmatta_retCode_t shellmatta_ymodem_poll(        shellmatta_handle_t handle);
+
+#endif /* _SHELLMATTA_YMODEM_H_ */
+
+/** @} */

File diff suppressed because it is too large
+ 2282 - 1171
test/framework/catch.hpp


+ 31 - 38
test/integrationtest/test_integration.cpp

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,12 +11,12 @@
  * @brief   integration test implementation for some general functions
  * @brief   integration test implementation for some general functions
  * @author  Stefan Strobel <stefan.strobel@shimatta.net>
  * @author  Stefan Strobel <stefan.strobel@shimatta.net>
  */
  */
-
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 extern "C" {
 extern "C" {
 #include "shellmatta.h"
 #include "shellmatta.h"
 }
 }
 #include <string.h>
 #include <string.h>
+using Catch::Matchers::Equals;
 
 
 static uint32_t write_callCnt = 0u;
 static uint32_t write_callCnt = 0u;
 static char write_data[1024];
 static char write_data[1024];
@@ -65,7 +65,6 @@ TEST_CASE( "shellmatta empty function" ) {
     shellmatta_handle_t handle;
     shellmatta_handle_t handle;
     char buffer[1024];
     char buffer[1024];
     char historyBuffer[1024];
     char historyBuffer[1024];
-    char *dummyData =   (char*)"\r\nshellmatta->";
 
 
     shellmatta_doInit(  &inst,
     shellmatta_doInit(  &inst,
                         &handle,
                         &handle,
@@ -84,8 +83,7 @@ TEST_CASE( "shellmatta empty function" ) {
     shellmatta_processData(handle, (char*)"\r", 1);
     shellmatta_processData(handle, (char*)"\r", 1);
 
 
     CHECK( write_length == 14u);
     CHECK( write_length == 14u);
-    REQUIRE( strcmp(dummyData, write_data) == 0);
-
+    REQUIRE_THAT( write_data, Equals("\r\nshellmatta->") );
 }
 }
 
 
 TEST_CASE( "shellmatta heredoc test" ) {
 TEST_CASE( "shellmatta heredoc test" ) {
@@ -94,9 +92,6 @@ TEST_CASE( "shellmatta heredoc test" ) {
     shellmatta_handle_t handle;
     shellmatta_handle_t handle;
     char buffer[1024];
     char buffer[1024];
     char historyBuffer[1024];
     char historyBuffer[1024];
-    char *dummyData =   (char*)"do this ";
-    char *dummyStdin =  (char*)"asdf\r\n"
-                        "1234";
 
 
     shellmatta_doInit(  &inst,
     shellmatta_doInit(  &inst,
                         &handle,
                         &handle,
@@ -119,9 +114,9 @@ TEST_CASE( "shellmatta heredoc test" ) {
                                 , 33);
                                 , 33);
 
 
     CHECK( doSomethingStdinLength == 10u);
     CHECK( doSomethingStdinLength == 10u);
-    CHECK( strcmp(dummyStdin, doSomethingStdin) == 0);
+    CHECK_THAT( doSomethingStdin, Equals("asdf\r\n1234") );
     CHECK( doSomethingLength == 8u);
     CHECK( doSomethingLength == 8u);
-    REQUIRE( strcmp(dummyData, doSomethingArguments) == 0);
+    REQUIRE_THAT( doSomethingArguments, Equals("do this ") );
 }
 }
 
 
 TEST_CASE( "shellmatta heredoc test empty" ) {
 TEST_CASE( "shellmatta heredoc test empty" ) {
@@ -130,7 +125,6 @@ TEST_CASE( "shellmatta heredoc test empty" ) {
     shellmatta_handle_t handle;
     shellmatta_handle_t handle;
     char buffer[1024];
     char buffer[1024];
     char historyBuffer[1024];
     char historyBuffer[1024];
-    char *dummyData =   (char*)"do this ";
 
 
     shellmatta_doInit(  &inst,
     shellmatta_doInit(  &inst,
                         &handle,
                         &handle,
@@ -153,7 +147,7 @@ TEST_CASE( "shellmatta heredoc test empty" ) {
     CHECK( doSomethingStdinLength == 0u);
     CHECK( doSomethingStdinLength == 0u);
     CHECK( NULL == doSomethingStdin );
     CHECK( NULL == doSomethingStdin );
     CHECK( doSomethingLength == 8u);
     CHECK( doSomethingLength == 8u);
-    REQUIRE( strcmp(dummyData, doSomethingArguments) == 0);
+    REQUIRE_THAT( doSomethingArguments, Equals("do this ") );
 }
 }
 
 
 TEST_CASE( "shellmatta remove function" ) {
 TEST_CASE( "shellmatta remove function" ) {
@@ -185,8 +179,7 @@ TEST_CASE( "shellmatta remove function" ) {
     shellmatta_processData(handle, (char*)"?\r", 2);
     shellmatta_processData(handle, (char*)"?\r", 2);
 
 
     CHECK( write_length == strlen(dummyData));
     CHECK( write_length == strlen(dummyData));
-    CHECK( strcmp(dummyData, write_data) == 0);
-
+    CHECK_THAT( write_data, Equals(dummyData) );
 
 
     write_callCnt = 0u;
     write_callCnt = 0u;
     memset(write_data, 0, sizeof(write_data));
     memset(write_data, 0, sizeof(write_data));
@@ -199,8 +192,8 @@ TEST_CASE( "shellmatta remove function" ) {
 
 
     shellmatta_processData(handle, (char*)"? 564 321 56 465 46\r", 20);
     shellmatta_processData(handle, (char*)"? 564 321 56 465 46\r", 20);
 
 
-    CHECK( write_length == strlen(dummyData));
-    CHECK( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    CHECK_THAT(write_data, Catch::Matchers::Equals(dummyData));
 
 
     write_callCnt = 0u;
     write_callCnt = 0u;
     memset(write_data, 0, sizeof(write_data));
     memset(write_data, 0, sizeof(write_data));
@@ -213,8 +206,8 @@ TEST_CASE( "shellmatta remove function" ) {
                     "help  ?  help [command] - print help or usage information\r\n"
                     "help  ?  help [command] - print help or usage information\r\n"
                     "\r\nshellmatta->";
                     "\r\nshellmatta->";
 
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }
 
 
 TEST_CASE( "shellmatta reset no prompt" ) {
 TEST_CASE( "shellmatta reset no prompt" ) {
@@ -247,8 +240,8 @@ TEST_CASE( "shellmatta reset no prompt" ) {
     shellmatta_resetShell(handle, false);
     shellmatta_resetShell(handle, false);
     shellmatta_processData(handle, (char*)"?\r", 2);
     shellmatta_processData(handle, (char*)"?\r", 2);
 
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }
 
 
 TEST_CASE( "shellmatta reset with prompt" ) {
 TEST_CASE( "shellmatta reset with prompt" ) {
@@ -282,8 +275,8 @@ TEST_CASE( "shellmatta reset with prompt" ) {
     shellmatta_resetShell(handle, true);
     shellmatta_resetShell(handle, true);
     shellmatta_processData(handle, (char*)"?\r", 2);
     shellmatta_processData(handle, (char*)"?\r", 2);
 
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }
 
 
 TEST_CASE( "shellmatta reset no prompt history buffer" ) {
 TEST_CASE( "shellmatta reset no prompt history buffer" ) {
@@ -322,8 +315,8 @@ TEST_CASE( "shellmatta reset no prompt history buffer" ) {
     shellmatta_processData(handle, (char*)"\033[A", 3u);
     shellmatta_processData(handle, (char*)"\033[A", 3u);
     shellmatta_processData(handle, (char*)"?\r", 2);
     shellmatta_processData(handle, (char*)"?\r", 2);
 
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }
 
 
 TEST_CASE( "shellmatta reset no prompt heredoc" ) {
 TEST_CASE( "shellmatta reset no prompt heredoc" ) {
@@ -362,8 +355,8 @@ TEST_CASE( "shellmatta reset no prompt heredoc" ) {
     /* now the new command should be processed */
     /* now the new command should be processed */
     shellmatta_processData(handle, (char*)"?\r", 2);
     shellmatta_processData(handle, (char*)"?\r", 2);
 
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }
 
 
 TEST_CASE( "shellmatta configure disable echo" ) {
 TEST_CASE( "shellmatta configure disable echo" ) {
@@ -400,8 +393,8 @@ TEST_CASE( "shellmatta configure disable echo" ) {
     /* check with echo enabled */
     /* check with echo enabled */
     shellmatta_processData(handle, (char*)"help this is some dummy Text\r\n", 30u);
     shellmatta_processData(handle, (char*)"help this is some dummy Text\r\n", 30u);
 
 
-    CHECK( write_length == strlen(dummyData));
-    CHECK( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    CHECK_THAT(write_data, Catch::Matchers::Equals(dummyData));
 
 
     write_callCnt = 0u;
     write_callCnt = 0u;
     memset(write_data, 0, sizeof(write_data));
     memset(write_data, 0, sizeof(write_data));
@@ -414,8 +407,8 @@ TEST_CASE( "shellmatta configure disable echo" ) {
     /* check with echo disabled */
     /* check with echo disabled */
     shellmatta_processData(handle, (char*)"help this is some dummy Text\r\n", 30u);
     shellmatta_processData(handle, (char*)"help this is some dummy Text\r\n", 30u);
 
 
-    CHECK( write_length == strlen(dummyData2));
-    REQUIRE( strcmp(dummyData2, write_data) == 0);
+    CHECK(write_length == strlen(dummyData2));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData2));
 }
 }
 
 
 TEST_CASE( "shellmatta configure mode" ) {
 TEST_CASE( "shellmatta configure mode" ) {
@@ -425,9 +418,9 @@ TEST_CASE( "shellmatta configure mode" ) {
     shellmatta_retCode_t ret;
     shellmatta_retCode_t ret;
     char buffer[1024];
     char buffer[1024];
     char historyBuffer[1024];
     char historyBuffer[1024];
-    char *dummyData =   (char*)"\r\nCommand: meow this is some dum123456my Text not found\r\nshellmatta->";
+    char *dummyData =   (char*)"\r\nCommand not found!\r\nshellmatta->";
 
 
-    char *dummyData2 =   (char*)"\r\nCommand: meow this is some dum123456t not found\r\nshellmatta->";
+    char *dummyData2 =   (char*)"\r\nCommand not found!\r\nshellmatta->";
 
 
     shellmatta_doInit(  &inst,
     shellmatta_doInit(  &inst,
                         &handle,
                         &handle,
@@ -448,8 +441,8 @@ TEST_CASE( "shellmatta configure mode" ) {
     /* check with insert mode */
     /* check with insert mode */
     shellmatta_processData(handle, (char*)"meow this is some dummy Text\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D123456\r\n", 57u);
     shellmatta_processData(handle, (char*)"meow this is some dummy Text\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D123456\r\n", 57u);
 
 
-    CHECK( write_length == strlen(dummyData));
-    CHECK( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    CHECK_THAT(write_data, Catch::Matchers::Equals(dummyData));
 
 
     write_callCnt = 0u;
     write_callCnt = 0u;
     memset(write_data, 0, sizeof(write_data));
     memset(write_data, 0, sizeof(write_data));
@@ -462,8 +455,8 @@ TEST_CASE( "shellmatta configure mode" ) {
     /* check with echo disabled */
     /* check with echo disabled */
     shellmatta_processData(handle, (char*)"meow this is some dummy Text\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D123456\r\n", 57u);
     shellmatta_processData(handle, (char*)"meow this is some dummy Text\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D123456\r\n", 57u);
 
 
-    CHECK( write_length == strlen(dummyData2));
-    REQUIRE( strcmp(dummyData2, write_data) == 0);
+    CHECK(write_length == strlen(dummyData2));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData2));
 }
 }
 
 
 TEST_CASE( "shellmatta configure delimiter" ) {
 TEST_CASE( "shellmatta configure delimiter" ) {
@@ -508,6 +501,6 @@ TEST_CASE( "shellmatta configure delimiter" ) {
     /* check with echo disabled */
     /* check with echo disabled */
     shellmatta_processData(handle, (char*)"doSomething argument\n", 21u);
     shellmatta_processData(handle, (char*)"doSomething argument\n", 21u);
 
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }

+ 3 - 3
test/integrationtest/test_integration_busy.cpp

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -133,7 +133,7 @@ TEST_CASE( "shellmatta busy 1" ) {
     CHECK( 10u  == busyCallCnt);
     CHECK( 10u  == busyCallCnt);
     CHECK( 1u   == notBusyCallCnt );
     CHECK( 1u   == notBusyCallCnt );
     CHECK( write_length == strlen(dummyData));
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }
 
 
 TEST_CASE( "shellmatta busy suspend with continuous mode" ) {
 TEST_CASE( "shellmatta busy suspend with continuous mode" ) {
@@ -179,5 +179,5 @@ TEST_CASE( "shellmatta busy suspend with continuous mode" ) {
     CHECK( 6u  == busyCallCnt);
     CHECK( 6u  == busyCallCnt);
     CHECK( 0u   == notBusyCallCnt );
     CHECK( 0u   == notBusyCallCnt );
     CHECK( write_length == strlen(dummyData));
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }

+ 5 - 5
test/integrationtest/test_integration_continue.cpp

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -74,7 +74,7 @@ static shellmatta_retCode_t continueCmdFct(shellmatta_handle_t handle, const cha
 
 
     /* -# the arguments shall stay the same on every call - data is transferred per stdin */
     /* -# the arguments shall stay the same on every call - data is transferred per stdin */
     CHECK(length == 28u);
     CHECK(length == 28u);
-    CHECK(strcmp(arguments, "continue some arguments meow") == 0);
+    CHECK_THAT(arguments, Catch::Matchers::Equals("continue some arguments meow"));
 
 
     contCallCnt ++;
     contCallCnt ++;
 
 
@@ -148,7 +148,7 @@ TEST_CASE( "shellmatta continue 1" ) {
     CHECK( 0u  == busyCallCnt);
     CHECK( 0u  == busyCallCnt);
     CHECK( 10u  == contCallCnt);
     CHECK( 10u  == contCallCnt);
     CHECK( write_length == strlen(dummyData));
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }
 
 
 TEST_CASE( "shellmatta continue cancel" ) {
 TEST_CASE( "shellmatta continue cancel" ) {
@@ -207,7 +207,7 @@ TEST_CASE( "shellmatta continue cancel" ) {
     CHECK( 0u  == busyCallCnt);
     CHECK( 0u  == busyCallCnt);
     CHECK( 5u  == contCallCnt);
     CHECK( 5u  == contCallCnt);
     CHECK( write_length == strlen(dummyData));
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }
 
 
 TEST_CASE( "shellmatta continue suspend with busy mode" ) {
 TEST_CASE( "shellmatta continue suspend with busy mode" ) {
@@ -280,5 +280,5 @@ TEST_CASE( "shellmatta continue suspend with busy mode" ) {
     CHECK( 10u  == contCallCnt);
     CHECK( 10u  == contCallCnt);
     CHECK( 2u  == busyCallCnt);
     CHECK( 2u  == busyCallCnt);
     CHECK( write_length == strlen(dummyData));
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }

+ 11 - 11
test/integrationtest/test_integration_help.cpp

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -55,7 +55,7 @@ static shellmatta_cmd_t cmd3 = {(char*)"cmd3", (char*)"", (char*)"", (char*)"",
 
 
 SCENARIO("Test the help function")
 SCENARIO("Test the help function")
 {
 {
-    GIVEN("An initialized and empty Shellmatte instance")
+    GIVEN("An initialized and empty Shellmatta instance")
     {
     {
         shellmatta_instance_t inst;
         shellmatta_instance_t inst;
         shellmatta_handle_t handle;
         shellmatta_handle_t handle;
@@ -97,7 +97,7 @@ SCENARIO("Test the help function")
                                                         "shellmatta->";
                                                         "shellmatta->";
                 CHECK(writeFct_fake.call_count == 23);
                 CHECK(writeFct_fake.call_count == 23);
                 CHECK(strlen(response) == fakeWriteLength);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
             }
         }
         }
 
 
@@ -122,7 +122,7 @@ SCENARIO("Test the help function")
                                                         "shellmatta->";
                                                         "shellmatta->";
                 CHECK(writeFct_fake.call_count == 20);
                 CHECK(writeFct_fake.call_count == 20);
                 CHECK(strlen(response) == fakeWriteLength);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
             }
         }
         }
     }
     }
@@ -130,7 +130,7 @@ SCENARIO("Test the help function")
 
 
 SCENARIO("Test if the help command prints the usage correctly")
 SCENARIO("Test if the help command prints the usage correctly")
 {
 {
-    GIVEN("An initialized and empty Shellmatte instance")
+    GIVEN("An initialized and empty Shellmatta instance")
     {
     {
         shellmatta_instance_t inst;
         shellmatta_instance_t inst;
         shellmatta_handle_t handle;
         shellmatta_handle_t handle;
@@ -172,7 +172,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "\r\nshellmatta->";
                                                         "\r\nshellmatta->";
                 CHECK(writeFct_fake.call_count == 22);
                 CHECK(writeFct_fake.call_count == 22);
                 CHECK(strlen(response) == fakeWriteLength);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
             }
         }
         }
 
 
@@ -197,7 +197,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "\r\nshellmatta->";
                                                         "\r\nshellmatta->";
                 CHECK(writeFct_fake.call_count == 19);
                 CHECK(writeFct_fake.call_count == 19);
                 CHECK(strlen(response) == fakeWriteLength);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
             }
         }
         }
 
 
@@ -219,7 +219,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "\r\nshellmatta->";
                                                         "\r\nshellmatta->";
                 CHECK(writeFct_fake.call_count == 15);
                 CHECK(writeFct_fake.call_count == 15);
                 CHECK(strlen(response) == fakeWriteLength);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
             }
         }
         }
 
 
@@ -242,7 +242,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "\r\nshellmatta->";
                                                         "\r\nshellmatta->";
                 CHECK(writeFct_fake.call_count == 15);
                 CHECK(writeFct_fake.call_count == 15);
                 CHECK(strlen(response) == fakeWriteLength);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
             }
         }
         }
 
 
@@ -264,7 +264,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "\r\nshellmatta->";
                                                         "\r\nshellmatta->";
                 CHECK(writeFct_fake.call_count == 45);
                 CHECK(writeFct_fake.call_count == 45);
                 CHECK(strlen(response) == fakeWriteLength);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
             }
         }
         }
 
 
@@ -289,7 +289,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "shellmatta->";
                                                         "shellmatta->";
                 CHECK(writeFct_fake.call_count == 28);
                 CHECK(writeFct_fake.call_count == 28);
                 CHECK(strlen(response) == fakeWriteLength);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
             }
         }
         }
     }
     }

+ 4 - 4
test/integrationtest/test_integration_history.cpp

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -62,7 +62,7 @@ char *commandSequence[] =
 
 
 SCENARIO("Test the history buffer with a fixed sequence of commands in there")
 SCENARIO("Test the history buffer with a fixed sequence of commands in there")
 {
 {
-    GIVEN("An initialized Shellmatte instance with some commands already in the history buffer")
+    GIVEN("An initialized Shellmatta instance with some commands already in the history buffer")
     {
     {
         shellmatta_instance_t inst;
         shellmatta_instance_t inst;
         shellmatta_handle_t handle;
         shellmatta_handle_t handle;
@@ -244,7 +244,7 @@ SCENARIO("Test the history buffer with a fixed sequence of commands in there")
 
 
 SCENARIO("Test how the history buffer handles more commands than fits inside the buffer")
 SCENARIO("Test how the history buffer handles more commands than fits inside the buffer")
 {
 {
-    GIVEN("An initialized Shellmatte instance with some commands already in the history buffer")
+    GIVEN("An initialized Shellmatta instance with some commands already in the history buffer")
     {
     {
         shellmatta_instance_t inst;
         shellmatta_instance_t inst;
         shellmatta_handle_t handle;
         shellmatta_handle_t handle;
@@ -352,7 +352,7 @@ SCENARIO("Test how the history buffer handles more commands than fits inside the
 
 
 SCENARIO("Test if the history buffer stores changes done during navigating")
 SCENARIO("Test if the history buffer stores changes done during navigating")
 {
 {
-    GIVEN("An initialized Shellmatte instance with some commands already in the history buffer")
+    GIVEN("An initialized Shellmatta instance with some commands already in the history buffer")
     {
     {
         shellmatta_instance_t inst;
         shellmatta_instance_t inst;
         shellmatta_handle_t handle;
         shellmatta_handle_t handle;

+ 3 - 3
test/integrationtest/test_integration_opt.cpp

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -175,7 +175,7 @@ TEST_CASE( "shellmatta option parser 1" ) {
     CHECK( lenE == 4u );
     CHECK( lenE == 4u );
     CHECK( (cntB == 0u && cntC == 0u && cntD == 0u && cntF == 0u && cntDef == 0u) );
     CHECK( (cntB == 0u && cntC == 0u && cntD == 0u && cntF == 0u && cntDef == 0u) );
     CHECK( write_length == strlen(dummyData));
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }
 
 
 TEST_CASE( "shellmatta option parser 2 - ignore \"--\"" ) {
 TEST_CASE( "shellmatta option parser 2 - ignore \"--\"" ) {
@@ -213,6 +213,6 @@ TEST_CASE( "shellmatta option parser 2 - ignore \"--\"" ) {
     CHECK( lenE == 4u );
     CHECK( lenE == 4u );
     CHECK( (cntB == 0u && cntC == 0u && cntD == 0u && cntF == 0u && cntDef == 0u) );
     CHECK( (cntB == 0u && cntC == 0u && cntD == 0u && cntF == 0u && cntDef == 0u) );
     CHECK( write_length == strlen(dummyData));
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }
 
 

+ 3 - 3
test/integrationtest/test_integration_optLong.cpp

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -185,7 +185,7 @@ TEST_CASE( "shellmatta long option parser 1" ) {
     CHECK( lenE == 4u );
     CHECK( lenE == 4u );
     CHECK( (cntB == 0u && cntC == 0u && cntD == 0u && cntF == 0u && cntDef == 0u) );
     CHECK( (cntB == 0u && cntC == 0u && cntD == 0u && cntF == 0u && cntDef == 0u) );
     CHECK( write_length == strlen(dummyData));
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }
 
 
 TEST_CASE( "shellmatta long option parser 2" ) {
 TEST_CASE( "shellmatta long option parser 2" ) {
@@ -225,5 +225,5 @@ TEST_CASE( "shellmatta long option parser 2" ) {
     CHECK( cntDef == 2u );
     CHECK( cntDef == 2u );
     CHECK( lenDef == 2u );
     CHECK( lenDef == 2u );
     CHECK( write_length == strlen(dummyData));
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 }

+ 226 - 0
test/integrationtest/test_integration_ymodem.cpp

@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_integration_ymodem.cpp
+ * @brief   integration test implementation for the ymodem module of the shellmatta
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+#include "test/framework/catch.hpp"
+extern "C" {
+    #include "shellmatta.h"
+}
+#include <string.h>
+
+static uint32_t write_callCnt = 0u;
+static char write_data[1024];
+static uint32_t write_length;
+static bool cancelled;
+static uint32_t receivedFileSize;
+static char *receivedFileName;
+static uint8_t *receivedPacketData;
+static uint32_t receivedPacketSize;
+static uint32_t receivedPacketNum;
+static bool transmissionCompleted;
+static shellmatta_retCode_t transmissionCompletedResult;
+
+static shellmatta_retCode_t writeFct(const char* data, uint32_t length)
+{
+    write_callCnt ++;
+    while((length > 0) && (write_length < sizeof(write_data)))
+    {
+        write_data[write_length] = *data;
+        data ++;
+        length --;
+        write_length ++;
+    }
+
+    return SHELLMATTA_OK;
+}
+
+static void ymodemCallbackCancel(shellmatta_handle_t handle)
+{
+    (void)handle;
+    cancelled = true;
+}
+
+static void ymodemCallbackReceiveHeader(shellmatta_handle_t handle,
+                                        uint32_t fileSize,
+                                        char* fileName) {
+    
+    shellmatta_ymodem_pause(handle);
+
+    receivedFileSize = fileSize;
+    receivedFileName = fileName;
+}
+
+static void ymodemCallbackReceivePacket(shellmatta_handle_t handle,
+                                        uint8_t *data,
+                                        uint32_t packetSize,
+                                        uint32_t packetNum)
+{
+    (void)handle;
+    receivedPacketData = data;
+    receivedPacketSize = packetSize;
+    receivedPacketNum = packetNum;
+    return;
+}
+
+static void ymodemCallbackTransmissionComplete(shellmatta_handle_t handle, 
+                                               shellmatta_retCode_t result)
+{
+    (void)handle;
+    transmissionCompleted = true;
+    transmissionCompletedResult = result;
+}
+
+SCENARIO("Test successful transmissions in ymodem")
+{
+    GIVEN("An initialized and empty Shellmatta instance")
+    {
+        shellmatta_retCode_t ret;
+        shellmatta_instance_t inst;
+        shellmatta_handle_t handle;
+        char buffer[1024u];
+        char historyBuffer[1024u];
+
+        cancelled = false;
+        receivedFileSize = 0u;
+        receivedFileName = NULL;
+        receivedPacketData = NULL;
+        receivedPacketSize = 0u;
+        receivedPacketNum = 0u;
+        transmissionCompleted = false;
+        transmissionCompletedResult = SHELLMATTA_USE_FAULT;
+
+        CHECK(SHELLMATTA_OK == shellmatta_doInit(   &inst,
+                                                    &handle,
+                                                    buffer,
+                                                    sizeof(buffer),
+                                                    historyBuffer,
+                                                    sizeof(historyBuffer),
+                                                    "shellmatta->",
+                                                    NULL,
+                                                    writeFct));
+
+        WHEN("Starting a ymodem session and passing data.")
+        {
+            ret = shellmatta_ymodem_init(handle,
+                                         NULL,
+                                         ymodemCallbackCancel,
+                                         ymodemCallbackReceiveHeader,
+                                         ymodemCallbackReceivePacket,
+                                         ymodemCallbackTransmissionComplete);
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("ymodem is polled")
+            {
+                write_length = 0u;
+                ret = shellmatta_processData(handle, (char *)"", 0);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("ymodem sends C starting character")
+                {
+                    CHECK(write_length == 1u);
+                    REQUIRE(write_data[0] == 'C');
+                }
+            }
+
+            AND_WHEN("Packet 0 is sent")
+            {
+                char packet0[1029u] = "\x02\x00\xFF" "Filename\0" "1044 \0";
+                packet0[1027u] = '\x55';
+                packet0[1028u] = '\x2E';
+                char packet1[133u] = "\x01\x01\xFE" "Some data...\0";
+                packet1[131u] = '\x09';
+                packet1[132u] = '\x75';
+                char packet2[1029u] = "\x02\x02\xFD" "Some additional data\0";
+                packet2[1027u] = '\xCD';
+                packet2[1028u] = '\xD4';
+
+                write_length = 0u;
+                ret = shellmatta_processData(handle, packet0, sizeof(packet0));
+                CHECK(ret == SHELLMATTA_OK);
+
+                CHECK(write_length == 0u);
+
+                ret = shellmatta_ymodem_resume(handle);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("ymodem calls the received header callback and sends an ACK")
+                {
+                    CHECK_THAT(receivedFileName, Catch::Matchers::Equals("Filename"));
+                    CHECK(receivedFileSize == 1044);
+
+                    CHECK(write_length == 2u);
+                    CHECK(write_data[0] == '\x06');
+                    REQUIRE(write_data[1] == 'C');
+
+                    AND_WHEN("The rest of the packets is sent")
+                    {
+                        write_length = 0u;
+                        ret = shellmatta_processData(handle, packet1, sizeof(packet1));
+                        CHECK(ret == SHELLMATTA_OK);
+
+                        CHECK_THAT((char *)receivedPacketData, Catch::Matchers::Equals("Some data..."));
+                        CHECK(receivedPacketSize == 128);
+                        CHECK(receivedPacketNum == 1);
+
+                        receivedPacketData = NULL;
+                        receivedPacketSize = 0u;
+                        receivedPacketNum = 0u;
+
+                        ret = shellmatta_processData(handle, packet2, sizeof(packet2));
+                        CHECK(ret == SHELLMATTA_OK);
+
+                        CHECK_THAT((char *)receivedPacketData, Catch::Matchers::Equals("Some additional data"));
+                        CHECK(receivedPacketSize == 20);
+                        CHECK(receivedPacketNum == 2);
+
+                        ret = shellmatta_processData(handle, (char*)"\x04", 1);
+                        CHECK(ret == SHELLMATTA_OK);
+
+                        ret = shellmatta_processData(handle, (char*)"\x04", 1);
+                        CHECK(ret == SHELLMATTA_OK);
+
+                        ret = shellmatta_processData(handle, (char*)"\x04", 1);
+                        CHECK(ret == SHELLMATTA_OK);
+
+                        CHECK(write_length == 8u);
+                        CHECK(write_data[0] == '\x06');
+                        CHECK(write_data[1] == '\x06');
+                        CHECK(write_data[2] == '\x06');
+                        CHECK(write_data[3] == 'C');
+                        CHECK(write_data[4] == '\x06');
+                        CHECK(write_data[5] == 'C');
+                        CHECK(write_data[6] == '\x06');
+                        CHECK(write_data[7] == 'C');
+
+                        write_length = 0u;
+                        memset(write_data, 0, sizeof(write_data));
+
+                        ret = shellmatta_processData(handle, (char*)"", 0);
+                        CHECK(ret == SHELLMATTA_OK);
+                        ret = shellmatta_processData(handle, (char*)"", 0);
+                        CHECK(ret == SHELLMATTA_OK);
+
+                        THEN("The transmission finishes successfully")
+                        {
+                            CHECK(write_length == 14);
+                            CHECK_THAT(write_data, Catch::Matchers::Equals("\r\nshellmatta->"));
+
+                            CHECK(transmissionCompleted == true);
+                            REQUIRE(transmissionCompletedResult == SHELLMATTA_OK);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 847 - 0
test/integrationtest_auth/test_integration_auth.cpp

@@ -0,0 +1,847 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_integration_auth.cpp
+ * @brief   integration test implementation for the authentication functions
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+#include "test/framework/catch.hpp"
+extern "C" {
+#include "shellmatta.h"
+}
+#include <string.h>
+
+static uint32_t write_callCnt = 0u;
+static char write_data[1024];
+static uint32_t write_length;
+static uint32_t privateCallCount;
+static uint32_t logUserId;
+static shellmatta_auth_log_event_t logEvent;
+
+#define TEST_SHELLMATTA_SETUP   shellmatta_retCode_t ret;                   \
+                                shellmatta_instance_t inst;                 \
+                                shellmatta_handle_t handle;                 \
+                                char buffer[1024] = {0};                    \
+                                char historyBuffer[1024] = {0};             \
+                                                                            \
+                                shellmatta_doInit(  &inst,                  \
+                                                    &handle,                \
+                                                    buffer,                 \
+                                                    sizeof(buffer),         \
+                                                    historyBuffer,          \
+                                                    sizeof(historyBuffer),  \
+                                                    "shellmatta->",         \
+                                                    NULL,                   \
+                                                    writeFct);              \
+                                                                            \
+                                write_callCnt = 0u;                         \
+                                memset(write_data, 0, sizeof(write_data));  \
+                                write_length = 0u;                          \
+                                privateCallCount = 0u;                      \
+                                logUserId = 255u;                           \
+                                logEvent = SHELLMATTA_AUTH_EVENT_NONE;      \
+                                                                            \
+                                shellmatta_addCmd(handle, &publicCmd);      \
+                                shellmatta_addCmd(handle, &privateCmd);
+
+#define TEST_SHELLMATTA_AUTH_SETUP  shellmatta_auth_user_t userList[] = {                                                   \
+                                        {1, false, "shimatta", "12345678"},                                                 \
+                                        {2, false, "not_shimatta", "87654321"},                                             \
+                                        {3, true, "root", "rootpw"}                                                         \
+                                    };                                                                                      \
+                                                                                                                            \
+                                    uint32_t privateCmdPerms[] = {1};                                                       \
+                                    shellmatta_auth_perm_t permList[] = {                                                   \
+                                        {"private", privateCmdPerms, sizeof(privateCmdPerms)/sizeof(privateCmdPerms[0])},   \
+                                        {"additional", privateCmdPerms, sizeof(privateCmdPerms)/sizeof(privateCmdPerms[0])} \
+                                    };                                                                                      \
+                                                                                                                            \
+                                    shellmatta_auth_init(handle, userList, 3, permList, 2, false, NULL, NULL);
+
+static shellmatta_retCode_t writeFct(const char* data, uint32_t length)
+{
+    write_callCnt ++;
+    while((length > 0) && (write_length < sizeof(write_data)))
+    {
+        write_data[write_length] = *data;
+        data ++;
+        length --;
+        write_length ++;
+    }
+
+    return SHELLMATTA_OK;
+}
+
+static shellmatta_retCode_t publicCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    (void)      handle;
+    (void)      arguments;
+    (void)      length;
+    shellmatta_retCode_t ret = SHELLMATTA_OK;
+
+    return ret;
+}
+shellmatta_cmd_t publicCmd = {(char*)"public", (char*)"p", NULL, NULL, publicCmdFct, NULL, NULL};
+
+
+static shellmatta_retCode_t privateCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    (void)      handle;
+    (void)      arguments;
+    (void)      length;
+
+    privateCallCount ++;
+
+    return SHELLMATTA_OK;
+}
+shellmatta_cmd_t privateCmd = {(char*)"private", (char*)"r", NULL, NULL, privateCmdFct, NULL, NULL};
+
+
+static shellmatta_retCode_t additionalCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    (void)      handle;
+    (void)      arguments;
+    (void)      length;
+
+    return SHELLMATTA_OK;
+}
+shellmatta_cmd_t additionalCmd = {(char*)"additional", (char*)"a", NULL, NULL, additionalCmdFct, NULL, NULL};
+
+
+SCENARIO("Check help auth uninitialized") {
+    GIVEN("An initialized shellmatta instance without initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        WHEN("The help command is called") {
+
+            ret = shellmatta_processData(handle, (char*)"help\r", 5);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The help command prints all commands - without login/logout.") {
+
+                char *dummyData =   (char*) "help\r\n"
+                                            "help     ?  help [command] - print help or usage information\r\n"
+                                            "private  r\r\n"
+                                            "public   p\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+            }
+        }
+    }
+}
+
+SCENARIO("Check auth unauthorized") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("The help command is called") {
+
+            ret = shellmatta_processData(handle, (char*)"help\r", 5);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The help command prints only public commands.") {
+
+                char *dummyData =   (char*) "help\r\n"
+                                            "help    ?   help [command] - print help or usage information\r\n"
+                                            "login   li  Login command\r\n"
+                                            "logout  lo  Logout command\r\n"
+                                            "public  p\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+            }
+        }
+
+        WHEN("A private function is called") {
+
+            ret = shellmatta_processData(handle, (char*)"private\r", 8);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The command is not executed.") {
+
+                char *dummyData =   (char*) "private\r\n"
+                                            "\r\n"
+                                            "Command: private not found\r\n"
+                                            "shellmatta->";
+
+                CHECK(privateCallCount == 0u);
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+            }
+        }
+
+        WHEN("Autocomplete is triggered") {
+
+            ret = shellmatta_processData(handle, (char*)"\t\t", 2);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("Only public commands are shown.") {
+
+                char *dummyData =   (char*) "\r\n"
+                                            "help    ?    login    li    logout    lo    public    p    \r\n"
+                                            "shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+            }
+        }
+    }
+}
+
+SCENARIO("Check authorized") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("The user shimatta is logged in") {
+
+            ret = shellmatta_auth_login(handle, 1);
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("The help command is called") {
+
+                ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The help command prints all commands.") {
+
+                    char *dummyData =   (char*) "help\r\n"
+                                                "help     ?   help [command] - print help or usage information\r\n"
+                                                "login    li  Login command\r\n"
+                                                "logout   lo  Logout command\r\n"
+                                                "private  r\r\n"
+                                                "public   p\r\n"
+                                                "\r\n"
+                                                "shimatta@shellmatta->";
+
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                    AND_THEN("The shimatta user is logged in") {
+                        char usernameBuffer[16] = {0};
+                        uint32_t usernameBufferLength = sizeof(usernameBuffer);
+
+                        CHECK(1 == shellmatta_auth_getLoggedInUserId(handle));
+                        ret = shellmatta_auth_getLoggedInUserName(handle, usernameBuffer, &usernameBufferLength);
+                        CHECK(ret == SHELLMATTA_OK);
+                        CHECK(usernameBufferLength == strlen("shimatta"));
+                        REQUIRE_THAT(usernameBuffer, Catch::Matchers::Equals("shimatta"));
+                    }
+                }
+
+                AND_WHEN("The user is logged out using the logout command") {
+                    write_length = 0;
+                    memset(write_data, 0, sizeof(write_data));
+                    ret = shellmatta_processData(handle, (char*)"logout\r", 7);
+                    CHECK(ret == SHELLMATTA_OK);
+
+                    THEN("The Shellmatta prints the logout message") {
+                        char *dummyData =   (char*) "logout\r\n"
+                                                    "good bye\r\n\r\n"
+                                                    "shellmatta->";
+                        CHECK(write_length == strlen(dummyData));
+                        REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                    }
+
+                    AND_WHEN("The help command is called") {
+
+                        write_length = 0;
+                        memset(write_data, 0, sizeof(write_data));
+                        ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                        CHECK(ret == SHELLMATTA_OK);
+
+                        THEN("The help command prints only public commands.") {
+
+                            char *dummyData =   (char*) "help\r\n"
+                                                        "help    ?   help [command] - print help or usage information\r\n"
+                                                        "login   li  Login command\r\n"
+                                                        "logout  lo  Logout command\r\n"
+                                                        "public  p\r\n"
+                                                        "\r\n"
+                                                        "shellmatta->";
+
+                            CHECK(write_length == strlen(dummyData));
+                            REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                        }
+                    }
+                }
+            }
+            AND_WHEN("A private function is called") {
+
+                ret = shellmatta_processData(handle, (char*)"private\r", 8);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The command executed.") {
+
+                    char *dummyData =   (char*) "private\r\n"
+                                                "\r\n"
+                                                "shimatta@shellmatta->";
+
+                    CHECK(privateCallCount == 1u);
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                }
+            }
+        }
+        WHEN("The user not_shellmatta is logged in") {
+
+            ret = shellmatta_auth_login(handle, 2);
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("The help command is called") {
+
+                ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The help command prints not all commands.") {
+
+                    char *dummyData =   (char*) "help\r\n"
+                                                "help    ?   help [command] - print help or usage information\r\n"
+                                                "login   li  Login command\r\n"
+                                                "logout  lo  Logout command\r\n"
+                                                "public  p\r\n"
+                                                "\r\n"
+                                                "not_shimatta@shellmatta->";
+
+                    CHECK(ret == SHELLMATTA_OK);
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                    AND_THEN("The not_shimatta user is logged in") {
+                        char usernameBuffer[16] = {0};
+                        uint32_t usernameBufferLength = sizeof(usernameBuffer);
+
+                        CHECK(2 == shellmatta_auth_getLoggedInUserId(handle));
+                        ret = shellmatta_auth_getLoggedInUserName(handle, usernameBuffer, &usernameBufferLength);
+                        CHECK(ret == SHELLMATTA_OK);
+                        CHECK(usernameBufferLength == strlen("not_shimatta"));
+                        REQUIRE_THAT(usernameBuffer, Catch::Matchers::Equals("not_shimatta"));
+                    }
+                }
+            }
+            AND_WHEN("A private function is called") {
+
+                ret = shellmatta_processData(handle, (char*)"private\r", 8);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The command is not executed.") {
+
+                    char *dummyData =   (char*) "private\r\n"
+                                                "\r\n"
+                                                "Command: private not found\r\n"
+                                                "not_shimatta@shellmatta->";
+
+                    CHECK(privateCallCount == 0u);
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                }
+            }
+        }
+        WHEN("The user root is logged in") {
+            ret = shellmatta_auth_login(handle, 3);
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("The help command is called") {
+
+                ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The help command prints all commands.") {
+
+                    char *dummyData =   (char*) "help\r\n"
+                                                "help     ?   help [command] - print help or usage information\r\n"
+                                                "login    li  Login command\r\n"
+                                                "logout   lo  Logout command\r\n"
+                                                "private  r\r\n"
+                                                "public   p\r\n"
+                                                "\r\n"
+                                                "root@shellmatta->";
+
+                    CHECK(ret == SHELLMATTA_OK);
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Check login command with privileged user") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("The user shimatta logs in interactively using the correct credentials") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r\n"
+                                                        "shimatta\r\n"
+                                                        "12345678\r", 26);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed - password is hidden") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "shimatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "shimatta@shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta logs in interactively using wrong credentials") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r\n"
+                                                        "shimatta\r\n"
+                                                        "11111111\r", 26);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("A warning message is printed") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "shimatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "username or password is wrong\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is not logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta logs in interactively manipulating the input") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r"
+                                                        "shimg\batta\r"
+                                                        "1h" "\x7f" "2346\033[D5\033[C78\b8\r", 36);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed - password is hidden") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "shimg\033[1D\033[K\033[s\033[uatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "shimatta@shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta logs in passing the credentials none interactively") {
+
+            ret = shellmatta_processData(handle, (char*)"login -u shimatta -p 12345678\r", 30);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed") {
+                char *dummyData =   (char*) "login -u shimatta -p 12345678\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "shimatta@shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta logs in passing the credentials half interactively") {
+
+            ret = shellmatta_processData(handle, (char*)"login -u shimatta\r12345678\r", 27);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed - password is hidden") {
+                char *dummyData =   (char*) "login -u shimatta\r\n\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "shimatta@shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta tries to login non interactively without username") {
+
+            ret = shellmatta_processData(handle, (char*)"login -p 12345678\r", 18);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("A warning message is printed") {
+                char *dummyData =   (char*) "login -p 12345678\r\n"
+                                            "Missing username\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is not logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta tries to login using the wrong options") {
+
+            ret = shellmatta_processData(handle, (char*)"login -o meow\r", 14);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("A warning message is printed") {
+                char *dummyData =   (char*) "login -o meow\r\n"
+                                            "Unknown option\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is not logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Check login command with unprivileged user") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("The user not_shimatta logs in interactively using the correct credentials") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r"
+                                                        "not_shimatta\r"
+                                                        "87654321\r", 28);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed - password is hidden") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "not_shimatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "not_shimatta@shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The not_shimatta user is logged in") {
+                    REQUIRE(2 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Check adding commands after the authentication is initialized") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("A command is added") {
+
+            ret = shellmatta_addCmd(handle, &additionalCmd);
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("The help command is called") {
+
+                ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The additional command is not shown as it requires login") {
+                    char *dummyData =   (char*) "help\r\n"
+                                                "help    ?   help [command] - print help or usage information\r\n"
+                                                "login   li  Login command\r\n"
+                                                "logout  lo  Logout command\r\n"
+                                                "public  p\r\n"
+                                                "\r\n"
+                                                "shellmatta->";
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                    AND_WHEN("The root user is logged in") {
+                        write_length = 0;
+                        memset(write_data, 0, sizeof(write_data));
+
+                        ret = shellmatta_auth_login(handle, 3);
+                        CHECK(ret == SHELLMATTA_OK);
+
+                        AND_WHEN("The help command is called") {
+
+                            ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                            CHECK(ret == SHELLMATTA_OK);
+
+                            THEN("all commands are shown including the additional command") {
+                                char *dummyData =   (char*) "help\r\n"
+                                                            "additional  a\r\n"
+                                                            "help        ?   help [command] - print help or usage information\r\n"
+                                                            "login       li  Login command\r\n"
+                                                            "logout      lo  Logout command\r\n"
+                                                            "private     r\r\n"
+                                                            "public      p\r\n"
+                                                            "\r\n"
+                                                            "root@shellmatta->";
+                                CHECK(write_length == strlen(dummyData));
+                                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+shellmatta_retCode_t customLogin(const uint32_t userId, const char *password)
+{
+    if ((userId == 1) && (0 == strcmp(password, "123456789")))
+    {
+        return SHELLMATTA_OK;
+    }
+    else if ((userId == 2) && (0 == strcmp(password, "876543210")))
+    {
+        return SHELLMATTA_OK;
+    }
+
+    return SHELLMATTA_ERROR;
+}
+
+void logFct(const uint32_t userId, shellmatta_auth_log_event_t event)
+{
+    logUserId = userId;
+    logEvent = event;
+}
+
+SCENARIO("Check custom login") {
+    GIVEN("An initialized shellmatta instance with initialized auth with custom login and log") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        shellmatta_auth_user_t userList[] = {
+            {1, false, "shimatta", NULL},
+            {2, false, "not_shimatta", NULL}
+        };
+
+        uint32_t privateCmdPerms[] = {1};
+        shellmatta_auth_perm_t permList[] = {
+            {"private", privateCmdPerms, sizeof(privateCmdPerms)/sizeof(privateCmdPerms[0])}
+        };
+
+        shellmatta_auth_init(handle, userList, 2, permList, 1, false, customLogin, logFct);
+
+        WHEN("The user shimatta logs in interactively using the correct credentials") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r"
+                                                        "shimatta\r"
+                                                        "123456789\r", 25);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed - password is hidden") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "shimatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "shimatta@shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+
+                    AND_WHEN("The shimatta user logs out") {
+                        ret = shellmatta_processData(handle, (char*)"logout\r", 7);
+                        CHECK(ret == SHELLMATTA_OK);
+                        THEN("The logout event is logged") {
+                            CHECK(1 == logUserId);
+                            REQUIRE(SHELLMATTA_AUTH_EVENT_LOGOUT == logEvent);
+                        }
+                    }
+
+                }
+
+                AND_THEN("The login event is logged") {
+                    CHECK(1 == logUserId);
+                    REQUIRE(SHELLMATTA_AUTH_EVENT_LOGIN == logEvent);
+                }
+            }
+        }
+
+        WHEN("The user shimatta logs in interactively using the wrong credentials") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r"
+                                                        "shimatta\r"
+                                                        "12345678\r", 24);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("Login error message is printed") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "shimatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "username or password is wrong\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is not logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+
+                AND_THEN("The failed login event is logged") {
+                    CHECK(1 == logUserId);
+                    REQUIRE(SHELLMATTA_AUTH_EVENT_LOGIN_FAILED == logEvent);
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Check custom login with custom login function") {
+    GIVEN("An initialized shellmatta instance with initialized auth with custom login and log") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        shellmatta_auth_user_t userList[] = {
+            {1, false, "shimatta", NULL},
+            {2, false, "not_shimatta", NULL}
+        };
+
+        uint32_t privateCmdPerms[] = {1};
+        shellmatta_auth_perm_t permList[] = {
+            {"private", privateCmdPerms, sizeof(privateCmdPerms)/sizeof(privateCmdPerms[0])}
+        };
+
+        shellmatta_auth_init(handle, userList, 2, permList, 1, true, customLogin, logFct);
+
+        WHEN("The help command is called") {
+
+            ret = shellmatta_processData(handle, (char*)"help\r", 5);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("There is no login command") {
+                char *dummyData =   (char*) "help\r\n"
+                                            "help    ?   help [command] - print help or usage information\r\n"
+                                            "logout  lo  Logout command\r\n"
+                                            "public  p\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+            }
+        }
+    }
+}
+
+SCENARIO("Check if passwords can be changed") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("The password of the shellmatta user is changed") {
+
+            ret = shellmatta_auth_chpasswd(handle, "shimatta", "new_password");
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("The user shimatta logs in passing the new credentials") {
+
+                ret = shellmatta_processData(handle, (char*)"login -u shimatta -p new_password\r", 34);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+
+            }
+
+            AND_WHEN("The user shimatta logs in passing the old credentials") {
+
+                ret = shellmatta_processData(handle, (char*)"login -u shimatta -p 12345678\r", 30);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The shimatta user is not logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Use functions with wrong parameters") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("Trying to log in user 0") {
+
+            ret = shellmatta_auth_login(handle, 0);
+
+            THEN("Shellmatta returns SHELLMATTA_ERROR") {
+                CHECK(ret == SHELLMATTA_ERROR);
+                AND_THEN("No user is logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+    }
+}

+ 11 - 0
test/integrationtest_auth/test_main.cpp

@@ -0,0 +1,11 @@
+// 010-TestCase.cpp
+
+// Let Catch provide main():
+#define CATCH_CONFIG_MAIN
+
+#include "test/framework/catch.hpp"
+extern "C" {
+#include "test/framework/fff.h"
+DEFINE_FFF_GLOBALS
+}
+

+ 506 - 0
test/integrationtest_transport/test_integration_transport.cpp

@@ -0,0 +1,506 @@
+/*
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_integration_transport.cpp
+ * @brief   integration test implementation for the transport layer
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+#include "test/framework/catch.hpp"
+extern "C" {
+#include "shellmatta.h"
+}
+#include <string.h>
+
+static uint32_t write_callCnt = 0u;
+static char write_data[1024];
+static uint32_t write_length;
+static const char *doSomethingArguments;
+static uint32_t doSomethingLength;
+static char *doSomethingStdin;
+static uint32_t doSomethingStdinLength;
+
+static shellmatta_retCode_t writeFct(const char* data, uint32_t length)
+{
+    write_callCnt ++;
+    while((length > 0) && (write_length < sizeof(write_data)))
+    {
+        write_data[write_length] = *data;
+        data ++;
+        length --;
+        write_length ++;
+    }
+
+    return SHELLMATTA_OK;
+}
+
+static shellmatta_retCode_t doSomething(shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    doSomethingArguments = arguments;
+    doSomethingLength = length;
+
+    shellmatta_read(handle, &doSomethingStdin, &doSomethingStdinLength);
+
+    shellmatta_printf(handle, "%s - length: %u", arguments, length);
+    return SHELLMATTA_OK;
+}
+shellmatta_cmd_t doSomethingCmd =
+{
+    (char*)"doSomething",
+    (char*)"do",
+    (char*)"Function does something",
+    (char*)"use me, please",
+    doSomething,
+    NULL
+};
+
+static shellmatta_retCode_t largePayload(shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    uint32_t i;
+
+    for (i = 0u; i < 8; i ++)
+    {
+        shellmatta_printf(handle, "This is my very very very very large payload\r\n");
+    }
+
+    (void)arguments;
+    (void)length;
+
+    return SHELLMATTA_OK;
+}
+shellmatta_cmd_t largePayloadCmd =
+{
+    (char*)"largePayload",
+    (char*)"lp",
+    (char*)"Function returns large payload",
+    (char*)"i have much to say",
+    largePayload,
+    NULL
+};
+
+static shellmatta_retCode_t largePayloadAtOnce(shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    shellmatta_write(handle,
+                    (char*)"This is my very very very very large payload\r\n"
+                    "This is my very very very very large payload\r\n"
+                    "This is my very very very very large payload\r\n"
+                    "This is my very very very very large payload\r\n"
+                    "This is my very very very very large payload\r\n"
+                    "This is my very very very very large payload\r\n"
+                    "This is my very very very very large payload\r\n"
+                    "This is my very very very very large payload\r\n",
+                    46u*8u);
+
+    (void)arguments;
+    (void)length;
+
+    return SHELLMATTA_OK;
+}
+shellmatta_cmd_t largePayloadAtOnceCmd =
+{
+    (char*)"largePayload",
+    (char*)"lp",
+    (char*)"Function returns large payload",
+    (char*)"i have much to say",
+    largePayloadAtOnce,
+    NULL
+};
+
+SCENARIO("Integration test of Transport layer", "[integration, transport]")
+{
+    GIVEN("Shellmatta up and running with one command")
+    {
+        shellmatta_instance_t inst;
+        shellmatta_handle_t handle;
+        char buffer[1024];
+        char historyBuffer[1024];
+
+        shellmatta_doInit(  &inst,
+                            &handle,
+                            buffer,
+                            sizeof(buffer),
+                            historyBuffer,
+                            sizeof(historyBuffer),
+                            "shellmatta->",
+                            NULL,
+                            writeFct);
+        shellmatta_addCmd(handle, &doSomethingCmd);
+
+        write_callCnt = 0u;
+        memset(write_data, 0, sizeof(write_data));
+        write_length = 0u;
+
+        WHEN("Invalid CRC is passed")
+        {
+            /* check with invalid payload */
+            shellmatta_processData(handle, (char*)"\x01\x01\x00\x16\x00\x00\x00\x00"
+                                                  "doSomething argument\r\n"
+                                                  "\x00\x00\x00\x00", 34u);
+
+            THEN("Shellmatta responds with CRC error")
+            {
+                char *dummyData =   (char*)"crc error\r\n\r\nshellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE(strcmp(dummyData, write_data) == 0);
+            }
+        }
+
+        WHEN("Valid CRC is passed")
+        {
+            /* check with valid payload - disable echo to reduce cluttering */
+            shellmatta_configure(handle, SHELLMATTA_MODE_INSERT, false, '\r');
+            shellmatta_processData(handle, (char*)"\x01\x01\x00\x16\x00\x00\x00\x00"
+                                                "doSomething argument\r\n"
+                                                "\x7b\x49\xfa\x72", 34u);
+            THEN("The shellmatta responds to the command")
+            {
+                char *dummyData =   (char*)"\x01\x01\x00\x2f\x00\x01\x01\x01"
+                                           "doSomething argument - length: 20"
+                                           "\r\n"
+                                           "shellmatta->"
+                                           "\x4c\x9f\xd9\xa7";
+
+                CHECK(write_length == 59u);
+                REQUIRE(memcmp(write_data, dummyData, 59) == 0);
+            }
+        }
+
+        WHEN("Large payload is written by shellmatta in steps")
+        {
+            /* add large payload command */
+            shellmatta_addCmd(handle, &largePayloadCmd);
+
+            /* check with valid payload - disable echo to reduce cluttering */
+            shellmatta_configure(handle, SHELLMATTA_MODE_INSERT, false, '\r');
+            shellmatta_processData(handle, (char*)"\x01\x01\x00\x0e\x00\x00\x00\x00"
+                                                "largePayload\r\n"
+                                                "\xad\x33\x31\xcc", 26u);
+            THEN("The shellmatta responds multiple telegrams")
+            {
+                char *dummyData =   (char*)"\x01\x01\x00\xff\x00\x01\x01\x01"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very"
+                                           "\x00\x9c\x41\xe1"
+                                           "\x01\x01\x00\x7f\x00\x01\x01\x02"
+                                           " very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "\r\n"
+                                           "shellmatta->"
+                                           "\x39\x75\x0d\x6b";
+
+                CHECK(write_length == 406u);
+                REQUIRE(memcmp(write_data, dummyData, 406u) == 0);
+            }
+        }
+
+        WHEN("Large payload is written by shellmatta at once")
+        {
+            /* add large payload command */
+            shellmatta_addCmd(handle, &largePayloadAtOnceCmd);
+
+            /* check with valid payload - disable echo to reduce cluttering */
+            shellmatta_configure(handle, SHELLMATTA_MODE_INSERT, false, '\r');
+            shellmatta_processData(handle, (char*)"\x01\x01\x00\x0e\x00\x00\x00\x00"
+                                                "largePayload\r\n"
+                                                "\xad\x33\x31\xcc", 26u);
+            THEN("The shellmatta responds multiple telegrams")
+            {
+                char *dummyData =   (char*)"\x01\x01\x00\xff\x00\x01\x01\x01"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very"
+                                           "\x00\x9c\x41\xe1"
+                                           "\x01\x01\x00\x7f\x00\x01\x01\x02"
+                                           " very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "This is my very very very very large payload\r\n"
+                                           "\r\n"
+                                           "shellmatta->"
+                                           "\x39\x75\x0d\x6b";
+
+                CHECK(write_length == 406u);
+                REQUIRE(memcmp(write_data, dummyData, 406u) == 0);
+            }
+        }
+
+        WHEN("manual mode is used after transport layer mode")
+        {
+            /* check with valid payload - disable echo to reduce cluttering */
+            shellmatta_configure(handle, SHELLMATTA_MODE_INSERT, false, '\r');
+            shellmatta_processData(handle, (char*)"\x01\x01\x00\x16\x00\x00\x00\x00"
+                                                "doSomething argument\r\n"
+                                                "\x7b\x49\xfa\x72", 34u);
+            shellmatta_processData(handle, (char*)"doSomething argument\r\n", 22u);
+            THEN("The shellmatta responds to the command")
+            {
+                char *dummyData =   (char*)"\x01\x01\x00\x2f\x00\x01\x01\x01"
+                                           "doSomething argument - length: 20"
+                                           "\r\n"
+                                           "shellmatta->"
+                                           "\x4c\x9f\xd9\xa7"
+                                           "doSomething argument - length: 20\r\n"
+                                           "shellmatta->";
+
+                CHECK(write_length == 106);
+                REQUIRE(memcmp(write_data, dummyData, 106) == 0);
+            }
+        }
+
+        WHEN("disabling auto flush")
+        {
+            /* check with valid payload - disable echo to reduce cluttering */
+            shellmatta_configure(handle, SHELLMATTA_MODE_INSERT, false, '\r');
+            shellmatta_transport_configure(handle, false, true, NULL, NULL);
+            shellmatta_processData(handle, (char*)"\x01\x01\x00\x16\x00\x00\x00\x00"
+                                                "doSomething argument\r\n"
+                                                "\x7b\x49\xfa\x72", 34u);
+
+            THEN("The Shellmatta does not respond")
+            {
+                CHECK(write_length == 0u);
+
+                AND_WHEN("The flush method is called")
+                {
+                    shellmatta_transport_flush(handle);
+                    THEN("The shellmatta returns the data")
+                    {
+
+                        char *dummyData =   (char*)"\x01\x01\x00\x2f\x00\x01\x01\x01"
+                                                "doSomething argument - length: 20"
+                                                "\r\n"
+                                                "shellmatta->"
+                                                "\x4c\x9f\xd9\xa7";
+
+                        CHECK(write_length == 59u);
+                        REQUIRE(memcmp(write_data, dummyData, 59) == 0);
+                    }
+                }
+            }
+        }
+
+        WHEN("Sequence counter is requested")
+        {
+            /* request sequence counter */
+            shellmatta_processData(handle, (char*)"\x01\x01\x01\x00\x00\x00\x00\x00"
+                                                  "\xc4\xa3\x07\xe6", 12u);
+            THEN("The valid Sequence counter is returned")
+            {
+                char *dummyData =   (char*)"\x01\x01\x81\x00\x00\x01\x01\x01"
+                                           "\xb5\xcd\x78\xde";
+
+                CHECK(write_length == 12);
+                REQUIRE(memcmp(write_data, dummyData, 12) == 0);
+            }
+            AND_WHEN("Sequence counter is requested again")
+            {
+                /* request sequence counter again */
+                write_callCnt = 0u;
+                memset(write_data, 0, sizeof(write_data));
+                write_length = 0u;
+                shellmatta_processData(handle, (char*)"\x01\x01\x01\x00\x00\x00\x00\x00"
+                                                      "\xc4\xa3\x07\xe6", 12u);
+                THEN("The next Sequence counter is returned")
+                {
+                    char *dummyData =   (char*)"\x01\x01\x81\x00\x00\x01\x02\x02"
+                                               "\x07\xe9\x7a\xa7";
+
+                    CHECK(write_length == 12);
+                    REQUIRE(memcmp(write_data, dummyData, 12) == 0);
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Integration test of Transport layer with mandatory transport layer", "[integration, transport]")
+{
+    GIVEN("Shellmatta up and running with one command")
+    {
+        shellmatta_instance_t inst;
+        shellmatta_handle_t handle;
+        shellmatta_retCode_t ret;
+        char buffer[1024];
+        char historyBuffer[1024];
+
+        shellmatta_doInit(  &inst,
+                            &handle,
+                            buffer,
+                            sizeof(buffer),
+                            historyBuffer,
+                            sizeof(historyBuffer),
+                            "shellmatta->",
+                            NULL,
+                            writeFct);
+        shellmatta_addCmd(handle, &doSomethingCmd);
+
+        write_callCnt = 0u;
+        memset(write_data, 0, sizeof(write_data));
+        write_length = 0u;
+
+        WHEN("The tansport layer is set to mandatory") {
+            ret = shellmatta_transport_configure(handle, true, false, NULL, NULL);
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("manual mode is used after transport layer mode")
+            {
+                /* check with valid payload - disable echo to reduce cluttering */
+                shellmatta_configure(handle, SHELLMATTA_MODE_INSERT, false, '\r');
+                shellmatta_processData(handle, (char*)"\x01\x01\x00\x16\x00\x00\x00\x00"
+                                                    "doSomething argument\r\n"
+                                                    "\x7b\x49\xfa\x72", 34u);
+                shellmatta_processData(handle, (char*)"doSomething argument\r\n", 22u);
+                THEN("The shellmatta responds only to the first command - and waits for a new telegram to start")
+                {
+                    char *dummyData =   (char*)"\x01\x01\x00\x2f\x00\x01\x01\x01"
+                                            "doSomething argument - length: 20"
+                                            "\r\n"
+                                            "shellmatta->"
+                                            "\x4c\x9f\xd9\xa7";
+
+                    CHECK(write_length == 59);
+                    CHECK(memcmp(write_data, dummyData, write_length) == 0);
+                    REQUIRE(inst.transportLayer.state == SHELLMATTA_TRANSPORT_STATE_WAIT);
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Integration test of Transport layer - reset transport layer", "[integration, transport]")
+{
+    GIVEN("Shellmatta up and running with one command")
+    {
+        shellmatta_instance_t inst;
+        shellmatta_handle_t handle;
+        shellmatta_retCode_t ret;
+        char buffer[1024];
+        char historyBuffer[1024];
+
+        shellmatta_doInit(  &inst,
+                            &handle,
+                            buffer,
+                            sizeof(buffer),
+                            historyBuffer,
+                            sizeof(historyBuffer),
+                            "shellmatta->",
+                            NULL,
+                            writeFct);
+        shellmatta_addCmd(handle, &doSomethingCmd);
+
+        write_callCnt = 0u;
+        memset(write_data, 0, sizeof(write_data));
+        write_length = 0u;
+
+        WHEN("The tansport layer is set to mandatory") {
+            ret = shellmatta_transport_configure(handle, true, false, NULL, NULL);
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("A partial telegram is passed")
+            {
+                /* check with valid payload - disable echo to reduce cluttering */
+                shellmatta_configure(handle, SHELLMATTA_MODE_INSERT, false, '\r');
+                shellmatta_processData(handle, (char*)"\x01\x01\x00\x16\x00\x00\x00\x00"
+                                                    "doSomething argument", 28u);
+                THEN("The transport layer is in state SHELLMATTA_TRANSPORT_STATE_GET_PAYLOAD")
+                {
+                    CHECK(write_length == 0);
+                    CHECK_THAT(inst.transportLayer.inPacket.payload, Catch::Matchers::Equals("doSomething argument"));
+                    REQUIRE(inst.transportLayer.state == SHELLMATTA_TRANSPORT_STATE_GET_PAYLOAD);
+
+                    AND_WHEN("The transport layer is resetted")
+                    {
+                        ret = shellmatta_transport_reset(handle);
+                        CHECK(ret == SHELLMATTA_OK);
+
+                        THEN("The transport layer is in state wait again and no payload is set") {
+                            CHECK(write_length == 0);
+                            CHECK(inst.transportLayer.inPacket.payload[0] == '\0');
+                            REQUIRE(inst.transportLayer.state == SHELLMATTA_TRANSPORT_STATE_WAIT);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Integration test of Transport layer - addressing", "[integration, transport]")
+{
+    GIVEN("Shellmatta up and running with one command - transport layer with UUID")
+    {
+        shellmatta_instance_t inst;
+        shellmatta_handle_t handle;
+        shellmatta_retCode_t ret;
+        char buffer[1024];
+        char historyBuffer[1024];
+
+        shellmatta_doInit(  &inst,
+                            &handle,
+                            buffer,
+                            sizeof(buffer),
+                            historyBuffer,
+                            sizeof(historyBuffer),
+                            "shellmatta->",
+                            NULL,
+                            writeFct);
+        shellmatta_addCmd(handle, &doSomethingCmd);
+
+        ret = shellmatta_transport_configure(handle, true, false, (uint8_t *)"123456789012345", NULL);
+        CHECK(ret == SHELLMATTA_OK);
+
+        write_callCnt = 0u;
+        memset(write_data, 0, sizeof(write_data));
+        write_length = 0u;
+
+        WHEN("The shellmatta is searched using an invalid range") {
+            ret = shellmatta_transport_configure(handle, true, false, NULL, NULL);
+
+            /* check with valid payload - disable echo to reduce cluttering */
+            shellmatta_configure(handle, SHELLMATTA_MODE_INSERT, false, '\r');
+            shellmatta_processData(handle, (char*)"\x01\x01\x03\x20\x00\x00\x00\x00"
+                                                    "234567890123456\0"
+                                                    "345678901234567\0"
+                                                    "\x93\x0c\xc3\x04", 44u);
+
+            THEN("The shellmatta does not respond")
+            {
+                CHECK(write_length == 0);
+                CHECK(inst.transportLayer.sequenceH2S == 1);
+                REQUIRE(inst.transportLayer.state == SHELLMATTA_TRANSPORT_STATE_WAIT);
+            }
+        }
+
+        WHEN("The shellmatta is searched using a valid range") {
+            ret = shellmatta_transport_configure(handle, true, false, NULL, NULL);
+
+            /* check with valid payload - disable echo to reduce cluttering */
+            shellmatta_configure(handle, SHELLMATTA_MODE_INSERT, false, '\r');
+            shellmatta_processData(handle, (char*)"\x01\x01\x03\x20\x00\x00\x00\x00"
+                                                    "123456789012344\0"
+                                                    "345678901234567\0"
+                                                    "\x9b\x69\x59\x5f", 44u);
+
+            THEN("The shellmatta does not respond")
+            {
+                CHECK(write_length == 28);
+                CHECK(inst.transportLayer.sequenceH2S == 1);
+                REQUIRE(inst.transportLayer.state == SHELLMATTA_TRANSPORT_STATE_WAIT);
+            }
+        }
+    }
+}

+ 11 - 0
test/integrationtest_transport/test_main.cpp

@@ -0,0 +1,11 @@
+// 010-TestCase.cpp
+
+// Let Catch provide main():
+#define CATCH_CONFIG_MAIN
+
+#include "test/framework/catch.hpp"
+extern "C" {
+#include "test/framework/fff.h"
+DEFINE_FFF_GLOBALS
+}
+

+ 16 - 3
test/unittest/shellmatta/test_shellmatta_doInit.cpp

@@ -1,14 +1,27 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_shellmatta_doInit.c
+ * @brief   unittest for shellmatta doInit function - currently not implemented
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta.c"
 #include "src/shellmatta.c"
+#include "src/shellmatta_transport.c"
+#include "src/shellmatta_crc.c"
 #include <string.h>
 #include <string.h>
 
 
 TEST_CASE( "shellmatta dummy" ) {
 TEST_CASE( "shellmatta dummy" ) {
 
 
     shellmatta_instance_t inst;
     shellmatta_instance_t inst;
-    //shellmatta_handle_t handle;
     inst.inputCount = 0u;
     inst.inputCount = 0u;
 
 
-    //shellmatta_doInit(&inst, &handle, )
-
     REQUIRE( inst.inputCount == 0u);
     REQUIRE( inst.inputCount == 0u);
 }
 }

+ 14 - 2
test/unittest/shellmatta_autocomplete/test_autocomplete_run.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_autodomplete_run.c
+ * @brief   unittest for shellmatta aurocomplete_run function - currently not implemented
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_autocomplete.c"
 #include "src/shellmatta_autocomplete.c"
 #include <string.h>
 #include <string.h>
@@ -7,7 +21,5 @@ TEST_CASE( "shellmatta_autocomplete_run dummy" ) {
     shellmatta_instance_t inst;
     shellmatta_instance_t inst;
     inst.inputCount = 0u;
     inst.inputCount = 0u;
 
 
-    autocomplete_run(&inst);
-
     REQUIRE( inst.inputCount == 0u);
     REQUIRE( inst.inputCount == 0u);
 }
 }

+ 35 - 0
test/unittest/shellmatta_crc/test_crc16Fast.cpp

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_crc16Fast.h
+ * @brief   unittest implementation for crc16 fast function
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+#include "test/framework/catch.hpp"
+#include <string.h>
+
+#define SHELLMATTA_TRANSPORT
+#undef SHELLMATTA_YMODEM_CRC_NO_LOOKUP
+#include "test_crc_data.h"
+#include "src/shellmatta_crc.c"
+
+TEST_CASE( "shellmatta_crc crc16Fast" ) {
+
+    uint32_t crc = crc16Fast((char*)"123456789", 9, crc16Table);
+
+    REQUIRE(crc == 0x31C3u);
+}
+
+TEST_CASE( "shellmatta_crc crc16Fast - more data" ) {
+
+    uint32_t crc = crc16Fast((char*)data, sizeof(data), crc16Table);
+
+    REQUIRE(crc == data_crc_16);
+}

+ 35 - 0
test/unittest/shellmatta_crc/test_crc16Slow.cpp

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_crc16Slow.h
+ * @brief   unittest implementation for crc16 slow function
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+#include "test/framework/catch.hpp"
+#include <string.h>
+
+#define SHELLMATTA_TRANSPORT
+#define SHELLMATTA_YMODEM_CRC_NO_LOOKUP
+#include "test_crc_data.h"
+#include "src/shellmatta_crc.c"
+
+TEST_CASE("shellmatta_crc crc16Slow" ) {
+
+    uint32_t crc = crc16Slow((char*)"123456789", 9);
+
+    REQUIRE(crc == 0x31C3u);
+}
+
+TEST_CASE("shellmatta_crc crc16Slow - more data") {
+
+    uint32_t crc = crc16Slow((char*)data, sizeof(data));
+
+    REQUIRE(crc == data_crc_16);
+}

+ 35 - 0
test/unittest/shellmatta_crc/test_crc32Fast.cpp

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_crc32Fast.h
+ * @brief   unittest implementation for crc32 fast function
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+#include "test/framework/catch.hpp"
+#include <string.h>
+
+#define SHELLMATTA_TRANSPORT
+#undef SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP
+#include "test_crc_data.h"
+#include "src/shellmatta_crc.c"
+
+TEST_CASE( "shellmatta_crc crc32Fast" ) {
+
+    uint32_t crc = crc32Fast((char*)"123456789", 9, crc32Table);
+
+    REQUIRE(crc == 0xCBF43926u);
+}
+
+TEST_CASE( "shellmatta_crc crc32Fast - more data" ) {
+
+    uint32_t crc = crc32Fast((char*)data, sizeof(data), crc32Table);
+
+    REQUIRE(crc == data_crc_32);
+}

+ 35 - 0
test/unittest/shellmatta_crc/test_crc32Slow.cpp

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_crc16Slow.h
+ * @brief   unittest implementation for crc16 slow function
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+#include "test/framework/catch.hpp"
+#include <string.h>
+
+#define SHELLMATTA_TRANSPORT
+#define SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP
+#include "test_crc_data.h"
+#include "src/shellmatta_crc.c"
+
+TEST_CASE( "shellmatta_crc crc32Slow" ) {
+
+    uint32_t crc = crc32Slow((char*)"123456789", 9);
+
+    REQUIRE( crc == 0xCBF43926u);
+}
+
+TEST_CASE( "shellmatta_crc crc32Slow - more data" ) {
+
+    uint32_t crc = crc32Slow((char*)data, sizeof(data));
+
+    REQUIRE(crc == data_crc_32);
+}

+ 280 - 0
test/unittest/shellmatta_crc/test_crc_data.h

@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_crc_data.h
+ * @brief   test data for the crc implementation
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+#ifndef _TEST_CRC_DATA_H_
+#define _TEST_CRC_DATA_H_
+
+const uint8_t data[] = {
+    0xB8, 0xCA, 0xFD, 0x92, 0x55, 0x6A, 0x2F, 0x22, 0x5B, 0xB3, 0x0F, 0xEE, 0x69, 0x4E, 0x83, 0x4A,
+    0x2A, 0x4E, 0x5E, 0x29, 0x58, 0x2C, 0xE0, 0x21, 0x1C, 0x92, 0x76, 0x7C, 0x87, 0xB1, 0x00, 0x18,
+    0x25, 0xFE, 0x49, 0x24, 0xDC, 0x8F, 0x82, 0x09, 0x9D, 0x83, 0xE6, 0x01, 0xA3, 0x12, 0xDF, 0x39,
+    0x2E, 0x4A, 0x3F, 0x75, 0x0D, 0x8D, 0x7E, 0x9D, 0x5B, 0x48, 0x01, 0x9C, 0x3D, 0x92, 0xD5, 0x81,
+    0x93, 0x97, 0x7C, 0xD1, 0xDE, 0xFD, 0x97, 0x6D, 0xD3, 0xB1, 0x9D, 0x84, 0x55, 0xCB, 0xF1, 0x69,
+    0xDC, 0x6B, 0xF1, 0x5D, 0x78, 0xC6, 0xDE, 0x87, 0xF5, 0xC9, 0xBE, 0xB8, 0xDA, 0xFE, 0x83, 0xC0,
+    0x3D, 0xA0, 0x2B, 0x61, 0xA9, 0x10, 0x9F, 0x25, 0x92, 0x06, 0x7A, 0xB2, 0x17, 0x43, 0x0E, 0x59,
+    0x02, 0x95, 0x4B, 0xF4, 0x56, 0x70, 0x54, 0x60, 0xCF, 0x79, 0xF5, 0x16, 0x26, 0xBA, 0x3D, 0xBC,
+    0x03, 0x59, 0xEF, 0xB1, 0xEC, 0x1A, 0x90, 0xDC, 0x93, 0xFE, 0x46, 0x5F, 0x60, 0xB8, 0xCC, 0xD8,
+    0x12, 0xE0, 0x27, 0x61, 0xCA, 0x13, 0xF6, 0x7D, 0xF5, 0x6D, 0x72, 0x93, 0xCC, 0xFD, 0x7D, 0xAF,
+    0x69, 0x32, 0x64, 0xB1, 0xBA, 0x60, 0x23, 0x12, 0xB2, 0xC8, 0x50, 0xEF, 0x8E, 0xDC, 0x05, 0x0A,
+    0x20, 0x99, 0x65, 0xDE, 0x5A, 0x31, 0xA1, 0x08, 0x96, 0x6E, 0x85, 0x9E, 0x5A, 0x74, 0xFE, 0x27,
+    0x97, 0xD3, 0x2C, 0x67, 0x8E, 0x1A, 0xD6, 0x1A, 0xF0, 0x48, 0x6A, 0x5E, 0xE2, 0xD6, 0xD7, 0x69,
+    0xE8, 0x41, 0xE8, 0xBA, 0xF0, 0x3C, 0xF4, 0xFF, 0x03, 0xFA, 0x02, 0x3B, 0x45, 0x3E, 0xC2, 0x07,
+    0x58, 0x17, 0xEB, 0xEA, 0x42, 0x77, 0xE9, 0x1B, 0x72, 0x15, 0xE8, 0x38, 0x82, 0x3F, 0xA5, 0xC0,
+    0xC6, 0x8C, 0x96, 0x59, 0xDA, 0x9A, 0x51, 0x32, 0xB4, 0x43, 0x41, 0x02, 0xE4, 0xF1, 0x0C, 0x85,
+    0x1B, 0x0C, 0x4B, 0x6A, 0x17, 0x96, 0x63, 0x11, 0x80, 0x7A, 0xA7, 0x9F, 0x76, 0x25, 0x16, 0x2F,
+    0xBC, 0x63, 0x5B, 0x31, 0xCB, 0xA8, 0xE2, 0x46, 0x43, 0x2D, 0x1E, 0x1F, 0x71, 0x93, 0x66, 0x2B,
+    0xF6, 0xF4, 0xF9, 0x25, 0xB1, 0x8E, 0x0D, 0xCB, 0x88, 0x78, 0x02, 0x4A, 0xAC, 0x0B, 0x13, 0x2B,
+    0x71, 0xE3, 0x26, 0xCC, 0xD9, 0xB4, 0x91, 0xB6, 0x6F, 0x54, 0xF9, 0x52, 0x0D, 0xA2, 0x9B, 0xD7,
+    0x9F, 0x47, 0xA5, 0x71, 0x19, 0x67, 0x74, 0xEC, 0x1A, 0xC2, 0xDD, 0x82, 0xF6, 0xE6, 0xCC, 0x7D,
+    0x2D, 0x5B, 0xE9, 0xCB, 0x7E, 0x01, 0x0D, 0xCE, 0x1B, 0x03, 0xB5, 0xEF, 0xB9, 0x0D, 0xBC, 0xBD,
+    0x72, 0xAE, 0x04, 0xB6, 0xBA, 0x1E, 0xEB, 0xEC, 0xEA, 0xC0, 0x9E, 0x25, 0x07, 0x22, 0xB3, 0x40,
+    0xDE, 0x51, 0x9B, 0xDF, 0x98, 0xC7, 0xCA, 0xB0, 0x4E, 0x3E, 0xBC, 0xDC, 0x5E, 0x38, 0x1E, 0x62,
+    0x6E, 0x09, 0xD0, 0x72, 0x65, 0xA5, 0x85, 0x16, 0xD1, 0x8D, 0x2E, 0xA5, 0x7A, 0x9B, 0x7E, 0xE5,
+    0x17, 0x7E, 0x38, 0xCF, 0x69, 0x30, 0x00, 0x52, 0x32, 0xB9, 0xF8, 0x98, 0xC7, 0xFB, 0x59, 0xA1,
+    0x39, 0x6A, 0xC6, 0x36, 0x4E, 0xE0, 0x1E, 0x89, 0xCD, 0xF9, 0xFB, 0x07, 0xCC, 0xA3, 0x29, 0x31,
+    0x0D, 0xCD, 0xBE, 0x77, 0x98, 0x5B, 0xAB, 0x7C, 0x16, 0xDE, 0xDA, 0x30, 0xA2, 0xA3, 0x4A, 0xA7,
+    0x18, 0x18, 0xA5, 0xA5, 0x0E, 0xA8, 0x51, 0x39, 0xFE, 0x87, 0xF7, 0xE6, 0x5E, 0x02, 0xF0, 0x3B,
+    0x99, 0x60, 0x31, 0xC5, 0x2F, 0x5C, 0x88, 0xCB, 0x6C, 0xCB, 0x56, 0x49, 0x83, 0xF0, 0x11, 0xF8,
+    0xF9, 0x8D, 0x35, 0x7A, 0xA0, 0xCC, 0x82, 0xED, 0xA8, 0x6F, 0x98, 0x70, 0x75, 0xF1, 0x57, 0x70,
+    0x3A, 0x8A, 0x97, 0xBC, 0x9C, 0x3A, 0x1D, 0xB2, 0xCC, 0x52, 0xE4, 0x1B, 0xE2, 0x15, 0x12, 0x6A,
+    0x6A, 0x77, 0x3C, 0x81, 0x64, 0x0B, 0xD5, 0x40, 0x34, 0x9D, 0xD9, 0x66, 0x3A, 0x1F, 0x24, 0x91,
+    0x11, 0xD5, 0xFB, 0x72, 0xB1, 0xF0, 0xB0, 0x76, 0xF0, 0xF7, 0x7F, 0x72, 0x19, 0xBB, 0xF6, 0x26,
+    0xA0, 0xBA, 0x89, 0x9A, 0x20, 0x1B, 0x33, 0xA2, 0x30, 0xB0, 0x37, 0x1D, 0xBB, 0xAC, 0x62, 0xB1,
+    0xE4, 0xFE, 0x6C, 0x13, 0xA5, 0x6D, 0x4D, 0x30, 0xB7, 0xF4, 0xA8, 0xAD, 0x68, 0xFD, 0xA9, 0xAC,
+    0x72, 0x6C, 0xEC, 0xBB, 0xFB, 0xA5, 0x4B, 0x56, 0x4B, 0xFB, 0xB4, 0x81, 0xE9, 0x30, 0x60, 0x39,
+    0x1C, 0xF4, 0xFE, 0xDE, 0x12, 0x94, 0xC4, 0xCC, 0x24, 0x38, 0x63, 0xC1, 0xF2, 0x6D, 0x5D, 0xCE,
+    0x5C, 0xD8, 0x3A, 0xEC, 0x81, 0x47, 0x8D, 0x74, 0x5B, 0x88, 0xD6, 0x0E, 0x99, 0xB4, 0xAE, 0xE6,
+    0xC7, 0xDD, 0xC7, 0x25, 0xF5, 0x3C, 0xA7, 0x0E, 0x5C, 0x63, 0x37, 0x35, 0xBE, 0x0E, 0x85, 0xB2,
+    0x7D, 0x7C, 0x4D, 0x4B, 0xA2, 0x90, 0x2F, 0xE6, 0x57, 0x10, 0xA7, 0xD7, 0x84, 0xB7, 0x25, 0xC8,
+    0x97, 0x11, 0xE2, 0x50, 0xB2, 0x30, 0x4F, 0x89, 0xAC, 0xCC, 0x31, 0x24, 0xB8, 0x57, 0xD9, 0xD2,
+    0x9A, 0x0A, 0x00, 0x08, 0xB4, 0x07, 0x2B, 0x6D, 0x5E, 0x03, 0xB5, 0x82, 0x47, 0x2B, 0xDF, 0x42,
+    0xE2, 0x1C, 0x6F, 0xD8, 0x0E, 0x2F, 0xD6, 0xA8, 0x82, 0x7B, 0xE0, 0x40, 0xAE, 0x36, 0x57, 0xFD,
+    0x19, 0x6A, 0x36, 0x67, 0x6D, 0x24, 0x3E, 0x9B, 0xAF, 0x86, 0x13, 0x48, 0x64, 0x75, 0x39, 0x0E,
+    0xA2, 0xBF, 0x8F, 0x4D, 0x34, 0xED, 0x1D, 0xA7, 0x6F, 0x2F, 0x59, 0xCB, 0x53, 0x0B, 0x3F, 0x55,
+    0x08, 0xB6, 0xD3, 0xC3, 0xEB, 0x54, 0xE9, 0xD8, 0xAF, 0x6F, 0x55, 0xF7, 0x41, 0x71, 0xD7, 0x38,
+    0x73, 0xEF, 0x6B, 0x52, 0xB2, 0x11, 0xC6, 0x98, 0x2D, 0x59, 0x33, 0x9E, 0x40, 0xA8, 0x15, 0x51,
+    0x15, 0x3D, 0xC0, 0x87, 0xAD, 0xF9, 0x73, 0x5F, 0xE8, 0x4B, 0x96, 0xF0, 0x25, 0x6B, 0x9F, 0x20,
+    0x98, 0xD5, 0x2D, 0x9E, 0x7A, 0x34, 0x3A, 0x61, 0x94, 0x1F, 0x8A, 0x25, 0xEF, 0x58, 0xA2, 0xBA,
+    0x94, 0x81, 0xEA, 0x34, 0x98, 0x68, 0xE4, 0x3F, 0x05, 0x59, 0x74, 0x2B, 0x3F, 0x26, 0xBC, 0x7A,
+    0xF7, 0xCD, 0x03, 0xFB, 0xE2, 0xE8, 0xA3, 0xBA, 0xA3, 0x5A, 0xFF, 0x5E, 0xC0, 0xD4, 0xF1, 0xB0,
+    0x7D, 0x38, 0x42, 0x60, 0xF4, 0xE9, 0x09, 0x5D, 0xD8, 0x8C, 0x11, 0x31, 0x9F, 0xD8, 0x9B, 0x4F,
+    0x19, 0x67, 0x22, 0x48, 0xA4, 0xAE, 0xF2, 0x31, 0x7E, 0x97, 0xB6, 0xDF, 0xF5, 0x4F, 0x55, 0xA4,
+    0x6B, 0x4E, 0xBD, 0xB4, 0x6D, 0xBA, 0x75, 0x6E, 0x54, 0x8D, 0x13, 0x1E, 0x3B, 0x2C, 0xF0, 0xFC,
+    0x2C, 0x68, 0xC0, 0x78, 0xDF, 0xFF, 0xD9, 0x28, 0x6B, 0x19, 0x55, 0xCE, 0xB6, 0x6B, 0x60, 0x5C,
+    0x9F, 0xE1, 0x54, 0xEB, 0x13, 0x0D, 0x7E, 0xFF, 0x94, 0xB6, 0xA3, 0xA7, 0xEC, 0x3F, 0xB0, 0x2F,
+    0x02, 0xC9, 0x17, 0x92, 0x16, 0x44, 0xD0, 0xD3, 0xD5, 0xD5, 0x0A, 0xE9, 0x0F, 0x41, 0xED, 0xF4,
+    0xFC, 0x43, 0x04, 0xD7, 0x5B, 0x05, 0x3B, 0x6F, 0xD3, 0x17, 0x71, 0x0F, 0x6F, 0xA3, 0x17, 0xEF,
+    0x10, 0xB6, 0x68, 0xB1, 0x2D, 0xDD, 0x13, 0x3C, 0x4A, 0x75, 0x85, 0x7E, 0xED, 0x5D, 0x16, 0xDA,
+    0x0A, 0xFB, 0xCE, 0x5C, 0x1C, 0xBC, 0x8B, 0xF1, 0x73, 0x74, 0xAD, 0x32, 0x66, 0x5F, 0xA3, 0x93,
+    0x71, 0x90, 0xF4, 0x04, 0x70, 0x1E, 0xA1, 0x41, 0x7D, 0x56, 0xF8, 0x71, 0x25, 0xBF, 0x3D, 0xCF,
+    0xF4, 0xC5, 0xB7, 0x75, 0x94, 0x3F, 0x0F, 0x8D, 0xF8, 0x45, 0x0E, 0x7C, 0x55, 0xEC, 0x16, 0xC7,
+    0xE1, 0xEC, 0x03, 0xCD, 0x8C, 0x4D, 0x3D, 0x93, 0x45, 0x8A, 0x1D, 0x3B, 0x6E, 0xDB, 0x05, 0xEA,
+    0x8B, 0x8D, 0xC7, 0x2C, 0x62, 0x90, 0x2E, 0x1E, 0x0A, 0xB5, 0xCD, 0xF0, 0xA7, 0x39, 0x76, 0x8C,
+    0xC2, 0x92, 0xE0, 0x63, 0x94, 0xA5, 0x70, 0xB6, 0x9D, 0xD6, 0x2F, 0xE7, 0x63, 0x9A, 0x58, 0x96,
+    0x41, 0x77, 0x0C, 0xA4, 0x8A, 0xA3, 0x0F, 0x52, 0x77, 0xA4, 0xAA, 0x24, 0xA7, 0xA8, 0x0F, 0x37,
+    0x1B, 0x7E, 0xDA, 0x32, 0xFE, 0x55, 0x83, 0x05, 0xA3, 0xB4, 0xEE, 0x15, 0x83, 0x57, 0x63, 0x91,
+    0x2F, 0xDA, 0x98, 0x11, 0x75, 0x62, 0xA1, 0xAF, 0x2F, 0xA5, 0xE4, 0x41, 0x88, 0x0F, 0x6F, 0x6F,
+    0x96, 0xE2, 0x44, 0xB7, 0xA5, 0x81, 0x88, 0xAD, 0x9B, 0x51, 0x9E, 0xF8, 0x32, 0xE2, 0x94, 0xED,
+    0x11, 0x40, 0x7D, 0xBC, 0xEE, 0xA9, 0x95, 0x8A, 0x47, 0xFF, 0x44, 0x05, 0x5E, 0xB8, 0x67, 0x31,
+    0x7E, 0x22, 0x74, 0x87, 0xC6, 0x40, 0x51, 0xAF, 0xEA, 0x90, 0x07, 0x5A, 0xB6, 0x80, 0xA1, 0x11,
+    0x45, 0x68, 0xD7, 0x01, 0x27, 0x4A, 0x61, 0x10, 0xF8, 0xB0, 0x12, 0xC2, 0x23, 0x60, 0x10, 0xCD,
+    0xC8, 0xD6, 0xC6, 0x47, 0x03, 0x9C, 0x76, 0xE0, 0x1B, 0x06, 0x76, 0x95, 0x3B, 0xE8, 0x83, 0xB7,
+    0x44, 0xC1, 0x53, 0xB3, 0x0A, 0x3E, 0x3F, 0x9E, 0x65, 0x1D, 0x5F, 0xB4, 0x3B, 0xC2, 0xE7, 0x07,
+    0xCB, 0x99, 0xB2, 0x64, 0x95, 0x52, 0xEA, 0xDE, 0xFB, 0xB9, 0x99, 0xD1, 0x47, 0x76, 0xEC, 0x5B,
+    0xFB, 0x5D, 0x34, 0x8B, 0xA1, 0x29, 0xEB, 0xBA, 0x83, 0xB6, 0x54, 0xD2, 0xEF, 0x1D, 0x79, 0x76,
+    0x02, 0x4F, 0x98, 0x52, 0x1E, 0x05, 0x4A, 0x05, 0x6F, 0x25, 0xEA, 0x69, 0x3D, 0xF9, 0x15, 0x2F,
+    0xE7, 0xCF, 0x3F, 0x0A, 0xBE, 0xE4, 0xBC, 0xF1, 0x21, 0xB3, 0xB0, 0x23, 0x94, 0x02, 0xCE, 0xF3,
+    0xB0, 0x50, 0xDC, 0x9B, 0x1E, 0x72, 0x52, 0x86, 0x13, 0x93, 0xA1, 0xDC, 0xDE, 0xD3, 0xE6, 0x3D,
+    0x97, 0x41, 0x21, 0xF1, 0x00, 0xF5, 0x2C, 0x0C, 0x0B, 0x6F, 0x14, 0x2F, 0xBA, 0x9A, 0x86, 0xFF,
+    0x3C, 0x06, 0x73, 0x71, 0x71, 0x41, 0x26, 0x7D, 0x4B, 0x5D, 0x68, 0xE5, 0xB1, 0x0B, 0x6B, 0x18,
+    0xCE, 0xE0, 0x99, 0x65, 0xFE, 0xA5, 0x8B, 0xF6, 0xC0, 0xC8, 0xB7, 0x64, 0x63, 0x4D, 0x97, 0xC0,
+    0x43, 0xE1, 0x69, 0x6C, 0xE4, 0xDD, 0xC1, 0x28, 0x32, 0x64, 0x81, 0x23, 0xB4, 0xED, 0x01, 0xFA,
+    0x83, 0xDC, 0x7D, 0xED, 0x40, 0x01, 0xFD, 0xC2, 0x75, 0x1E, 0x61, 0x14, 0x04, 0xCB, 0x47, 0x00,
+    0x97, 0x53, 0xDD, 0x84, 0x3C, 0x76, 0xEF, 0xE9, 0x96, 0x0C, 0xBD, 0x19, 0x56, 0x0A, 0x5B, 0xBC,
+    0xDF, 0x6A, 0xB5, 0x75, 0x43, 0xDB, 0x78, 0xA3, 0x10, 0x5A, 0x72, 0x72, 0x88, 0x05, 0x35, 0x2D,
+    0x3B, 0xD4, 0x01, 0x19, 0x2F, 0xFF, 0x52, 0x49, 0xF7, 0x3D, 0x87, 0x2E, 0x7C, 0x38, 0x80, 0xDF,
+    0x41, 0xC4, 0x3F, 0x4E, 0x78, 0xC9, 0xC8, 0xF3, 0x2D, 0xE3, 0xDB, 0x99, 0x4D, 0x33, 0x4F, 0x58,
+    0x69, 0xDF, 0x1D, 0xEC, 0x68, 0x2F, 0x60, 0xF0, 0x8C, 0x62, 0xD8, 0xAE, 0x7C, 0x8D, 0xC9, 0x87,
+    0x3D, 0x27, 0x2C, 0x2D, 0x46, 0x23, 0x8D, 0x2E, 0x1C, 0xA9, 0x21, 0x86, 0x24, 0xCF, 0xDB, 0x36,
+    0x8C, 0xF1, 0x8C, 0x23, 0x87, 0x83, 0x60, 0xAE, 0x3F, 0x6D, 0x42, 0xC8, 0x25, 0x67, 0xE5, 0x7B,
+    0x98, 0xD3, 0xA0, 0x28, 0x03, 0x08, 0x39, 0xF5, 0xE1, 0x1D, 0x5F, 0x1A, 0x55, 0x97, 0x70, 0x22,
+    0x46, 0x90, 0xBB, 0x4A, 0x1F, 0x39, 0x72, 0x77, 0xAB, 0xD0, 0xE7, 0x8F, 0xB4, 0x65, 0xD7, 0x26,
+    0x4E, 0x0D, 0xD3, 0xBD, 0xFF, 0x58, 0x14, 0x0C, 0x32, 0x35, 0x42, 0x19, 0x99, 0x8D, 0xFC, 0x1A,
+    0x6B, 0x40, 0x2E, 0x4E, 0xB6, 0x55, 0x86, 0x5F, 0x24, 0x84, 0x81, 0xFA, 0xE0, 0x6E, 0xF4, 0x9A,
+    0x8C, 0x1E, 0x14, 0xCC, 0x79, 0xBA, 0x3A, 0x5C, 0x7C, 0x6C, 0x0D, 0x2F, 0x1E, 0xF9, 0xBD, 0xBF,
+    0x03, 0x8E, 0x7D, 0x81, 0xC9, 0x9E, 0x62, 0xA3, 0xAF, 0x04, 0x5A, 0xE5, 0xCF, 0xA8, 0xE8, 0x89,
+    0xB5, 0x54, 0xC2, 0x99, 0xA7, 0x96, 0x9A, 0xF3, 0xDF, 0xBE, 0x94, 0xE7, 0x86, 0x65, 0x4A, 0x56,
+    0x4C, 0x08, 0x50, 0x99, 0xC5, 0xA1, 0x9E, 0xA0, 0x06, 0x52, 0x52, 0x0E, 0x1E, 0x81, 0xAF, 0x4B,
+    0x61, 0xFF, 0x53, 0xCC, 0xB2, 0x1A, 0xF6, 0x00, 0x2D, 0xAF, 0x43, 0xB2, 0xE7, 0x9F, 0x89, 0xCA,
+    0xB5, 0x41, 0x69, 0xB2, 0x0E, 0xAB, 0xA9, 0xDA, 0x95, 0xEF, 0xDE, 0x19, 0xDC, 0xA8, 0x9E, 0xDC,
+    0x59, 0x75, 0x50, 0x71, 0xB7, 0x38, 0xE8, 0xD9, 0xEE, 0x44, 0x16, 0xE7, 0xCD, 0xB9, 0xB8, 0xA8,
+    0xE4, 0xD1, 0x98, 0x48, 0xFC, 0xD2, 0xC3, 0xF8, 0x80, 0xE6, 0x05, 0x8E, 0x8F, 0x13, 0x59, 0xDC,
+    0x9D, 0x0D, 0x54, 0xF9, 0xC8, 0xA6, 0xD8, 0xF6, 0x5E, 0x07, 0xA0, 0xBE, 0x33, 0x0B, 0x67, 0x22,
+    0xB1, 0x51, 0xC6, 0x3E, 0xD8, 0xEC, 0x02, 0xC3, 0x9A, 0xC1, 0x63, 0xD7, 0x2E, 0xFA, 0xDC, 0x8E,
+    0x5E, 0x26, 0x12, 0x38, 0xE8, 0xDB, 0x08, 0xF3, 0x6D, 0x06, 0x06, 0x54, 0x8E, 0x2E, 0x7A, 0x0E,
+    0x28, 0x62, 0xED, 0xDB, 0xE3, 0x95, 0x50, 0x2B, 0x6E, 0x90, 0x28, 0x43, 0x25, 0xD9, 0x75, 0xDA,
+    0x04, 0x20, 0x4E, 0x65, 0x12, 0x17, 0x8B, 0x92, 0xBE, 0xD3, 0x02, 0xAC, 0xC2, 0x01, 0x29, 0xE5,
+    0x8A, 0xA6, 0x1C, 0xC9, 0x4F, 0x2C, 0x69, 0x43, 0x3A, 0xE8, 0x18, 0x06, 0x55, 0x71, 0xC6, 0x4C,
+    0x28, 0x5F, 0xE0, 0x1E, 0x33, 0x5A, 0x47, 0xB8, 0xA9, 0x84, 0xE3, 0xA9, 0x2A, 0xA7, 0x01, 0xC5,
+    0x4D, 0xC4, 0x75, 0x15, 0x44, 0xD5, 0xDF, 0x40, 0xEF, 0xE1, 0x8A, 0x38, 0x11, 0xC6, 0xC5, 0x13,
+    0x9A, 0x4E, 0xB6, 0x63, 0x2B, 0x6B, 0xF8, 0x6C, 0x39, 0xB3, 0x8A, 0x15, 0x94, 0x85, 0xE2, 0x71,
+    0x17, 0x68, 0x30, 0x33, 0xDD, 0x77, 0x18, 0x7F, 0x32, 0x15, 0x6A, 0xD2, 0x21, 0x21, 0xBD, 0x04,
+    0x5D, 0x5A, 0xD1, 0x97, 0xCF, 0xD2, 0x2E, 0xDD, 0x2F, 0x7D, 0x6C, 0x9E, 0x41, 0x47, 0x00, 0x4C,
+    0xC7, 0x3F, 0x98, 0xF7, 0x28, 0xBE, 0x4C, 0x7D, 0x5E, 0xA7, 0x39, 0xB5, 0xC2, 0x0E, 0x4C, 0x95,
+    0xA6, 0xF2, 0x45, 0x83, 0xEA, 0xDC, 0x4C, 0x59, 0xFC, 0x86, 0x94, 0xD4, 0xE9, 0xDB, 0xE5, 0x62,
+    0x6C, 0xFD, 0x0B, 0x9E, 0x29, 0x19, 0x89, 0xDD, 0x80, 0x38, 0x0A, 0xA4, 0xA4, 0x5D, 0x65, 0xE3,
+    0xE0, 0x8C, 0x3B, 0x53, 0x37, 0x9C, 0x89, 0x57, 0xCA, 0xF3, 0xA0, 0x2D, 0xB6, 0x74, 0x6B, 0x60,
+    0x4C, 0x59, 0xFB, 0xC4, 0xD6, 0xBB, 0xB0, 0x67, 0x58, 0xF2, 0x85, 0x45, 0xEA, 0x24, 0x4D, 0xAD,
+    0xAB, 0xA1, 0xF0, 0x97, 0x68, 0xE7, 0xF0, 0x6F, 0x73, 0x6D, 0xC1, 0x02, 0x44, 0x87, 0xC4, 0x98,
+    0xDE, 0x0E, 0xF1, 0x6C, 0x1C, 0x9D, 0x78, 0x04, 0x5E, 0x81, 0xE6, 0x25, 0x2E, 0xBA, 0x9F, 0x57,
+    0xD8, 0xAE, 0xB5, 0x44, 0x22, 0x56, 0x62, 0x5D, 0x88, 0x23, 0xBD, 0x90, 0xA7, 0xCF, 0x74, 0xFF,
+    0xCF, 0xDB, 0x87, 0xFC, 0xD9, 0x79, 0x68, 0xC4, 0xBB, 0x13, 0xFC, 0xB2, 0x7A, 0xBD, 0x4B, 0xE9,
+    0x6E, 0x33, 0xEF, 0xB2, 0xFE, 0x47, 0x90, 0x03, 0x4C, 0xC7, 0xEF, 0xF9, 0x65, 0x4C, 0x53, 0x2F,
+    0x00, 0x83, 0x6B, 0x3E, 0xE0, 0xCF, 0xDF, 0xD9, 0x4C, 0x5E, 0x2C, 0x40, 0x4F, 0x0B, 0x91, 0x0F,
+    0xA7, 0xB7, 0x16, 0x9D, 0x8A, 0xDC, 0x03, 0x65, 0xB6, 0x8F, 0x43, 0x41, 0x77, 0x3E, 0x8D, 0x66,
+    0x86, 0xCD, 0x60, 0x61, 0xF8, 0xE3, 0x0D, 0x9A, 0xA2, 0x99, 0x6D, 0x05, 0xA2, 0xCB, 0x08, 0x18,
+    0xF3, 0xC2, 0xB8, 0x25, 0x46, 0xF9, 0x16, 0xAB, 0x72, 0x34, 0x3C, 0x52, 0x4C, 0x2E, 0xA4, 0x85,
+    0xA9, 0x83, 0x3E, 0xF7, 0xDD, 0xBC, 0xF8, 0x81, 0x04, 0x80, 0x4B, 0x1D, 0xD9, 0x65, 0x9A, 0xF8,
+    0xF4, 0xE0, 0x75, 0xCE, 0xA7, 0x48, 0xF9, 0x23, 0xE0, 0xF5, 0xEF, 0xFA, 0xC5, 0xE4, 0x6A, 0x13,
+    0xE6, 0x76, 0xEF, 0xF6, 0x3D, 0x25, 0x7A, 0x2E, 0x6B, 0x53, 0xE6, 0x88, 0xD2, 0x84, 0x87, 0x46,
+    0x81, 0xA3, 0x02, 0x81, 0x16, 0x37, 0xAD, 0x3E, 0x13, 0x94, 0x07, 0xE8, 0x3A, 0x70, 0x09, 0x39,
+    0xED, 0x77, 0x72, 0xB8, 0xBB, 0xAE, 0x40, 0x63, 0x84, 0xD7, 0xF2, 0x28, 0xDD, 0x19, 0x60, 0x3F,
+    0xA3, 0xA0, 0x28, 0x8A, 0xF1, 0xF7, 0x0C, 0x8F, 0xD4, 0x56, 0xC1, 0xB3, 0x73, 0x24, 0xFD, 0xC4,
+    0xA2, 0x5D, 0xDA, 0xFC, 0xEF, 0xAA, 0xCB, 0x06, 0xB2, 0x52, 0xB7, 0xC5, 0xBD, 0x5A, 0x0B, 0xC2,
+    0x9B, 0x6F, 0xC3, 0x99, 0x89, 0x7D, 0xC0, 0xCF, 0x9C, 0x06, 0xEF, 0xD4, 0xB0, 0x97, 0x16, 0x28,
+    0x20, 0x05, 0x4E, 0xE3, 0x64, 0x32, 0x70, 0x23, 0x09, 0x92, 0x10, 0x09, 0xAA, 0xBE, 0xC3, 0x52,
+    0xD9, 0xAE, 0xC6, 0xC2, 0x23, 0x84, 0x49, 0xDD, 0x9D, 0xF1, 0xF7, 0xA8, 0xA2, 0xA3, 0x79, 0x78,
+    0xB1, 0x4C, 0x08, 0xF4, 0x99, 0x1F, 0x59, 0xE9, 0x54, 0xE5, 0x6D, 0x85, 0x52, 0x01, 0x17, 0x18,
+    0x06, 0xFE, 0x33, 0x7E, 0xF7, 0x88, 0xFA, 0xB7, 0xB9, 0xEA, 0xD3, 0x71, 0x6F, 0x67, 0x9E, 0x6F,
+    0xD7, 0x16, 0x58, 0x1A, 0x00, 0x12, 0x83, 0xA9, 0x10, 0x21, 0xD3, 0xAB, 0xD3, 0x25, 0xE8, 0xE0,
+    0xFA, 0x04, 0x26, 0xA8, 0x34, 0xCC, 0xFA, 0x83, 0x88, 0x46, 0x11, 0x52, 0xB1, 0x44, 0x51, 0x6A,
+    0x47, 0x49, 0x9F, 0xA1, 0x02, 0x6F, 0xC0, 0xDB, 0x6E, 0x9D, 0xD9, 0xD2, 0xC2, 0x6E, 0x6D, 0x18,
+    0xC6, 0x68, 0xC9, 0x82, 0xF9, 0x53, 0x44, 0x89, 0x57, 0xE0, 0xD2, 0x56, 0x77, 0xE2, 0xB3, 0x6D,
+    0xE8, 0xD1, 0x56, 0x3E, 0xF8, 0x5C, 0xB4, 0x19, 0x54, 0x34, 0xAB, 0x37, 0x27, 0x64, 0x30, 0xD5,
+    0xAC, 0xD7, 0x5E, 0xAE, 0x5D, 0xE9, 0xA9, 0x37, 0x24, 0x15, 0xCC, 0x6A, 0x42, 0x2B, 0x37, 0x1B,
+    0xD7, 0x9D, 0x07, 0x05, 0x33, 0xC6, 0xDA, 0x23, 0x5D, 0x48, 0x08, 0xF7, 0x7E, 0xD3, 0x10, 0xCF,
+    0x22, 0x03, 0x3A, 0x37, 0x68, 0x1A, 0xCB, 0x1D, 0xA2, 0xC8, 0x4A, 0x60, 0x09, 0x4D, 0xA7, 0xBE,
+    0x66, 0x9E, 0x50, 0x71, 0xF5, 0x5B, 0x7E, 0xDA, 0xD4, 0xBB, 0x45, 0x16, 0xB7, 0xCD, 0x3F, 0x60,
+    0xD3, 0xA1, 0xC5, 0x86, 0x17, 0x38, 0x21, 0xEF, 0x3B, 0x5E, 0x28, 0xEB, 0x35, 0xBB, 0x1F, 0x46,
+    0x19, 0xCE, 0xE5, 0x60, 0x76, 0x8F, 0xC2, 0x46, 0xBC, 0xF6, 0x4A, 0x7C, 0x37, 0xA5, 0x43, 0x8A,
+    0x9D, 0x69, 0x7E, 0x6E, 0x5C, 0x59, 0xF8, 0x87, 0x08, 0xC1, 0xDA, 0xA6, 0xA6, 0x2C, 0x0E, 0x42,
+    0xA7, 0x26, 0x8D, 0x15, 0xE0, 0x9A, 0x9B, 0x91, 0xCB, 0xE5, 0x92, 0xF4, 0xD5, 0xF5, 0xF7, 0xEF,
+    0x93, 0x18, 0xF3, 0x21, 0x1B, 0x55, 0x6E, 0xE1, 0xDB, 0x5F, 0x64, 0x0E, 0xAC, 0x99, 0x3A, 0xEA,
+    0xFF, 0xA5, 0x22, 0x35, 0x52, 0x77, 0xD3, 0x0A, 0x6A, 0xF7, 0x2C, 0x2C, 0xDD, 0x98, 0x89, 0xD7,
+    0xFC, 0x70, 0xCB, 0x39, 0x2D, 0xCA, 0x77, 0x1E, 0x36, 0x2B, 0x61, 0x83, 0x0F, 0x42, 0xBB, 0x16,
+    0x3F, 0x4F, 0x93, 0xCC, 0xE1, 0xE5, 0x06, 0x23, 0xB7, 0x22, 0xBE, 0xB8, 0x10, 0xAF, 0x7B, 0x2E,
+    0x52, 0x35, 0xBE, 0xB1, 0x61, 0x1A, 0xDA, 0x82, 0x51, 0x9A, 0xFE, 0x4C, 0x07, 0xA8, 0xFC, 0x44,
+    0xBE, 0x2A, 0xE3, 0x46, 0x92, 0x69, 0xA7, 0x74, 0x83, 0xDC, 0x7F, 0x10, 0xA2, 0x9E, 0xA4, 0x84,
+    0x44, 0x31, 0x98, 0xEA, 0x76, 0x6B, 0x32, 0x77, 0x17, 0xA6, 0xFD, 0x91, 0x44, 0x93, 0xBF, 0x96,
+    0x04, 0x41, 0x28, 0x77, 0x60, 0x49, 0xFA, 0xB8, 0x53, 0x22, 0x3A, 0x8E, 0x3A, 0x0E, 0x2E, 0x0D,
+    0xB4, 0x30, 0x3C, 0xA8, 0x22, 0xA5, 0xEF, 0x88, 0x29, 0xCE, 0xB4, 0x60, 0xE7, 0x0C, 0x18, 0xD3,
+    0xCC, 0xA3, 0x8F, 0x94, 0x3C, 0x90, 0x1A, 0xCC, 0x63, 0x73, 0x50, 0x72, 0xF5, 0xEC, 0x98, 0x9F,
+    0xB9, 0x03, 0x9D, 0x14, 0x0E, 0x76, 0x54, 0x68, 0xD9, 0x12, 0x0D, 0xAA, 0x86, 0x63, 0x6E, 0x62,
+    0x09, 0x64, 0x54, 0x38, 0x08, 0x10, 0xF2, 0xB3, 0x9E, 0xD4, 0xB4, 0xDE, 0x63, 0x68, 0xB3, 0xB8,
+    0x9E, 0x7F, 0xC4, 0xB8, 0xD7, 0x50, 0x78, 0xE9, 0x30, 0xFA, 0x86, 0x44, 0x2C, 0x29, 0x80, 0x53,
+    0xDE, 0x9A, 0xCD, 0x61, 0x9B, 0x5A, 0x46, 0x95, 0xA7, 0xCD, 0xEF, 0xDB, 0x88, 0xF6, 0xA6, 0x76,
+    0xE1, 0x7E, 0xCF, 0x86, 0x0F, 0x68, 0x48, 0x06, 0xE9, 0x8F, 0x31, 0xE7, 0x56, 0x35, 0x5B, 0x57,
+    0xA4, 0x61, 0x5E, 0x71, 0xC0, 0xC6, 0xA9, 0xBC, 0xD4, 0x69, 0x1B, 0x54, 0xDC, 0x4F, 0xEB, 0x9D,
+    0x35, 0xDB, 0xEE, 0xD2, 0x39, 0xB8, 0x81, 0xDB, 0x73, 0x5E, 0xB3, 0x30, 0xF7, 0xA2, 0x66, 0xC4,
+    0xE7, 0xD6, 0x83, 0x2F, 0x36, 0x70, 0x85, 0x97, 0x2D, 0x39, 0xE9, 0x17, 0x4C, 0x6F, 0x52, 0x95,
+    0x80, 0x79, 0x65, 0x54, 0xD0, 0xFD, 0xB8, 0xA8, 0xF2, 0x7B, 0x46, 0xA1, 0x75, 0xCD, 0x59, 0x93,
+    0x69, 0x1D, 0xCC, 0xC4, 0xB1, 0x38, 0x18, 0xB8, 0x70, 0x50, 0x9D, 0xD7, 0x37, 0x97, 0xFB, 0x6A,
+    0xDD, 0x3C, 0x8F, 0x28, 0x41, 0xB8, 0x53, 0xD3, 0x3E, 0x7B, 0xB9, 0x9F, 0xAA, 0x5B, 0x3F, 0x62,
+    0x1D, 0x5E, 0xDA, 0xBF, 0xD7, 0xC0, 0x73, 0xD6, 0x10, 0x47, 0x0F, 0x2D, 0x71, 0x4B, 0x5E, 0xCC,
+    0x9B, 0x0D, 0xD7, 0xCE, 0xEC, 0x2F, 0x8F, 0xE3, 0xE3, 0x77, 0x6C, 0x73, 0xE4, 0x30, 0x79, 0x73,
+    0x2D, 0xC3, 0x64, 0x12, 0x44, 0x71, 0x7C, 0xCC, 0x31, 0x39, 0xA9, 0x93, 0x42, 0x54, 0x45, 0x0E,
+    0x3C, 0xD9, 0xBF, 0x2C, 0x26, 0x6C, 0x7B, 0x86, 0x1F, 0x0F, 0x57, 0x4B, 0xE4, 0x78, 0xBC, 0xAB,
+    0xF5, 0x7A, 0x36, 0x14, 0x87, 0x75, 0xEB, 0x99, 0xAE, 0xC6, 0x6E, 0x68, 0x68, 0xBE, 0xCD, 0x27,
+    0x77, 0x91, 0xDB, 0x88, 0x3A, 0x3B, 0xF9, 0x8D, 0xE7, 0x64, 0x02, 0x37, 0xE2, 0xA0, 0x0F, 0x95,
+    0x05, 0xB8, 0x2F, 0x7D, 0x22, 0xBC, 0x4E, 0x5F, 0x13, 0x13, 0xEF, 0xF1, 0x10, 0xDA, 0x6B, 0xB4,
+    0x36, 0x2A, 0xD4, 0x8F, 0x63, 0x2F, 0xC1, 0xEC, 0xE3, 0x1B, 0x8B, 0x2D, 0x87, 0x5C, 0xD0, 0x5E,
+    0x22, 0xB3, 0x40, 0x6E, 0x8D, 0xFA, 0x06, 0x65, 0xA3, 0xC7, 0x55, 0x53, 0xE1, 0x3C, 0xE3, 0xF5,
+    0x98, 0x9D, 0x67, 0x52, 0xD3, 0x9D, 0x5F, 0xBC, 0x6C, 0x5F, 0xA5, 0x06, 0xF1, 0xA2, 0xAE, 0xD7,
+    0x46, 0xA5, 0x71, 0x68, 0x33, 0xA6, 0x49, 0x16, 0x53, 0x0F, 0x5C, 0x9A, 0xF2, 0xBC, 0x50, 0xCD,
+    0xEF, 0xE7, 0x65, 0x44, 0xAF, 0x9E, 0x32, 0x39, 0x94, 0xDE, 0x95, 0x7F, 0xB3, 0xAC, 0xAE, 0x77,
+    0x9B, 0xCF, 0xDD, 0x50, 0x74, 0xFC, 0x21, 0x00, 0xCC, 0x9A, 0x53, 0xB5, 0xCE, 0x78, 0x21, 0xC3,
+    0xC3, 0x09, 0xB3, 0x3E, 0x10, 0x10, 0x6D, 0xC5, 0x1F, 0xCA, 0x35, 0x3A, 0xD2, 0xF9, 0x28, 0x57,
+    0x83, 0x72, 0xB3, 0x73, 0xA1, 0xFA, 0x69, 0xD7, 0x6D, 0x9C, 0x1F, 0x78, 0x77, 0xCF, 0x17, 0x04,
+    0xCD, 0x08, 0x49, 0x7C, 0x04, 0x94, 0x17, 0xE4, 0x82, 0xD8, 0xF3, 0xBB, 0xC9, 0x4B, 0xC9, 0x35,
+    0x93, 0xD7, 0x35, 0x7C, 0x03, 0x65, 0xD4, 0x70, 0x46, 0xCD, 0x38, 0x9A, 0x5F, 0x66, 0x4A, 0x5F,
+    0xFD, 0xEC, 0x35, 0x9E, 0x8A, 0x90, 0x0D, 0x3F, 0xEB, 0x43, 0xD0, 0x6C, 0x86, 0xA9, 0x90, 0x72,
+    0x93, 0x47, 0xBA, 0x80, 0xD4, 0xC4, 0xE8, 0xC7, 0x1E, 0x6A, 0xA8, 0xB6, 0x70, 0x25, 0x23, 0x48,
+    0x74, 0xC4, 0x97, 0xA8, 0x99, 0x2D, 0xFC, 0xA2, 0x38, 0xC9, 0x64, 0x9A, 0x6A, 0x5D, 0xD3, 0x15,
+    0x80, 0x11, 0xAE, 0xF4, 0x43, 0x63, 0xFD, 0xFA, 0x6D, 0x31, 0x11, 0x4B, 0x05, 0x39, 0x62, 0xD7,
+    0x8A, 0x9F, 0xA4, 0x04, 0x1A, 0x59, 0x69, 0xFE, 0xFE, 0xAB, 0xD6, 0x77, 0x4C, 0xF6, 0x39, 0xC7,
+    0x8A, 0x8A, 0x8F, 0xB1, 0x74, 0x50, 0x40, 0x4D, 0x63, 0x68, 0xA5, 0xBC, 0xEF, 0x13, 0x17, 0xC7,
+    0xCA, 0x92, 0xA5, 0x7C, 0xE9, 0xC5, 0xAC, 0x68, 0x83, 0xB0, 0xE6, 0x17, 0x76, 0x46, 0xBF, 0xD5,
+    0x16, 0x07, 0xEF, 0xF8, 0x7E, 0x60, 0xB4, 0x25, 0xDF, 0xD6, 0x2D, 0x52, 0x71, 0x67, 0xAA, 0x78,
+    0xF0, 0xB8, 0xF7, 0x41, 0xD8, 0xE6, 0xEE, 0x19, 0xC4, 0x22, 0xE5, 0x74, 0xA7, 0x63, 0xB6, 0x32,
+    0xBD, 0xE4, 0x76, 0x69, 0x6B, 0x2A, 0x2D, 0x0E, 0x78, 0xC6, 0x04, 0x36, 0x44, 0x2D, 0xD5, 0xEE,
+    0xF2, 0x2C, 0x0A, 0xE7, 0xAB, 0xF7, 0x30, 0x6D, 0x70, 0xCE, 0xB9, 0x6B, 0x0F, 0xA9, 0xC1, 0x73,
+    0x4A, 0x80, 0xDF, 0x0B, 0x3B, 0x09, 0x55, 0xB6, 0x78, 0x0A, 0x1B, 0x78, 0x94, 0xA2, 0xA6, 0xD1,
+    0xF4, 0x10, 0x64, 0x67, 0x1D, 0xF4, 0x45, 0xE6, 0xEA, 0x06, 0xDC, 0xBD, 0x56, 0xB5, 0xD8, 0xD2,
+    0xBF, 0x3D, 0xF8, 0x48, 0xE1, 0x1C, 0xA9, 0xEF, 0xDC, 0xF5, 0xF5, 0x0B, 0x00, 0x46, 0x80, 0x6B,
+    0x51, 0x88, 0x9D, 0x1D, 0xD9, 0x9F, 0xD5, 0x27, 0x4B, 0xA2, 0x5B, 0x10, 0x95, 0x6A, 0x4A, 0x2C,
+    0x51, 0x83, 0xA4, 0xEE, 0x44, 0x48, 0x7A, 0xB2, 0x55, 0x5F, 0xAA, 0xC7, 0x9D, 0xDD, 0x19, 0xAE,
+    0x99, 0xBE, 0x62, 0xC9, 0x82, 0x7E, 0x5A, 0xF9, 0x5E, 0xF9, 0xD9, 0xEC, 0x5A, 0xEF, 0xB4, 0x04,
+    0x67, 0xBC, 0xDB, 0x30, 0x40, 0x34, 0xEF, 0x18, 0x49, 0xA4, 0xE6, 0x67, 0xF4, 0x71, 0x7B, 0x2C,
+    0x8D, 0xDF, 0x74, 0x8E, 0xAB, 0xDA, 0x24, 0x4A, 0xA2, 0xEB, 0x8B, 0xBF, 0xA8, 0xAD, 0x0D, 0x7F,
+    0x9F, 0x58, 0xA6, 0xA4, 0xA2, 0x4B, 0xFF, 0x60, 0xD2, 0xA3, 0xE9, 0x89, 0xFF, 0x4E, 0x04, 0x1E,
+    0x24, 0x1B, 0xA8, 0xF7, 0xDF, 0xC0, 0x56, 0x2A, 0x4B, 0xD9, 0x3C, 0xDA, 0xF5, 0x54, 0x9C, 0x67,
+    0xC9, 0xCA, 0x25, 0x44, 0x2E, 0xBC, 0x79, 0xED, 0xBD, 0xC2, 0x87, 0xB2, 0x2F, 0x04, 0x68, 0x5F,
+    0x8A, 0xA8, 0xE7, 0xEE, 0x9A, 0x01, 0xE8, 0xCE, 0x41, 0xAA, 0x48, 0x72, 0x2A, 0xD6, 0xFE, 0x29,
+    0xE9, 0x88, 0x8B, 0x6E, 0x9C, 0x7C, 0xFE, 0x45, 0x8C, 0xE7, 0x26, 0x49, 0x69, 0x67, 0xAC, 0x6F,
+    0x1B, 0xBE, 0x2D, 0xC3, 0x4E, 0x35, 0xA5, 0x8D, 0x1F, 0xC7, 0xA2, 0xA1, 0xA7, 0x69, 0x23, 0xD7,
+    0x39, 0x0D, 0x1E, 0xE4, 0x97, 0x41, 0x03, 0x14, 0x75, 0x7F, 0xC4, 0x97, 0x04, 0x91, 0x2A, 0x71,
+    0x6C, 0x9A, 0x8C, 0x2C, 0x5F, 0xB3, 0x2D, 0xE6, 0x35, 0x1E, 0xD0, 0x64, 0x3C, 0x88, 0x4F, 0x27,
+    0x24, 0xD9, 0x39, 0xCD, 0xBD, 0x87, 0xD2, 0x27, 0x61, 0x7A, 0xF1, 0xCE, 0xCD, 0xDD, 0x92, 0x2E,
+    0x43, 0x7E, 0x28, 0x40, 0x27, 0x99, 0xF2, 0x79, 0x86, 0x22, 0xEB, 0x9C, 0x30, 0xF1, 0x1A, 0x73,
+    0x4C, 0x6E, 0x4B, 0xB3, 0xA2, 0x8E, 0x87, 0x72, 0xED, 0x4C, 0xCF, 0x00, 0x03, 0xEC, 0xE5, 0x10,
+    0x99, 0xAE, 0x39, 0x7C, 0xF2, 0xC9, 0x3C, 0x0B, 0xCA, 0xC8, 0xA2, 0x0D, 0x3D, 0xA7, 0x72, 0xB8,
+    0x85, 0x53, 0xD8, 0x84, 0xCC, 0x59, 0x15, 0x0C, 0x6C, 0xEC, 0x17, 0x24, 0x5B, 0xA2, 0x79, 0x29,
+    0x9F, 0x74, 0x0E, 0xBF, 0x02, 0xE8, 0x27, 0x83, 0x6D, 0x8A, 0x36, 0x62, 0x91, 0xF0, 0x96, 0x99,
+    0xD8, 0x14, 0x75, 0x93, 0xB7, 0xAF, 0x41, 0x2F, 0xE3, 0xD7, 0x14, 0x16, 0xFC, 0x29, 0xFA, 0x2A,
+    0xB6, 0x1B, 0x07, 0x4E, 0x8C, 0x61, 0xA1, 0xF0, 0x8E, 0x66, 0x7D, 0x28, 0xCF, 0x57, 0x1B, 0x5A,
+    0x81, 0x3F, 0xCE, 0x95, 0xD2, 0x1C, 0xA3, 0x39, 0x0B, 0x0D, 0xA7, 0x94, 0x84, 0xEC, 0x65, 0x6D,
+    0x75, 0xF7, 0x98, 0xD1, 0xB9, 0x5F, 0x6E, 0x81, 0x01, 0xDE, 0xE1, 0xD0, 0x0C, 0xAA, 0xE9, 0xE5,
+    0xF0, 0x68, 0xA1, 0xA4, 0x81, 0xF0, 0xA9, 0xAF, 0x52, 0x11, 0x44, 0x42, 0x01, 0x9C, 0x0D, 0xED,
+    0xA6, 0x5A, 0x4A, 0x55, 0xA7, 0xD5, 0x26, 0x8C, 0x4D, 0xF8, 0x60, 0xAE, 0xD3, 0xFC, 0x3C, 0xC9,
+    0xCB, 0x24, 0xC2, 0x40, 0x19, 0x3D, 0x95, 0x35, 0xDA, 0xEC, 0xF1, 0xA7, 0xF8, 0x2D, 0x98, 0x49,
+    0x49, 0x9E, 0xBB, 0x49, 0x65, 0x78, 0x34, 0x89, 0xAE, 0x3F, 0x8C, 0xFD, 0x21, 0xA3, 0xA5, 0x37,
+    0xEA, 0x10, 0x17, 0x4A, 0xE7, 0xDC, 0x7C, 0x98, 0x78, 0x2A, 0x4C, 0x2F, 0x62, 0xD8, 0x00, 0xC6,
+    0x8F, 0x21, 0x9B, 0x82, 0xF9, 0xC1, 0xD7, 0x15, 0x14, 0xBF, 0x89, 0xDA, 0x69, 0x39, 0x08, 0x04,
+    0x5A, 0xCA, 0x9B, 0x08, 0x28, 0x68, 0x48, 0xC5, 0xB6, 0xDA, 0x84, 0x28, 0xA9, 0x19, 0x94, 0x4A,
+    0xE0, 0x43, 0xAE, 0x38, 0x5D, 0xEF, 0x22, 0xF0, 0x21, 0x0C, 0x14, 0x45, 0x90, 0x9E, 0x9E, 0xAB,
+    0x5A, 0xF5, 0x5D, 0x25, 0x12, 0x40, 0xB5, 0xD0, 0xD2, 0x90, 0x5C, 0xC7, 0xAF, 0xB1, 0xF6, 0x63,
+    0xD6, 0x69, 0xCF, 0x08, 0x7E, 0x02, 0xFD, 0x00, 0x2F, 0x3A, 0x78, 0x24, 0xF2, 0xF3, 0xF2, 0x49,
+    0x61, 0x38, 0x7F, 0xAF, 0xC9, 0x87, 0x55, 0xEE, 0xBC, 0x65, 0x2D, 0x21, 0xC8, 0xA5, 0x1C, 0x3F,
+    0x3F, 0xFC, 0xE9, 0xF0, 0x3C, 0xC0, 0x23, 0x4C, 0x48, 0xE5, 0x98, 0x40, 0x5B, 0x9F, 0xE5, 0xA0,
+    0x16, 0x3E, 0x3A, 0x18, 0x6B, 0x26, 0x8D, 0x7C, 0x1B, 0xF5, 0xE2, 0x32, 0xBB, 0x3C, 0x52, 0xB2,
+    0x1F, 0x69, 0xFE, 0x57, 0x6C, 0xB2, 0x24, 0x03, 0x2C, 0x29, 0xEA, 0x46, 0x0E, 0x4C, 0xAC, 0x15,
+    0x56, 0xB6, 0xD6, 0x36, 0x05, 0xC8, 0x97, 0xF7, 0x49, 0x5E, 0xFB, 0xDA, 0xC4, 0x03, 0x34, 0x34,
+    0xAB, 0x21, 0x22, 0x03, 0xD9, 0x27, 0x63, 0x73, 0x4E, 0xA9, 0x78, 0xC8, 0xC2, 0xE9, 0xCF, 0xB3,
+    0x31, 0x54, 0xB4, 0x43, 0x9B, 0xDB, 0x80, 0x03, 0x52, 0x46, 0x8D, 0xDC, 0x94, 0xCB, 0xB6, 0xE0,
+    0x4E, 0x99, 0x7E, 0x22, 0x3E, 0x2F, 0x16, 0x13, 0xD6, 0x8B, 0xDF, 0x3B, 0x9F, 0xA9, 0x2A, 0x26,
+    0xED, 0xCD, 0x46, 0xDF, 0x23, 0x95, 0x29, 0x65, 0xF9, 0xD5, 0x3E, 0xDE, 0x4F, 0xA8, 0x1E, 0x77,
+    0xA8, 0x4A, 0x50, 0x42, 0x4D, 0x9F, 0x4B, 0x7B, 0xA1, 0x79, 0x52, 0xF8, 0x46, 0x01, 0xED, 0xC1,
+    0x01, 0xDD, 0x12, 0x0A, 0x8A, 0xEB, 0x48, 0x0A, 0xB2, 0xB6, 0x4B, 0x6D, 0x8D, 0xF2, 0x06, 0x5C,
+    0x8A, 0xB1, 0xE7, 0x5A, 0xAB, 0x12, 0xDF, 0x68, 0x3C, 0xA1, 0x95, 0x3C, 0xC6, 0xAD, 0x9D, 0x7A,
+    0x1A, 0x43, 0xB5, 0x2C, 0xAE, 0x9A, 0x67, 0xFF, 0xA8, 0x19, 0x84, 0xF7, 0x5A, 0x47, 0x5C, 0x97,
+    0xFB, 0x4F, 0xA8, 0xC0, 0xF1, 0xE5, 0x86, 0xBA, 0xEC, 0xB4, 0x04, 0x29, 0xA7, 0xAA, 0x12, 0xEA,
+    0x1A, 0xC2, 0xDC, 0x0D, 0x62, 0x21, 0xE1, 0x77, 0xB9, 0xB2, 0x4C, 0xCF, 0x35, 0x86, 0x63, 0xD3,
+    0x36, 0xA9, 0x0E, 0x31, 0xAC, 0x39, 0xC8, 0x76, 0xA9, 0xE9, 0x8C, 0xC3, 0xE3, 0x3C, 0x79, 0x4E,
+    0x14, 0x21, 0x4B, 0xDE, 0x6D, 0xC4, 0xE9, 0xC9, 0x75, 0xBA, 0x9C, 0x2D, 0x14, 0xD4, 0xB2, 0xFF
+};
+
+const uint32_t data_crc_16 = 0x5A57u;
+const uint32_t data_crc_32 = 0x7322E75Eu;
+
+#endif

+ 15 - 1
test/unittest/shellmatta_escape/test_escape_processArrowKeys.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_escape_processArrowKeys.c
+ * @brief   unittest for shellmatta escape_processArrowKeys - currently not implemented
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_escape.c"
 #include "src/shellmatta_escape.c"
 #include <string.h>
 #include <string.h>
@@ -9,5 +23,5 @@ TEST_CASE( "shellmatta_escape dummy" ) {
 
 
     escape_processArrowKeys(&inst);
     escape_processArrowKeys(&inst);
 
 
-    REQUIRE( inst.inputCount == 0u);
+    REQUIRE(inst.inputCount == 0u);
 }
 }

+ 16 - 4
test/unittest/shellmatta_history/test_appendHistoryByte.cpp

@@ -1,13 +1,25 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_appendHistoryByte.c
+ * @brief   unittest for shellmatta appendHistoryByte - currently not implemented
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_history.c"
 #include "src/shellmatta_history.c"
 #include <string.h>
 #include <string.h>
 
 
-TEST_CASE( "shellmatta_history dummy" ) {
+TEST_CASE("shellmatta_history dummy" ) {
 
 
     shellmatta_instance_t inst;
     shellmatta_instance_t inst;
     inst.inputCount = 0u;
     inst.inputCount = 0u;
 
 
-    //appendHistoryByte(&inst, 'a');
-
-    REQUIRE( inst.inputCount == 0u);
+    REQUIRE(inst.inputCount == 0u);
 }
 }

+ 14 - 0
test/unittest/shellmatta_opt/test_opt_findNextHunk.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_opt_findNextHunk.c
+ * @brief   unittest for shellmatta opt_findNextHunk
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_opt.c"
 #include "src/shellmatta_opt.c"
 #include <string.h>
 #include <string.h>

+ 14 - 0
test/unittest/shellmatta_opt/test_opt_peekNextHunk.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_opt_peekNextHunk.c
+ * @brief   unittest for shellmatta opt_peekNextHunk
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_opt.c"
 #include "src/shellmatta_opt.c"
 #include <string.h>
 #include <string.h>

+ 14 - 0
test/unittest/shellmatta_utils/test_utils_clearInput.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_clearInput.c
+ * @brief   unittest for shellmatta utils_clearInput
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.c"
 #include "src/shellmatta_utils.c"
 #include <string.h>
 #include <string.h>

+ 14 - 0
test/unittest/shellmatta_utils/test_utils_eraseLine.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_eraseLine.c
+ * @brief   unittest for shellmatta utils_eraseLine
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.c"
 #include "src/shellmatta_utils.c"
 #include <string.h>
 #include <string.h>

+ 14 - 2
test/unittest/shellmatta_utils/test_utils_forwardCursor.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_forwardCursor.c
+ * @brief   unittest for shellmatta utils_forwardCursor
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.c"
 #include "src/shellmatta_utils.c"
 #include <string.h>
 #include <string.h>
@@ -14,7 +28,6 @@ static shellmatta_retCode_t writeFct(const char* data, uint32_t length)
     return SHELLMATTA_OK;
     return SHELLMATTA_OK;
 }
 }
 
 
-
 TEST_CASE( "shellmatta_utils_forwardCursor normal" ) {
 TEST_CASE( "shellmatta_utils_forwardCursor normal" ) {
 
 
     shellmatta_instance_t inst;
     shellmatta_instance_t inst;
@@ -63,7 +76,6 @@ TEST_CASE( "shellmatta_utils_forwardCursor normal echo off" ) {
     REQUIRE( strncmp("\0\0\0\0", write_data, 4u) == 0);
     REQUIRE( strncmp("\0\0\0\0", write_data, 4u) == 0);
 }
 }
 
 
-
 TEST_CASE( "shellmatta_utils_forwardCursor forward by 12 with cursor at 5 and input count at 10" ) {
 TEST_CASE( "shellmatta_utils_forwardCursor forward by 12 with cursor at 5 and input count at 10" ) {
 
 
     shellmatta_instance_t inst;
     shellmatta_instance_t inst;

+ 14 - 0
test/unittest/shellmatta_utils/test_utils_insertChars.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_insertChars.c
+ * @brief   unittest for shellmatta utils_insertChars
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.c"
 #include "src/shellmatta_utils.c"
 #include <string.h>
 #include <string.h>

+ 14 - 0
test/unittest/shellmatta_utils/test_utils_removeChars.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_removeChars.c
+ * @brief   unittest for shellmatta utils_removeChars
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.h"
 #include "src/shellmatta_utils.h"
 #include <string.h>
 #include <string.h>

+ 14 - 0
test/unittest/shellmatta_utils/test_utils_restoreCursorPos.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_restoreCursorPos.c
+ * @brief   unittest for shellmatta utils_restoreCursorPos
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.c"
 #include "src/shellmatta_utils.c"
 #include <string.h>
 #include <string.h>

+ 14 - 0
test/unittest/shellmatta_utils/test_utils_rewindCursor.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_rewindCursor.c
+ * @brief   unittest for shellmatta utils_rewindCursor
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.c"
 #include "src/shellmatta_utils.c"
 #include <string.h>
 #include <string.h>

+ 14 - 0
test/unittest/shellmatta_utils/test_utils_saveCursorPos.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_saveCursorPos.c
+ * @brief   unittest for shellmatta utils_saveCursorPos
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.c"
 #include "src/shellmatta_utils.c"
 #include <string.h>
 #include <string.h>

+ 170 - 0
test/unittest/shellmatta_utils/test_utils_shellAsciiToUInt32.cpp

@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_shellAsciiToUInt32.c
+ * @brief   unittest for shellmatta utils_shellAsciiToUInt32
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+#include "test/framework/catch.hpp"
+#include "src/shellmatta_utils.c"
+#include <string.h>
+
+SCENARIO("Check utils_shellAsciiToUInt32 with valid strings")
+{
+    GIVEN("Initialized shellmatta utils module")
+    {
+        shellmatta_retCode_t ret;
+        uint32_t result = 0xFFFFFFFFu;
+        WHEN("Passing 123456789 with base 10")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"123456789", &result, 10u);
+            THEN("utils_shellAsciiToUInt32 returns successfully")
+            {
+                CHECK(ret == SHELLMATTA_OK);
+                REQUIRE(result == 123456789);
+            }
+        }
+        WHEN("Passing 4294967295 with base 10")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"4294967295", &result, 10u);
+            THEN("utils_shellAsciiToUInt32 returns successfully")
+            {
+                CHECK(ret == SHELLMATTA_OK);
+                REQUIRE(result == 4294967295);
+            }
+        }
+        WHEN("Passing 110100100001110011100011 with base 2")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"110100100001110011100011", &result, 2u);
+            THEN("utils_shellAsciiToUInt32 returns successfully")
+            {
+                CHECK(ret == SHELLMATTA_OK);
+                REQUIRE(result == 0b110100100001110011100011);
+            }
+        }
+        WHEN("Passing 435476600 with base 8")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"435476600", &result, 8u);
+            THEN("utils_shellAsciiToUInt32 returns successfully")
+            {
+                CHECK(ret == SHELLMATTA_OK);
+                REQUIRE(result == 0435476600);
+            }
+        }
+        WHEN("Passing AABBCCDD with base 16")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"AABBCCDD", &result, 16u);
+            THEN("utils_shellAsciiToUInt32 returns successfully")
+            {
+                CHECK(ret == SHELLMATTA_OK);
+                REQUIRE(result == 0xAABBCCDDu);
+            }
+        }
+        WHEN("Passing deadbeef with base 16")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"deadbeef", &result, 16u);
+            THEN("utils_shellAsciiToUInt32 returns successfully")
+            {
+                CHECK(ret == SHELLMATTA_OK);
+                REQUIRE(result == 0xdeadbeefu);
+            }
+        }
+        WHEN("Passing an empty string with base 16")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"", &result, 16u);
+            THEN("utils_shellAsciiToUInt32 returns successfully")
+            {
+                CHECK(ret == SHELLMATTA_OK);
+                REQUIRE(result == 0u);
+            }
+        }
+    }
+}
+
+SCENARIO("Check utils_shellAsciiToUInt32 with invalid strings")
+{
+    GIVEN("Initialized shellmatta utils module")
+    {
+        shellmatta_retCode_t ret;
+        uint32_t result = 0xFFFFFFFFu;
+        WHEN("Passing 4294967296 with base 10 - overflow")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"4294967296", &result, 10u);
+            THEN("utils_shellAsciiToUInt32 returns an error")
+            {
+                REQUIRE(ret == SHELLMATTA_ERROR);
+            }
+        }
+        WHEN("Passing 123456 with base 2 - wrong base")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"123456", &result, 2u);
+            THEN("utils_shellAsciiToUInt32 returns an error")
+            {
+                REQUIRE(ret == SHELLMATTA_ERROR);
+            }
+        }
+        WHEN("Passing FOOBAR with base 16 - wrong characters")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"FOOBAR", &result, 16u);
+            THEN("utils_shellAsciiToUInt32 returns an error")
+            {
+                REQUIRE(ret == SHELLMATTA_ERROR);
+            }
+        }
+        WHEN("Passing = with base 16 - wrong characters")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"=", &result, 16u);
+            THEN("utils_shellAsciiToUInt32 returns an error")
+            {
+                REQUIRE(ret == SHELLMATTA_ERROR);
+            }
+        }
+        WHEN("Passing / with base 16 - wrong characters")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"/", &result, 16u);
+            THEN("utils_shellAsciiToUInt32 returns an error")
+            {
+                REQUIRE(ret == SHELLMATTA_ERROR);
+            }
+        }
+        WHEN("Passing } with base 16 - wrong characters")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"}", &result, 16u);
+            THEN("utils_shellAsciiToUInt32 returns an error")
+            {
+                REQUIRE(ret == SHELLMATTA_ERROR);
+            }
+        }
+        WHEN("Passing invalid string")
+        {
+            ret = utils_shellAsciiToUInt32(NULL, &result, 16u);
+            THEN("utils_shellAsciiToUInt32 returns an error")
+            {
+                CHECK(ret == SHELLMATTA_USE_FAULT);
+            }
+        }
+        WHEN("Passing invalid result")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"", NULL, 16u);
+            THEN("utils_shellAsciiToUInt32 returns an error")
+            {
+                CHECK(ret == SHELLMATTA_USE_FAULT);
+            }
+        }
+        WHEN("Passing invalid base")
+        {
+            ret = utils_shellAsciiToUInt32((char*)"", &result, 17u);
+            THEN("utils_shellAsciiToUInt32 returns an error")
+            {
+                CHECK(ret == SHELLMATTA_USE_FAULT);
+            }
+        }
+    }
+}

+ 29 - 15
test/unittest/shellmatta_utils/test_utils_shellItoa.cpp

@@ -1,52 +1,66 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_shellItoa.c
+ * @brief   unittest for shellmatta utils_shellItoa
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.c"
 #include "src/shellmatta_utils.c"
 #include <string.h>
 #include <string.h>
 
 
-TEST_CASE( "shellmatta_utils.c - itoa - 123456 base 10" ) {
+TEST_CASE( "shellmatta_utils.c - itoa - 123456 base 10") {
     char buffer[64];
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(123456, buffer, 10) == 6 );
-    REQUIRE( strcmp(buffer, "123456") == 0);
+    CHECK(utils_shellItoa(123456, buffer, 10) == 6);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("123456"));
 }
 }
 
 
 TEST_CASE( "shellmatta_utils.c - itoa - 0x0ABBCCDD base 16") {
 TEST_CASE( "shellmatta_utils.c - itoa - 0x0ABBCCDD base 16") {
     char buffer[64];
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(0x0ABBCCDD, buffer, 16) == 7 );
-    REQUIRE( strcmp(buffer, "ABBCCDD") == 0);
+    CHECK(utils_shellItoa(0x0ABBCCDD, buffer, 16) == 7);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("ABBCCDD"));
 }
 }
 
 
 TEST_CASE( "shellmatta_utils.c - itoa - -574236 base 10") {
 TEST_CASE( "shellmatta_utils.c - itoa - -574236 base 10") {
     char buffer[64];
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(-574236, buffer, 10) == 7 );
-    REQUIRE( strcmp(buffer, "-574236") == 0);
+    CHECK(utils_shellItoa(-574236, buffer, 10) == 7);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("-574236"));
 }
 }
 
 
 TEST_CASE( "shellmatta_utils.c - itoa - 0x80000000 base 2") {
 TEST_CASE( "shellmatta_utils.c - itoa - 0x80000000 base 2") {
     char buffer[64];
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(0x80000000, buffer, 2) == 33 );
-    REQUIRE( strcmp(buffer, "-10000000000000000000000000000000") == 0);
+    CHECK(utils_shellItoa(0x80000000, buffer, 2) == 33);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("-10000000000000000000000000000000"));
 }
 }
 
 
 TEST_CASE( "shellmatta_utils.c - itoa - 0x7FFFFFFF base 2") {
 TEST_CASE( "shellmatta_utils.c - itoa - 0x7FFFFFFF base 2") {
     char buffer[64];
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(0x7FFFFFFF, buffer, 2) == 31 );
-    REQUIRE( strcmp(buffer, "1111111111111111111111111111111") == 0);
+    CHECK(utils_shellItoa(0x7FFFFFFF, buffer, 2) == 31);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("1111111111111111111111111111111"));
 }
 }
 
 
 TEST_CASE( "shellmatta_utils.c - itoa - 0x7FFFFFFF base 1 - wrong base") {
 TEST_CASE( "shellmatta_utils.c - itoa - 0x7FFFFFFF base 1 - wrong base") {
     char buffer[64];
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(0x7FFFFFFF, buffer, 1) == 0 );
-    REQUIRE( strcmp(buffer, "\0") == 0);
+    CHECK(utils_shellItoa(0x7FFFFFFF, buffer, 1) == 0);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("\0"));
 }
 }
 
 
 TEST_CASE( "shellmatta_utils.c - itoa - 0x7FFFFFFF base 17 - wrong base") {
 TEST_CASE( "shellmatta_utils.c - itoa - 0x7FFFFFFF base 17 - wrong base") {
     char buffer[64];
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(0x7FFFFFFF, buffer, 17) == 0 );
-    REQUIRE( strcmp(buffer, "\0") == 0);
+    CHECK(utils_shellItoa(0x7FFFFFFF, buffer, 17) == 0);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("\0"));
 }
 }

+ 14 - 0
test/unittest/shellmatta_utils/test_utils_terminateInput.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_terminateInput.c
+ * @brief   unittest for shellmatta utils_terminateInput
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.c"
 #include "src/shellmatta_utils.c"
 #include <string.h>
 #include <string.h>

+ 18 - 4
test/unittest/shellmatta_utils/test_utils_writeEcho.cpp

@@ -1,3 +1,17 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_utils_writeEcho.c
+ * @brief   unittest for shellmatta utils_writeEcho
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.c"
 #include "src/shellmatta_utils.c"
 #include <string.h>
 #include <string.h>
@@ -17,8 +31,8 @@ static shellmatta_retCode_t writeFct(const char* data, uint32_t length)
 TEST_CASE( "shellmatta_writeEcho echo enabled" ) {
 TEST_CASE( "shellmatta_writeEcho echo enabled" ) {
 
 
     shellmatta_instance_t inst;
     shellmatta_instance_t inst;
-    char buffer[20];
-    char dummyData[29];
+    char buffer[20] = {0};
+    char dummyData[29] = "asd";
 
 
     inst.buffer = buffer;
     inst.buffer = buffer;
     inst.bufferSize = 20;
     inst.bufferSize = 20;
@@ -42,8 +56,8 @@ TEST_CASE( "shellmatta_writeEcho echo enabled" ) {
 TEST_CASE( "shellmatta_writeEcho echo disabled" ) {
 TEST_CASE( "shellmatta_writeEcho echo disabled" ) {
 
 
     shellmatta_instance_t inst;
     shellmatta_instance_t inst;
-    char buffer[20];
-    char dummyData[29];
+    char buffer[20] = {0};
+    char dummyData[29] = "asd";
 
 
     inst.buffer = buffer;
     inst.buffer = buffer;
     inst.bufferSize = 20;
     inst.bufferSize = 20;

+ 18 - 0
test/unittest/shellmatta_ymodem/test_ymodem.cpp

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file    test_ymodem.c
+ * @brief   unittest for shellmatta ymodem - currently not implemented
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+#include "test/framework/catch.hpp"
+#include "src/shellmatta_ymodem.c"
+
+/* empty unittest - just added to fix linking issues - no unittests for ymodem right now */

+ 0 - 1
test/unittest/test_main.cpp

@@ -4,4 +4,3 @@
 #define CATCH_CONFIG_MAIN
 #define CATCH_CONFIG_MAIN
 
 
 #include "test/framework/catch.hpp"
 #include "test/framework/catch.hpp"
-