/* * unit.c — pure logic tests, no PulseAudio required * * Tests: signal encoding, timing arithmetic, baud math, handshake frame. * Compile: gcc -Wall -O2 -Iinclude $(pkg-config --cflags libpulse) -o test/unit test/unit.c */ #include #include "zebra.h" #include "modem.h" #include "test.h" /* ------------------------------------------------------------------ * * signal encoding * * ------------------------------------------------------------------ */ static void t_bit_to_vol(void) { T_EQ(zebra_bit_to_vol(0), ZEBRA_VOL_SPACE); T_EQ(zebra_bit_to_vol(1), ZEBRA_VOL_MARK); } static void t_vol_to_bit(void) { T_EQ(zebra_vol_to_bit(ZEBRA_VOL_SPACE), 0); T_EQ(zebra_vol_to_bit(ZEBRA_VOL_MARK), 1); T_EQ(zebra_vol_to_bit(0), 0); T_EQ(zebra_vol_to_bit(100), 1); T_EQ(zebra_vol_to_bit(ZEBRA_VOL_THRESHOLD), -1); T_EQ(zebra_vol_to_bit(ZEBRA_VOL_THRESHOLD - 1), 0); T_EQ(zebra_vol_to_bit(ZEBRA_VOL_THRESHOLD + 1), 1); } static void t_signal_roundtrip(void) { T_EQ(zebra_vol_to_bit(zebra_bit_to_vol(0)), 0); T_EQ(zebra_vol_to_bit(zebra_bit_to_vol(1)), 1); } static void t_signal_separation(void) { /* MARK and SPACE must be on opposite sides of threshold */ T_GT((int)ZEBRA_VOL_MARK, (int)ZEBRA_VOL_THRESHOLD); T_GT((int)ZEBRA_VOL_THRESHOLD, (int)ZEBRA_VOL_SPACE); } /* ------------------------------------------------------------------ * * timing arithmetic * * ------------------------------------------------------------------ */ static void t_ts_add_basic(void) { struct timespec ts = {1, 0}; ts_add_ns(&ts, 500000000L); T_EQ(ts.tv_sec, 1); T_EQ(ts.tv_nsec, 500000000L); } static void t_ts_add_overflow(void) { struct timespec ts = {1, 800000000L}; ts_add_ns(&ts, 400000000L); /* 800M + 400M = 1200M → carry */ T_EQ(ts.tv_sec, 2); T_EQ(ts.tv_nsec, 200000000L); } static void t_ts_add_multi_second(void) { struct timespec ts = {0, 0}; ts_add_ns(&ts, 3500000000L); /* 3.5 seconds */ T_EQ(ts.tv_sec, 3); T_EQ(ts.tv_nsec, 500000000L); } static void t_ts_add_zero(void) { struct timespec ts = {5, 123456789L}; ts_add_ns(&ts, 0); T_EQ(ts.tv_sec, 5); T_EQ(ts.tv_nsec, 123456789L); } /* ------------------------------------------------------------------ * * baud math * * ------------------------------------------------------------------ */ static void t_baud_from_avg_ns(void) { /* 400µs poll → 500 baud (4x oversample: 2e8/400000) */ T_EQ(zebra_baud_from_avg_ns(400000L), 500); /* 200µs poll → 1000 baud */ T_EQ(zebra_baud_from_avg_ns(200000L), 1000); /* 1ms poll → 200 baud */ T_EQ(zebra_baud_from_avg_ns(1000000L), 200); } static void t_baud_clamp_min(void) { /* extremely slow: result clamped to ZEBRA_BAUD_MIN */ T_EQ(zebra_baud_from_avg_ns(1000000000L), ZEBRA_BAUD_MIN); } static void t_baud_clamp_max(void) { /* extremely fast: result clamped to ZEBRA_BAUD_MAX */ T_EQ(zebra_baud_from_avg_ns(1L), ZEBRA_BAUD_MAX); } static void t_baud_zero_guard(void) { /* zero avg_ns returns safe default */ T_EQ(zebra_baud_from_avg_ns(0L), ZEBRA_BAUD_DEFAULT); } static void t_baud_range(void) { /* result always in valid range for a sweep of latencies */ long ns[] = {1, 100, 1000, 10000, 100000, 400000, 1000000, 10000000L, 100000000L}; for (int i = 0; i < 9; i++) { int b = zebra_baud_from_avg_ns(ns[i]); T_GE(b, ZEBRA_BAUD_MIN); T_LE(b, ZEBRA_BAUD_MAX); } } /* ------------------------------------------------------------------ * * handshake frame * * ------------------------------------------------------------------ */ static void t_hs_build_magic(void) { uint8_t frame[ZEBRA_HS_FRAME_LEN]; zebra_hs_build(frame, 1000); T_EQ(frame[0], ZEBRA_HS_MAGIC_0); T_EQ(frame[1], ZEBRA_HS_MAGIC_1); T_EQ(frame[2], ZEBRA_HS_TYPE_OFFER); } static void t_hs_build_baud_encoding(void) { uint8_t frame[ZEBRA_HS_FRAME_LEN]; zebra_hs_build(frame, 0x0342); /* 834 decimal */ T_EQ(frame[3], 0x42); /* low byte */ T_EQ(frame[4], 0x03); /* high byte */ } static void t_hs_build_checksum(void) { uint8_t frame[ZEBRA_HS_FRAME_LEN]; zebra_hs_build(frame, 1072); uint8_t ck = frame[0] ^ frame[1] ^ frame[2] ^ frame[3] ^ frame[4]; T_EQ(frame[5], ck); } static void t_hs_parse_valid(void) { uint8_t frame[ZEBRA_HS_FRAME_LEN]; uint16_t baud = 0; zebra_hs_build(frame, 1072); T_EQ(zebra_hs_parse(frame, &baud), 0); T_EQ(baud, 1072); } static void t_hs_roundtrip(void) { uint16_t bauds[] = {1, 10, 100, 500, 1000, 1072, 2000, 9999, 65535}; for (int i = 0; i < 9; i++) { uint8_t frame[ZEBRA_HS_FRAME_LEN]; uint16_t got = 0; zebra_hs_build(frame, bauds[i]); T_EQ(zebra_hs_parse(frame, &got), 0); T_EQ(got, bauds[i]); } } static void t_hs_parse_bad_magic0(void) { uint8_t frame[ZEBRA_HS_FRAME_LEN]; uint16_t baud = 0; zebra_hs_build(frame, 100); frame[0] ^= 0xFF; /* corrupt first magic byte */ T_EQ(zebra_hs_parse(frame, &baud), -1); } static void t_hs_parse_bad_magic1(void) { uint8_t frame[ZEBRA_HS_FRAME_LEN]; uint16_t baud = 0; zebra_hs_build(frame, 100); frame[1] ^= 0xFF; T_EQ(zebra_hs_parse(frame, &baud), -1); } static void t_hs_parse_bad_type(void) { uint8_t frame[ZEBRA_HS_FRAME_LEN]; uint16_t baud = 0; zebra_hs_build(frame, 100); frame[2] = 0xFF; /* unknown type */ T_EQ(zebra_hs_parse(frame, &baud), -1); } static void t_hs_parse_bad_checksum(void) { uint8_t frame[ZEBRA_HS_FRAME_LEN]; uint16_t baud = 0; zebra_hs_build(frame, 100); frame[5] ^= 0x01; /* flip one checksum bit */ T_EQ(zebra_hs_parse(frame, &baud), -1); } static void t_hs_parse_corrupt_baud(void) { uint8_t frame[ZEBRA_HS_FRAME_LEN]; uint16_t baud = 0; zebra_hs_build(frame, 100); frame[3] ^= 0x01; /* corrupt baud byte without fixing checksum */ T_EQ(zebra_hs_parse(frame, &baud), -1); } /* ------------------------------------------------------------------ * * READY frame * * ------------------------------------------------------------------ */ static void t_hs_build_ready_fields(void) { uint8_t f[ZEBRA_HS_FRAME_LEN]; zebra_hs_build_ready(f); T_EQ(f[0], ZEBRA_HS_MAGIC_0); T_EQ(f[1], ZEBRA_HS_MAGIC_1); T_EQ(f[2], ZEBRA_HS_TYPE_READY); T_EQ(f[3], 0); T_EQ(f[4], 0); } static void t_hs_ready_checksum(void) { uint8_t f[ZEBRA_HS_FRAME_LEN]; zebra_hs_build_ready(f); uint8_t ck = f[0] ^ f[1] ^ f[2] ^ f[3] ^ f[4]; T_EQ(f[5], ck); } static void t_hs_ready_not_offer(void) { /* READY frame must NOT parse as OFFER (type guard) */ uint8_t f[ZEBRA_HS_FRAME_LEN]; uint16_t baud = 0; zebra_hs_build_ready(f); T_EQ(zebra_hs_parse(f, &baud), -1); } static void t_hs_ready_distinct_from_offer(void) { /* READY and OFFER frames must differ in the type byte */ T_NEQ(ZEBRA_HS_TYPE_READY, ZEBRA_HS_TYPE_OFFER); } /* ------------------------------------------------------------------ * * main * * ------------------------------------------------------------------ */ int main(void) { T_SECTION("signal encoding"); T_RUN(t_bit_to_vol); T_RUN(t_vol_to_bit); T_RUN(t_signal_roundtrip); T_RUN(t_signal_separation); T_SECTION("timing arithmetic"); T_RUN(t_ts_add_basic); T_RUN(t_ts_add_overflow); T_RUN(t_ts_add_multi_second); T_RUN(t_ts_add_zero); T_SECTION("baud math"); T_RUN(t_baud_from_avg_ns); T_RUN(t_baud_clamp_min); T_RUN(t_baud_clamp_max); T_RUN(t_baud_zero_guard); T_RUN(t_baud_range); T_SECTION("handshake frame: OFFER"); T_RUN(t_hs_build_magic); T_RUN(t_hs_build_baud_encoding); T_RUN(t_hs_build_checksum); T_RUN(t_hs_parse_valid); T_RUN(t_hs_roundtrip); T_RUN(t_hs_parse_bad_magic0); T_RUN(t_hs_parse_bad_magic1); T_RUN(t_hs_parse_bad_type); T_RUN(t_hs_parse_bad_checksum); T_RUN(t_hs_parse_corrupt_baud); T_SECTION("handshake frame: READY"); T_RUN(t_hs_build_ready_fields); T_RUN(t_hs_ready_checksum); T_RUN(t_hs_ready_not_offer); T_RUN(t_hs_ready_distinct_from_offer); T_SUMMARY(); }