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/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..7ba5de89 --- /dev/null +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c @@ -0,0 +1,237 @@ +// 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; + +/* 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 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 = { + {FreeRtosTcpStream_Open, FreeRtosTcpStream_Send, FreeRtosTcpStream_Read, FreeRtosTcpStream_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); + FreeRtosTcpStream_CloseSocket(self); + *self = DESTROYED_INSTANCE; +} + +static inline FreeRtosTcpStream* FreeRtosTcpStream_From(struct SolidSyslogStream* self) +{ + return (FreeRtosTcpStream*) self; +} + +static bool FreeRtosTcpStream_Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) +{ + FreeRtosTcpStream* stream = FreeRtosTcpStream_From(self); + if (FreeRtosTcpStream_IsClosed(stream)) + { + FreeRtosTcpStream_OpenSocket(stream); + if (FreeRtosTcpStream_IsOpen(stream)) + { + FreeRtosTcpStream_ConnectOrCloseOnFailure(stream, addr); + } + } + return FreeRtosTcpStream_IsOpen(stream); +} + +static inline bool FreeRtosTcpStream_IsOpen(const FreeRtosTcpStream* stream) +{ + return stream->socket != FREERTOS_INVALID_SOCKET; +} + +static inline bool FreeRtosTcpStream_IsClosed(const FreeRtosTcpStream* stream) +{ + return !FreeRtosTcpStream_IsOpen(stream); +} + +static void FreeRtosTcpStream_OpenSocket(FreeRtosTcpStream* stream) +{ + stream->socket = FreeRTOS_socket(FREERTOS_AF_INET, FREERTOS_SOCK_STREAM, FREERTOS_IPPROTO_TCP); +} + +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 (FreeRtosTcpStream_IsOpen(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 FreeRtosTcpStream_Read(struct SolidSyslogStream* self, void* buffer, size_t size) +{ + FreeRtosTcpStream* stream = FreeRtosTcpStream_From(self); + SolidSyslogSsize result = READ_FAILED; + if (FreeRtosTcpStream_IsOpen(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 FreeRtosTcpStream_Close(struct SolidSyslogStream* self) +{ + FreeRtosTcpStream_CloseSocket(FreeRtosTcpStream_From(self)); +} + +static void FreeRtosTcpStream_CloseSocket(FreeRtosTcpStream* stream) +{ + if (FreeRtosTcpStream_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..a3308053 --- /dev/null +++ b/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp @@ -0,0 +1,304 @@ +#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" + +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) +{ + SolidSyslogFreeRtosTcpStreamStorage storage{}; + struct SolidSyslogStream* stream = nullptr; + SolidSyslogAddressStorage addrStorage{}; + struct SolidSyslogAddress* addr = nullptr; + char readBuffer[16] = {0}; + + 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(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); + } + + void teardown() override + { + 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); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenCreatesTcpSocket) +{ + openStream(); + 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) +{ + openStream(); + LONGS_EQUAL(pdMS_TO_TICKS(200), FreeRtosSocketsFake_SndTimeoAtConnect()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenCallsConnectWithSocketAndAddress) +{ + openStream(); + 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) +{ + openStream(); + LONGS_EQUAL(0, FreeRtosSocketsFake_LastSndTimeoSet()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenClearsRecvTimeoutAfterConnect) +{ + openStream(); + LONGS_EQUAL(0, FreeRtosSocketsFake_LastRcvTimeoSet()); + LONGS_EQUAL(1, FreeRtosSocketsFake_RcvTimeoSetCallCount()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenCallsSetsockoptWithReturnedSocketAndLevelZero) +{ + openStream(); + POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastSetsockoptSocket()); + LONGS_EQUAL(0, FreeRtosSocketsFake_LastSetsockoptLevel()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenPassesTickTypeSizedOptionLengthToSetsockopt) +{ + openStream(); + LONGS_EQUAL(sizeof(TickType_t), FreeRtosSocketsFake_LastSetsockoptOptionLength()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenReturnsFalseOnConnectFailure) +{ + FreeRtosSocketsFake_SetConnectFails(true); + CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenClosesSocketOnConnectFailure) +{ + FreeRtosSocketsFake_SetConnectFails(true); + openStream(); + CHECK_SOCKET_CLOSED_ONCE(); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenIsIdempotent) +{ + openStream(); + 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) +{ + 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(TEST_MESSAGE_LEN, FreeRtosSocketsFake_LastSendLength()); + LONGS_EQUAL(0, FreeRtosSocketsFake_LastSendFlags()); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendReturnsTrueOnFullWrite) +{ + openStream(); + CHECK_TRUE(SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN)); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendReturnsFalseOnShortWrite) +{ + openStream(); + FreeRtosSocketsFake_SetSendReturn(TEST_SHORT_WRITE_BYTES); + CHECK_FALSE(SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN)); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendDoesNotRetryAfterShortWrite) +{ + openStream(); + FreeRtosSocketsFake_SetSendReturn(TEST_SHORT_WRITE_BYTES); + SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN); + CALLED_FAKE(FreeRtosSocketsFake_Send, ONCE); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendClosesSocketOnShortWrite) +{ + openStream(); + FreeRtosSocketsFake_SetSendReturn(TEST_SHORT_WRITE_BYTES); + SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN); + CHECK_SOCKET_CLOSED_ONCE(); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendReturnsFalseOnError) +{ + openStream(); + FreeRtosSocketsFake_SetSendFails(true); + CHECK_FALSE(SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN)); +} + +TEST(SolidSyslogFreeRtosTcpStream, SendClosesSocketOnError) +{ + openStream(); + FreeRtosSocketsFake_SetSendFails(true); + SolidSyslogStream_Send(stream, TEST_MESSAGE, TEST_MESSAGE_LEN); + CHECK_SOCKET_CLOSED_ONCE(); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsNegativeOneBeforeOpen) +{ + LONGS_EQUAL(-1, readIntoBuffer()); + CALLED_FAKE(FreeRtosSocketsFake_Recv, NEVER); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadCallsFreeRtosRecvWithSocketBufferAndLength) +{ + openStream(); + readIntoBuffer(); + CALLED_FAKE(FreeRtosSocketsFake_Recv, ONCE); + POINTERS_EQUAL(FreeRtosSocketsFake_LastSocketReturned(), FreeRtosSocketsFake_LastRecvSocket()); + POINTERS_EQUAL(readBuffer, FreeRtosSocketsFake_LastRecvBuffer()); + LONGS_EQUAL(sizeof(readBuffer), FreeRtosSocketsFake_LastRecvLength()); + LONGS_EQUAL(0, FreeRtosSocketsFake_LastRecvFlags()); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsBytesOnSuccess) +{ + openStream(); + FreeRtosSocketsFake_SetRecvReturn(TEST_READ_BYTES); + LONGS_EQUAL(TEST_READ_BYTES, readIntoBuffer()); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsZeroWhenWouldBlock) +{ + openStream(); + FreeRtosSocketsFake_SetRecvReturn(0); + LONGS_EQUAL(0, readIntoBuffer()); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadLeavesSocketOpenWhenWouldBlock) +{ + openStream(); + FreeRtosSocketsFake_SetRecvReturn(0); + readIntoBuffer(); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, NEVER); +} + +TEST(SolidSyslogFreeRtosTcpStream, ReadReturnsNegativeOneOnErrorAndClosesSocket) +{ + openStream(); + FreeRtosSocketsFake_SetRecvFails(true); + LONGS_EQUAL(-1, readIntoBuffer()); + CHECK_SOCKET_CLOSED_ONCE(); +} + +TEST(SolidSyslogFreeRtosTcpStream, CloseWithoutOpenIsNoOp) +{ + SolidSyslogStream_Close(stream); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, NEVER); +} + +TEST(SolidSyslogFreeRtosTcpStream, CloseClosesOpenSocket) +{ + openStream(); + SolidSyslogStream_Close(stream); + CHECK_SOCKET_CLOSED_ONCE(); +} + +TEST(SolidSyslogFreeRtosTcpStream, CloseIsIdempotent) +{ + openStream(); + SolidSyslogStream_Close(stream); + SolidSyslogStream_Close(stream); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); +} + +TEST(SolidSyslogFreeRtosTcpStream, DestroyClosesOpenSocket) +{ + openStream(); + SolidSyslogFreeRtosTcpStream_Destroy(stream); + CHECK_SOCKET_CLOSED_ONCE(); +} + +TEST(SolidSyslogFreeRtosTcpStream, DestroyAfterCloseDoesNotCloseAgain) +{ + openStream(); + 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..4ecadde8 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,39 @@ 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); + 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); Socket_t FreeRtosSocketsFake_LastClosesocketSocket(void); diff --git a/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c b/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c index 0eba45b9..00bdccff 100644 --- a/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c +++ b/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c @@ -18,6 +18,39 @@ 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 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; @@ -45,6 +78,39 @@ 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; + lastSetsockoptSocket = NULL; + lastSetsockoptLevel = 0; + lastSetsockoptOptionName = 0; + lastSetsockoptOptionLength = 0; + sndTimeoAtConnect = 0; + closesocketCallCount = 0; lastClosesocketSocket = NULL; } @@ -59,6 +125,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 +235,180 @@ 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; +} + +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) +{ + lastSetsockoptSocket = xSocket; + lastSetsockoptLevel = lLevel; + lastSetsockoptOptionName = lOptionName; + lastSetsockoptOptionLength = 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; 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: