Browse Source

Merge branch 'feature/add_ymodem_receive_functionalities' of shimatta/shellmatta into develop

shimatta 9 months ago
parent
commit
42e4707486
47 changed files with 2540 additions and 403 deletions
  1. 3 0
      README.md
  2. 103 10
      api/shellmatta.h
  3. 30 5
      cfg/cppcheck/cppcheck_suppressions.xml
  4. 575 271
      cfg/doxygen/doxyfile
  5. 2 0
      doc/shellmatta.dox
  6. 0 5
      doc/shellmatta_transport_layer.dox
  7. 102 0
      doc/shellmatta_ymodem.dox
  8. 86 4
      example/main.c
  9. 11 5
      makefile
  10. 22 8
      src/shellmatta.c
  11. 12 12
      src/shellmatta_auth.c
  12. 111 25
      src/shellmatta_crc.c
  13. 4 2
      src/shellmatta_crc.h
  14. 13 3
      src/shellmatta_transport.c
  15. 8 1
      src/shellmatta_transport.h
  16. 76 8
      src/shellmatta_utils.c
  17. 36 19
      src/shellmatta_utils.h
  18. 531 0
      src/shellmatta_ymodem.c
  19. 33 0
      src/shellmatta_ymodem.h
  20. 2 2
      test/integrationtest/test_integration_help.cpp
  21. 3 3
      test/integrationtest/test_integration_history.cpp
  22. 226 0
      test/integrationtest/test_integration_ymodem.cpp
  23. 14 3
      test/unittest/shellmatta/test_shellmatta_doInit.cpp
  24. 14 2
      test/unittest/shellmatta_autocomplete/test_autocomplete_run.cpp
  25. 35 0
      test/unittest/shellmatta_crc/test_crc16Fast.cpp
  26. 35 0
      test/unittest/shellmatta_crc/test_crc16Slow.cpp
  27. 17 2
      test/unittest/shellmatta_crc/test_crc32Fast.cpp
  28. 17 2
      test/unittest/shellmatta_crc/test_crc32Slow.cpp
  29. 18 3
      test/unittest/shellmatta_crc/test_crc32_data.h
  30. 15 1
      test/unittest/shellmatta_escape/test_escape_processArrowKeys.cpp
  31. 16 4
      test/unittest/shellmatta_history/test_appendHistoryByte.cpp
  32. 14 0
      test/unittest/shellmatta_opt/test_opt_findNextHunk.cpp
  33. 14 0
      test/unittest/shellmatta_opt/test_opt_peekNextHunk.cpp
  34. 14 0
      test/unittest/shellmatta_utils/test_utils_clearInput.cpp
  35. 14 0
      test/unittest/shellmatta_utils/test_utils_eraseLine.cpp
  36. 14 2
      test/unittest/shellmatta_utils/test_utils_forwardCursor.cpp
  37. 14 0
      test/unittest/shellmatta_utils/test_utils_insertChars.cpp
  38. 14 0
      test/unittest/shellmatta_utils/test_utils_removeChars.cpp
  39. 14 0
      test/unittest/shellmatta_utils/test_utils_restoreCursorPos.cpp
  40. 14 0
      test/unittest/shellmatta_utils/test_utils_rewindCursor.cpp
  41. 14 0
      test/unittest/shellmatta_utils/test_utils_saveCursorPos.cpp
  42. 170 0
      test/unittest/shellmatta_utils/test_utils_shellAsciiToUInt32.cpp
  43. 14 0
      test/unittest/shellmatta_utils/test_utils_shellItoa.cpp
  44. 14 0
      test/unittest/shellmatta_utils/test_utils_terminateInput.cpp
  45. 14 0
      test/unittest/shellmatta_utils/test_utils_writeEcho.cpp
  46. 18 0
      test/unittest/shellmatta_ymodem/test_ymodem.cpp
  47. 0 1
      test/unittest/test_main.cpp

+ 3 - 0
README.md

@@ -149,6 +149,9 @@ There are some defines you can use to change the behaviour of the shellmatta:
 | 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
 

+ 103 - 10
api/shellmatta.h

@@ -26,7 +26,7 @@
 /* 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.
  */
@@ -192,6 +192,85 @@ typedef struct shellmatta_cmd
     struct shellmatta_cmd   *next;      /**< pointer to next command or NULL        */
 } 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
 
 /**
@@ -224,14 +303,14 @@ typedef uint32_t (*shellmatta_transport_crc_t)(const char* data, const uint32_t
  */
 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 */
+    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;
 
 /**
@@ -253,6 +332,7 @@ typedef struct
     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                       */
@@ -308,6 +388,7 @@ typedef struct
     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                */
@@ -323,7 +404,7 @@ typedef struct
     shellmatta_auth_log_t           logFct;             /**< auth log function                      */
 #endif
 #ifdef SHELLMATTA_TRANSPORT
-    uint32_t                        transportBusyMark;  /**< transport processing position during 
+    uint32_t                        transportBusyMark;  /**< transport processing position during
                                                              busy cmd execution                     */
     shellmatta_transport_layer_t    transportLayer;     /**< transport layer instance               */
 #endif
@@ -432,6 +513,18 @@ shellmatta_retCode_t shellmatta_auth_chpasswd(              shellmatta_handle_t
 
 #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
 
 /** @} */

+ 30 - 5
cfg/cppcheck/cppcheck_suppressions.xml

@@ -19,11 +19,6 @@
         <fileName>src/shellmatta.c</fileName>
         <symbolName>shellmatta_resetShell</symbolName>
     </suppress>
-    <suppress>
-        <id>unusedFunction</id>
-        <fileName>src/shellmatta.c</fileName>
-        <symbolName>shellmatta_addCmd</symbolName>
-    </suppress>
     <suppress>
         <id>unusedFunction</id>
         <fileName>src/shellmatta.c</fileName>
@@ -44,6 +39,16 @@
         <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>
@@ -69,4 +74,24 @@
         <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


+ 2 - 0
doc/shellmatta.dox

@@ -45,4 +45,6 @@
 
     @subpage shellmatta_transport_layer
 
+    @subpage shellmatta_ymodem
+
 */

+ 0 - 5
doc/shellmatta_transport_layer.dox

@@ -149,9 +149,4 @@
     To enable the lookup table less CRC just define
     ``SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP`` during compilation.
 
-
-    @section shellmatta_transport_layer_control Controlling the transport layer
-
-    @todo Not implemented yet
-
 */

+ 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.
+
+*/

+ 86 - 4
example/main.c

@@ -95,7 +95,7 @@ static shellmatta_retCode_t reset(shellmatta_handle_t handle, const char *argume
     uint32_t argLen;
     bool printPrompt = false;
 
-    static const shellmatta_opt_long_t options[] = 
+    static const shellmatta_opt_long_t options[] =
     {
         {"prompt",  'p',    SHELLMATTA_OPT_ARG_REQUIRED},
         {NULL,      '\0',   SHELLMATTA_OPT_ARG_NONE}
@@ -175,6 +175,75 @@ static shellmatta_retCode_t busy(shellmatta_handle_t handle, const char *argumen
 }
 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)
 {
@@ -203,6 +272,8 @@ int main(int argc, char **argv)
         return f;
     }
 
+    printf("Starting Shellmatta\n");
+
     shellmatta_doInit(  &instance,
                         &handle,
                         buffer,
@@ -220,6 +291,7 @@ int main(int argc, char **argv)
     shellmatta_addCmd(handle, &resetCommand);
     shellmatta_addCmd(handle, &continuousCommand);
     shellmatta_addCmd(handle, &busyCommand);
+    shellmatta_addCmd(handle, &ymodemCommand);
 
 
     shellmatta_auth_user_t userList[] = {
@@ -236,18 +308,28 @@ int main(int argc, char **argv)
 
     shellmatta_auth_init(handle, userList, 2, permList, 2, false, NULL, NULL);
 
+    int i = 0;
     while(exitRequest == false)
     {
         char c;
         shellmatta_retCode_t ret;
+        int flags = fcntl(f, F_GETFL, 0);
+        fcntl(f, F_SETFL, flags | O_NONBLOCK);
         int res = 0;
         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
         {
+            res = res > 0 ? res : 0;
             ret = shellmatta_processData(handle, &c, res);
             if(SHELLMATTA_BUSY == ret)
             {

+ 11 - 5
makefile

@@ -31,12 +31,13 @@ SOURCES :=  src/shellmatta.c                \
             src/shellmatta_history.c        \
             src/shellmatta_utils.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      \
-                            src/shellmatta_crc.c
+                            src/shellmatta_transport.c
 AUTH_SOURCES     := $(SOURCES) src/shellmatta_auth.c
 EXAMPLE_SOURCES  := example/main.c
 EXAMPLE_SOURCES  += $(filter-out $(EXAMPLE_SOURCES),$(AUTH_SOURCES))
@@ -62,6 +63,7 @@ UNITTEST_SOURCES := test/unittest/test_main.cpp
                     test/unittest/shellmatta_opt/test_opt_peekNextHunk.cpp              \
                     test/unittest/shellmatta_utils/test_utils_writeEcho.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_restoreCursorPos.cpp      \
                     test/unittest/shellmatta_utils/test_utils_eraseLine.cpp             \
@@ -76,7 +78,10 @@ UNITTEST_SOURCES := test/unittest/test_main.cpp
                     test/unittest/shellmatta_history/test_appendHistoryByte.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_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                  \
                             test/integrationtest/test_integration.cpp           \
@@ -85,7 +90,8 @@ INTEGRATIONTEST_SOURCES :=  test/integrationtest/test_main.cpp
                             test/integrationtest/test_integration_continue.cpp  \
                             test/integrationtest/test_integration_busy.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

+ 22 - 8
src/shellmatta.c

@@ -23,6 +23,7 @@
 #include "shellmatta_utils.h"
 #include "shellmatta_escape.h"
 #include "shellmatta_opt.h"
+#include "shellmatta_ymodem.h"
 #ifdef SHELLMATTA_TRANSPORT
 #include "shellmatta_transport.h"
 #endif
@@ -47,14 +48,14 @@ static shellmatta_retCode_t shellmatta_processDataInt(shellmatta_handle_t     ha
                                                       char                    *data,
                                                       uint32_t                 size)
 {
-    shellmatta_cmd_t        *cmd;
-    uint8_t                 cmdExecuted = 0u;
-    uint32_t                cmdLen;
-    char                    *tempString;
+    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;
+    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)
@@ -77,6 +78,11 @@ static shellmatta_retCode_t shellmatta_processDataInt(shellmatta_handle_t     ha
             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))
     {
@@ -108,8 +114,14 @@ static shellmatta_retCode_t shellmatta_processDataInt(shellmatta_handle_t     ha
     /** -# 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 */
-        if(NULL != inst->continuousCmd)
+        else if(NULL != inst->continuousCmd)
         {
             /** -# copy data and call command function */
             inst->buffer[inst->stdinIdx]        = data[inst->byteCounter];
@@ -128,6 +140,7 @@ static shellmatta_retCode_t shellmatta_processDataInt(shellmatta_handle_t     ha
             {
                 /** -# cancel continue session */
                 utils_terminateInput(inst);
+
                 ret = SHELLMATTA_OK;
             }
             else if(SHELLMATTA_CONTINUE == ret)
@@ -531,6 +544,7 @@ shellmatta_retCode_t shellmatta_resetShell(shellmatta_handle_t handle, bool prin
         inst->hereStartIdx          = 0u;
         inst->hereDelimiterIdx      = 0u;
         inst->hereLength            = 0u;
+        inst->ymodem.state          = SHELLMATTA_YMODEM_INACTIVE;
         shellmatta_opt_init(inst, 0u);
 
 #ifdef SHELLMATTA_AUTHENTICATION

+ 12 - 12
src/shellmatta_auth.c

@@ -34,7 +34,7 @@
  * @return      #SHELLMATTA_BUSY    => reading in data
  *              #SHELLMATTA_OK      => delimiter (e.g. enter) detected
  */
-shellmatta_retCode_t inputWrapper(shellmatta_instance_t *inst, char data, bool hide)
+static shellmatta_retCode_t inputWrapper(shellmatta_instance_t *inst, char data, bool hide)
 {
     shellmatta_retCode_t ret = SHELLMATTA_BUSY;
     bool echoEnabledBackup = inst->echoEnabled;
@@ -108,7 +108,7 @@ static uint32_t getUserIdFromName(shellmatta_instance_t *inst, const char *usern
  * @param[in]       userId      userId to check the password for
  * @param[in]       password    password to check for - pointer to string
  */
-void checkPassword(shellmatta_handle_t handle, uint32_t userId, char *password)
+static void checkPassword(shellmatta_handle_t handle, uint32_t userId, char *password)
 {
     shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
     uint32_t                approvedUserId  = 0u;
@@ -144,11 +144,11 @@ void checkPassword(shellmatta_handle_t handle, uint32_t userId, char *password)
     /** -# print login result */
     if (0 == approvedUserId)
     {
-        shellmatta_write(handle, "username or password is wrong\r\n", 31);
+        SHELLMATTA_WRITE("username or password is wrong\r\n", 31);
     }
     else
     {
-        shellmatta_write(handle, "login successful\r\n", 18);
+        SHELLMATTA_WRITE("login successful\r\n", 18);
         (void)shellmatta_auth_login(handle, approvedUserId);
     }
 }
@@ -201,7 +201,7 @@ static shellmatta_retCode_t loginCmdFct(const shellmatta_handle_t handle, const
                         }
                         break;
                     default:
-                        shellmatta_write(handle, "Unknown option\r\n", 16);
+                        SHELLMATTA_WRITE("Unknown option\r\n", 16);
                         return SHELLMATTA_USE_FAULT;
                         break;
                 }
@@ -220,13 +220,13 @@ static shellmatta_retCode_t loginCmdFct(const shellmatta_handle_t handle, const
                 inst->cursor = 0u;
 
                 inst->tmpUserId = getUserIdFromName(handle, username);
-                shellmatta_write(handle, "\r\nenter password:\r\n", 19);
+                SHELLMATTA_WRITE("\r\nenter password:\r\n", 19);
                 inst->loginState = SHELLMATTA_AUTH_PASSWORD;
                 ret = SHELLMATTA_CONTINUE;
             }
             else if (NULL != password)
             {
-                shellmatta_write(handle, "Missing username\r\n", 18);
+                SHELLMATTA_WRITE("Missing username\r\n", 18);
                 ret = SHELLMATTA_USE_FAULT;
             }
             else
@@ -234,7 +234,7 @@ static shellmatta_retCode_t loginCmdFct(const shellmatta_handle_t handle, const
                 /** -# no credentials are passed with the command - start the interactive input */
                 inst->inputCount = 0u;
                 inst->cursor = 0u;
-                shellmatta_write(handle, "enter username:\r\n", 17);
+                SHELLMATTA_WRITE("enter username:\r\n", 17);
                 inst->loginState = SHELLMATTA_AUTH_USERNAME;
                 ret = SHELLMATTA_CONTINUE;
             }
@@ -253,7 +253,7 @@ static shellmatta_retCode_t loginCmdFct(const shellmatta_handle_t handle, const
                 inst->inputCount = 0u;
                 inst->cursor = 0u;
 
-                shellmatta_write(handle, "\r\nenter password:\r\n", 19);
+                SHELLMATTA_WRITE("\r\nenter password:\r\n", 19);
                 inst->loginState = SHELLMATTA_AUTH_PASSWORD;
             }
             ret = SHELLMATTA_CONTINUE;
@@ -266,7 +266,7 @@ static shellmatta_retCode_t loginCmdFct(const shellmatta_handle_t handle, const
             {
                 /** -# check input username and password */
                 inst->buffer[inst->inputCount] = '\0';
-                shellmatta_write(handle, "\r\n", 2);
+                SHELLMATTA_WRITE("\r\n", 2);
                 checkPassword(handle, inst->tmpUserId, inst->buffer);
                 inst->loginState = SHELLMATTA_AUTH_IDLE;
             }
@@ -311,7 +311,7 @@ static shellmatta_retCode_t logoutCmdFct(const shellmatta_handle_t handle, const
     }
 
     shellmatta_auth_logout(handle);
-    shellmatta_write(handle, "good bye\r\n", 10);
+    SHELLMATTA_WRITE("good bye\r\n", 10);
 
     (void)arguments;
     (void)length;
@@ -410,7 +410,7 @@ shellmatta_retCode_t shellmatta_auth_init(shellmatta_handle_t       handle,
  * @param[in]   handle      shellmatta instance handle
  * @param[in]   userId      userId to login
  * @return      errorcode   #SHELLMATTA_OK
- *                          #SHELLMATTA_ERR (user does not exist)
+ *                          #SHELLMATTA_ERROR (user does not exist)
  *                          #SHELLMATTA_USE_FAULT (param err)
  */
 shellmatta_retCode_t shellmatta_auth_login(shellmatta_handle_t handle, uint32_t userId)

+ 111 - 25
src/shellmatta_crc.c

@@ -14,6 +14,8 @@
 
 #include "shellmatta_crc.h"
 
+#ifdef SHELLMATTA_TRANSPORT
+
 #ifndef SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP
 /** \brief CRC Lookup table for 0x04c11db7 reflected */
 uint32_t crc32Table[] = {
@@ -50,9 +52,29 @@ uint32_t crc32Table[] = {
     0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
     0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
 };
-#endif
 
-#ifdef SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP
+/**
+ * @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
@@ -69,9 +91,8 @@ static uint32_t reverse(uint32_t x, uint32_t bits)
     return x >> (32u - bits);
 }
 
-
 /**
- * @brief       Computes the crc32-checksum of a buffer. O(4n)
+ * @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
@@ -109,41 +130,106 @@ static uint32_t crc32Slow(const char* data, uint32_t size)
     return crcTemp;
 }
 
-#else
+#endif /* SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP */
+
 /**
- * @brief       Computes the crc32-checksum of a buffer. O(n)
+ * @brief       Computes the crc32-checksum of a buffer.
  * @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 crc32Calc(const char* data, uint32_t size)
 {
-    uint32_t i;
-    uint32_t crcTemp = 0xffffffffu;
+    #ifdef  SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP
+        return crc32Slow(data, size);
+    #else
+        return crc32Fast(data, size, crc32Table);
+    #endif
+}
 
-    for (i = 0u; i < size; i++)
-    {
-        uint8_t index = data[i] ^ (crcTemp & 0xffu);
-        crcTemp = lookupTable[index] ^ (crcTemp >> 8u);
-    }
+#endif /* SHELLMATTA_TRANSPORT */
 
-    return ~crcTemp;
+#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;
 }
-#endif
 
+#else
 
 /**
- * @brief       Computes the crc32-checksum of a buffer.
+ * @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 crc32 value
- */
-uint32_t crc32Calc(const char* data, uint32_t size)
+ * @return      crc             calculated crc16 value
+*/
+uint16_t crc16Calc(const char* data, uint32_t size)
 {
-    #ifdef  SHELLMATTA_TRANSPORT_CRC_NO_LOOKUP
-        return crc32Slow(data, size);
+    #ifdef SHELLMATTA_YMODEM_CRC_NO_LOOKUP
+        return crc16Slow(data, size);
     #else
-        return crc32Fast(data, size, crc32Table);
-    #endif
+        return crc16Fast(data, size, crc16Table);
+    #endif /* SHELLMATTA_YMODEM_CRC_NO_LOOKUP */
 }

+ 4 - 2
src/shellmatta_crc.h

@@ -17,9 +17,11 @@
 
 #include <stdint.h>
 
-#define CRC32_POLYNOM   0x04c11db7u     /**< crc-32 ethernet 802.3                          */
-#define BITS_PER_BYTE   ((uint8_t)8)    /**< amount of bits per byte; to avoid magic number */
+#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_ */

+ 13 - 3
src/shellmatta_transport.c

@@ -9,8 +9,14 @@
 /**
  * @file    shellmatta_transport.c
  * @brief   transport layer functions of shellmatta
- * @author  Simon Fischer <fischer.simon.1991@gmail.com>
+ * @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"
@@ -223,7 +229,9 @@ shellmatta_retCode_t shellmatta_transport_process(shellmatta_transport_layer_t
     /** -# look for start of header */
     case SHELLMATTA_TRANSPORT_STATE_WAIT:
         /** -# if start of header is found, continue transport layer fsm */
-        if(SHELLMATTA_TRANSPORT_START_OF_HEADER == byte)
+        /** -# 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;
@@ -428,7 +436,7 @@ shellmatta_retCode_t shellmatta_transport_process(shellmatta_transport_layer_t
  * 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
+ * @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
@@ -515,3 +523,5 @@ shellmatta_retCode_t shellmatta_transport_flush(shellmatta_handle_t handle)
     return ret;
 }
 #endif
+
+/** @} */

+ 8 - 1
src/shellmatta_transport.h

@@ -9,7 +9,12 @@
 /**
  * @file    shellmatta_transport.h
  * @brief   transport layer functions of shellmatta
- * @author  Simon Fischer <fischer.simon.1991@gmail.com>
+ * @authors Simon Fischer <dev@s-fischer.net> Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+/**
+ * @addtogroup shellmatta_transport
+ * @{
  */
 
 #ifndef _SHELLMATTA_TRANSPORT_H_
@@ -78,3 +83,5 @@ shellmatta_retCode_t shellmatta_transport_write(shellmatta_transport_layer_t *tr
                                                 uint32_t length);
 
 #endif /* _SHELLMATTA_TRANSPORT_H_ */
+
+/** @} */

+ 76 - 8
src/shellmatta_utils.c

@@ -88,6 +88,69 @@ uint32_t utils_shellItoa(int32_t value, char *buffer, uint32_t base)
     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
  * @param[in]   inst    pointer to shellmatta instance
@@ -474,16 +537,21 @@ void utils_terminateInput(shellmatta_instance_t *inst)
     inst->stdinLength       = 0u;
     inst->continuousCmd     = NULL;
     inst->busyCmd           = NULL;
-    SHELLMATTA_WRITE("\r\n", 2u);
-#ifdef SHELLMATTA_AUTHENTICATION
-    inst->loginState        = SHELLMATTA_AUTH_IDLE;
-    if (NULL != inst->userPointer)
+
+    /** -# print prompt and username when no ymodem session is running */
+    if(SHELLMATTA_YMODEM_INACTIVE == inst->ymodem.state)
     {
-        SHELLMATTA_WRITE(inst->userPointer->username, strlen(inst->userPointer->username));
-        SHELLMATTA_WRITE("@", 1);
-    }
+        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));
+        SHELLMATTA_WRITE(inst->prompt, strlen(inst->prompt));
+    }
 }
 
 /**

+ 36 - 19
src/shellmatta_utils.h

@@ -16,6 +16,7 @@
  * @addtogroup shellmatta_utils
  * @{
  */
+
 #ifndef _SHELLMATTA_UTILS_H_
 #define _SHELLMATTA_UTILS_H_
 
@@ -50,7 +51,6 @@
  * @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]   cnt     count of bytes to send
- * @param[in]   fct     write function
  */
 #define SHELLMATTA_PRINT_BUFFER(buffer,cnt)             \
     while((cnt) > sizeof((buffer)))                     \
@@ -106,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,
-                        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);
+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
 
 /** @} */
-

+ 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_ */
+
+/** @} */

+ 2 - 2
test/integrationtest/test_integration_help.cpp

@@ -55,7 +55,7 @@ static shellmatta_cmd_t cmd3 = {(char*)"cmd3", (char*)"", (char*)"", (char*)"",
 
 SCENARIO("Test the help function")
 {
-    GIVEN("An initialized and empty Shellmatte instance")
+    GIVEN("An initialized and empty Shellmatta instance")
     {
         shellmatta_instance_t inst;
         shellmatta_handle_t handle;
@@ -130,7 +130,7 @@ SCENARIO("Test the help function")
 
 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_handle_t handle;

+ 3 - 3
test/integrationtest/test_integration_history.cpp

@@ -62,7 +62,7 @@ char *commandSequence[] =
 
 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_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")
 {
-    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_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")
 {
-    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_handle_t handle;

+ 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);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 14 - 3
test/unittest/shellmatta/test_shellmatta_doInit.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_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 "src/shellmatta.c"
 #include "src/shellmatta_transport.c"
@@ -7,10 +21,7 @@
 TEST_CASE( "shellmatta dummy" ) {
 
     shellmatta_instance_t inst;
-    //shellmatta_handle_t handle;
     inst.inputCount = 0u;
 
-    //shellmatta_doInit(&inst, &handle, )
-
     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 "src/shellmatta_autocomplete.c"
 #include <string.h>
@@ -7,7 +21,5 @@ TEST_CASE( "shellmatta_autocomplete_run dummy" ) {
     shellmatta_instance_t inst;
     inst.inputCount = 0u;
 
-    autocomplete_run(&inst);
-
     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);
+}

+ 17 - 2
test/unittest/shellmatta_crc/test_crc32Fast.cpp

@@ -1,8 +1,23 @@
+/*
+ * 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_crc32_data.h"
+#include "test_crc_data.h"
 #include "src/shellmatta_crc.c"
 
 TEST_CASE( "shellmatta_crc crc32Fast" ) {
@@ -16,5 +31,5 @@ TEST_CASE( "shellmatta_crc crc32Fast - more data" ) {
 
     uint32_t crc = crc32Fast((char*)data, sizeof(data), crc32Table);
 
-    REQUIRE(crc == data_crc);
+    REQUIRE(crc == data_crc_32);
 }

+ 17 - 2
test/unittest/shellmatta_crc/test_crc32Slow.cpp

@@ -1,8 +1,23 @@
+/*
+ * 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_crc32_data.h"
+#include "test_crc_data.h"
 #include "src/shellmatta_crc.c"
 
 TEST_CASE( "shellmatta_crc crc32Slow" ) {
@@ -16,5 +31,5 @@ TEST_CASE( "shellmatta_crc crc32Slow - more data" ) {
 
     uint32_t crc = crc32Slow((char*)data, sizeof(data));
 
-    REQUIRE(crc == data_crc);
+    REQUIRE(crc == data_crc_32);
 }

+ 18 - 3
test/unittest/shellmatta_crc/test_crc32_data.h

@@ -1,5 +1,19 @@
-#ifndef _TEST_CRC32_DATA_H_
-#define _TEST_CRC32_DATA_H_
+/*
+ * 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,
@@ -260,6 +274,7 @@ const uint8_t data[] = {
     0x14, 0x21, 0x4B, 0xDE, 0x6D, 0xC4, 0xE9, 0xC9, 0x75, 0xBA, 0x9C, 0x2D, 0x14, 0xD4, 0xB2, 0xFF
 };
 
-const uint32_t data_crc = 0x7322E75E;
+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 "src/shellmatta_escape.c"
 #include <string.h>
@@ -9,5 +23,5 @@ TEST_CASE( "shellmatta_escape dummy" ) {
 
     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 "src/shellmatta_history.c"
 #include <string.h>
 
-TEST_CASE( "shellmatta_history dummy" ) {
+TEST_CASE("shellmatta_history dummy" ) {
 
     shellmatta_instance_t inst;
     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 "src/shellmatta_opt.c"
 #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 "src/shellmatta_opt.c"
 #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 "src/shellmatta_utils.c"
 #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 "src/shellmatta_utils.c"
 #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 "src/shellmatta_utils.c"
 #include <string.h>
@@ -14,7 +28,6 @@ static shellmatta_retCode_t writeFct(const char* data, uint32_t length)
     return SHELLMATTA_OK;
 }
 
-
 TEST_CASE( "shellmatta_utils_forwardCursor normal" ) {
 
     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);
 }
 
-
 TEST_CASE( "shellmatta_utils_forwardCursor forward by 12 with cursor at 5 and input count at 10" ) {
 
     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 "src/shellmatta_utils.c"
 #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 "src/shellmatta_utils.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 "src/shellmatta_utils.c"
 #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 "src/shellmatta_utils.c"
 #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 "src/shellmatta_utils.c"
 #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);
+            }
+        }
+    }
+}

+ 14 - 0
test/unittest/shellmatta_utils/test_utils_shellItoa.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_shellItoa.c
+ * @brief   unittest for shellmatta utils_shellItoa
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
 #include "test/framework/catch.hpp"
 #include "src/shellmatta_utils.c"
 #include <string.h>

+ 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 "src/shellmatta_utils.c"
 #include <string.h>

+ 14 - 0
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 "src/shellmatta_utils.c"
 #include <string.h>

+ 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
 
 #include "test/framework/catch.hpp"
-