diff --git a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h index 10e3b8a4..835f2f4b 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h @@ -11,6 +11,9 @@ EXTERN_C_BEGIN { POSIXMESSAGEQUEUEBUFFER_ERROR_POOL_EXHAUSTED, POSIXMESSAGEQUEUEBUFFER_ERROR_UNKNOWN_DESTROY, + POSIXMESSAGEQUEUEBUFFER_ERROR_MQ_OPEN_FAILED, + POSIXMESSAGEQUEUEBUFFER_ERROR_SEND_FAILED, + POSIXMESSAGEQUEUEBUFFER_ERROR_RECEIVE_FAILED, POSIXMESSAGEQUEUEBUFFER_ERROR_MAX }; diff --git a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c index 91342f5a..51996e3a 100644 --- a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c +++ b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c @@ -1,5 +1,6 @@ #include "SolidSyslogPosixMessageQueueBuffer.h" +#include #include #include #include @@ -8,10 +9,13 @@ #include #include "SolidSyslogBufferDefinition.h" +#include "SolidSyslogError.h" #include "SolidSyslogFormatter.h" #include "SolidSyslogNullBuffer.h" +#include "SolidSyslogPosixMessageQueueBufferErrors.h" #include "SolidSyslogPosixMessageQueueBufferPrivate.h" #include "SolidSyslogPosixProcessId.h" +#include "SolidSyslogPrival.h" enum { @@ -28,7 +32,7 @@ static inline struct SolidSyslogPosixMessageQueueBuffer* PosixMessageQueueBuffer static inline const char* PosixMessageQueueBuffer_QueueName(struct SolidSyslogPosixMessageQueueBuffer* self); // NOLINTBEGIN(bugprone-easily-swappable-parameters) -- distinct semantic meaning; mirrors the public _Create signature plus a per-slot discriminator -void PosixMessageQueueBuffer_Initialise( +bool PosixMessageQueueBuffer_Initialise( struct SolidSyslogBuffer* base, size_t maxMessageSize, long maxMessages, @@ -58,6 +62,7 @@ void PosixMessageQueueBuffer_Initialise( self->MaxMessageSize = maxMessageSize; self->Base.Write = PosixMessageQueueBuffer_Write; self->Base.Read = PosixMessageQueueBuffer_Read; + return self->Mq != (mqd_t) -1; } void PosixMessageQueueBuffer_Cleanup(struct SolidSyslogBuffer* base) @@ -77,19 +82,44 @@ static inline const char* PosixMessageQueueBuffer_QueueName(struct SolidSyslogPo static bool PosixMessageQueueBuffer_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead) { - struct SolidSyslogPosixMessageQueueBuffer* self = PosixMessageQueueBuffer_SelfFromBase(base); - ssize_t received = mq_receive(self->Mq, data, maxSize, NULL); - bool success = received >= 0; - - *bytesRead = success ? (size_t) received : 0U; - + bool success = false; + if (bytesRead != NULL) + { + struct SolidSyslogPosixMessageQueueBuffer* self = PosixMessageQueueBuffer_SelfFromBase(base); + ssize_t received = mq_receive(self->Mq, data, maxSize, NULL); + success = received >= 0; + + /* Capture errno immediately after mq_receive so the EAGAIN test below + * stays a pure predicate and is decoupled from errno's lifetime + * between the errno-setting call and the read (MISRA C:2012 Rule 22.10). + * EAGAIN is the empty-queue poll signal — part of the happy path and + * must stay silent. Any other errno is a real failure worth surfacing. */ + int receiveErrno = success ? 0 : errno; + if (!success && (receiveErrno != EAGAIN)) + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &PosixMessageQueueBufferErrorSource, + (uint8_t) POSIXMESSAGEQUEUEBUFFER_ERROR_RECEIVE_FAILED + ); + } + + *bytesRead = success ? (size_t) received : 0U; + } return success; } static void PosixMessageQueueBuffer_Write(struct SolidSyslogBuffer* base, const void* data, size_t size) { struct SolidSyslogPosixMessageQueueBuffer* self = PosixMessageQueueBuffer_SelfFromBase(base); - mq_send(self->Mq, data, size, 0); + if (mq_send(self->Mq, data, size, 0) != 0) + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &PosixMessageQueueBufferErrorSource, + (uint8_t) POSIXMESSAGEQUEUEBUFFER_ERROR_SEND_FAILED + ); + } } static inline struct SolidSyslogPosixMessageQueueBuffer* PosixMessageQueueBuffer_SelfFromBase( diff --git a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferMessages.c b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferMessages.c index 83c136af..437beddb 100644 --- a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferMessages.c +++ b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferMessages.c @@ -10,6 +10,11 @@ static const char* PosixMessageQueueBufferError_AsString(uint8_t code) "SolidSyslogPosixMessageQueueBuffer_Create pool exhausted; returning fallback buffer", [POSIXMESSAGEQUEUEBUFFER_ERROR_UNKNOWN_DESTROY] = "SolidSyslogPosixMessageQueueBuffer_Destroy called with a handle not issued by this pool", + [POSIXMESSAGEQUEUEBUFFER_ERROR_MQ_OPEN_FAILED] = + "SolidSyslogPosixMessageQueueBuffer_Create mq_open failed; returning fallback buffer", + [POSIXMESSAGEQUEUEBUFFER_ERROR_SEND_FAILED] = + "SolidSyslogPosixMessageQueueBuffer_Write mq_send failed; record dropped", + [POSIXMESSAGEQUEUEBUFFER_ERROR_RECEIVE_FAILED] = "SolidSyslogPosixMessageQueueBuffer_Read mq_receive failed", }; const char* result = "unknown"; if (code < (uint8_t) POSIXMESSAGEQUEUEBUFFER_ERROR_MAX) diff --git a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferPrivate.h b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferPrivate.h index 43a2272d..f1be91bd 100644 --- a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferPrivate.h +++ b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferPrivate.h @@ -2,6 +2,7 @@ #define SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFERPRIVATE_H #include +#include #include #include "SolidSyslogBufferDefinition.h" @@ -21,7 +22,7 @@ struct SolidSyslogPosixMessageQueueBuffer size_t MaxMessageSize; }; -void PosixMessageQueueBuffer_Initialise( +bool PosixMessageQueueBuffer_Initialise( struct SolidSyslogBuffer* base, size_t maxMessageSize, long maxMessages, diff --git a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferStatic.c b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferStatic.c index 4a87d6fe..31b249c9 100644 --- a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferStatic.c +++ b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferStatic.c @@ -37,13 +37,30 @@ struct SolidSyslogBuffer* SolidSyslogPosixMessageQueueBuffer_Create(size_t maxMe struct SolidSyslogBuffer* handle = SolidSyslogNullBuffer_Get(); if (SolidSyslogPoolAllocator_IndexIsValid(&PosixMessageQueueBuffer_Allocator, index) == true) { - PosixMessageQueueBuffer_Initialise( + bool opened = PosixMessageQueueBuffer_Initialise( &PosixMessageQueueBuffer_Pool[index].Base, maxMessageSize, maxMessages, index ); - handle = &PosixMessageQueueBuffer_Pool[index].Base; + if (opened) + { + handle = &PosixMessageQueueBuffer_Pool[index].Base; + } + else + { + (void) SolidSyslogPoolAllocator_FreeIfInUse( + &PosixMessageQueueBuffer_Allocator, + index, + PosixMessageQueueBuffer_CleanupAtIndex, + NULL + ); + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &PosixMessageQueueBufferErrorSource, + (uint8_t) POSIXMESSAGEQUEUEBUFFER_ERROR_MQ_OPEN_FAILED + ); + } } else { diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index bb4a720f..ae547a6d 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -122,6 +122,7 @@ if(SOLIDSYSLOG_POSIX) SolidSyslogGetAddrInfoResolverTest.cpp SolidSyslogPosixDatagramTest.cpp SolidSyslogPosixTcpStreamTest.cpp + MqFakeTest.cpp SocketFakeTest.cpp ) endif() diff --git a/Tests/MqFakeTest.cpp b/Tests/MqFakeTest.cpp new file mode 100644 index 00000000..22ddc94e --- /dev/null +++ b/Tests/MqFakeTest.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include +#include + +#include "MqFake.h" +#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 + +static mqd_t OpenTestQueue(const char* name, long maxMessages = 4, size_t maxMessageSize = 64) +{ + struct mq_attr attr = {0, maxMessages, static_cast(maxMessageSize), 0, {0}}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) -- POSIX mq_open is a varargs function when O_CREAT is set + return mq_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, &attr); +} + +// clang-format off +TEST_GROUP(MqFake) +{ + void setup() override { MqFake_Reset(); } +}; +// clang-format on + +TEST(MqFake, OpenIncrementsCount) +{ + OpenTestQueue("/test"); + CALLED_FAKE(MqFake_Open, ONCE); +} + +TEST(MqFake, OpenCapturesName) +{ + OpenTestQueue("/captured"); + STRCMP_EQUAL("/captured", MqFake_LastOpenName()); +} + +TEST(MqFake, OpenCapturesAttrFields) +{ + OpenTestQueue("/test", 7, 128); + LONGS_EQUAL(7, MqFake_LastOpenMaxMessages()); + LONGS_EQUAL(128, MqFake_LastOpenMaxMessageSize()); +} + +TEST(MqFake, OpenReturnsDistinctMqdPerCall) +{ + mqd_t first = OpenTestQueue("/first"); + mqd_t second = OpenTestQueue("/second"); + CHECK(first != second); + CHECK(first != (mqd_t) -1); + CHECK(second != (mqd_t) -1); +} + +TEST(MqFake, FailNextOpenReturnsMinusOneAndSetsErrno) +{ + MqFake_FailNextOpen(EINVAL); + mqd_t result = OpenTestQueue("/test"); + LONGS_EQUAL(-1, (long) result); + LONGS_EQUAL(EINVAL, errno); +} + +TEST(MqFake, FailNextOpenIsOneShot) +{ + MqFake_FailNextOpen(EINVAL); + (void) OpenTestQueue("/first"); + mqd_t second = OpenTestQueue("/second"); + CHECK(second != (mqd_t) -1); +} + +TEST(MqFake, OpenNameHistoryReturnsNameByIndex) +{ + OpenTestQueue("/alpha"); + OpenTestQueue("/beta"); + STRCMP_EQUAL("/alpha", MqFake_OpenNameAt(0)); + STRCMP_EQUAL("/beta", MqFake_OpenNameAt(1)); +} + +TEST(MqFake, SendStoresMessageForReceive) +{ + mqd_t mqd = OpenTestQueue("/test"); + LONGS_EQUAL(0, mq_send(mqd, "payload", 7, 0)); + + char buf[16] = {}; + ssize_t received = mq_receive(mqd, buf, sizeof(buf), nullptr); + LONGS_EQUAL(7, received); + STRCMP_EQUAL("payload", buf); +} + +TEST(MqFake, ReceiveFromEmptyQueueReturnsMinusOneEagain) +{ + mqd_t mqd = OpenTestQueue("/test"); + + char buf[16] = {}; + ssize_t result = mq_receive(mqd, buf, sizeof(buf), nullptr); + LONGS_EQUAL(-1, result); + LONGS_EQUAL(EAGAIN, errno); +} + +TEST(MqFake, FailNextSendReturnsMinusOneAndSetsErrno) +{ + mqd_t mqd = OpenTestQueue("/test"); + MqFake_FailNextSend(EMSGSIZE); + LONGS_EQUAL(-1, mq_send(mqd, "x", 1, 0)); + LONGS_EQUAL(EMSGSIZE, errno); +} + +/* Natural overflow — sending past mq_attr.mq_maxmsg must surface as + * -1/EAGAIN per POSIX, not silently succeed. */ +TEST(MqFake, SendBeyondMaxMessagesReturnsMinusOneEagain) +{ + mqd_t mqd = OpenTestQueue("/test", /*maxMessages=*/2, /*maxMessageSize=*/64); + LONGS_EQUAL(0, mq_send(mqd, "a", 1, 0)); + LONGS_EQUAL(0, mq_send(mqd, "b", 1, 0)); + + LONGS_EQUAL(-1, mq_send(mqd, "c", 1, 0)); + LONGS_EQUAL(EAGAIN, errno); +} + +/* Natural oversize — POSIX requires -1/EMSGSIZE, not truncation. */ +TEST(MqFake, SendLargerThanMaxMessageSizeReturnsMinusOneEmsgsize) +{ + mqd_t mqd = OpenTestQueue("/test", /*maxMessages=*/4, /*maxMessageSize=*/4); + LONGS_EQUAL(-1, mq_send(mqd, "fivefive", 8, 0)); + LONGS_EQUAL(EMSGSIZE, errno); +} + +TEST(MqFake, FailNextReceiveReturnsMinusOneAndSetsErrno) +{ + mqd_t mqd = OpenTestQueue("/test"); + mq_send(mqd, "x", 1, 0); + MqFake_FailNextReceive(EBADMSG); + char buf[16] = {}; + LONGS_EQUAL(-1, mq_receive(mqd, buf, sizeof(buf), nullptr)); + LONGS_EQUAL(EBADMSG, errno); +} + +TEST(MqFake, CloseIncrementsCount) +{ + mqd_t mqd = OpenTestQueue("/test"); + mq_close(mqd); + CALLED_FAKE(MqFake_Close, ONCE); + LONGS_EQUAL((long) mqd, (long) MqFake_LastClosedMqd()); +} + +TEST(MqFake, UnlinkIncrementsCountAndCapturesName) +{ + mq_unlink("/gone"); + CALLED_FAKE(MqFake_Unlink, ONCE); + STRCMP_EQUAL("/gone", MqFake_LastUnlinkName()); +} + +TEST(MqFake, IsolatedQueuesDoNotShareMessages) +{ + mqd_t alpha = OpenTestQueue("/alpha"); + mqd_t beta = OpenTestQueue("/beta"); + + mq_send(alpha, "from-alpha", 10, 0); + + char buf[16] = {}; + ssize_t result = mq_receive(beta, buf, sizeof(buf), nullptr); + LONGS_EQUAL(-1, result); + LONGS_EQUAL(EAGAIN, errno); +} diff --git a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp index d254ef79..32bf0a86 100644 --- a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp +++ b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp @@ -1,3 +1,4 @@ +#include #include #include "TestUtils.h" @@ -7,6 +8,7 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-f // macros #include "ConfigLockFake.h" #include "ErrorHandlerFake.h" +#include "MqFake.h" #include "SolidSyslogBuffer.h" #include "SolidSyslogBufferDefinition.h" #include "SolidSyslogPosixMessageQueueBuffer.h" @@ -33,6 +35,7 @@ TEST_GROUP(SolidSyslogPosixMessageQueueBuffer) void setup() override { + MqFake_Reset(); // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros buffer = SolidSyslogPosixMessageQueueBuffer_Create(SOLIDSYSLOG_MAX_MESSAGE_SIZE, 10); readSize = 0; @@ -102,6 +105,83 @@ TEST(SolidSyslogPosixMessageQueueBuffer, SecondReadAfterSingleWriteReturnsFalse) CHECK_FALSE(Read()); } +TEST(SolidSyslogPosixMessageQueueBuffer, WriteWhenMqSendFailsReportsError) +{ + ErrorHandlerFake_Install(nullptr); + MqFake_FailNextSend(EAGAIN); + + Write(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + POINTERS_EQUAL(&PosixMessageQueueBufferErrorSource, ErrorHandlerFake_LastSource()); + UNSIGNED_LONGS_EQUAL(POSIXMESSAGEQUEUEBUFFER_ERROR_SEND_FAILED, ErrorHandlerFake_LastCode()); +} + +/* Regression guard: the production check is errno-agnostic + * (`if (mq_send(...) != 0)`), so EAGAIN and EMSGSIZE flow through + * the same branch today. A future contributor who adds errno-specific + * handling has to deliberately drop one of these cases. */ +TEST(SolidSyslogPosixMessageQueueBuffer, WriteWithOversizedMessageReportsError) +{ + ErrorHandlerFake_Install(nullptr); + MqFake_FailNextSend(EMSGSIZE); + + Write(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + UNSIGNED_LONGS_EQUAL(POSIXMESSAGEQUEUEBUFFER_ERROR_SEND_FAILED, ErrorHandlerFake_LastCode()); +} + +TEST(SolidSyslogPosixMessageQueueBuffer, ReadWhenMqReceiveFailsReportsError) +{ + ErrorHandlerFake_Install(nullptr); + MqFake_FailNextReceive(EMSGSIZE); + + Read(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + POINTERS_EQUAL(&PosixMessageQueueBufferErrorSource, ErrorHandlerFake_LastSource()); + UNSIGNED_LONGS_EQUAL(POSIXMESSAGEQUEUEBUFFER_ERROR_RECEIVE_FAILED, ErrorHandlerFake_LastCode()); +} + +/* Regression guard: empty queue (EAGAIN) is part of the happy poll loop + * and must NOT emit. The error handler must remain untouched. */ +TEST(SolidSyslogPosixMessageQueueBuffer, ReadFromEmptyQueueDoesNotEmitError) +{ + ErrorHandlerFake_Install(nullptr); + + CHECK_FALSE(Read()); + + CALLED_FAKE(ErrorHandlerFake_Handle, NEVER); +} + +/* A NULL bytesRead* would crash on `*bytesRead = 0`. Guard at the + * Read entry — invalid caller usage, not a runtime failure, so no + * error code is emitted; just a defensive false return. */ +TEST(SolidSyslogPosixMessageQueueBuffer, ReadWithNullBytesReadDoesNotCrash) +{ + char data[16] = {}; + CHECK_FALSE(SolidSyslogBuffer_Read(buffer, data, sizeof(data), nullptr)); +} + +/* After Destroy the slot's abstract-base vtable is the shared NullBuffer's, so + * Write/Read through the stale handle is a safe no-op rather than a NULL-fn-pointer + * crash. NullBuffer.Write swallows; NullBuffer.Read returns false with bytesRead=0. */ +TEST(SolidSyslogPosixMessageQueueBuffer, UseAfterDestroyIsCrashSafeViaNullBufferVtable) +{ + SolidSyslogPosixMessageQueueBuffer_Destroy(buffer); + + SolidSyslogBuffer_Write(buffer, "x", 1); + char data[16] = {}; + size_t bytesRead = 99; + CHECK_FALSE(SolidSyslogBuffer_Read(buffer, data, sizeof(data), &bytesRead)); + LONGS_EQUAL(0, bytesRead); + + buffer = SolidSyslogPosixMessageQueueBuffer_Create(SOLIDSYSLOG_MAX_MESSAGE_SIZE, 10); // for teardown +} + TEST(SolidSyslogPosixMessageQueueBuffer, ServiceSendsMessageWrittenViaLog) { struct SolidSyslogSender* fakeSender = SenderFake_Create(); @@ -118,24 +198,6 @@ TEST(SolidSyslogPosixMessageQueueBuffer, ServiceSendsMessageWrittenViaLog) SenderFake_Destroy(fakeSender); } -IGNORE_TEST(SolidSyslogPosixMessageQueueBuffer, HappyPathOnly) - -{ - // Error handling not yet implemented — see Epic #31 - // Create with zero maxMessageSize or maxMessages - // Create when mq_open fails returns NULL - // Write with NULL buffer does not crash - // Write with NULL data does not crash - // Read with NULL buffer does not crash - // Read with NULL data does not crash - // Read with NULL bytesRead does not crash - // Destroy with NULL buffer does not crash - // Write when queue is full (back-pressure / overflow) - // - // Blocking mode not yet implemented — see S4.5 or later - // Read blocks waiting for a message (O_NONBLOCK removed) -} - // NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) #define CHECK_IS_FALLBACK(handle, pool) \ do \ @@ -157,6 +219,11 @@ TEST_GROUP(SolidSyslogPosixMessageQueueBufferPool) struct SolidSyslogBuffer* pooled[SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE] = {}; struct SolidSyslogBuffer* overflow = nullptr; + void setup() override + { + MqFake_Reset(); + } + void teardown() override { for (auto* handle : pooled) @@ -212,6 +279,36 @@ TEST(SolidSyslogPosixMessageQueueBufferPool, ExhaustedCreateReportsError) UNSIGNED_LONGS_EQUAL(POSIXMESSAGEQUEUEBUFFER_ERROR_POOL_EXHAUSTED, ErrorHandlerFake_LastCode()); } +TEST(SolidSyslogPosixMessageQueueBufferPool, CreateOnMqOpenFailureReportsError) +{ + ErrorHandlerFake_Install(nullptr); + MqFake_FailNextOpen(EINVAL); + + overflow = MakeBuffer(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + POINTERS_EQUAL(&PosixMessageQueueBufferErrorSource, ErrorHandlerFake_LastSource()); + UNSIGNED_LONGS_EQUAL(POSIXMESSAGEQUEUEBUFFER_ERROR_MQ_OPEN_FAILED, ErrorHandlerFake_LastCode()); +} + +TEST(SolidSyslogPosixMessageQueueBufferPool, CreateOnMqOpenFailureReleasesSlot) +{ + MqFake_FailNextOpen(EINVAL); + + overflow = MakeBuffer(); + + // Fill the pool *after* the failed Create; if the failed Create had leaked + // its acquired slot, the pool would overflow into the fallback one slot + // sooner — and FillPool's last MakeBuffer would return the same NullBuffer + // singleton as `overflow`, since both Creates would have run out of slots. + FillPool(); + for (auto* slot : pooled) + { + CHECK_TEXT(slot != overflow, "Pool slot collided with the failed-Create fallback handle"); + } +} + TEST(SolidSyslogPosixMessageQueueBufferPool, FallbackReadAndWriteAreNoOps) { FillPool(); @@ -282,6 +379,22 @@ TEST(SolidSyslogPosixMessageQueueBufferPool, DestroyOfUnknownHandleReportsWarnin UNSIGNED_LONGS_EQUAL(POSIXMESSAGEQUEUEBUFFER_ERROR_UNKNOWN_DESTROY, ErrorHandlerFake_LastCode()); } +/* Destroy(NULL) is reachable from any integrator who keeps a NullBuffer-fallback + * handle and later releases it. The IndexFromHandle search returns POOL_SIZE + * (no slot matches NULL), IndexIsValid returns false, so the FreeIfInUse branch + * is skipped — caller sees an UNKNOWN_DESTROY warning, no crash. */ +TEST(SolidSyslogPosixMessageQueueBufferPool, DestroyOfNullHandleReportsWarningWithoutCrashing) +{ + ErrorHandlerFake_Install(nullptr); + + SolidSyslogPosixMessageQueueBuffer_Destroy(nullptr); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + POINTERS_EQUAL(&PosixMessageQueueBufferErrorSource, ErrorHandlerFake_LastSource()); + UNSIGNED_LONGS_EQUAL(POSIXMESSAGEQUEUEBUFFER_ERROR_UNKNOWN_DESTROY, ErrorHandlerFake_LastCode()); +} + TEST(SolidSyslogPosixMessageQueueBufferPool, DestroyOfStaleHandleReportsWarning) { pooled[0] = MakeBuffer(); diff --git a/Tests/Support/CMakeLists.txt b/Tests/Support/CMakeLists.txt index 85925c9e..8d8ff36f 100644 --- a/Tests/Support/CMakeLists.txt +++ b/Tests/Support/CMakeLists.txt @@ -9,6 +9,7 @@ target_link_libraries(ConfigLockFake PUBLIC ${PROJECT_NAME}) if(SOLIDSYSLOG_POSIX) add_library(PosixFakes STATIC ClockFake.c + MqFake.c SafeStringStandard.c SocketFake.c ) diff --git a/Tests/Support/MqFake.c b/Tests/Support/MqFake.c new file mode 100644 index 00000000..42816953 --- /dev/null +++ b/Tests/Support/MqFake.c @@ -0,0 +1,436 @@ +#include "MqFake.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SafeString.h" +#include "SolidSyslogTunables.h" + +enum +{ + MQFAKE_MAX_QUEUES = 4, + /* Storage cap for the in-memory ring per queue. Per-call enforcement + * uses the caller's `mq_attr.mq_maxmsg` instead, so this value just + * needs to be at least as large as the largest `maxMessages` any test + * passes to `mq_open`. Bumping it is cheap (BSS only). */ + MQFAKE_MAX_MESSAGES_PER_QUEUE = 16, + MQFAKE_MAX_OPEN_HISTORY = 8, + MQFAKE_MAX_NAME_LEN = 64, + MQFAKE_HANDLE_BASE = 100, + MQFAKE_SEND_BUF_SIZE = SOLIDSYSLOG_MAX_MESSAGE_SIZE + 1U +}; + +struct MqFakeQueue +{ + long maxMessages; + size_t maxMessageSize; + size_t messageLens[MQFAKE_MAX_MESSAGES_PER_QUEUE]; + int head; + int tail; + int count; + bool inUse; + char messages[MQFAKE_MAX_MESSAGES_PER_QUEUE][SOLIDSYSLOG_MAX_MESSAGE_SIZE]; +}; + +static struct MqFakeQueue queues[MQFAKE_MAX_QUEUES]; + +static bool nextOpenShouldFail; +static int nextOpenErrno; +static int openCallCount; +static char openNameHistory[MQFAKE_MAX_OPEN_HISTORY][MQFAKE_MAX_NAME_LEN]; +static char lastOpenName[MQFAKE_MAX_NAME_LEN]; +static int lastOpenOflag; +static long lastOpenMaxMessages; +static size_t lastOpenMaxMessageSize; + +static bool nextSendShouldFail; +static int nextSendErrno; +static int sendCallCount; +static mqd_t lastSendMqd; +static char lastSendBufCopy[MQFAKE_SEND_BUF_SIZE]; +static size_t lastSendLen; + +static bool nextReceiveShouldFail; +static int nextReceiveErrno; +static int receiveCallCount; +static mqd_t lastReceiveMqd; +static size_t lastReceiveMaxLen; + +static int closeCallCount; +static mqd_t lastClosedMqd; + +static int unlinkCallCount; +static char lastUnlinkName[MQFAKE_MAX_NAME_LEN]; + +void MqFake_Reset(void) +{ + /* Reset errno so a stale value from a prior test cannot leak. */ + errno = 0; + + for (size_t i = 0; i < (size_t) MQFAKE_MAX_QUEUES; i++) + { + queues[i].inUse = false; + queues[i].maxMessages = 0; + queues[i].maxMessageSize = 0; + queues[i].head = 0; + queues[i].tail = 0; + queues[i].count = 0; + for (size_t m = 0; m < (size_t) MQFAKE_MAX_MESSAGES_PER_QUEUE; m++) + { + queues[i].messageLens[m] = 0; + } + } + + nextOpenShouldFail = false; + nextOpenErrno = 0; + openCallCount = 0; + for (size_t i = 0; i < (size_t) MQFAKE_MAX_OPEN_HISTORY; i++) + { + openNameHistory[i][0] = '\0'; + } + lastOpenName[0] = '\0'; + lastOpenOflag = 0; + lastOpenMaxMessages = 0; + lastOpenMaxMessageSize = 0; + + nextSendShouldFail = false; + nextSendErrno = 0; + sendCallCount = 0; + lastSendMqd = (mqd_t) -1; + lastSendBufCopy[0] = '\0'; + lastSendLen = 0; + + nextReceiveShouldFail = false; + nextReceiveErrno = 0; + receiveCallCount = 0; + lastReceiveMqd = (mqd_t) -1; + lastReceiveMaxLen = 0; + + closeCallCount = 0; + lastClosedMqd = (mqd_t) -1; + + unlinkCallCount = 0; + lastUnlinkName[0] = '\0'; +} + +void MqFake_FailNextOpen(int errnoValue) +{ + nextOpenShouldFail = true; + nextOpenErrno = errnoValue; +} + +int MqFake_OpenCallCount(void) +{ + return openCallCount; +} + +const char* MqFake_LastOpenName(void) +{ + return lastOpenName; +} + +int MqFake_LastOpenOflag(void) +{ + return lastOpenOflag; +} + +long MqFake_LastOpenMaxMessages(void) +{ + return lastOpenMaxMessages; +} + +size_t MqFake_LastOpenMaxMessageSize(void) +{ + return lastOpenMaxMessageSize; +} + +const char* MqFake_OpenNameAt(int callIndex) +{ + const char* result = ""; + if ((callIndex >= 0) && (callIndex < MQFAKE_MAX_OPEN_HISTORY)) + { + result = openNameHistory[callIndex]; + } + return result; +} + +void MqFake_FailNextSend(int errnoValue) +{ + nextSendShouldFail = true; + nextSendErrno = errnoValue; +} + +int MqFake_SendCallCount(void) +{ + return sendCallCount; +} + +mqd_t MqFake_LastSendMqd(void) +{ + return lastSendMqd; +} + +const char* MqFake_LastSendBufAsString(void) +{ + return lastSendBufCopy; +} + +size_t MqFake_LastSendLen(void) +{ + return lastSendLen; +} + +void MqFake_FailNextReceive(int errnoValue) +{ + nextReceiveShouldFail = true; + nextReceiveErrno = errnoValue; +} + +int MqFake_ReceiveCallCount(void) +{ + return receiveCallCount; +} + +mqd_t MqFake_LastReceiveMqd(void) +{ + return lastReceiveMqd; +} + +size_t MqFake_LastReceiveMaxLen(void) +{ + return lastReceiveMaxLen; +} + +int MqFake_CloseCallCount(void) +{ + return closeCallCount; +} + +mqd_t MqFake_LastClosedMqd(void) +{ + return lastClosedMqd; +} + +int MqFake_UnlinkCallCount(void) +{ + return unlinkCallCount; +} + +const char* MqFake_LastUnlinkName(void) +{ + return lastUnlinkName; +} + +static int MqFake_AllocateQueue(void) +{ + int result = -1; + for (int i = 0; i < MQFAKE_MAX_QUEUES; i++) + { + if (!queues[i].inUse) + { + queues[i].inUse = true; + queues[i].head = 0; + queues[i].tail = 0; + queues[i].count = 0; + result = i; + break; + } + } + return result; +} + +static struct MqFakeQueue* MqFake_QueueFromMqd(mqd_t mqd) +{ + struct MqFakeQueue* result = NULL; + int index = (int) mqd - MQFAKE_HANDLE_BASE; + if ((index >= 0) && (index < MQFAKE_MAX_QUEUES) && queues[index].inUse) + { + result = &queues[index]; + } + return result; +} + +/* NOLINTNEXTLINE(cert-dcl50-cpp,readability-inconsistent-declaration-parameter-name) -- POSIX API; varargs and parameter names differ from glibc internal names */ +mqd_t mq_open(const char* name, int oflag, ...) +{ + mqd_t result = (mqd_t) -1; + + if (nextOpenShouldFail) + { + nextOpenShouldFail = false; + errno = nextOpenErrno; + } + else + { + long requestedMaxMessages = 0; + size_t requestedMaxMessageSize = 0; + if ((oflag & O_CREAT) != 0) + { + va_list ap; + va_start(ap, oflag); + (void) va_arg(ap, mode_t); /* mode — unused by fake */ + const struct mq_attr* attr = va_arg(ap, struct mq_attr*); + va_end(ap); + if (attr != NULL) + { + requestedMaxMessages = attr->mq_maxmsg; + requestedMaxMessageSize = (size_t) attr->mq_msgsize; + } + } + + int index = MqFake_AllocateQueue(); + if (index >= 0) + { + queues[index].maxMessages = requestedMaxMessages; + queues[index].maxMessageSize = requestedMaxMessageSize; + result = (mqd_t) (index + MQFAKE_HANDLE_BASE); + } + else + { + errno = EMFILE; + } + + lastOpenOflag = oflag; + lastOpenMaxMessages = requestedMaxMessages; + lastOpenMaxMessageSize = requestedMaxMessageSize; + } + + SafeString_Copy(lastOpenName, sizeof(lastOpenName), name); + if (openCallCount < MQFAKE_MAX_OPEN_HISTORY) + { + SafeString_Copy(openNameHistory[openCallCount], sizeof(openNameHistory[openCallCount]), name); + } + openCallCount++; + return result; +} + +// NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name) -- POSIX API; parameter names differ from glibc internal names +int mq_close(mqd_t mqd) +{ + closeCallCount++; + lastClosedMqd = mqd; + struct MqFakeQueue* queue = MqFake_QueueFromMqd(mqd); + if (queue != NULL) + { + queue->inUse = false; + } + return 0; +} + +int mq_unlink(const char* name) +{ + unlinkCallCount++; + SafeString_Copy(lastUnlinkName, sizeof(lastUnlinkName), name); + return 0; +} + +// NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name,bugprone-easily-swappable-parameters) -- POSIX API; parameter names and signature differ from glibc internal names +int mq_send(mqd_t mqd, const char* msg, size_t msgLen, unsigned int msgPrio) +{ + (void) msgPrio; + + sendCallCount++; + lastSendMqd = mqd; + lastSendLen = msgLen; + size_t copyLen = (msgLen < (sizeof(lastSendBufCopy) - 1U)) ? msgLen : (sizeof(lastSendBufCopy) - 1U); + if (copyLen > 0U) + { + memcpy(lastSendBufCopy, msg, copyLen); + } + lastSendBufCopy[copyLen] = '\0'; + + int result = -1; + if (nextSendShouldFail) + { + nextSendShouldFail = false; + errno = nextSendErrno; + } + else + { + struct MqFakeQueue* queue = MqFake_QueueFromMqd(mqd); + if (queue == NULL) + { + errno = EBADF; + } + else if (msgLen > queue->maxMessageSize) + { + /* POSIX: mq_send must return -1/EMSGSIZE when msgLen exceeds the + * queue's per-message size. Truncating instead would mask real-kernel + * behaviour from tests. */ + errno = EMSGSIZE; + } + else if (queue->count >= (int) queue->maxMessages) + { + /* POSIX: with O_NONBLOCK and the queue at capacity, mq_send must + * return -1/EAGAIN. Use the per-queue limit captured at mq_open, + * not the fake's storage cap. */ + errno = EAGAIN; + } + else + { + if (msgLen > 0U) + { + memcpy(queue->messages[queue->tail], msg, msgLen); + } + queue->messageLens[queue->tail] = msgLen; + queue->tail = (queue->tail + 1) % MQFAKE_MAX_MESSAGES_PER_QUEUE; + queue->count++; + result = 0; + } + } + return result; +} + +// NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name,bugprone-easily-swappable-parameters,readability-non-const-parameter) -- POSIX API; parameter names and signature differ from glibc internal names +ssize_t mq_receive(mqd_t mqd, char* msg, size_t msgLen, unsigned int* msgPrio) +{ + (void) msgPrio; + + receiveCallCount++; + lastReceiveMqd = mqd; + lastReceiveMaxLen = msgLen; + + ssize_t result = -1; + if (nextReceiveShouldFail) + { + nextReceiveShouldFail = false; + errno = nextReceiveErrno; + } + else + { + struct MqFakeQueue* queue = MqFake_QueueFromMqd(mqd); + if (queue == NULL) + { + errno = EBADF; + } + else if (queue->count == 0) + { + errno = EAGAIN; + } + else + { + size_t pending = queue->messageLens[queue->head]; + if (pending > msgLen) + { + errno = EMSGSIZE; + } + else + { + if (pending > 0U) + { + memcpy(msg, queues[(int) mqd - MQFAKE_HANDLE_BASE].messages[queue->head], pending); + } + queue->head = (queue->head + 1) % MQFAKE_MAX_MESSAGES_PER_QUEUE; + queue->count--; + result = (ssize_t) pending; + } + } + } + return result; +} diff --git a/Tests/Support/MqFake.h b/Tests/Support/MqFake.h new file mode 100644 index 00000000..2aa56ac5 --- /dev/null +++ b/Tests/Support/MqFake.h @@ -0,0 +1,51 @@ +#ifndef MQFAKE_H +#define MQFAKE_H + +#include +#include + +#include "ExternC.h" + +EXTERN_C_BEGIN + + void MqFake_Reset(void); + + /* open configuration — one-shot, consumed by the next mq_open call */ + void MqFake_FailNextOpen(int errnoValue); + + /* open accessors */ + int MqFake_OpenCallCount(void); + const char* MqFake_LastOpenName(void); + int MqFake_LastOpenOflag(void); + long MqFake_LastOpenMaxMessages(void); + size_t MqFake_LastOpenMaxMessageSize(void); + const char* MqFake_OpenNameAt(int callIndex); + + /* send configuration — one-shot, consumed by the next mq_send call */ + void MqFake_FailNextSend(int errnoValue); + + /* send accessors */ + int MqFake_SendCallCount(void); + mqd_t MqFake_LastSendMqd(void); + const char* MqFake_LastSendBufAsString(void); + size_t MqFake_LastSendLen(void); + + /* receive configuration — one-shot, consumed by the next mq_receive call */ + void MqFake_FailNextReceive(int errnoValue); + + /* receive accessors */ + int MqFake_ReceiveCallCount(void); + mqd_t MqFake_LastReceiveMqd(void); + size_t MqFake_LastReceiveMaxLen(void); + + /* close accessors */ + int MqFake_CloseCallCount(void); + mqd_t MqFake_LastClosedMqd(void); + + /* unlink accessors */ + int MqFake_UnlinkCallCount(void); + const char* MqFake_LastUnlinkName(void); + +EXTERN_C_END + +#endif /* MQFAKE_H */ diff --git a/misra_suppressions.txt b/misra_suppressions.txt index 09225d8d..d0bd263f 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -53,7 +53,7 @@ misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddressStatic.c:34 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddressStatic.c:54 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixDatagram.c:55 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixFile.c:66 -misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c:99 +misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c:129 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixMutex.c:47 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixTcpStream.c:75 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockAddress.c:9 @@ -148,8 +148,8 @@ misra-c2012-5.7:Core/Source/SolidSyslogCrc16Policy.c:10 misra-c2012-5.7:Core/Source/SolidSyslogOriginSdPrivate.h:9 misra-c2012-5.7:Platform/FreeRtos/Source/SolidSyslogFreeRtosSysUpTime.c:7 misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixHostname.c:10 -misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c:17 -misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferPrivate.h:11 +misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c:21 +misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferPrivate.h:12 misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixSysUpTime.c:6 misra-c2012-5.7:Platform/Windows/Source/SolidSyslogWindowsClock.c:21 misra-c2012-5.7:Platform/Windows/Source/SolidSyslogWindowsHostname.c:19