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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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