/* * test_frame_parse.c -- host unit test for the receive-side frame check. * * cc -std=c89 -Wall -o /tmp/tfp harness/udpstream/tests/test_frame_parse.c \ * harness/udpstream/frame_parse.c && /tmp/tfp * * The frames under test are GOLDEN VECTORS, not something this test or * frame_parse.c computed: the bytes in `golden` and `golden_short` came from * frame.py, the sender's implementation of the same spec. That is what makes * the test a cross-check of the C parser rather than a tautology: if the C CRC, * the field offsets, the CRC's coverage or the length rule were wrong, these * frames would not be judged as frame.py judged them. * * THE CRC COVERS HEADER BYTES 0..11 ONLY (ruling R14). That has a consequence * this test states explicitly rather than leaving implicit: * * * a corrupted PAYLOAD byte is ACCEPTED -- the bytes between the header and * the end of the frame are not hashed here; each datagram's Ethernet FCS, * validated by the LANCE, is what protects them; * * a corrupted HEADER byte (inside bytes 0..11) is REJECTED; * * a TRUNCATED frame (len < 16 + payload_len) is REJECTED by the length * bound, which is the check that has to carry truncation now. * * Regenerate both vectors with: * * cd harness/udpstream && python3 -c ' * import frame * pl = bytes((i*7+3) & 0xFF for i in range(80)) * d = frame.pack_header(1, 0x1234, len(pl), 1, 8, payload=pl) + pl * print("golden:", len(d)); print("\t" + "".join("0x%02X, " % b for b in d)) * pl = bytes((i*7+3) & 0xFF for i in range(81)) * d = frame.pack_header(1, 0x1235, len(pl), 1, 8, payload=pl) + pl * print("golden_short:", len(d)); print("\t" + "".join("0x%02X, " % b for b in d))' * * `golden_short` claims 81 payload bytes and is delivered with only 80 of them, * so its header (and its header CRC) is valid while its length is one short -- * only the length bound can reject it. (kind 1 = KIND_STRIP.) */ #include #include #include "../frame_parse.h" static int checks; static int failures; static void check(int cond, const char *what) { checks++; if (!cond) { failures++; printf("FAIL: %s\n", what); } } static const unsigned char golden[] = { 0x56, 0x53, 0x01, 0x01, 0x34, 0x12, 0x50, 0x00, 0x01, 0x08, 0x00, 0x00, 0x0B, 0x39, 0x00, 0x00, 0x03, 0x0A, 0x11, 0x18, 0x1F, 0x26, 0x2D, 0x34, 0x3B, 0x42, 0x49, 0x50, 0x57, 0x5E, 0x65, 0x6C, 0x73, 0x7A, 0x81, 0x88, 0x8F, 0x96, 0x9D, 0xA4, 0xAB, 0xB2, 0xB9, 0xC0, 0xC7, 0xCE, 0xD5, 0xDC, 0xE3, 0xEA, 0xF1, 0xF8, 0xFF, 0x06, 0x0D, 0x14, 0x1B, 0x22, 0x29, 0x30, 0x37, 0x3E, 0x45, 0x4C, 0x53, 0x5A, 0x61, 0x68, 0x6F, 0x76, 0x7D, 0x84, 0x8B, 0x92, 0x99, 0xA0, 0xA7, 0xAE, 0xB5, 0xBC, 0xC3, 0xCA, 0xD1, 0xD8, 0xDF, 0xE6, 0xED, 0xF4, 0xFB, 0x02, 0x09, 0x10, 0x17, 0x1E, 0x25, 0x2C }; /* The same shape, one payload byte longer than what is delivered. */ static const unsigned char golden_short[] = { 0x56, 0x53, 0x01, 0x01, 0x35, 0x12, 0x51, 0x00, 0x01, 0x08, 0x00, 0x00, 0x78, 0x3B, 0x00, 0x00, 0x03, 0x0A, 0x11, 0x18, 0x1F, 0x26, 0x2D, 0x34, 0x3B, 0x42, 0x49, 0x50, 0x57, 0x5E, 0x65, 0x6C, 0x73, 0x7A, 0x81, 0x88, 0x8F, 0x96, 0x9D, 0xA4, 0xAB, 0xB2, 0xB9, 0xC0, 0xC7, 0xCE, 0xD5, 0xDC, 0xE3, 0xEA, 0xF1, 0xF8, 0xFF, 0x06, 0x0D, 0x14, 0x1B, 0x22, 0x29, 0x30, 0x37, 0x3E, 0x45, 0x4C, 0x53, 0x5A, 0x61, 0x68, 0x6F, 0x76, 0x7D, 0x84, 0x8B, 0x92, 0x99, 0xA0, 0xA7, 0xAE, 0xB5, 0xBC, 0xC3, 0xCA, 0xD1, 0xD8, 0xDF, 0xE6, 0xED, 0xF4, 0xFB, 0x02, 0x09, 0x10, 0x17, 0x1E, 0x25, 0x2C, 0x33 }; #define GOLDEN_LEN ((unsigned int) sizeof golden) #define GOLDEN_PLEN 80 static unsigned char buf[256]; static fp_header h; /* Copy the golden frame into the mutable buffer and return its length. */ static unsigned int load(void) { memcpy(buf, golden, sizeof golden); return GOLDEN_LEN; } int main(void) { unsigned int len; int rc; /* ---- the well-formed frame is accepted, with every field read ---- */ len = load(); memset(&h, 0, sizeof h); rc = fp_parse(buf, len, &h); check(rc == 1, "golden frame accepted"); check(h.kind == FP_KIND_STRIP, "kind == KIND_STRIP"); check(h.frame_no == 0x1234, "frame_no == 0x1234 (little-endian)"); check(h.rows == 1, "rows == 1"); check(h.stretch == 8, "stretch == 8"); check(h.payload_len == GOLDEN_PLEN, "payload_len == 80"); check(h.payload_off == FP_HEADER_SIZE, "payload_off == 16"); /* ---- a flipped PAYLOAD byte is ACCEPTED (ruling R14) ---- * * The CRC is over the header only, so the body is not hashed here. The * Ethernet FCS of each datagram, validated by the LANCE, is what * protects those bytes; this parser must not pretend otherwise. */ len = load(); buf[FP_HEADER_SIZE + 40] = (unsigned char) (buf[FP_HEADER_SIZE + 40] ^ 0x01); check(fp_parse(buf, len, &h) == 1, "flipped payload byte ACCEPTED (header-only CRC; FCS is the chip's job)"); /* ---- a flipped header byte inside the CRC prefix is rejected ---- */ len = load(); buf[8] = (unsigned char) (buf[8] + 1); check(fp_parse(buf, len, &h) == 0, "flipped header byte rejected"); len = load(); buf[3] = (unsigned char) (buf[3] ^ 0x80); /* kind, inside the prefix */ check(fp_parse(buf, len, &h) == 0, "flipped kind byte rejected"); /* ---- a valid header whose payload_len exceeds what arrived ---- * * `golden_short` claims 81 payload bytes. Delivered whole it is a valid * frame (so the vector is not simply broken); one byte short it must be * rejected by the LENGTH BOUND alone, because its header CRC is right. */ { unsigned int n = (unsigned int) sizeof golden_short; memcpy(buf, golden_short, sizeof golden_short); check(fp_parse(buf, n, &h) == 1, "golden_short delivered whole is accepted"); check(h.payload_len == 81, "golden_short payload_len == 81"); memcpy(buf, golden_short, sizeof golden_short); check(fp_parse(buf, n - 1, &h) == 0, "length bound rejects len < 16 + payload_len (header CRC valid)"); } /* ---- short buffers: one byte short of the frame, and below the header ---- */ len = load(); check(fp_parse(buf, GOLDEN_LEN - 1, &h) == 0, "truncated payload (one byte short) rejected"); check(fp_parse(buf, FP_HEADER_SIZE - 1, &h) == 0, "buffer shorter than the header rejected"); check(fp_parse(buf, 0, &h) == 0, "empty buffer rejected"); /* ---- wrong magic ---- */ len = load(); buf[0] = 'X'; check(fp_parse(buf, len, &h) == 0, "bad magic byte 0 rejected"); len = load(); buf[1] = 'X'; check(fp_parse(buf, len, &h) == 0, "bad magic byte 1 rejected"); /* ---- wrong version (0 and 2 both differ from 1) ---- */ len = load(); buf[2] = 0x00; check(fp_parse(buf, len, &h) == 0, "version 0 rejected"); len = load(); buf[2] = 0x02; check(fp_parse(buf, len, &h) == 0, "version 2 rejected"); /* ---- wrong stored CRC, either byte ---- */ len = load(); buf[FP_CRC_OFF] = (unsigned char) (buf[FP_CRC_OFF] + 1); check(fp_parse(buf, len, &h) == 0, "corrupt crc low byte rejected"); len = load(); buf[FP_CRC_OFF + 1] = (unsigned char) (buf[FP_CRC_OFF + 1] + 1); check(fp_parse(buf, len, &h) == 0, "corrupt crc high byte rejected"); /* ---- editing payload_len is a header edit: the header CRC catches it ---- */ len = load(); buf[6] = 0x40; /* payload_len 0x40 instead of 0x50 */ check(fp_parse(buf, len, &h) == 0, "header payload_len edit (CRC not recomputed) rejected"); /* ---- payload_len is authoritative: a tail in the slot is not the frame ---- */ len = load(); buf[GOLDEN_LEN] = 0xFF; check(fp_parse(buf, GOLDEN_LEN + 1, &h) == 1, "trailing byte past the frame ignored"); /* ---- a bare header (payload_len 0) is a valid frame ... * * 56 53 01 01 09 00 00 00 00 00 00 00 AA D1 00 00 -- frame.py's * pack_header(1, 9, 0, 0, 0, payload=b""), whose CRC covers the 12 * prefix bytes only. It pins the empty-payload case. */ { static const unsigned char bare[] = { 0x56, 0x53, 0x01, 0x01, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xD1, 0x00, 0x00 }; memcpy(buf, bare, sizeof bare); memset(&h, 0, sizeof h); rc = fp_parse(buf, (unsigned int) sizeof bare, &h); check(rc == 1, "bare header (payload_len 0) accepted"); check(h.frame_no == 9, "bare header frame_no == 9"); check(h.payload_len == 0, "bare header payload_len == 0"); /* and one byte missing is not a header */ check(fp_parse(buf, (unsigned int) sizeof bare - 1, &h) == 0, "bare header, one byte short, rejected"); } /* ---- fp_crc(): the exported CRC the transmit side seals with ---- * * Task 6's terminal builds its own status header and must use the same * CRC as the parser. These are the canonical check values, not values * this code produced: CCITT-FALSE over "123456789" is 0x29B1, and the * empty input is the initial value. The golden frame's own CRC bytes * are the third reading, so the function and fp_parse() cannot drift. */ { static const unsigned char nine[] = "123456789"; unsigned int crc; crc = fp_crc((unsigned char *) nine, 9); check(crc == 0x29B1, "fp_crc(\"123456789\") == 0x29B1 (CCITT-FALSE)"); check(fp_crc((unsigned char *) nine, 0) == 0xFFFF, "fp_crc(empty) == 0xFFFF (the initial value)"); crc = fp_crc((unsigned char *) golden, FP_PREFIX_SIZE); check(crc == (((unsigned int) golden[FP_CRC_OFF + 1] << 8) | (unsigned int) golden[FP_CRC_OFF]), "fp_crc over the golden prefix equals its stored crc16"); } printf("frame_parse: %d/%d ok\n", checks - failures, checks); return failures != 0; }