From 1b97bf27a9a0b9e39176926df6666ce2879c8dad Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 11 May 2026 16:54:15 +0000 Subject: [PATCH 1/3] feat: SolidSyslogFreeRtosTcpStream adapter with TCP fakes --- .../Interface/SolidSyslogFreeRtosTcpStream.h | 27 ++ .../Source/SolidSyslogFreeRtosTcpStream.c | 164 +++++++++++ Tests/FreeRtos/CMakeLists.txt | 26 ++ .../SolidSyslogFreeRtosTcpStreamTest.cpp | 274 ++++++++++++++++++ .../Interface/FreeRtosSocketsFake.h | 40 +++ .../Source/FreeRtosSocketsFake.c | 238 +++++++++++++++ 6 files changed, 769 insertions(+) create mode 100644 Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h create mode 100644 Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c create mode 100644 Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h new file mode 100644 index 00000000..e7da96b3 --- /dev/null +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h @@ -0,0 +1,27 @@ +#ifndef SOLIDSYSLOGFREERTOSTCPSTREAM_H +#define SOLIDSYSLOGFREERTOSTCPSTREAM_H + +#include "ExternC.h" + +#include + +EXTERN_C_BEGIN + + struct SolidSyslogStream; + + enum + { + SOLIDSYSLOG_FREERTOSTCPSTREAM_SIZE = sizeof(intptr_t) * 8 + }; + + typedef struct + { + intptr_t slots[(SOLIDSYSLOG_FREERTOSTCPSTREAM_SIZE + sizeof(intptr_t) - 1) / sizeof(intptr_t)]; + } SolidSyslogFreeRtosTcpStreamStorage; + + struct SolidSyslogStream* SolidSyslogFreeRtosTcpStream_Create(SolidSyslogFreeRtosTcpStreamStorage * storage); + void SolidSyslogFreeRtosTcpStream_Destroy(struct SolidSyslogStream * stream); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGFREERTOSTCPSTREAM_H */ diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c new file mode 100644 index 00000000..97dbb4c7 --- /dev/null +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c @@ -0,0 +1,164 @@ +// NOLINTBEGIN(performance-no-int-to-ptr) -- FREERTOS_INVALID_SOCKET is ((Socket_t)~0U) from FreeRTOS-Plus-TCP; the int-to-ptr cast is intrinsic to the upstream +// sentinel and unavoidable. + +#include "SolidSyslogFreeRtosTcpStream.h" + +#include "FreeRTOS.h" +#include "FreeRTOS_Sockets.h" + +#include "SolidSyslogAddressInternal.h" +#include "SolidSyslogMacros.h" +#include "SolidSyslogStream.h" +#include "SolidSyslogStreamDefinition.h" + +typedef struct SolidSyslogFreeRtosTcpStream FreeRtosTcpStream; + +struct SolidSyslogFreeRtosTcpStream +{ + struct SolidSyslogStream base; + Socket_t socket; +}; + +/* FreeRTOS-Plus-TCP does not expose non-blocking connect with select(), so we + * bound the blocking connect call with SO_SNDTIMEO instead. 200 ms is short + * enough that the Service task keeps draining predictably during an outage, + * long enough for a healthy peer to ACK over slirp/LAN. After connect both + * timeouts go back to 0 so subsequent Send/Read follow the non-blocking + * single-call contract from SolidSyslogStream. */ +static const TickType_t CONNECT_TIMEOUT_TICKS = pdMS_TO_TICKS(200); +static const TickType_t NO_TIMEOUT_TICKS = 0; + +static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); +static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size); +static void Close(struct SolidSyslogStream* self); +static inline FreeRtosTcpStream* FreeRtosTcpStream_From(struct SolidSyslogStream* self); +static inline bool IsOpen(const FreeRtosTcpStream* stream); +static void SetSendTimeout(Socket_t socket, TickType_t ticks); +static void SetRecvTimeout(Socket_t socket, TickType_t ticks); +static void CloseSocket(FreeRtosTcpStream* stream); + +SOLIDSYSLOG_STATIC_ASSERT(sizeof(FreeRtosTcpStream) <= SOLIDSYSLOG_FREERTOSTCPSTREAM_SIZE, + "SOLIDSYSLOG_FREERTOSTCPSTREAM_SIZE is too small for SolidSyslogFreeRtosTcpStream layout"); + +static const FreeRtosTcpStream DEFAULT_INSTANCE = { + {Open, Send, Read, Close}, + FREERTOS_INVALID_SOCKET, +}; + +static const FreeRtosTcpStream DESTROYED_INSTANCE = { + {NULL, NULL, NULL, NULL}, + FREERTOS_INVALID_SOCKET, +}; + +struct SolidSyslogStream* SolidSyslogFreeRtosTcpStream_Create(SolidSyslogFreeRtosTcpStreamStorage* storage) +{ + FreeRtosTcpStream* stream = (FreeRtosTcpStream*) storage; + *stream = DEFAULT_INSTANCE; + return &stream->base; +} + +void SolidSyslogFreeRtosTcpStream_Destroy(struct SolidSyslogStream* stream) +{ + FreeRtosTcpStream* self = FreeRtosTcpStream_From(stream); + CloseSocket(self); + *self = DESTROYED_INSTANCE; +} + +static inline FreeRtosTcpStream* FreeRtosTcpStream_From(struct SolidSyslogStream* self) +{ + return (FreeRtosTcpStream*) self; +} + +static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) +{ + FreeRtosTcpStream* stream = FreeRtosTcpStream_From(self); + if (!IsOpen(stream)) + { + stream->socket = FreeRTOS_socket(FREERTOS_AF_INET, FREERTOS_SOCK_STREAM, FREERTOS_IPPROTO_TCP); + if (IsOpen(stream)) + { + const struct freertos_sockaddr* dest = SolidSyslogAddress_AsConstFreertosSockaddr(addr); + SetSendTimeout(stream->socket, CONNECT_TIMEOUT_TICKS); + if (FreeRTOS_connect(stream->socket, dest, sizeof(*dest)) == 0) + { + SetSendTimeout(stream->socket, NO_TIMEOUT_TICKS); + SetRecvTimeout(stream->socket, NO_TIMEOUT_TICKS); + } + else + { + CloseSocket(stream); + } + } + } + return IsOpen(stream); +} + +static inline bool IsOpen(const FreeRtosTcpStream* stream) +{ + return stream->socket != FREERTOS_INVALID_SOCKET; +} + +static void SetSendTimeout(Socket_t socket, TickType_t ticks) +{ + (void) FreeRTOS_setsockopt(socket, 0, FREERTOS_SO_SNDTIMEO, &ticks, sizeof(ticks)); +} + +static void SetRecvTimeout(Socket_t socket, TickType_t ticks) +{ + (void) FreeRTOS_setsockopt(socket, 0, FREERTOS_SO_RCVTIMEO, &ticks, sizeof(ticks)); +} + +static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size) +{ + FreeRtosTcpStream* stream = FreeRtosTcpStream_From(self); + bool sent = false; + if (IsOpen(stream)) + { + BaseType_t rc = FreeRTOS_send(stream->socket, buffer, size, 0); + sent = (rc >= 0) && ((size_t) rc == size); + if (!sent) + { + CloseSocket(stream); + } + } + return sent; +} + +/* FreeRTOS_recv with RCVTIMEO=0 returns 0 when no data is available (would + * block); the Service thread treats that as "nothing to read right now". + * Negative returns are real errors — close the socket and surface failure. */ +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size) +{ + FreeRtosTcpStream* stream = FreeRtosTcpStream_From(self); + SolidSyslogSsize result = -1; + if (IsOpen(stream)) + { + BaseType_t rc = FreeRTOS_recv(stream->socket, buffer, size, 0); + if (rc >= 0) + { + result = (SolidSyslogSsize) rc; + } + else + { + CloseSocket(stream); + } + } + return result; +} + +static void Close(struct SolidSyslogStream* self) +{ + CloseSocket(FreeRtosTcpStream_From(self)); +} + +static void CloseSocket(FreeRtosTcpStream* stream) +{ + if (IsOpen(stream)) + { + (void) FreeRTOS_closesocket(stream->socket); + stream->socket = FREERTOS_INVALID_SOCKET; + } +} + +// NOLINTEND(performance-no-int-to-ptr) diff --git a/Tests/FreeRtos/CMakeLists.txt b/Tests/FreeRtos/CMakeLists.txt index 88e56143..b25408e0 100644 --- a/Tests/FreeRtos/CMakeLists.txt +++ b/Tests/FreeRtos/CMakeLists.txt @@ -38,6 +38,32 @@ else() add_test(NAME SolidSyslogFreeRtosDatagramTest COMMAND SolidSyslogFreeRtosDatagramTest) + add_executable(SolidSyslogFreeRtosTcpStreamTest + SolidSyslogFreeRtosTcpStreamTest.cpp + main.cpp + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c + ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c + ) + + target_link_libraries(SolidSyslogFreeRtosTcpStreamTest PRIVATE + ${PROJECT_NAME} + CppUTest + CppUTestExt + ) + + target_include_directories(SolidSyslogFreeRtosTcpStreamTest PRIVATE + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Interface + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source + ${CMAKE_SOURCE_DIR}/Core/Interface + ${CMAKE_SOURCE_DIR}/Core/Source + ${CMAKE_SOURCE_DIR}/Tests/Support + ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Interface + $ENV{FREERTOS_KERNEL_PATH}/include + $ENV{FREERTOS_PLUS_TCP_PATH}/source/include + ) + + add_test(NAME SolidSyslogFreeRtosTcpStreamTest COMMAND SolidSyslogFreeRtosTcpStreamTest) + add_executable(SolidSyslogFreeRtosStaticResolverTest SolidSyslogFreeRtosStaticResolverTest.cpp main.cpp diff --git a/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp b/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp new file mode 100644 index 00000000..760f433a --- /dev/null +++ b/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp @@ -0,0 +1,274 @@ +#include "TestUtils.h" +#include "CppUTest/TestHarness.h" + +using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings NEVER/ONCE/TWICE/THRICE into scope for the CALLED_* + // macros + +#include "SolidSyslogAddress.h" +#include "SolidSyslogFreeRtosTcpStream.h" +#include "SolidSyslogStream.h" + +#include "FreeRtosSocketsFake.h" + +#include "FreeRTOS.h" +#include "FreeRTOS_IP.h" +#include "FreeRTOS_Sockets.h" + +// clang-format off +TEST_GROUP(SolidSyslogFreeRtosTcpStream) +{ + SolidSyslogFreeRtosTcpStreamStorage storage{}; + struct SolidSyslogStream* stream = nullptr; + SolidSyslogAddressStorage addrStorage{}; + struct SolidSyslogAddress* addr = nullptr; + + void setup() override + { + FreeRtosSocketsFake_Reset(); + stream = SolidSyslogFreeRtosTcpStream_Create(&storage); + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- char-type aliasing into platform layout, storage is intptr_t-aligned + auto* sin = reinterpret_cast(&addrStorage); + sin->sin_family = FREERTOS_AF_INET; + sin->sin_port = FreeRTOS_htons(514); + sin->sin_address.ulIP_IPv4 = FreeRTOS_inet_addr_quick(10, 0, 2, 2); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- platform-layout cast, see above + addr = reinterpret_cast(&addrStorage); + } + + void teardown() override + { + SolidSyslogFreeRtosTcpStream_Destroy(stream); + } +}; + +// clang-format on + +TEST(SolidSyslogFreeRtosTcpStream, CreateReturnsNonNullStream) +{ + CHECK(stream != nullptr); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenCreatesTcpSocket) +{ + SolidSyslogStream_Open(stream, addr); + CALLED_FAKE(FreeRtosSocketsFake_Socket, ONCE); + LONGS_EQUAL(FREERTOS_AF_INET, FreeRtosSocketsFake_LastSocketDomain()); + LONGS_EQUAL(FREERTOS_SOCK_STREAM, FreeRtosSocketsFake_LastSocketType()); + LONGS_EQUAL(FREERTOS_IPPROTO_TCP, FreeRtosSocketsFake_LastSocketProtocol()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenReturnsTrueOnSuccess) +{ + CHECK_TRUE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenReturnsFalseWhenSocketFails) +{ + FreeRtosSocketsFake_SetSocketFails(true); + CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenSetsConnectTimeoutBeforeConnect) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(pdMS_TO_TICKS(200), FreeRtosSocketsFake_SndTimeoAtConnect()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenCallsConnectWithSocketAndAddress) +{ + SolidSyslogStream_Open(stream, addr); + CALLED_FAKE(FreeRtosSocketsFake_Connect, ONCE); + POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastConnectSocket()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- platform-layout cast, see setup + POINTERS_EQUAL(reinterpret_cast(addr), FreeRtosSocketsFake_LastConnectAddress()); + LONGS_EQUAL(sizeof(struct freertos_sockaddr), FreeRtosSocketsFake_LastConnectAddressLength()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenClearsSendTimeoutAfterConnect) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(0, FreeRtosSocketsFake_LastSndTimeoSet()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenClearsRecvTimeoutAfterConnect) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(0, FreeRtosSocketsFake_LastRcvTimeoSet()); + LONGS_EQUAL(1, FreeRtosSocketsFake_RcvTimeoSetCallCount()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenReturnsFalseOnConnectFailure) +{ + FreeRtosSocketsFake_SetConnectFails(true); + CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenClosesSocketOnConnectFailure) +{ + FreeRtosSocketsFake_SetConnectFails(true); + SolidSyslogStream_Open(stream, addr); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); + POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastClosesocketSocket()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenIsIdempotent) +{ + SolidSyslogStream_Open(stream, addr); + CHECK_TRUE(SolidSyslogStream_Open(stream, addr)); + CALLED_FAKE(FreeRtosSocketsFake_Socket, ONCE); + CALLED_FAKE(FreeRtosSocketsFake_Connect, ONCE); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendFailsBeforeOpen) +{ + CHECK_FALSE(SolidSyslogStream_Send(stream, "x", 1)); + CALLED_FAKE(FreeRtosSocketsFake_Send, NEVER); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendCallsFreeRtosSendWithSocketBufferAndLength) +{ + static const char TEST_MESSAGE[] = "hello"; + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Send(stream, TEST_MESSAGE, sizeof(TEST_MESSAGE) - 1); + CALLED_FAKE(FreeRtosSocketsFake_Send, ONCE); + POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastSendSocket()); + POINTERS_EQUAL(TEST_MESSAGE, FreeRtosSocketsFake_LastSendBuffer()); + LONGS_EQUAL(sizeof(TEST_MESSAGE) - 1, FreeRtosSocketsFake_LastSendLength()); + LONGS_EQUAL(0, FreeRtosSocketsFake_LastSendFlags()); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendReturnsTrueOnFullWrite) +{ + SolidSyslogStream_Open(stream, addr); + CHECK_TRUE(SolidSyslogStream_Send(stream, "hello", 5)); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendReturnsFalseOnShortWrite) +{ + SolidSyslogStream_Open(stream, addr); + FreeRtosSocketsFake_SetSendReturn(3); + CHECK_FALSE(SolidSyslogStream_Send(stream, "hello", 5)); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendDoesNotRetryAfterShortWrite) +{ + SolidSyslogStream_Open(stream, addr); + FreeRtosSocketsFake_SetSendReturn(3); + SolidSyslogStream_Send(stream, "hello", 5); + CALLED_FAKE(FreeRtosSocketsFake_Send, ONCE); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendClosesSocketOnShortWrite) +{ + SolidSyslogStream_Open(stream, addr); + FreeRtosSocketsFake_SetSendReturn(3); + SolidSyslogStream_Send(stream, "hello", 5); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); + POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastClosesocketSocket()); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendReturnsFalseOnError) +{ + SolidSyslogStream_Open(stream, addr); + FreeRtosSocketsFake_SetSendFails(true); + CHECK_FALSE(SolidSyslogStream_Send(stream, "hello", 5)); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendClosesSocketOnError) +{ + SolidSyslogStream_Open(stream, addr); + FreeRtosSocketsFake_SetSendFails(true); + SolidSyslogStream_Send(stream, "hello", 5); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsNegativeOneBeforeOpen) +{ + char buf[16]; + LONGS_EQUAL(-1, SolidSyslogStream_Read(stream, buf, sizeof(buf))); + CALLED_FAKE(FreeRtosSocketsFake_Recv, NEVER); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadCallsFreeRtosRecvWithSocketBufferAndLength) +{ + char buf[16]; + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + CALLED_FAKE(FreeRtosSocketsFake_Recv, ONCE); + POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastRecvSocket()); + POINTERS_EQUAL(buf, FreeRtosSocketsFake_LastRecvBuffer()); + LONGS_EQUAL(sizeof(buf), FreeRtosSocketsFake_LastRecvLength()); + LONGS_EQUAL(0, FreeRtosSocketsFake_LastRecvFlags()); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsBytesOnSuccess) +{ + char buf[16]; + SolidSyslogStream_Open(stream, addr); + FreeRtosSocketsFake_SetRecvReturn(7); + LONGS_EQUAL(7, SolidSyslogStream_Read(stream, buf, sizeof(buf))); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsZeroWhenWouldBlock) +{ + char buf[16]; + SolidSyslogStream_Open(stream, addr); + FreeRtosSocketsFake_SetRecvReturn(0); + LONGS_EQUAL(0, SolidSyslogStream_Read(stream, buf, sizeof(buf))); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadLeavesSocketOpenWhenWouldBlock) +{ + char buf[16]; + SolidSyslogStream_Open(stream, addr); + FreeRtosSocketsFake_SetRecvReturn(0); + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, NEVER); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsNegativeOneOnErrorAndClosesSocket) +{ + char buf[16]; + SolidSyslogStream_Open(stream, addr); + FreeRtosSocketsFake_SetRecvFails(true); + LONGS_EQUAL(-1, SolidSyslogStream_Read(stream, buf, sizeof(buf))); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); +} + +TEST(SolidSyslogFreeRtosTcpStream, CloseWithoutOpenIsNoOp) +{ + SolidSyslogStream_Close(stream); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, NEVER); +} + +TEST(SolidSyslogFreeRtosTcpStream, CloseClosesOpenSocket) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); + POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastClosesocketSocket()); +} + +TEST(SolidSyslogFreeRtosTcpStream, CloseIsIdempotent) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + SolidSyslogStream_Close(stream); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); +} + +TEST(SolidSyslogFreeRtosTcpStream, DestroyClosesOpenSocket) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogFreeRtosTcpStream_Destroy(stream); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); +} + +TEST(SolidSyslogFreeRtosTcpStream, DestroyAfterCloseDoesNotCloseAgain) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + SolidSyslogFreeRtosTcpStream_Destroy(stream); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); +} diff --git a/Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h b/Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h index 32de4768..cb7601da 100644 --- a/Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h +++ b/Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h @@ -19,6 +19,17 @@ EXTERN_C_BEGIN /* sendto configuration */ void FreeRtosSocketsFake_SetSendtoFails(bool fails); + /* connect configuration */ + void FreeRtosSocketsFake_SetConnectFails(bool fails); + + /* send configuration */ + void FreeRtosSocketsFake_SetSendFails(bool fails); /* returns -pdFREERTOS_ERRNO_ENOTCONN */ + void FreeRtosSocketsFake_SetSendReturn(BaseType_t value); /* explicit return for short-write scenarios */ + + /* recv configuration */ + void FreeRtosSocketsFake_SetRecvFails(bool fails); /* returns -pdFREERTOS_ERRNO_ENOTCONN */ + void FreeRtosSocketsFake_SetRecvReturn(BaseType_t value); /* explicit return; 0 models a zero-timeout would-block */ + /* socket accessors */ unsigned FreeRtosSocketsFake_SocketCallCount(void); BaseType_t FreeRtosSocketsFake_LastSocketDomain(void); @@ -35,6 +46,35 @@ EXTERN_C_BEGIN const struct freertos_sockaddr* FreeRtosSocketsFake_LastSendtoDestination(void); socklen_t FreeRtosSocketsFake_LastSendtoDestinationLength(void); + /* connect accessors */ + unsigned FreeRtosSocketsFake_ConnectCallCount(void); + Socket_t FreeRtosSocketsFake_LastConnectSocket(void); + const struct freertos_sockaddr* FreeRtosSocketsFake_LastConnectAddress(void); + socklen_t FreeRtosSocketsFake_LastConnectAddressLength(void); + /* Snapshot of SO_SNDTIMEO observed at the moment FreeRTOS_connect was + * called — proves the bounded-connect contract (timeout set before + * connect, cleared after). 0 if connect was never called. */ + TickType_t FreeRtosSocketsFake_SndTimeoAtConnect(void); + + /* send accessors */ + unsigned FreeRtosSocketsFake_SendCallCount(void); + Socket_t FreeRtosSocketsFake_LastSendSocket(void); + const void* FreeRtosSocketsFake_LastSendBuffer(void); + size_t FreeRtosSocketsFake_LastSendLength(void); + BaseType_t FreeRtosSocketsFake_LastSendFlags(void); + + /* recv accessors */ + unsigned FreeRtosSocketsFake_RecvCallCount(void); + Socket_t FreeRtosSocketsFake_LastRecvSocket(void); + void* FreeRtosSocketsFake_LastRecvBuffer(void); + size_t FreeRtosSocketsFake_LastRecvLength(void); + BaseType_t FreeRtosSocketsFake_LastRecvFlags(void); + + /* setsockopt accessors */ + TickType_t FreeRtosSocketsFake_LastSndTimeoSet(void); + TickType_t FreeRtosSocketsFake_LastRcvTimeoSet(void); + unsigned FreeRtosSocketsFake_RcvTimeoSetCallCount(void); + /* closesocket accessors */ unsigned FreeRtosSocketsFake_ClosesocketCallCount(void); Socket_t FreeRtosSocketsFake_LastClosesocketSocket(void); diff --git a/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c b/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c index 0eba45b9..efe1ea19 100644 --- a/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c +++ b/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c @@ -18,6 +18,35 @@ static const struct freertos_sockaddr* lastSendtoDestination = NULL; static socklen_t lastSendtoDestinationLength = 0; static bool sendtoFails = false; +static unsigned connectCallCount = 0; +static Socket_t lastConnectSocket = NULL; +static const struct freertos_sockaddr* lastConnectAddress = NULL; +static socklen_t lastConnectAddressLength = 0; +static bool connectFails = false; + +static unsigned sendCallCount = 0; +static Socket_t lastSendSocket = NULL; +static const void* lastSendBuffer = NULL; +static size_t lastSendLength = 0; +static BaseType_t lastSendFlags = 0; +static bool sendFails = false; +static bool sendReturnSet = false; +static BaseType_t sendReturnValue = 0; + +static unsigned recvCallCount = 0; +static Socket_t lastRecvSocket = NULL; +static void* lastRecvBuffer = NULL; +static size_t lastRecvLength = 0; +static BaseType_t lastRecvFlags = 0; +static bool recvFails = false; +static bool recvReturnSet = false; +static BaseType_t recvReturnValue = 0; + +static TickType_t lastSndTimeoSet = 0; +static TickType_t lastRcvTimeoSet = 0; +static unsigned rcvTimeoSetCallCount = 0; +static TickType_t sndTimeoAtConnect = 0; + static unsigned closesocketCallCount = 0; static Socket_t lastClosesocketSocket = NULL; @@ -45,6 +74,35 @@ void FreeRtosSocketsFake_Reset(void) lastSendtoDestinationLength = 0; sendtoFails = false; + connectCallCount = 0; + lastConnectSocket = NULL; + lastConnectAddress = NULL; + lastConnectAddressLength = 0; + connectFails = false; + + sendCallCount = 0; + lastSendSocket = NULL; + lastSendBuffer = NULL; + lastSendLength = 0; + lastSendFlags = 0; + sendFails = false; + sendReturnSet = false; + sendReturnValue = 0; + + recvCallCount = 0; + lastRecvSocket = NULL; + lastRecvBuffer = NULL; + lastRecvLength = 0; + lastRecvFlags = 0; + recvFails = false; + recvReturnSet = false; + recvReturnValue = 0; + + lastSndTimeoSet = 0; + lastRcvTimeoSet = 0; + rcvTimeoSetCallCount = 0; + sndTimeoAtConnect = 0; + closesocketCallCount = 0; lastClosesocketSocket = NULL; } @@ -59,6 +117,33 @@ void FreeRtosSocketsFake_SetSendtoFails(bool fails) sendtoFails = fails; } +void FreeRtosSocketsFake_SetConnectFails(bool fails) +{ + connectFails = fails; +} + +void FreeRtosSocketsFake_SetSendFails(bool fails) +{ + sendFails = fails; +} + +void FreeRtosSocketsFake_SetSendReturn(BaseType_t value) +{ + sendReturnSet = true; + sendReturnValue = value; +} + +void FreeRtosSocketsFake_SetRecvFails(bool fails) +{ + recvFails = fails; +} + +void FreeRtosSocketsFake_SetRecvReturn(BaseType_t value) +{ + recvReturnSet = true; + recvReturnValue = value; +} + unsigned FreeRtosSocketsFake_SocketCallCount(void) { return socketCallCount; @@ -142,6 +227,159 @@ int32_t FreeRTOS_sendto(Socket_t xSocket, const void* pvBuffer, size_t uxTotalDa return sendtoFails ? -pdFREERTOS_ERRNO_ENOBUFS : (int32_t) uxTotalDataLength; } +TickType_t FreeRtosSocketsFake_SndTimeoAtConnect(void) +{ + return sndTimeoAtConnect; +} + +TickType_t FreeRtosSocketsFake_LastSndTimeoSet(void) +{ + return lastSndTimeoSet; +} + +TickType_t FreeRtosSocketsFake_LastRcvTimeoSet(void) +{ + return lastRcvTimeoSet; +} + +unsigned FreeRtosSocketsFake_RcvTimeoSetCallCount(void) +{ + return rcvTimeoSetCallCount; +} + +BaseType_t FreeRTOS_setsockopt(Socket_t xSocket, int32_t lLevel, int32_t lOptionName, const void* pvOptionValue, size_t uxOptionLength) +{ + (void) xSocket; + (void) lLevel; + (void) uxOptionLength; + if (lOptionName == FREERTOS_SO_SNDTIMEO) + { + lastSndTimeoSet = *(const TickType_t*) pvOptionValue; + } + else if (lOptionName == FREERTOS_SO_RCVTIMEO) + { + lastRcvTimeoSet = *(const TickType_t*) pvOptionValue; + ++rcvTimeoSetCallCount; + } + return 0; +} + +BaseType_t FreeRTOS_connect(Socket_t xClientSocket, const struct freertos_sockaddr* pxAddress, socklen_t xAddressLength) +{ + ++connectCallCount; + lastConnectSocket = xClientSocket; + lastConnectAddress = pxAddress; + lastConnectAddressLength = xAddressLength; + sndTimeoAtConnect = lastSndTimeoSet; + return connectFails ? -pdFREERTOS_ERRNO_ENOTCONN : 0; +} + +unsigned FreeRtosSocketsFake_ConnectCallCount(void) +{ + return connectCallCount; +} + +Socket_t FreeRtosSocketsFake_LastConnectSocket(void) +{ + return lastConnectSocket; +} + +const struct freertos_sockaddr* FreeRtosSocketsFake_LastConnectAddress(void) +{ + return lastConnectAddress; +} + +socklen_t FreeRtosSocketsFake_LastConnectAddressLength(void) +{ + return lastConnectAddressLength; +} + +BaseType_t FreeRTOS_send(Socket_t xSocket, const void* pvBuffer, size_t uxDataLength, BaseType_t xFlags) +{ + ++sendCallCount; + lastSendSocket = xSocket; + lastSendBuffer = pvBuffer; + lastSendLength = uxDataLength; + lastSendFlags = xFlags; + if (sendFails) + { + return -pdFREERTOS_ERRNO_ENOTCONN; + } + if (sendReturnSet) + { + return sendReturnValue; + } + return (BaseType_t) uxDataLength; +} + +unsigned FreeRtosSocketsFake_SendCallCount(void) +{ + return sendCallCount; +} + +Socket_t FreeRtosSocketsFake_LastSendSocket(void) +{ + return lastSendSocket; +} + +const void* FreeRtosSocketsFake_LastSendBuffer(void) +{ + return lastSendBuffer; +} + +size_t FreeRtosSocketsFake_LastSendLength(void) +{ + return lastSendLength; +} + +BaseType_t FreeRtosSocketsFake_LastSendFlags(void) +{ + return lastSendFlags; +} + +BaseType_t FreeRTOS_recv(Socket_t xSocket, void* pvBuffer, size_t uxBufferLength, BaseType_t xFlags) +{ + ++recvCallCount; + lastRecvSocket = xSocket; + lastRecvBuffer = pvBuffer; + lastRecvLength = uxBufferLength; + lastRecvFlags = xFlags; + if (recvFails) + { + return -pdFREERTOS_ERRNO_ENOTCONN; + } + if (recvReturnSet) + { + return recvReturnValue; + } + return 0; +} + +unsigned FreeRtosSocketsFake_RecvCallCount(void) +{ + return recvCallCount; +} + +Socket_t FreeRtosSocketsFake_LastRecvSocket(void) +{ + return lastRecvSocket; +} + +void* FreeRtosSocketsFake_LastRecvBuffer(void) +{ + return lastRecvBuffer; +} + +size_t FreeRtosSocketsFake_LastRecvLength(void) +{ + return lastRecvLength; +} + +BaseType_t FreeRtosSocketsFake_LastRecvFlags(void) +{ + return lastRecvFlags; +} + unsigned FreeRtosSocketsFake_ClosesocketCallCount(void) { return closesocketCallCount; From a3e89dd4fb2f69126aaa4b2564cae965184d9c5e Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 11 May 2026 17:53:39 +0000 Subject: [PATCH 2/3] feat: S08.09 wire FreeRTOS TCP transport into BDD target - Flip ipconfigUSE_TCP=1 in Bdd/Targets/FreeRtos/FreeRTOSIPConfig.h with keep-alive enabled (10s) and the network buffer descriptor pool doubled (8 -> 16) to cover TCP retransmit windows alongside in-flight UDP. - Wire SolidSyslogSwitchingSender into Bdd/Targets/FreeRtos/main.c with UDP + TCP inner senders; default to UDP so existing @udp scenarios stay green, and add a `transport` set handler that routes through BddTargetSwitchConfig_SetByName so `--transport tcp` from the Behave harness lands as a transport flip over the UART. - Bump INTERACTIVE_TASK_STACK_DEPTH from *40 to *48 to absorb the extra formatter+address storage that StreamSender.Connect / TransmitFramed layer on top of the UDP-only stack consumption. - Pin setsockopt arg coverage in SolidSyslogFreeRtosTcpStreamTest (socket handle, level=0, optlen=sizeof(TickType_t)) so any regression in the bounded-connect contract surfaces at the test bar. - Add `--transport` -> `transport` translation in Behave's target_driver.py and admit @tcp scenarios in the freertos compose tag filter (excluding the @buffered store-and-forward features that need the file abstraction not yet on FreeRTOS). --- Bdd/Targets/FreeRtos/CMakeLists.txt | 2 + Bdd/Targets/FreeRtos/FreeRTOSIPConfig.h | 18 ++++-- Bdd/Targets/FreeRtos/main.c | 60 +++++++++++++++++-- Bdd/features/steps/target_driver.py | 5 ++ .../SolidSyslogFreeRtosTcpStreamTest.cpp | 13 ++++ .../Interface/FreeRtosSocketsFake.h | 4 ++ .../Source/FreeRtosSocketsFake.c | 51 ++++++++++++---- ci/docker-compose.bdd.yml | 6 +- 8 files changed, 137 insertions(+), 22 deletions(-) diff --git a/Bdd/Targets/FreeRtos/CMakeLists.txt b/Bdd/Targets/FreeRtos/CMakeLists.txt index 3d2d23ff..eaf7f762 100644 --- a/Bdd/Targets/FreeRtos/CMakeLists.txt +++ b/Bdd/Targets/FreeRtos/CMakeLists.txt @@ -146,9 +146,11 @@ add_executable(SolidSyslogBddTarget ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolver.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosSysUpTime.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetInteractive.c ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetIps.c ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetLanguage.c + ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetSwitchConfig.c $ ) diff --git a/Bdd/Targets/FreeRtos/FreeRTOSIPConfig.h b/Bdd/Targets/FreeRtos/FreeRTOSIPConfig.h index 204fe77a..0f8c3ca5 100644 --- a/Bdd/Targets/FreeRtos/FreeRTOSIPConfig.h +++ b/Bdd/Targets/FreeRtos/FreeRTOSIPConfig.h @@ -1,17 +1,20 @@ #ifndef FREERTOS_IP_CONFIG_H #define FREERTOS_IP_CONFIG_H -/* FreeRTOS-Plus-TCP configuration for the QEMU mps2-an385 bring-up smoke - * test (slice 3b.1 of S08.03). UDP-only, IPv4 static address, no DHCP / DNS / - * LLMNR / NBNS / TCP / IPv6. The smoke task creates a UDP socket and sends a - * single datagram to the slirp gateway (10.0.2.2:5514). */ +/* FreeRTOS-Plus-TCP configuration for the QEMU mps2-an385 BDD target. UDP + + * TCP, IPv4 static address, no DHCP / DNS / LLMNR / NBNS / IPv6. UDP was + * brought up in S08.03; S08.09 enables TCP for the SolidSyslogFreeRtosTcpStream + * adapter so the SwitchingSender's TCP branch can land its frames on the + * syslog-ng oracle alongside the existing UDP scenarios. */ #define ipconfigBYTE_ORDER pdFREERTOS_LITTLE_ENDIAN #define ipconfigUSE_IPv4 1 #define ipconfigUSE_IPv6 0 #define ipconfigUSE_RA 0 -#define ipconfigUSE_TCP 0 +#define ipconfigUSE_TCP 1 +#define ipconfigTCP_KEEP_ALIVE 1 +#define ipconfigTCP_KEEP_ALIVE_INTERVAL 10 #define ipconfigUSE_DHCP 0 #define ipconfigUSE_DHCPv6 0 #define ipconfigUSE_DHCP_HOOK 0 @@ -30,7 +33,10 @@ #define ipconfigSUPPORT_SELECT_FUNCTION 0 #define ipconfigFILTER_OUT_NON_ETHERNET_II_FRAMES 1 -#define ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS 8 +/* Doubled from 8 — TCP needs descriptors for its retransmit window in + * addition to the in-flight UDP frames, and 8 is already tight on the BDD + * scenarios. The extra ~3 KB of .bss is trivial against mps2-an385 SRAM. */ +#define ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS 16 #define ipconfigEVENT_QUEUE_LENGTH (ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS + 5) #define ipconfigNETWORK_MTU 1500 diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index fe72710a..f31e2adb 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -22,6 +22,7 @@ #include "BddTargetInteractive.h" #include "BddTargetIps.h" #include "BddTargetLanguage.h" +#include "BddTargetSwitchConfig.h" #include "SolidSyslog.h" #include "SolidSyslogAtomicCounter.h" #include "SolidSyslogCircularBuffer.h" @@ -33,10 +34,13 @@ #include "SolidSyslogFreeRtosMutex.h" #include "SolidSyslogFreeRtosStaticResolver.h" #include "SolidSyslogFreeRtosSysUpTime.h" +#include "SolidSyslogFreeRtosTcpStream.h" #include "SolidSyslogMetaSd.h" #include "SolidSyslogNullStore.h" #include "SolidSyslogOriginSd.h" #include "SolidSyslogPrival.h" +#include "SolidSyslogStreamSender.h" +#include "SolidSyslogSwitchingSender.h" #include "SolidSyslogTimeQuality.h" #include "SolidSyslogTimeQualitySd.h" #include "SolidSyslogUdpSender.h" @@ -85,12 +89,17 @@ * Empirically the task hard-faults at *16 (8 KB) once the SolidSyslog * setup runs — newlib printf and the formatter path together exceed that * budget. *32 (16 KB) was stable while the line buffer was 256 B; the - * MAX_LINE_LENGTH bump to 2048 B in slice 6 adds ~4 KB peak (line + name - * frames) so *40 (20 KB) gives ~4 KB headroom. A follow-up will introduce + * MAX_LINE_LENGTH bump to 2048 B in slice 6 added ~4 KB peak (line + name + * frames) so *40 (20 KB) gave ~4 KB headroom. S08.09 adds a TCP path — + * StreamSender.Connect's address+host-formatter storage and + * TransmitFramed's prefix formatter consume an extra ~512 B of stack on + * top of the UDP-only chain — empirically tipping a *40 budget over the + * edge when the SwitchingSender flips transports under repeated sends. + * *48 (24 KB) restores ~4 KB headroom. A follow-up will introduce * CMake-driven memory scaling once the budget is properly characterised. * The task only exists in this single-task example, so heap_4 (96 KB) * absorbs it. */ -#define INTERACTIVE_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 40U) +#define INTERACTIVE_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 48U) /* Static IPv4 wiring matching the QEMU slirp default. 10.0.2.15 is the * standard slirp DHCP-allocated guest address; we hardcode it here so no @@ -134,6 +143,8 @@ static NetworkEndPoint_t networkEndPoint; static SolidSyslogFreeRtosStaticResolverStorage resolverStorage; static SolidSyslogFreeRtosDatagramStorage datagramStorage; +static SolidSyslogFreeRtosTcpStreamStorage tcpStreamStorage; +static SolidSyslogStreamSenderStorage tcpSenderStorage; /* CircularBuffer + FreeRtosMutex composition for cross-task emission. * 8 max-sized messages is comfortably above the 3-message BDD scenarios @@ -306,6 +317,16 @@ static bool OnSet(const char* name, const char* value) g_message.severity = (enum SolidSyslog_Severity) parsed; return true; } + if (strcmp(name, "transport") == 0) + { + /* Switching sender selector — "udp" / "tcp" / "tls" / "mtls" route + * through BddTargetSwitchConfig (Bdd/Targets/Common). The FreeRTOS + * target only wires UDP and TCP inner senders today; selecting tls + * falls through to the SwitchingSender's nil sender, surfacing the + * gap as a send failure rather than a crash. */ + BddTargetSwitchConfig_SetByName(value); + return true; + } return false; } @@ -355,7 +376,35 @@ static void InteractiveTask(void* argument) .endpoint = GetEndpoint, .endpointVersion = GetEndpointVersion, }; - struct SolidSyslogSender* sender = SolidSyslogUdpSender_Create(&udpConfig); + struct SolidSyslogSender* udpSender = SolidSyslogUdpSender_Create(&udpConfig); + + /* Plain TCP path via the new FreeRTOS Plus-TCP stream adapter. Shares the + * UDP endpoint callbacks because the BDD oracle (syslog-ng) listens on the + * same host:port for both transports — the syslog-ng config in + * Bdd/syslog-ng/syslog-ng.conf has a TCP listener on 5514 alongside UDP. */ + struct SolidSyslogStream* stream = SolidSyslogFreeRtosTcpStream_Create(&tcpStreamStorage); + struct SolidSyslogStreamSenderConfig tcpConfig = { + .resolver = resolver, + .stream = stream, + .endpoint = GetEndpoint, + .endpointVersion = GetEndpointVersion, + }; + struct SolidSyslogSender* tcpSender = SolidSyslogStreamSender_Create(&tcpSenderStorage, &tcpConfig); + + /* SwitchingSender lets `set transport ` flip the active transport + * at runtime. Default to UDP so existing UDP-tagged scenarios stay green; + * `--transport tcp` flowing through the behave harness lands here as + * `set transport tcp` over the UART and switches before the first send. */ + static struct SolidSyslogSender* inners[2]; + inners[BDD_TARGET_SWITCH_UDP] = udpSender; + inners[BDD_TARGET_SWITCH_TCP] = tcpSender; + struct SolidSyslogSwitchingSenderConfig switchConfig = { + .senders = inners, + .senderCount = sizeof(inners) / sizeof(inners[0]), + .selector = BddTargetSwitchConfig_Selector, + }; + BddTargetSwitchConfig_SetByName("udp"); + struct SolidSyslogSender* sender = SolidSyslogSwitchingSender_Create(&switchConfig); /* CircularBuffer drained by ServiceTask below, with a FreeRtosMutex * gating concurrent producers (interactive task today; multi-task @@ -412,6 +461,9 @@ static void InteractiveTask(void* argument) SolidSyslogNullStore_Destroy(); SolidSyslogCircularBuffer_Destroy(buffer); SolidSyslogFreeRtosMutex_Destroy(mutex); + SolidSyslogSwitchingSender_Destroy(); + SolidSyslogStreamSender_Destroy(tcpSender); + SolidSyslogFreeRtosTcpStream_Destroy(stream); SolidSyslogUdpSender_Destroy(); SolidSyslogFreeRtosDatagram_Destroy(datagram); SolidSyslogFreeRtosStaticResolver_Destroy(resolver); diff --git a/Bdd/features/steps/target_driver.py b/Bdd/features/steps/target_driver.py index ec50a5cc..d48c51d9 100644 --- a/Bdd/features/steps/target_driver.py +++ b/Bdd/features/steps/target_driver.py @@ -37,6 +37,11 @@ "--severity": "severity", "--msgid": "msgid", "--message": "msg", + # `--transport ` lands as `set transport ` on the UART; + # OnSet routes it through BddTargetSwitchConfig_SetByName to flip the + # SolidSyslogSwitchingSender's active inner sender. Added in S08.09 with + # the FreeRTOS TCP stream adapter. + "--transport": "transport", } diff --git a/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp b/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp index 760f433a..ac67d515 100644 --- a/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp +++ b/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp @@ -98,6 +98,19 @@ TEST(SolidSyslogFreeRtosTcpStream, OpenClearsRecvTimeoutAfterConnect) LONGS_EQUAL(1, FreeRtosSocketsFake_RcvTimeoSetCallCount()); } +TEST(SolidSyslogFreeRtosTcpStream, OpenCallsSetsockoptWithReturnedSocketAndLevelZero) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastSetsockoptSocket()); + LONGS_EQUAL(0, FreeRtosSocketsFake_LastSetsockoptLevel()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenPassesTickTypeSizedOptionLengthToSetsockopt) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(sizeof(TickType_t), FreeRtosSocketsFake_LastSetsockoptOptionLength()); +} + TEST(SolidSyslogFreeRtosTcpStream, OpenReturnsFalseOnConnectFailure) { FreeRtosSocketsFake_SetConnectFails(true); diff --git a/Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h b/Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h index cb7601da..4ecadde8 100644 --- a/Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h +++ b/Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h @@ -74,6 +74,10 @@ EXTERN_C_BEGIN TickType_t FreeRtosSocketsFake_LastSndTimeoSet(void); TickType_t FreeRtosSocketsFake_LastRcvTimeoSet(void); unsigned FreeRtosSocketsFake_RcvTimeoSetCallCount(void); + Socket_t FreeRtosSocketsFake_LastSetsockoptSocket(void); + int32_t FreeRtosSocketsFake_LastSetsockoptLevel(void); + int32_t FreeRtosSocketsFake_LastSetsockoptOptionName(void); + size_t FreeRtosSocketsFake_LastSetsockoptOptionLength(void); /* closesocket accessors */ unsigned FreeRtosSocketsFake_ClosesocketCallCount(void); diff --git a/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c b/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c index efe1ea19..00bdccff 100644 --- a/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c +++ b/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c @@ -42,10 +42,14 @@ static bool recvFails = false; static bool recvReturnSet = false; static BaseType_t recvReturnValue = 0; -static TickType_t lastSndTimeoSet = 0; -static TickType_t lastRcvTimeoSet = 0; -static unsigned rcvTimeoSetCallCount = 0; -static TickType_t sndTimeoAtConnect = 0; +static TickType_t lastSndTimeoSet = 0; +static TickType_t lastRcvTimeoSet = 0; +static unsigned rcvTimeoSetCallCount = 0; +static Socket_t lastSetsockoptSocket = NULL; +static int32_t lastSetsockoptLevel = 0; +static int32_t lastSetsockoptOptionName = 0; +static size_t lastSetsockoptOptionLength = 0; +static TickType_t sndTimeoAtConnect = 0; static unsigned closesocketCallCount = 0; static Socket_t lastClosesocketSocket = NULL; @@ -98,10 +102,14 @@ void FreeRtosSocketsFake_Reset(void) recvReturnSet = false; recvReturnValue = 0; - lastSndTimeoSet = 0; - lastRcvTimeoSet = 0; - rcvTimeoSetCallCount = 0; - sndTimeoAtConnect = 0; + lastSndTimeoSet = 0; + lastRcvTimeoSet = 0; + rcvTimeoSetCallCount = 0; + lastSetsockoptSocket = NULL; + lastSetsockoptLevel = 0; + lastSetsockoptOptionName = 0; + lastSetsockoptOptionLength = 0; + sndTimeoAtConnect = 0; closesocketCallCount = 0; lastClosesocketSocket = NULL; @@ -247,11 +255,32 @@ unsigned FreeRtosSocketsFake_RcvTimeoSetCallCount(void) return rcvTimeoSetCallCount; } +Socket_t FreeRtosSocketsFake_LastSetsockoptSocket(void) +{ + return lastSetsockoptSocket; +} + +int32_t FreeRtosSocketsFake_LastSetsockoptLevel(void) +{ + return lastSetsockoptLevel; +} + +int32_t FreeRtosSocketsFake_LastSetsockoptOptionName(void) +{ + return lastSetsockoptOptionName; +} + +size_t FreeRtosSocketsFake_LastSetsockoptOptionLength(void) +{ + return lastSetsockoptOptionLength; +} + BaseType_t FreeRTOS_setsockopt(Socket_t xSocket, int32_t lLevel, int32_t lOptionName, const void* pvOptionValue, size_t uxOptionLength) { - (void) xSocket; - (void) lLevel; - (void) uxOptionLength; + lastSetsockoptSocket = xSocket; + lastSetsockoptLevel = lLevel; + lastSetsockoptOptionName = lOptionName; + lastSetsockoptOptionLength = uxOptionLength; if (lOptionName == FREERTOS_SO_SNDTIMEO) { lastSndTimeoSet = *(const TickType_t*) pvOptionValue; diff --git a/ci/docker-compose.bdd.yml b/ci/docker-compose.bdd.yml index 82a761eb..3bd5e3f8 100644 --- a/ci/docker-compose.bdd.yml +++ b/ci/docker-compose.bdd.yml @@ -101,7 +101,11 @@ services: environment: - BDD_TARGET=freertos - EXAMPLE_BINARY=build/freertos-cross/Bdd/Targets/FreeRtos/SolidSyslogBddTarget.elf - command: behave --junit --junit-directory Bdd/junit --tags='not @wip and not @freertoswip and not @rtc and @udp' Bdd/features/ + # S08.09 admits @tcp scenarios so tcp_transport.feature runs alongside the + # existing @udp ones; @buffered excludes store-and-forward features that + # need a file-backed store (no FreeRTOS file abstraction yet), and + # @windows_wip excludes the Windows-only TCP singletask variant. + command: behave --junit --junit-directory Bdd/junit --tags='not @wip and not @freertoswip and not @rtc and not @buffered and not @windows_wip and (@udp or @tcp)' Bdd/features/ volumes: bdd-output-linux: From 0f89934da36a8b8cd44846327d19e129a5e59442 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 11 May 2026 18:15:49 +0000 Subject: [PATCH 3/3] refactor: tighten FreeRtosTcpStream readability and test fixture Production (Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c): - Prefix every static with FreeRtosTcpStream_ for MISRA file-scope identifier uniqueness. - Replace negative guard `if (!IsOpen)` with `IsClosed` predicate so Open's outer condition reads positively. - Extract OpenSocket / ConnectOrCloseOnFailure / TryConnect / ClearTimeouts so Open / Send / Read each read as a single sequence at the stream-of-abstraction level (no native FreeRTOS calls inline). - Mirror the same Try* / *OrCloseOnFailure shape across Send and Read (SendOrCloseOnFailure / TrySend / AllBytesSent and ReceiveOrCloseOnFailure) so the three paths now read symmetrically. - Rename `rc` -> `sentCount` / `receivedCount` for intent. - Name the magic literals: SETSOCKOPT_LEVEL_DEFAULT and SEND_RECV_FLAGS_DEFAULT for the FreeRTOS-Plus-TCP placeholder zeros, and READ_FAILED for the -1 EOF/error sentinel from SolidSyslogStream_Read. Tests (Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp): - Lift TEST_PORT / TEST_MESSAGE / TEST_MESSAGE_LEN / TEST_SHORT_WRITE_BYTES / TEST_READ_BYTES to file scope; replace inline literals throughout. - Add openStream() / readIntoBuffer() helpers on the TEST_GROUP so the arrange step in 25+ tests collapses to one line. - Add CHECK_SOCKET_CLOSED_ONCE macro that pins both the close call count and the identity of the closed socket. Apply it across the close-on-failure tests, tightening three sites that previously only pinned the call count (SendClosesSocketOnError, ReadReturnsNegativeOneOnErrorAndClosesSocket, DestroyClosesOpenSocket). --- .../Source/SolidSyslogFreeRtosTcpStream.c | 183 ++++++++++++------ .../SolidSyslogFreeRtosTcpStreamTest.cpp | 141 ++++++++------ 2 files changed, 207 insertions(+), 117 deletions(-) diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c index 97dbb4c7..7ba5de89 100644 --- a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c @@ -28,21 +28,46 @@ struct SolidSyslogFreeRtosTcpStream static const TickType_t CONNECT_TIMEOUT_TICKS = pdMS_TO_TICKS(200); static const TickType_t NO_TIMEOUT_TICKS = 0; -static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); -static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); -static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size); -static void Close(struct SolidSyslogStream* self); +/* SolidSyslogStream_Read returns < 0 to signal EOF/error (socket closed + * internally); -1 is the in-tree convention shared with Posix/Winsock. */ +static const SolidSyslogSsize READ_FAILED = -1; + +enum +{ + /* FreeRTOS-Plus-TCP's setsockopt ignores the level argument (no + * SOL_SOCKET vs IPPROTO_TCP split; option codes are flat); pass 0 by + * convention. */ + SETSOCKOPT_LEVEL_DEFAULT = 0, + /* No MSG_PEEK / MSG_DONTWAIT / zero-copy — the timeouts cleared after + * connect already give us the non-blocking single-call behaviour + * SolidSyslogStream requires. */ + SEND_RECV_FLAGS_DEFAULT = 0 +}; + +static bool FreeRtosTcpStream_Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); +static bool FreeRtosTcpStream_Send(struct SolidSyslogStream* self, const void* buffer, size_t size); +static SolidSyslogSsize FreeRtosTcpStream_Read(struct SolidSyslogStream* self, void* buffer, size_t size); +static void FreeRtosTcpStream_Close(struct SolidSyslogStream* self); static inline FreeRtosTcpStream* FreeRtosTcpStream_From(struct SolidSyslogStream* self); -static inline bool IsOpen(const FreeRtosTcpStream* stream); -static void SetSendTimeout(Socket_t socket, TickType_t ticks); -static void SetRecvTimeout(Socket_t socket, TickType_t ticks); -static void CloseSocket(FreeRtosTcpStream* stream); +static inline bool FreeRtosTcpStream_IsOpen(const FreeRtosTcpStream* stream); +static inline bool FreeRtosTcpStream_IsClosed(const FreeRtosTcpStream* stream); +static void FreeRtosTcpStream_OpenSocket(FreeRtosTcpStream* stream); +static void FreeRtosTcpStream_ConnectOrCloseOnFailure(FreeRtosTcpStream* stream, const struct SolidSyslogAddress* addr); +static bool FreeRtosTcpStream_TryConnect(FreeRtosTcpStream* stream, const struct SolidSyslogAddress* addr); +static void FreeRtosTcpStream_ClearTimeouts(Socket_t socket); +static void FreeRtosTcpStream_SetSendTimeout(Socket_t socket, TickType_t ticks); +static void FreeRtosTcpStream_SetRecvTimeout(Socket_t socket, TickType_t ticks); +static bool FreeRtosTcpStream_SendOrCloseOnFailure(FreeRtosTcpStream* stream, const void* buffer, size_t size); +static bool FreeRtosTcpStream_TrySend(FreeRtosTcpStream* stream, const void* buffer, size_t size); +static bool FreeRtosTcpStream_AllBytesSent(BaseType_t sentCount, size_t expected); +static SolidSyslogSsize FreeRtosTcpStream_ReceiveOrCloseOnFailure(FreeRtosTcpStream* stream, void* buffer, size_t size); +static void FreeRtosTcpStream_CloseSocket(FreeRtosTcpStream* stream); SOLIDSYSLOG_STATIC_ASSERT(sizeof(FreeRtosTcpStream) <= SOLIDSYSLOG_FREERTOSTCPSTREAM_SIZE, "SOLIDSYSLOG_FREERTOSTCPSTREAM_SIZE is too small for SolidSyslogFreeRtosTcpStream layout"); static const FreeRtosTcpStream DEFAULT_INSTANCE = { - {Open, Send, Read, Close}, + {FreeRtosTcpStream_Open, FreeRtosTcpStream_Send, FreeRtosTcpStream_Read, FreeRtosTcpStream_Close}, FREERTOS_INVALID_SOCKET, }; @@ -61,7 +86,7 @@ struct SolidSyslogStream* SolidSyslogFreeRtosTcpStream_Create(SolidSyslogFreeRto void SolidSyslogFreeRtosTcpStream_Destroy(struct SolidSyslogStream* stream) { FreeRtosTcpStream* self = FreeRtosTcpStream_From(stream); - CloseSocket(self); + FreeRtosTcpStream_CloseSocket(self); *self = DESTROYED_INSTANCE; } @@ -70,91 +95,139 @@ static inline FreeRtosTcpStream* FreeRtosTcpStream_From(struct SolidSyslogStream return (FreeRtosTcpStream*) self; } -static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) +static bool FreeRtosTcpStream_Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) { FreeRtosTcpStream* stream = FreeRtosTcpStream_From(self); - if (!IsOpen(stream)) + if (FreeRtosTcpStream_IsClosed(stream)) { - stream->socket = FreeRTOS_socket(FREERTOS_AF_INET, FREERTOS_SOCK_STREAM, FREERTOS_IPPROTO_TCP); - if (IsOpen(stream)) + FreeRtosTcpStream_OpenSocket(stream); + if (FreeRtosTcpStream_IsOpen(stream)) { - const struct freertos_sockaddr* dest = SolidSyslogAddress_AsConstFreertosSockaddr(addr); - SetSendTimeout(stream->socket, CONNECT_TIMEOUT_TICKS); - if (FreeRTOS_connect(stream->socket, dest, sizeof(*dest)) == 0) - { - SetSendTimeout(stream->socket, NO_TIMEOUT_TICKS); - SetRecvTimeout(stream->socket, NO_TIMEOUT_TICKS); - } - else - { - CloseSocket(stream); - } + FreeRtosTcpStream_ConnectOrCloseOnFailure(stream, addr); } } - return IsOpen(stream); + return FreeRtosTcpStream_IsOpen(stream); } -static inline bool IsOpen(const FreeRtosTcpStream* stream) +static inline bool FreeRtosTcpStream_IsOpen(const FreeRtosTcpStream* stream) { return stream->socket != FREERTOS_INVALID_SOCKET; } -static void SetSendTimeout(Socket_t socket, TickType_t ticks) +static inline bool FreeRtosTcpStream_IsClosed(const FreeRtosTcpStream* stream) { - (void) FreeRTOS_setsockopt(socket, 0, FREERTOS_SO_SNDTIMEO, &ticks, sizeof(ticks)); + return !FreeRtosTcpStream_IsOpen(stream); } -static void SetRecvTimeout(Socket_t socket, TickType_t ticks) +static void FreeRtosTcpStream_OpenSocket(FreeRtosTcpStream* stream) { - (void) FreeRTOS_setsockopt(socket, 0, FREERTOS_SO_RCVTIMEO, &ticks, sizeof(ticks)); + stream->socket = FreeRTOS_socket(FREERTOS_AF_INET, FREERTOS_SOCK_STREAM, FREERTOS_IPPROTO_TCP); } -static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size) +static void FreeRtosTcpStream_ConnectOrCloseOnFailure(FreeRtosTcpStream* stream, const struct SolidSyslogAddress* addr) +{ + if (FreeRtosTcpStream_TryConnect(stream, addr)) + { + FreeRtosTcpStream_ClearTimeouts(stream->socket); + } + else + { + FreeRtosTcpStream_CloseSocket(stream); + } +} + +static bool FreeRtosTcpStream_TryConnect(FreeRtosTcpStream* stream, const struct SolidSyslogAddress* addr) +{ + const struct freertos_sockaddr* dest = SolidSyslogAddress_AsConstFreertosSockaddr(addr); + FreeRtosTcpStream_SetSendTimeout(stream->socket, CONNECT_TIMEOUT_TICKS); + return FreeRTOS_connect(stream->socket, dest, sizeof(*dest)) == 0; +} + +static void FreeRtosTcpStream_ClearTimeouts(Socket_t socket) +{ + FreeRtosTcpStream_SetSendTimeout(socket, NO_TIMEOUT_TICKS); + FreeRtosTcpStream_SetRecvTimeout(socket, NO_TIMEOUT_TICKS); +} + +static void FreeRtosTcpStream_SetSendTimeout(Socket_t socket, TickType_t ticks) +{ + (void) FreeRTOS_setsockopt(socket, SETSOCKOPT_LEVEL_DEFAULT, FREERTOS_SO_SNDTIMEO, &ticks, sizeof(ticks)); +} + +static void FreeRtosTcpStream_SetRecvTimeout(Socket_t socket, TickType_t ticks) +{ + (void) FreeRTOS_setsockopt(socket, SETSOCKOPT_LEVEL_DEFAULT, FREERTOS_SO_RCVTIMEO, &ticks, sizeof(ticks)); +} + +static bool FreeRtosTcpStream_Send(struct SolidSyslogStream* self, const void* buffer, size_t size) { FreeRtosTcpStream* stream = FreeRtosTcpStream_From(self); bool sent = false; - if (IsOpen(stream)) + if (FreeRtosTcpStream_IsOpen(stream)) { - BaseType_t rc = FreeRTOS_send(stream->socket, buffer, size, 0); - sent = (rc >= 0) && ((size_t) rc == size); - if (!sent) - { - CloseSocket(stream); - } + sent = FreeRtosTcpStream_SendOrCloseOnFailure(stream, buffer, size); } return sent; } +static bool FreeRtosTcpStream_SendOrCloseOnFailure(FreeRtosTcpStream* stream, const void* buffer, size_t size) +{ + bool sent = FreeRtosTcpStream_TrySend(stream, buffer, size); + if (!sent) + { + FreeRtosTcpStream_CloseSocket(stream); + } + return sent; +} + +static bool FreeRtosTcpStream_TrySend(FreeRtosTcpStream* stream, const void* buffer, size_t size) +{ + BaseType_t sentCount = FreeRTOS_send(stream->socket, buffer, size, SEND_RECV_FLAGS_DEFAULT); + return FreeRtosTcpStream_AllBytesSent(sentCount, size); +} + +static bool FreeRtosTcpStream_AllBytesSent(BaseType_t sentCount, size_t expected) +{ + return (sentCount >= 0) && ((size_t) sentCount == expected); +} + /* FreeRTOS_recv with RCVTIMEO=0 returns 0 when no data is available (would * block); the Service thread treats that as "nothing to read right now". * Negative returns are real errors — close the socket and surface failure. */ -static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size) +static SolidSyslogSsize FreeRtosTcpStream_Read(struct SolidSyslogStream* self, void* buffer, size_t size) { FreeRtosTcpStream* stream = FreeRtosTcpStream_From(self); - SolidSyslogSsize result = -1; - if (IsOpen(stream)) + SolidSyslogSsize result = READ_FAILED; + if (FreeRtosTcpStream_IsOpen(stream)) { - BaseType_t rc = FreeRTOS_recv(stream->socket, buffer, size, 0); - if (rc >= 0) - { - result = (SolidSyslogSsize) rc; - } - else - { - CloseSocket(stream); - } + result = FreeRtosTcpStream_ReceiveOrCloseOnFailure(stream, buffer, size); + } + return result; +} + +static SolidSyslogSsize FreeRtosTcpStream_ReceiveOrCloseOnFailure(FreeRtosTcpStream* stream, void* buffer, size_t size) +{ + BaseType_t receivedCount = FreeRTOS_recv(stream->socket, buffer, size, SEND_RECV_FLAGS_DEFAULT); + SolidSyslogSsize result = READ_FAILED; + if (receivedCount >= 0) + { + result = (SolidSyslogSsize) receivedCount; + } + else + { + FreeRtosTcpStream_CloseSocket(stream); } return result; } -static void Close(struct SolidSyslogStream* self) +static void FreeRtosTcpStream_Close(struct SolidSyslogStream* self) { - CloseSocket(FreeRtosTcpStream_From(self)); + FreeRtosTcpStream_CloseSocket(FreeRtosTcpStream_From(self)); } -static void CloseSocket(FreeRtosTcpStream* stream) +static void FreeRtosTcpStream_CloseSocket(FreeRtosTcpStream* stream) { - if (IsOpen(stream)) + if (FreeRtosTcpStream_IsOpen(stream)) { (void) FreeRTOS_closesocket(stream->socket); stream->socket = FREERTOS_INVALID_SOCKET; diff --git a/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp b/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp index ac67d515..a3308053 100644 --- a/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp +++ b/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp @@ -14,6 +14,12 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-f #include "FreeRTOS_IP.h" #include "FreeRTOS_Sockets.h" +static const uint16_t TEST_PORT = 514; +static const char TEST_MESSAGE[] = "hello"; +static const size_t TEST_MESSAGE_LEN = sizeof(TEST_MESSAGE) - 1U; +static const BaseType_t TEST_SHORT_WRITE_BYTES = 3; +static const BaseType_t TEST_READ_BYTES = 7; + // clang-format off TEST_GROUP(SolidSyslogFreeRtosTcpStream) { @@ -21,6 +27,7 @@ TEST_GROUP(SolidSyslogFreeRtosTcpStream) struct SolidSyslogStream* stream = nullptr; SolidSyslogAddressStorage addrStorage{}; struct SolidSyslogAddress* addr = nullptr; + char readBuffer[16] = {0}; void setup() override { @@ -30,7 +37,7 @@ TEST_GROUP(SolidSyslogFreeRtosTcpStream) // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- char-type aliasing into platform layout, storage is intptr_t-aligned auto* sin = reinterpret_cast(&addrStorage); sin->sin_family = FREERTOS_AF_INET; - sin->sin_port = FreeRTOS_htons(514); + sin->sin_port = FreeRTOS_htons(TEST_PORT); sin->sin_address.ulIP_IPv4 = FreeRTOS_inet_addr_quick(10, 0, 2, 2); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- platform-layout cast, see above addr = reinterpret_cast(&addrStorage); @@ -40,10 +47,30 @@ TEST_GROUP(SolidSyslogFreeRtosTcpStream) { SolidSyslogFreeRtosTcpStream_Destroy(stream); } + + void openStream() const + { + SolidSyslogStream_Open(stream, addr); + } + + SolidSyslogSsize readIntoBuffer() + { + return SolidSyslogStream_Read(stream, readBuffer, sizeof(readBuffer)); + } }; // clang-format on +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) +#define CHECK_SOCKET_CLOSED_ONCE() \ + do \ + { \ + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); \ + POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastClosesocketSocket()); \ + } while (0) + +// NOLINTEND(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) + TEST(SolidSyslogFreeRtosTcpStream, CreateReturnsNonNullStream) { CHECK(stream != nullptr); @@ -51,7 +78,7 @@ TEST(SolidSyslogFreeRtosTcpStream, CreateReturnsNonNullStream) TEST(SolidSyslogFreeRtosTcpStream, OpenCreatesTcpSocket) { - SolidSyslogStream_Open(stream, addr); + openStream(); CALLED_FAKE(FreeRtosSocketsFake_Socket, ONCE); LONGS_EQUAL(FREERTOS_AF_INET, FreeRtosSocketsFake_LastSocketDomain()); LONGS_EQUAL(FREERTOS_SOCK_STREAM, FreeRtosSocketsFake_LastSocketType()); @@ -71,13 +98,13 @@ TEST(SolidSyslogFreeRtosTcpStream, OpenReturnsFalseWhenSocketFails) TEST(SolidSyslogFreeRtosTcpStream, OpenSetsConnectTimeoutBeforeConnect) { - SolidSyslogStream_Open(stream, addr); + openStream(); LONGS_EQUAL(pdMS_TO_TICKS(200), FreeRtosSocketsFake_SndTimeoAtConnect()); } TEST(SolidSyslogFreeRtosTcpStream, OpenCallsConnectWithSocketAndAddress) { - SolidSyslogStream_Open(stream, addr); + openStream(); CALLED_FAKE(FreeRtosSocketsFake_Connect, ONCE); POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastConnectSocket()); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- platform-layout cast, see setup @@ -87,27 +114,27 @@ TEST(SolidSyslogFreeRtosTcpStream, OpenCallsConnectWithSocketAndAddress) TEST(SolidSyslogFreeRtosTcpStream, OpenClearsSendTimeoutAfterConnect) { - SolidSyslogStream_Open(stream, addr); + openStream(); LONGS_EQUAL(0, FreeRtosSocketsFake_LastSndTimeoSet()); } TEST(SolidSyslogFreeRtosTcpStream, OpenClearsRecvTimeoutAfterConnect) { - SolidSyslogStream_Open(stream, addr); + openStream(); LONGS_EQUAL(0, FreeRtosSocketsFake_LastRcvTimeoSet()); LONGS_EQUAL(1, FreeRtosSocketsFake_RcvTimeoSetCallCount()); } TEST(SolidSyslogFreeRtosTcpStream, OpenCallsSetsockoptWithReturnedSocketAndLevelZero) { - SolidSyslogStream_Open(stream, addr); + openStream(); POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastSetsockoptSocket()); LONGS_EQUAL(0, FreeRtosSocketsFake_LastSetsockoptLevel()); } TEST(SolidSyslogFreeRtosTcpStream, OpenPassesTickTypeSizedOptionLengthToSetsockopt) { - SolidSyslogStream_Open(stream, addr); + openStream(); LONGS_EQUAL(sizeof(TickType_t), FreeRtosSocketsFake_LastSetsockoptOptionLength()); } @@ -120,14 +147,13 @@ TEST(SolidSyslogFreeRtosTcpStream, OpenReturnsFalseOnConnectFailure) TEST(SolidSyslogFreeRtosTcpStream, OpenClosesSocketOnConnectFailure) { FreeRtosSocketsFake_SetConnectFails(true); - SolidSyslogStream_Open(stream, addr); - CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); - POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastClosesocketSocket()); + openStream(); + CHECK_SOCKET_CLOSED_ONCE(); } TEST(SolidSyslogFreeRtosTcpStream, OpenIsIdempotent) { - SolidSyslogStream_Open(stream, addr); + openStream(); CHECK_TRUE(SolidSyslogStream_Open(stream, addr)); CALLED_FAKE(FreeRtosSocketsFake_Socket, ONCE); CALLED_FAKE(FreeRtosSocketsFake_Connect, ONCE); @@ -141,112 +167,104 @@ TEST(SolidSyslogFreeRtosTcpStream, SendFailsBeforeOpen) TEST(SolidSyslogFreeRtosTcpStream, SendCallsFreeRtosSendWithSocketBufferAndLength) { - static const char TEST_MESSAGE[] = "hello"; - SolidSyslogStream_Open(stream, addr); - SolidSyslogStream_Send(stream, TEST_MESSAGE, sizeof(TEST_MESSAGE) - 1); + openStream(); + SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN); CALLED_FAKE(FreeRtosSocketsFake_Send, ONCE); POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastSendSocket()); POINTERS_EQUAL(TEST_MESSAGE, FreeRtosSocketsFake_LastSendBuffer()); - LONGS_EQUAL(sizeof(TEST_MESSAGE) - 1, FreeRtosSocketsFake_LastSendLength()); + LONGS_EQUAL(TEST_MESSAGE_LEN, FreeRtosSocketsFake_LastSendLength()); LONGS_EQUAL(0, FreeRtosSocketsFake_LastSendFlags()); } TEST(SolidSyslogFreeRtosTcpStream, SendReturnsTrueOnFullWrite) { - SolidSyslogStream_Open(stream, addr); - CHECK_TRUE(SolidSyslogStream_Send(stream, "hello", 5)); + openStream(); + CHECK_TRUE(SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN)); } TEST(SolidSyslogFreeRtosTcpStream, SendReturnsFalseOnShortWrite) { - SolidSyslogStream_Open(stream, addr); - FreeRtosSocketsFake_SetSendReturn(3); - CHECK_FALSE(SolidSyslogStream_Send(stream, "hello", 5)); + openStream(); + FreeRtosSocketsFake_SetSendReturn(TEST_SHORT_WRITE_BYTES); + CHECK_FALSE(SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN)); } TEST(SolidSyslogFreeRtosTcpStream, SendDoesNotRetryAfterShortWrite) { - SolidSyslogStream_Open(stream, addr); - FreeRtosSocketsFake_SetSendReturn(3); - SolidSyslogStream_Send(stream, "hello", 5); + openStream(); + FreeRtosSocketsFake_SetSendReturn(TEST_SHORT_WRITE_BYTES); + SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN); CALLED_FAKE(FreeRtosSocketsFake_Send, ONCE); } TEST(SolidSyslogFreeRtosTcpStream, SendClosesSocketOnShortWrite) { - SolidSyslogStream_Open(stream, addr); - FreeRtosSocketsFake_SetSendReturn(3); - SolidSyslogStream_Send(stream, "hello", 5); - CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); - POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastClosesocketSocket()); + openStream(); + FreeRtosSocketsFake_SetSendReturn(TEST_SHORT_WRITE_BYTES); + SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN); + CHECK_SOCKET_CLOSED_ONCE(); } TEST(SolidSyslogFreeRtosTcpStream, SendReturnsFalseOnError) { - SolidSyslogStream_Open(stream, addr); + openStream(); FreeRtosSocketsFake_SetSendFails(true); - CHECK_FALSE(SolidSyslogStream_Send(stream, "hello", 5)); + CHECK_FALSE(SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN)); } TEST(SolidSyslogFreeRtosTcpStream, SendClosesSocketOnError) { - SolidSyslogStream_Open(stream, addr); + openStream(); FreeRtosSocketsFake_SetSendFails(true); - SolidSyslogStream_Send(stream, "hello", 5); - CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); + SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN); + CHECK_SOCKET_CLOSED_ONCE(); } TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsNegativeOneBeforeOpen) { - char buf[16]; - LONGS_EQUAL(-1, SolidSyslogStream_Read(stream, buf, sizeof(buf))); + LONGS_EQUAL(-1, readIntoBuffer()); CALLED_FAKE(FreeRtosSocketsFake_Recv, NEVER); } TEST(SolidSyslogFreeRtosTcpStream, ReadCallsFreeRtosRecvWithSocketBufferAndLength) { - char buf[16]; - SolidSyslogStream_Open(stream, addr); - SolidSyslogStream_Read(stream, buf, sizeof(buf)); + openStream(); + readIntoBuffer(); CALLED_FAKE(FreeRtosSocketsFake_Recv, ONCE); POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastRecvSocket()); - POINTERS_EQUAL(buf, FreeRtosSocketsFake_LastRecvBuffer()); - LONGS_EQUAL(sizeof(buf), FreeRtosSocketsFake_LastRecvLength()); + POINTERS_EQUAL(readBuffer, FreeRtosSocketsFake_LastRecvBuffer()); + LONGS_EQUAL(sizeof(readBuffer), FreeRtosSocketsFake_LastRecvLength()); LONGS_EQUAL(0, FreeRtosSocketsFake_LastRecvFlags()); } TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsBytesOnSuccess) { - char buf[16]; - SolidSyslogStream_Open(stream, addr); - FreeRtosSocketsFake_SetRecvReturn(7); - LONGS_EQUAL(7, SolidSyslogStream_Read(stream, buf, sizeof(buf))); + openStream(); + FreeRtosSocketsFake_SetRecvReturn(TEST_READ_BYTES); + LONGS_EQUAL(TEST_READ_BYTES, readIntoBuffer()); } TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsZeroWhenWouldBlock) { - char buf[16]; - SolidSyslogStream_Open(stream, addr); + openStream(); FreeRtosSocketsFake_SetRecvReturn(0); - LONGS_EQUAL(0, SolidSyslogStream_Read(stream, buf, sizeof(buf))); + LONGS_EQUAL(0, readIntoBuffer()); } TEST(SolidSyslogFreeRtosTcpStream, ReadLeavesSocketOpenWhenWouldBlock) { - char buf[16]; - SolidSyslogStream_Open(stream, addr); + openStream(); FreeRtosSocketsFake_SetRecvReturn(0); - SolidSyslogStream_Read(stream, buf, sizeof(buf)); + readIntoBuffer(); CALLED_FAKE(FreeRtosSocketsFake_Closesocket, NEVER); } TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsNegativeOneOnErrorAndClosesSocket) { - char buf[16]; - SolidSyslogStream_Open(stream, addr); + openStream(); FreeRtosSocketsFake_SetRecvFails(true); - LONGS_EQUAL(-1, SolidSyslogStream_Read(stream, buf, sizeof(buf))); - CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); + LONGS_EQUAL(-1, readIntoBuffer()); + CHECK_SOCKET_CLOSED_ONCE(); } TEST(SolidSyslogFreeRtosTcpStream, CloseWithoutOpenIsNoOp) @@ -257,15 +275,14 @@ TEST(SolidSyslogFreeRtosTcpStream, CloseWithoutOpenIsNoOp) TEST(SolidSyslogFreeRtosTcpStream, CloseClosesOpenSocket) { - SolidSyslogStream_Open(stream, addr); + openStream(); SolidSyslogStream_Close(stream); - CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); - POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastClosesocketSocket()); + CHECK_SOCKET_CLOSED_ONCE(); } TEST(SolidSyslogFreeRtosTcpStream, CloseIsIdempotent) { - SolidSyslogStream_Open(stream, addr); + openStream(); SolidSyslogStream_Close(stream); SolidSyslogStream_Close(stream); CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); @@ -273,14 +290,14 @@ TEST(SolidSyslogFreeRtosTcpStream, CloseIsIdempotent) TEST(SolidSyslogFreeRtosTcpStream, DestroyClosesOpenSocket) { - SolidSyslogStream_Open(stream, addr); + openStream(); SolidSyslogFreeRtosTcpStream_Destroy(stream); - CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); + CHECK_SOCKET_CLOSED_ONCE(); } TEST(SolidSyslogFreeRtosTcpStream, DestroyAfterCloseDoesNotCloseAgain) { - SolidSyslogStream_Open(stream, addr); + openStream(); SolidSyslogStream_Close(stream); SolidSyslogFreeRtosTcpStream_Destroy(stream); CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE);