/**
 * @file WSServerBufferTest.cpp
 * @brief Reproduction test for CR-1: 1-byte heap OOB write in WSServer.
 *
 * Verifies that the receive buffer allocated in ClientReadLoop is large enough
 * to hold a maximal masked WebSocket frame (14-byte header + 65536 payload)
 * plus one extra byte for in-place NUL-termination, without overflowing.
 *
 * Build: linked into the GTest harness alongside MainGTest.cpp.
 */

#include "WSFrame.h"
#include <gtest/gtest.h>
#include <cstdlib>
#include <cstring>

using namespace StreamHub;

// Mirror the WSServer.h constant (TEST_WS_MAX_RECV_PAYLOAD = 65536).
static const uint32 TEST_WS_MAX_RECV_PAYLOAD = 65536u;

// Test: A maximal masked WebSocket frame (64-bit extended length, masked)
// with payloadLen = TEST_WS_MAX_RECV_PAYLOAD must fit within kRecvBuf, and
// payload[plen] must be a valid in-bounds index (for NUL-termination).
TEST(WSServerBufferTest, MaximalFrameFitsInRecvBuffer) {
    // Reproduce the exact buffer sizing logic from WSServer::ClientReadLoop.
    const uint32 kRecvBuf = TEST_WS_MAX_RECV_PAYLOAD + 14u + 1u;
    uint8 *buf = new uint8[kRecvBuf];

    // Build a maximal masked frame: FIN + TEXT, payloadLen=65536 (64-bit ext),
    // mask=1.
    uint8 frame[14 + 65536];
    frame[0] = WS_FIN_BIT | WS_OPCODE_TEXT;   // FIN + TEXT
    frame[1] = WS_MASK_BIT | 127u;             // masked + 64-bit length
    // 8-byte extended length = 65536
    uint64 plen = TEST_WS_MAX_RECV_PAYLOAD;
    for (int i = 7; i >= 0; i--) {
        frame[2 + i] = static_cast<uint8>(plen & 0xFFu);
        plen >>= 8u;
    }
    // 4-byte mask key
    frame[10] = 0xAA; frame[11] = 0xBB; frame[12] = 0xCC; frame[13] = 0xDD;
    // Payload (doesn't matter, just fill with zeros)
    memset(frame + 14, 0, 65536);

    // Copy into buf (simulating a TCP read)
    ASSERT_LE(sizeof(frame), static_cast<size_t>(kRecvBuf));
    memcpy(buf, frame, sizeof(frame));

    // Parse the header
    WSFrameHeader hdr;
    ASSERT_TRUE(WSParseHeader(buf, sizeof(frame), hdr));
    ASSERT_EQ(hdr.headerSize, 14u);
    ASSERT_EQ(hdr.payloadLen, static_cast<uint64>(TEST_WS_MAX_RECV_PAYLOAD));
    ASSERT_TRUE(hdr.masked);

    // Unmask
    uint8 *payload = buf + hdr.headerSize;
    WSUnmask(payload, static_cast<uint32>(hdr.payloadLen), hdr.maskKey);

    // The critical check: payload[plen] must be within the buffer.
    // Before the fix, kRecvBuf was 65550 and payload[65536] = buf[65550]
    // was one byte past the end.  After the fix (+1), it's in bounds.
    uint32 plenIdx = static_cast<uint32>(hdr.payloadLen);
    ASSERT_LT(hdr.headerSize + plenIdx, kRecvBuf)
        << "payload[plen] would be out of bounds — buffer overflow!";

    // Simulate the NUL-termination that WSServer does:
    uint8 savedByte = payload[plenIdx];
    payload[plenIdx] = '\0';
    // Verify it's within bounds (no ASan/heap overflow)
    EXPECT_EQ(payload[plenIdx], '\0');
    payload[plenIdx] = savedByte;

    delete[] buf;
}

// Test: Verify the old (buggy) buffer size would have overflowed.
// This documents the bug for future readers.
TEST(WSServerBufferTest, OldBufferSizeWouldOverflow) {
    const uint32 oldKRecvBuf = TEST_WS_MAX_RECV_PAYLOAD + 14u; // the buggy size
    const uint32 headerSize = 14u;
    const uint32 plen = TEST_WS_MAX_RECV_PAYLOAD;
    // headerSize + plen == oldKRecvBuf, so payload[plen] = buf[oldKRecvBuf]
    // is one byte past the end.
    ASSERT_EQ(headerSize + plen, oldKRecvBuf)
        << "Expected the old buffer to be exactly full (no room for NUL term)";
    // The fix adds +1:
    const uint32 newKRecvBuf = TEST_WS_MAX_RECV_PAYLOAD + 14u + 1u;
    ASSERT_LT(headerSize + plen, newKRecvBuf)
        << "New buffer must have room for the NUL-termination byte";
}
