/* * 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 #include #include #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& payload, const char* label) { std::vector wire; FrameCodec::emit(payload, wire); FrameCodec codec; std::vector> 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 big(64 * 1024); for (size_t i = 0; i < big.size(); ++i) big[i] = static_cast(i & 0xFF); round_trip(big, "64 KiB"); } // --- Exactly kMaxFrameBytes --- { std::vector max_payload(kMaxFrameBytes, 0x5A); std::vector wire; FrameCodec::emit(max_payload, wire); FrameCodec codec; std::vector> 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((bad_len >> 24) & 0xFF), static_cast((bad_len >> 16) & 0xFF), static_cast((bad_len >> 8) & 0xFF), static_cast( bad_len & 0xFF), }; FrameCodec codec; std::vector> frames; bool ok = codec.feed(header, 4, frames); CHECK(!ok); // must return false CHECK(frames.empty()); } // --- Byte-at-a-time delivery (reassembly) --- { std::vector payload = {1, 2, 3, 4, 5}; std::vector wire; FrameCodec::emit(payload, wire); FrameCodec codec; std::vector> 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 p1 = {0x01, 0x02}; std::vector p2 = {0xAA, 0xBB, 0xCC}; std::vector wire; FrameCodec::emit(p1, wire); FrameCodec::emit(p2, wire); FrameCodec codec; std::vector> 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 payload = {0xFF}; std::vector wire; FrameCodec::emit(payload, wire); // 5 bytes total (4 hdr + 1) FrameCodec codec; std::vector> 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; }