From 4ae9241f3d3b5a263bb2c0720921a64d0ffcc000 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Markus=20Lindel=C3=B6w?= <markus@lindeloew.se>
Date: Mon, 7 Nov 2022 13:25:17 +0100
Subject: [PATCH 1/2] Add tcs_receive_line

Resolves #49
---
 include/tinycsocket.h      | 120 +++++++++++++++++++++++++++++++++++++
 src/tinycsocket_common.c   |  94 +++++++++++++++++++++++++++++
 src/tinycsocket_internal.h |  20 +++++++
 src/tinycsocket_posix.c    |   2 +
 src/tinycsocket_win32.c    |   2 +
 tests/tests.cpp            |  54 +++++++++++++++++
 6 files changed, 292 insertions(+)

diff --git a/include/tinycsocket.h b/include/tinycsocket.h
index 33fe6d4..746d0cc 100644
--- a/include/tinycsocket.h
+++ b/include/tinycsocket.h
@@ -773,6 +773,28 @@ TcsReturnCode tcs_set_ip_multicast_drop(TcsSocket socket_ctx,
                                         const struct TcsAddress* local_address,
                                         const struct TcsAddress* multicast_address);
 
+/**
+* @brief Read up to and including a special character.
+*
+* This function ensures that the socket buffer will keep its data after the delimiter.
+* For performance it is recommended to read everything and split it yourself.
+*
+* @param socket_ctx is your in-out socket context.
+
+* @param socket_ctx is your in-out socket context.
+* @param buffer is a pointer to your buffer where you want to store the incoming data to.
+* @param buffer_size is the byte size of your buffer, for preventing overflows.
+* @param bytes_received is how many bytes that was successfully written to your buffer.
+* @param delimiter is your byte value where you want to stop reading. (including delimiter)
+* @return #TCS_SUCCESS if successful, otherwise the error code.
+* @see tcs_receive_netstring()
+*/
+TcsReturnCode tcs_receive_line(TcsSocket socket_ctx,
+                               uint8_t* buffer,
+                               size_t buffer_size,
+                               size_t* bytes_received,
+                               uint8_t delimiter);
+
 TcsReturnCode tcs_receive_netstring(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, size_t* bytes_received);
 
 TcsReturnCode tcs_send_netstring(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size);
@@ -1136,6 +1158,8 @@ static TcsReturnCode errno2retcode(int error_code)
     {
         case ECONNREFUSED:
             return TCS_ERROR_CONNECTION_REFUSED;
+        case EAGAIN:
+            return TCS_ERROR_TIMED_OUT;
         default:
             return TCS_ERROR_UNKNOWN;
     }
@@ -2252,6 +2276,8 @@ static TcsReturnCode wsaerror2retcode(int wsa_error)
             return TCS_ERROR_NOT_INITED;
         case WSAEWOULDBLOCK:
             return TCS_ERROR_WOULD_BLOCK;
+        case WSAETIMEDOUT:
+            return TCS_ERROR_TIMED_OUT;
         default:
             return TCS_ERROR_UNKNOWN;
     }
@@ -3438,6 +3464,100 @@ TcsReturnCode tcs_listen_to(TcsSocket socket_ctx, uint16_t local_port)
     return tcs_listen(socket_ctx, TCS_BACKLOG_SOMAXCONN);
 }
 
+TcsReturnCode tcs_receive_line(TcsSocket socket_ctx,
+                               uint8_t* buffer,
+                               size_t buffer_length,
+                               size_t* bytes_received,
+                               uint8_t delimter)
+{
+    if (socket_ctx == TCS_NULLSOCKET || buffer == NULL || buffer_length <= 0)
+        return TCS_ERROR_INVALID_ARGUMENT;
+
+    /*
+    *                    data in kernel buffer
+    *   |12345yyyyyyyyyyyyyyyyyyyyyyyy..............|
+    *
+    *       buffer_length ----------------------------------.
+    *       searched ----------------------.                |
+    *                                      |                |
+    *       bytes_peeked ------------------.                |
+    *       bytes_read ---------------.    |                |
+    *                                 v    v                v
+    *       data in arg buffer
+    *   |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx12345................|
+    */
+    size_t bytes_read = 0;
+    size_t bytes_peeked = 0;
+    size_t bytes_searched = 0;
+    while (bytes_read < buffer_length)
+    {
+        TcsReturnCode sts = TCS_SUCCESS;
+        size_t bytes_free_in_buffer = buffer_length - bytes_read;
+        size_t current_peeked;
+        sts = tcs_receive(socket_ctx, buffer + bytes_read, bytes_free_in_buffer, TCS_MSG_PEEK, &current_peeked);
+        if (sts != TCS_SUCCESS)
+        {
+            if (bytes_received != NULL)
+                *bytes_received = bytes_read;
+            return sts;
+        }
+        bytes_peeked += current_peeked;
+
+        if (current_peeked == 0)
+        {
+            // Make sure we block so we do not fast loop previous PEEK.
+            // Can not assume that peek with waitall is not crossplatform, needs to read
+            size_t current_read;
+            sts = tcs_receive(socket_ctx, buffer + bytes_read, 1, TCS_MSG_WAITALL, &current_read);
+            bytes_read += current_read;
+            bytes_peeked += current_read;
+
+            if (sts != TCS_SUCCESS)
+            {
+                if (bytes_received != NULL)
+                    *bytes_received = bytes_read;
+                return sts;
+            }
+        }
+
+        bool found_delimiter = false;
+
+        while (bytes_searched < bytes_peeked)
+        {
+            if (buffer[bytes_searched++] == delimter)
+            {
+                found_delimiter = true;
+                break;
+            }
+        }
+
+        // byte_searched == bytes_peeked if no delimiter was found
+        // after this block, bytes_read will also has the same value as they have
+        if (bytes_searched > bytes_read)
+        {
+            size_t bytes;
+            size_t bytes_to_read_to_catch_up = bytes_searched - bytes_read;
+            sts = tcs_receive(socket_ctx, buffer + bytes_read, bytes_to_read_to_catch_up, TCS_MSG_WAITALL, &bytes);
+            bytes_read += bytes;
+            if (sts != TCS_SUCCESS)
+            {
+                if (bytes_received != NULL)
+                    *bytes_received = bytes_read;
+                return sts;
+            }
+        }
+        if (found_delimiter)
+        {
+            if (bytes_received != NULL)
+                *bytes_received = bytes_read;
+            return sts;
+        }
+    }
+    if (bytes_received != NULL)
+        *bytes_received = bytes_read;
+    return TCS_ERROR_MEMORY;
+}
+
 TcsReturnCode tcs_receive_netstring(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_length, size_t* bytes_received)
 {
     if (socket_ctx == TCS_NULLSOCKET || buffer == NULL || buffer_length <= 0)
diff --git a/src/tinycsocket_common.c b/src/tinycsocket_common.c
index 4965d35..3462eff 100644
--- a/src/tinycsocket_common.c
+++ b/src/tinycsocket_common.c
@@ -198,6 +198,100 @@ TcsReturnCode tcs_listen_to(TcsSocket socket_ctx, uint16_t local_port)
     return tcs_listen(socket_ctx, TCS_BACKLOG_SOMAXCONN);
 }
 
+TcsReturnCode tcs_receive_line(TcsSocket socket_ctx,
+                               uint8_t* buffer,
+                               size_t buffer_length,
+                               size_t* bytes_received,
+                               uint8_t delimter)
+{
+    if (socket_ctx == TCS_NULLSOCKET || buffer == NULL || buffer_length <= 0)
+        return TCS_ERROR_INVALID_ARGUMENT;
+
+    /*
+    *                    data in kernel buffer
+    *   |12345yyyyyyyyyyyyyyyyyyyyyyyy..............|
+    *
+    *       buffer_length ----------------------------------.
+    *       searched ----------------------.                |
+    *                                      |                |
+    *       bytes_peeked ------------------.                |
+    *       bytes_read ---------------.    |                |
+    *                                 v    v                v
+    *       data in arg buffer
+    *   |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx12345................|
+    */
+    size_t bytes_read = 0;
+    size_t bytes_peeked = 0;
+    size_t bytes_searched = 0;
+    while (bytes_read < buffer_length)
+    {
+        TcsReturnCode sts = TCS_SUCCESS;
+        size_t bytes_free_in_buffer = buffer_length - bytes_read;
+        size_t current_peeked;
+        sts = tcs_receive(socket_ctx, buffer + bytes_read, bytes_free_in_buffer, TCS_MSG_PEEK, &current_peeked);
+        if (sts != TCS_SUCCESS)
+        {
+            if (bytes_received != NULL)
+                *bytes_received = bytes_read;
+            return sts;
+        }
+        bytes_peeked += current_peeked;
+
+        if (current_peeked == 0)
+        {
+            // Make sure we block so we do not fast loop previous PEEK.
+            // Can not assume that peek with waitall is not crossplatform, needs to read
+            size_t current_read;
+            sts = tcs_receive(socket_ctx, buffer + bytes_read, 1, TCS_MSG_WAITALL, &current_read);
+            bytes_read += current_read;
+            bytes_peeked += current_read;
+
+            if (sts != TCS_SUCCESS)
+            {
+                if (bytes_received != NULL)
+                    *bytes_received = bytes_read;
+                return sts;
+            }
+        }
+
+        bool found_delimiter = false;
+
+        while (bytes_searched < bytes_peeked)
+        {
+            if (buffer[bytes_searched++] == delimter)
+            {
+                found_delimiter = true;
+                break;
+            }
+        }
+
+        // byte_searched == bytes_peeked if no delimiter was found
+        // after this block, bytes_read will also has the same value as they have
+        if (bytes_searched > bytes_read)
+        {
+            size_t bytes;
+            size_t bytes_to_read_to_catch_up = bytes_searched - bytes_read;
+            sts = tcs_receive(socket_ctx, buffer + bytes_read, bytes_to_read_to_catch_up, TCS_MSG_WAITALL, &bytes);
+            bytes_read += bytes;
+            if (sts != TCS_SUCCESS)
+            {
+                if (bytes_received != NULL)
+                    *bytes_received = bytes_read;
+                return sts;
+            }
+        }
+        if (found_delimiter)
+        {
+            if (bytes_received != NULL)
+                *bytes_received = bytes_read;
+            return sts;
+        }
+    }
+    if (bytes_received != NULL)
+        *bytes_received = bytes_read;
+    return TCS_ERROR_MEMORY;
+}
+
 TcsReturnCode tcs_receive_netstring(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_length, size_t* bytes_received)
 {
     if (socket_ctx == TCS_NULLSOCKET || buffer == NULL || buffer_length <= 0)
diff --git a/src/tinycsocket_internal.h b/src/tinycsocket_internal.h
index f15a01c..db9530e 100644
--- a/src/tinycsocket_internal.h
+++ b/src/tinycsocket_internal.h
@@ -767,6 +767,26 @@ TcsReturnCode tcs_set_ip_multicast_drop(TcsSocket socket_ctx,
                                         const struct TcsAddress* local_address,
                                         const struct TcsAddress* multicast_address);
 
+/**
+* @brief Read up to and including a delimiter.
+*
+* This function ensures that the socket buffer will keep its data after the delimiter.
+* For performance it is recommended to read everything and split it yourself.
+*
+* @param socket_ctx is your in-out socket context.
+* @param buffer is a pointer to your buffer where you want to store the incoming data to.
+* @param buffer_size is the byte size of your buffer, for preventing overflows.
+* @param bytes_received is how many bytes that was successfully written to your buffer.
+* @param delimiter is your byte value where you want to stop reading. (including delimiter)
+* @return #TCS_SUCCESS if successful, otherwise the error code.
+* @see tcs_receive_netstring()
+*/
+TcsReturnCode tcs_receive_line(TcsSocket socket_ctx,
+                               uint8_t* buffer,
+                               size_t buffer_size,
+                               size_t* bytes_received,
+                               uint8_t delimiter);
+
 TcsReturnCode tcs_receive_netstring(TcsSocket socket_ctx, uint8_t* buffer, size_t buffer_size, size_t* bytes_received);
 
 TcsReturnCode tcs_send_netstring(TcsSocket socket_ctx, const uint8_t* buffer, size_t buffer_size);
diff --git a/src/tinycsocket_posix.c b/src/tinycsocket_posix.c
index 67e65f0..32397da 100644
--- a/src/tinycsocket_posix.c
+++ b/src/tinycsocket_posix.c
@@ -120,6 +120,8 @@ static TcsReturnCode errno2retcode(int error_code)
     {
         case ECONNREFUSED:
             return TCS_ERROR_CONNECTION_REFUSED;
+        case EAGAIN:
+            return TCS_ERROR_TIMED_OUT;
         default:
             return TCS_ERROR_UNKNOWN;
     }
diff --git a/src/tinycsocket_win32.c b/src/tinycsocket_win32.c
index 0464b55..fc35358 100644
--- a/src/tinycsocket_win32.c
+++ b/src/tinycsocket_win32.c
@@ -157,6 +157,8 @@ static TcsReturnCode wsaerror2retcode(int wsa_error)
             return TCS_ERROR_NOT_INITED;
         case WSAEWOULDBLOCK:
             return TCS_ERROR_WOULD_BLOCK;
+        case WSAETIMEDOUT:
+            return TCS_ERROR_TIMED_OUT;
         default:
             return TCS_ERROR_UNKNOWN;
     }
diff --git a/tests/tests.cpp b/tests/tests.cpp
index 3c1b015..aecba2c 100644
--- a/tests/tests.cpp
+++ b/tests/tests.cpp
@@ -225,6 +225,60 @@ TEST_CASE("Simple TCP Test")
     REQUIRE(tcs_lib_free() == TCS_SUCCESS);
 }
 
+TEST_CASE("tcs_receive_line")
+{
+    // Setup
+    REQUIRE(tcs_lib_init() == TCS_SUCCESS);
+
+    // Given
+    TcsSocket listen_socket = TCS_NULLSOCKET;
+    TcsSocket server_socket = TCS_NULLSOCKET;
+    TcsSocket client_socket = TCS_NULLSOCKET;
+
+    CHECK(tcs_create(&listen_socket, TCS_TYPE_TCP_IP4) == TCS_SUCCESS);
+    CHECK(tcs_create(&client_socket, TCS_TYPE_TCP_IP4) == TCS_SUCCESS);
+
+    CHECK(tcs_listen_to(listen_socket, 1212) == TCS_SUCCESS);
+    CHECK(tcs_connect(client_socket, "localhost", 1212) == TCS_SUCCESS);
+
+    CHECK(tcs_accept(listen_socket, &server_socket, NULL) == TCS_SUCCESS);
+    CHECK(tcs_destroy(&listen_socket) == TCS_SUCCESS);
+
+    // When
+    uint8_t msg[] = "hello:world";
+    uint8_t part1[32] = {0};
+    uint8_t part2[6] = {0};
+    uint8_t part3[32] = {0};
+    size_t part1_length = 0;
+    size_t part2_length = 0;
+    size_t part3_length = 0;
+
+    CHECK(tcs_send(client_socket, msg, sizeof(msg), TCS_MSG_SENDALL, NULL) == TCS_SUCCESS);
+    CHECK(tcs_receive_line(server_socket, part1, sizeof(part1), &part1_length, ':') == TCS_SUCCESS);
+    CHECK(tcs_receive_line(server_socket, part2, sizeof(part2), &part2_length, '\0') == TCS_SUCCESS);
+
+    CHECK(tcs_set_receive_timeout(server_socket, 10) == TCS_SUCCESS);
+    CHECK(tcs_receive_line(server_socket, part3, sizeof(part3), &part3_length, '\0') == TCS_ERROR_TIMED_OUT);
+
+    CHECK(tcs_send(client_socket, msg, sizeof(msg) - 3, TCS_MSG_SENDALL, NULL) == TCS_SUCCESS);
+    CHECK(tcs_receive_line(server_socket, part3, sizeof(part3), &part3_length, '\0') == TCS_ERROR_TIMED_OUT);
+    CHECK(tcs_send(client_socket, msg + sizeof(msg) - 3, 3, TCS_MSG_SENDALL, NULL) == TCS_SUCCESS);
+    CHECK(tcs_receive_line(server_socket, part3 + part3_length, sizeof(part3), &part3_length, '\0') == TCS_SUCCESS);
+
+    // Then
+    CHECK(part1_length == 6);
+    CHECK(part2_length == 6);
+    CHECK(part3_length == 3);
+    CHECK(memcmp(part1, msg, 6) == 0);
+    CHECK(memcmp(part2, msg + 6, 6) == 0);
+    CHECK(memcmp(part3, msg, sizeof(msg)) == 0);
+
+    // Clean up
+    CHECK(tcs_destroy(&client_socket) == TCS_SUCCESS);
+    CHECK(tcs_destroy(&server_socket) == TCS_SUCCESS);
+    REQUIRE(tcs_lib_free() == TCS_SUCCESS);
+}
+
 TEST_CASE("Simple TCP Netstring Test")
 {
     // Setup
-- 
GitLab


From 7e1ff813c0d5472d7807889e01c56fd11a4c260d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Markus=20Lindel=C3=B6w?= <markus@lindeloew.se>
Date: Fri, 13 Dec 2024 11:49:13 +0100
Subject: [PATCH 2/2] Fix Windows receive bug

---
 include/tinycsocket.h    | 18 +++++++++++-------
 src/tinycsocket_common.c |  6 +++---
 src/tinycsocket_win32.c  |  8 +++++++-
 3 files changed, 21 insertions(+), 11 deletions(-)

diff --git a/include/tinycsocket.h b/include/tinycsocket.h
index 746d0cc..38904b8 100644
--- a/include/tinycsocket.h
+++ b/include/tinycsocket.h
@@ -774,13 +774,11 @@ TcsReturnCode tcs_set_ip_multicast_drop(TcsSocket socket_ctx,
                                         const struct TcsAddress* multicast_address);
 
 /**
-* @brief Read up to and including a special character.
+* @brief Read up to and including a delimiter.
 *
 * This function ensures that the socket buffer will keep its data after the delimiter.
 * For performance it is recommended to read everything and split it yourself.
 *
-* @param socket_ctx is your in-out socket context.
-
 * @param socket_ctx is your in-out socket context.
 * @param buffer is a pointer to your buffer where you want to store the incoming data to.
 * @param buffer_size is the byte size of your buffer, for preventing overflows.
@@ -2580,11 +2578,15 @@ TcsReturnCode tcs_receive(TcsSocket socket_ctx,
             size_t left = buffer_size - received_so_far;
             TcsReturnCode sts = tcs_receive(socket_ctx, cursor, left, new_flags, &received_now);
             if (sts != TCS_SUCCESS)
+            {
+                if (bytes_received != NULL)
+                    *bytes_received = received_so_far;
                 return sts;
+            }
             received_so_far += received_now;
         }
         if (bytes_received != NULL)
-            *bytes_received = 0;
+            *bytes_received = received_so_far;
         return TCS_SUCCESS;
     }
 #endif
@@ -2593,6 +2595,8 @@ TcsReturnCode tcs_receive(TcsSocket socket_ctx,
 
     if (recv_status == 0)
     {
+        if (bytes_received != NULL)
+            *bytes_received = 0;
         return TCS_ERROR_SOCKET_CLOSED;
     }
     else if (recv_status != SOCKET_ERROR)
@@ -3493,7 +3497,7 @@ TcsReturnCode tcs_receive_line(TcsSocket socket_ctx,
     {
         TcsReturnCode sts = TCS_SUCCESS;
         size_t bytes_free_in_buffer = buffer_length - bytes_read;
-        size_t current_peeked;
+        size_t current_peeked = 0;
         sts = tcs_receive(socket_ctx, buffer + bytes_read, bytes_free_in_buffer, TCS_MSG_PEEK, &current_peeked);
         if (sts != TCS_SUCCESS)
         {
@@ -3507,7 +3511,7 @@ TcsReturnCode tcs_receive_line(TcsSocket socket_ctx,
         {
             // Make sure we block so we do not fast loop previous PEEK.
             // Can not assume that peek with waitall is not crossplatform, needs to read
-            size_t current_read;
+            size_t current_read = 0;
             sts = tcs_receive(socket_ctx, buffer + bytes_read, 1, TCS_MSG_WAITALL, &current_read);
             bytes_read += current_read;
             bytes_peeked += current_read;
@@ -3535,7 +3539,7 @@ TcsReturnCode tcs_receive_line(TcsSocket socket_ctx,
         // after this block, bytes_read will also has the same value as they have
         if (bytes_searched > bytes_read)
         {
-            size_t bytes;
+            size_t bytes = 0;
             size_t bytes_to_read_to_catch_up = bytes_searched - bytes_read;
             sts = tcs_receive(socket_ctx, buffer + bytes_read, bytes_to_read_to_catch_up, TCS_MSG_WAITALL, &bytes);
             bytes_read += bytes;
diff --git a/src/tinycsocket_common.c b/src/tinycsocket_common.c
index 3462eff..fe392e2 100644
--- a/src/tinycsocket_common.c
+++ b/src/tinycsocket_common.c
@@ -227,7 +227,7 @@ TcsReturnCode tcs_receive_line(TcsSocket socket_ctx,
     {
         TcsReturnCode sts = TCS_SUCCESS;
         size_t bytes_free_in_buffer = buffer_length - bytes_read;
-        size_t current_peeked;
+        size_t current_peeked = 0;
         sts = tcs_receive(socket_ctx, buffer + bytes_read, bytes_free_in_buffer, TCS_MSG_PEEK, &current_peeked);
         if (sts != TCS_SUCCESS)
         {
@@ -241,7 +241,7 @@ TcsReturnCode tcs_receive_line(TcsSocket socket_ctx,
         {
             // Make sure we block so we do not fast loop previous PEEK.
             // Can not assume that peek with waitall is not crossplatform, needs to read
-            size_t current_read;
+            size_t current_read = 0;
             sts = tcs_receive(socket_ctx, buffer + bytes_read, 1, TCS_MSG_WAITALL, &current_read);
             bytes_read += current_read;
             bytes_peeked += current_read;
@@ -269,7 +269,7 @@ TcsReturnCode tcs_receive_line(TcsSocket socket_ctx,
         // after this block, bytes_read will also has the same value as they have
         if (bytes_searched > bytes_read)
         {
-            size_t bytes;
+            size_t bytes = 0;
             size_t bytes_to_read_to_catch_up = bytes_searched - bytes_read;
             sts = tcs_receive(socket_ctx, buffer + bytes_read, bytes_to_read_to_catch_up, TCS_MSG_WAITALL, &bytes);
             bytes_read += bytes;
diff --git a/src/tinycsocket_win32.c b/src/tinycsocket_win32.c
index fc35358..a874da6 100644
--- a/src/tinycsocket_win32.c
+++ b/src/tinycsocket_win32.c
@@ -461,11 +461,15 @@ TcsReturnCode tcs_receive(TcsSocket socket_ctx,
             size_t left = buffer_size - received_so_far;
             TcsReturnCode sts = tcs_receive(socket_ctx, cursor, left, new_flags, &received_now);
             if (sts != TCS_SUCCESS)
+            {
+                if (bytes_received != NULL)
+                    *bytes_received = received_so_far;
                 return sts;
+            }
             received_so_far += received_now;
         }
         if (bytes_received != NULL)
-            *bytes_received = 0;
+            *bytes_received = received_so_far;
         return TCS_SUCCESS;
     }
 #endif
@@ -474,6 +478,8 @@ TcsReturnCode tcs_receive(TcsSocket socket_ctx,
 
     if (recv_status == 0)
     {
+        if (bytes_received != NULL)
+            *bytes_received = 0;
         return TCS_ERROR_SOCKET_CLOSED;
     }
     else if (recv_status != SOCKET_ERROR)
-- 
GitLab