151 lines
4.7 KiB
C++
151 lines
4.7 KiB
C++
|
|
/*
|
||
|
|
* test_frame_codec — unit test for FrameCodec::feed / ::emit.
|
||
|
|
*
|
||
|
|
* No third-party dependencies; runs under both the dev and m1-dev presets.
|
||
|
|
* 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;
|
||
|
|
}
|