Files
voice-cat/tests/test_frame_codec.cpp

151 lines
4.7 KiB
C++
Raw Permalink Normal View History

2026-06-15 23:48:44 +02:00
/*
* test_frame_codec unit test for FrameCodec::feed / ::emit.
*
* No third-party dependencies; runs under both the skeleton and dev presets.
2026-06-15 23:48:44 +02:00
* Tests: empty payload, single byte, 64 KiB, exact max-size, oversized (should reject),
* split delivery (bytes fed one-at-a-time), and batched multi-frame delivery.
*/
#include <cstdio>
#include <cstring>
#include <vector>
#include "protocol/protocol.h"
using namespace voicecat::protocol;
static int g_failures = 0;
#define CHECK(cond) \
do { \
if (!(cond)) { \
std::printf("FAIL [%s:%d]: %s\n", __FILE__, __LINE__, #cond); \
++g_failures; \
} \
} while (0)
// Round-trip a single payload through emit → feed.
static void round_trip(const std::vector<uint8_t>& payload, const char* label) {
std::vector<uint8_t> wire;
FrameCodec::emit(payload, wire);
FrameCodec codec;
std::vector<std::vector<uint8_t>> frames;
bool ok = codec.feed(wire.data(), wire.size(), frames);
CHECK(ok);
CHECK(frames.size() == 1);
if (!frames.empty()) {
CHECK(frames[0] == payload);
}
(void)label;
}
int main() {
// --- Empty payload ---
round_trip({}, "empty");
// --- Single byte ---
round_trip({0xAB}, "1 byte");
// --- 64 KiB payload ---
{
std::vector<uint8_t> big(64 * 1024);
for (size_t i = 0; i < big.size(); ++i) big[i] = static_cast<uint8_t>(i & 0xFF);
round_trip(big, "64 KiB");
}
// --- Exactly kMaxFrameBytes ---
{
std::vector<uint8_t> max_payload(kMaxFrameBytes, 0x5A);
std::vector<uint8_t> wire;
FrameCodec::emit(max_payload, wire);
FrameCodec codec;
std::vector<std::vector<uint8_t>> frames;
bool ok = codec.feed(wire.data(), wire.size(), frames);
CHECK(ok);
CHECK(frames.size() == 1);
if (!frames.empty()) CHECK(frames[0] == max_payload);
}
// --- One byte over kMaxFrameBytes — must be rejected ---
{
// Craft a fake header with length = kMaxFrameBytes + 1.
uint32_t bad_len = kMaxFrameBytes + 1;
uint8_t header[4] = {
static_cast<uint8_t>((bad_len >> 24) & 0xFF),
static_cast<uint8_t>((bad_len >> 16) & 0xFF),
static_cast<uint8_t>((bad_len >> 8) & 0xFF),
static_cast<uint8_t>( bad_len & 0xFF),
};
FrameCodec codec;
std::vector<std::vector<uint8_t>> frames;
bool ok = codec.feed(header, 4, frames);
CHECK(!ok); // must return false
CHECK(frames.empty());
}
// --- Byte-at-a-time delivery (reassembly) ---
{
std::vector<uint8_t> payload = {1, 2, 3, 4, 5};
std::vector<uint8_t> wire;
FrameCodec::emit(payload, wire);
FrameCodec codec;
std::vector<std::vector<uint8_t>> frames;
bool ok = true;
for (uint8_t b : wire) {
ok = codec.feed(&b, 1, frames);
if (!ok) break;
}
CHECK(ok);
CHECK(frames.size() == 1);
if (!frames.empty()) CHECK(frames[0] == payload);
}
// --- Multiple frames in a single feed() call ---
{
std::vector<uint8_t> p1 = {0x01, 0x02};
std::vector<uint8_t> p2 = {0xAA, 0xBB, 0xCC};
std::vector<uint8_t> wire;
FrameCodec::emit(p1, wire);
FrameCodec::emit(p2, wire);
FrameCodec codec;
std::vector<std::vector<uint8_t>> frames;
bool ok = codec.feed(wire.data(), wire.size(), frames);
CHECK(ok);
CHECK(frames.size() == 2);
if (frames.size() == 2) {
CHECK(frames[0] == p1);
CHECK(frames[1] == p2);
}
}
// --- pending_bytes() reflects partial state ---
{
std::vector<uint8_t> payload = {0xFF};
std::vector<uint8_t> wire;
FrameCodec::emit(payload, wire); // 5 bytes total (4 hdr + 1)
FrameCodec codec;
std::vector<std::vector<uint8_t>> frames;
// Feed only the header.
codec.feed(wire.data(), 4, frames);
CHECK(frames.empty());
CHECK(codec.pending_bytes() == 4);
// Feed the body.
codec.feed(wire.data() + 4, 1, frames);
CHECK(frames.size() == 1);
CHECK(codec.pending_bytes() == 0);
}
if (g_failures == 0) {
std::printf("frame_codec: all checks passed\n");
return 0;
}
std::printf("frame_codec: %d failure(s)\n", g_failures);
return 1;
}