Source: ~/.unfirehose/unfirehose.db (project_id=81, 4 sessions covering
2026-03-29 through 2026-04-05). Reconstructed via chronological replay
of Write/Edit tool_input on file_paths under /home/fox/zebra-report/.
stats:
files reconstructed: 20
writes baselined: all (zero missing)
edits applied: 68
edits unapplied: 8 (1 SKIP pre-baseline, 6 FAIL old_string drift, 1 AMBIGUOUS)
unapplied edits represent small drift in 6 files; baseline content for
each is intact. quality verification deferred to phase 2.
recovered tree:
CLAUDE.md, Makefile
src/{tx,rx,pulse,carrier,chat,bt}.c
include/{modem,zebra}.h
test/{functional,integration,unit}.c, test/test.h
web/{index,kernel}.html, web/blog/style.css
blog/build.py, blog/posts/{001-volume-modem,002-sse-chatroom}.md
report: /tmp/zebra_recover_report.txt
script: /tmp/zebra_recover.py
275 lines
8.5 KiB
C
275 lines
8.5 KiB
C
/*
|
|
* 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 <string.h>
|
|
#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();
|
|
}
|