diff --git a/Core/Interface/SolidSyslogPassthroughBufferErrors.h b/Core/Interface/SolidSyslogPassthroughBufferErrors.h index dbffac52..0a9b1293 100644 --- a/Core/Interface/SolidSyslogPassthroughBufferErrors.h +++ b/Core/Interface/SolidSyslogPassthroughBufferErrors.h @@ -11,6 +11,7 @@ EXTERN_C_BEGIN { PASSTHROUGHBUFFER_ERROR_POOL_EXHAUSTED, PASSTHROUGHBUFFER_ERROR_UNKNOWN_DESTROY, + PASSTHROUGHBUFFER_ERROR_NULL_SENDER, PASSTHROUGHBUFFER_ERROR_MAX }; diff --git a/Core/Source/SolidSyslogPassthroughBufferMessages.c b/Core/Source/SolidSyslogPassthroughBufferMessages.c index fd8b7908..8fdd9ea7 100644 --- a/Core/Source/SolidSyslogPassthroughBufferMessages.c +++ b/Core/Source/SolidSyslogPassthroughBufferMessages.c @@ -10,6 +10,8 @@ static const char* PassthroughBufferError_AsString(uint8_t code) "SolidSyslogPassthroughBuffer_Create pool exhausted; returning fallback buffer", [PASSTHROUGHBUFFER_ERROR_UNKNOWN_DESTROY] = "SolidSyslogPassthroughBuffer_Destroy called with a handle not issued by this pool", + [PASSTHROUGHBUFFER_ERROR_NULL_SENDER] = + "SolidSyslogPassthroughBuffer_Create called with NULL sender; returning fallback buffer", }; const char* result = "unknown"; if (code < (uint8_t) PASSTHROUGHBUFFER_ERROR_MAX) diff --git a/Core/Source/SolidSyslogPassthroughBufferStatic.c b/Core/Source/SolidSyslogPassthroughBufferStatic.c index 24905405..0c9e5291 100644 --- a/Core/Source/SolidSyslogPassthroughBufferStatic.c +++ b/Core/Source/SolidSyslogPassthroughBufferStatic.c @@ -27,21 +27,32 @@ static struct SolidSyslogPoolAllocator PassthroughBuffer_Allocator = { struct SolidSyslogBuffer* SolidSyslogPassthroughBuffer_Create(struct SolidSyslogSender* sender) { - size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&PassthroughBuffer_Allocator); struct SolidSyslogBuffer* handle = SolidSyslogNullBuffer_Get(); - if (SolidSyslogPoolAllocator_IndexIsValid(&PassthroughBuffer_Allocator, index)) - { - PassthroughBuffer_Initialise(&PassthroughBuffer_Pool[index].Base, sender); - handle = &PassthroughBuffer_Pool[index].Base; - } - else + if (sender == NULL) { SolidSyslog_Error( SOLIDSYSLOG_SEVERITY_ERROR, &PassthroughBufferErrorSource, - (uint8_t) PASSTHROUGHBUFFER_ERROR_POOL_EXHAUSTED + (uint8_t) PASSTHROUGHBUFFER_ERROR_NULL_SENDER ); } + else + { + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&PassthroughBuffer_Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&PassthroughBuffer_Allocator, index)) + { + PassthroughBuffer_Initialise(&PassthroughBuffer_Pool[index].Base, sender); + handle = &PassthroughBuffer_Pool[index].Base; + } + else + { + SolidSyslog_Error( + SOLIDSYSLOG_SEVERITY_ERROR, + &PassthroughBufferErrorSource, + (uint8_t) PASSTHROUGHBUFFER_ERROR_POOL_EXHAUSTED + ); + } + } return handle; } diff --git a/Tests/SolidSyslogPassthroughBufferTest.cpp b/Tests/SolidSyslogPassthroughBufferTest.cpp index 7f6f896a..45ad9683 100644 --- a/Tests/SolidSyslogPassthroughBufferTest.cpp +++ b/Tests/SolidSyslogPassthroughBufferTest.cpp @@ -5,8 +5,11 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings NEVER/ONCE/TWICE/THRICE into scope for the CALLED_* // macros +#include "ErrorHandlerFake.h" #include "SolidSyslogBuffer.h" #include "SolidSyslogPassthroughBuffer.h" +#include "SolidSyslogPassthroughBufferErrors.h" +#include "SolidSyslogPrival.h" #include "SolidSyslogTunables.h" #include "SenderFake.h" @@ -82,6 +85,18 @@ TEST(SolidSyslogPassthroughBuffer, ReadReturnsNothingToSend) CHECK_FALSE(sent); } +TEST(SolidSyslogPassthroughBuffer, DestroyWithNullHandleEmitsUnknownDestroyWarning) +{ + ErrorHandlerFake_Install(nullptr); + + SolidSyslogPassthroughBuffer_Destroy(nullptr); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + POINTERS_EQUAL(&PassthroughBufferErrorSource, ErrorHandlerFake_LastSource()); + UNSIGNED_LONGS_EQUAL(PASSTHROUGHBUFFER_ERROR_UNKNOWN_DESTROY, ErrorHandlerFake_LastCode()); +} + TEST(SolidSyslogPassthroughBuffer, UseAfterDestroyIsCrashSafeViaNullBufferVtable) { /* After Destroy the slot's abstract-base vtable is the shared NullBuffer's, so @@ -99,15 +114,6 @@ TEST(SolidSyslogPassthroughBuffer, UseAfterDestroyIsCrashSafeViaNullBufferVtable buffer = SolidSyslogPassthroughBuffer_Create(fakeSender); // for teardown } -IGNORE_TEST(SolidSyslogPassthroughBuffer, HappyPathOnly) - -{ - // Error handling not yet implemented — see Epic #31 - // Create with NULL sender returns NULL - // Write with NULL buffer does not crash - // Destroy with NULL buffer does not crash -} - // Pool tests — prove SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE caps live // instances and overflow falls back to a distinct no-op buffer. Generic // pool mechanics (lock counts, per-probe locking, stale-handle warning) @@ -185,3 +191,42 @@ TEST(SolidSyslogPassthroughBufferPool, FallbackWriteAndReadAreNoOps) LONGS_EQUAL(0, bytesRead); CALLED_FAKE_ON(SenderFake_Send, fakeSender, NEVER); } + +TEST(SolidSyslogPassthroughBufferPool, CreateWithNullSenderReportsError) +{ + ErrorHandlerFake_Install(nullptr); + + overflow = SolidSyslogPassthroughBuffer_Create(nullptr); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + POINTERS_EQUAL(&PassthroughBufferErrorSource, ErrorHandlerFake_LastSource()); + UNSIGNED_LONGS_EQUAL(PASSTHROUGHBUFFER_ERROR_NULL_SENDER, ErrorHandlerFake_LastCode()); +} + +TEST(SolidSyslogPassthroughBufferPool, CreateWithNullSenderReturnsFallbackDistinctFromAnyPoolSlot) +{ + FillPool(); + + overflow = SolidSyslogPassthroughBuffer_Create(nullptr); + + CHECK_TEXT(overflow != nullptr, "Fallback handle was nullptr"); + for (auto* slot : pooled) + { + CHECK_TEXT(overflow != slot, "Fallback handle collided with a pool slot"); + } +} + +TEST(SolidSyslogPassthroughBufferPool, CreateWithNullSenderDoesNotConsumeAPoolSlot) +{ + // If the failed Create had leaked its acquired slot, FillPool would overflow + // into the fallback one slot sooner and one pool slot would collide with + // `overflow` (both pointing at the NullBuffer singleton). + overflow = SolidSyslogPassthroughBuffer_Create(nullptr); + + FillPool(); + for (auto* slot : pooled) + { + CHECK_TEXT(slot != overflow, "Pool slot collided with the NULL-sender fallback handle"); + } +}