forked from arti/vt1200
Ruling R14: the frame CRC now covers header bytes 0..11 only. A payload-wide CRC cost the terminal ~139 instructions per payload byte (the parser, not the wire or the blitter, was the frame-rate ceiling), while every datagram already carries an Ethernet FCS the LANCE checks and the length bound catches truncation. frame.py, fp_parse and the harness's own frame signer follow the new rule; a corrupted payload byte is now ACCEPTED and a corrupted header byte rejected, stated explicitly in both test suites, and the C golden vectors were regenerated from frame.py. Measured after: RX_READY_A write -> first RX_OK read is 1 emulated frame for a 20,480-byte frame (84 before), which is the frame hook's resolution. Ruling R15: draw mode deletes its dump files before the run (wrapper and Lua) so a failed run cannot be judged on the previous screen; a wait that hits its cap reports "timed out waiting for the frame" and the checker reports it as a timeout, not as pixel mismatches; the checker asserts the VRAM canary word is 0xA5C3, so a payload that stopped arming it fails instead of passing quietly. rx mode now corrupts a header byte in slot B, because a payload flip is accepted by design under R14.
225 lines
8.4 KiB
C
225 lines
8.4 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <string.h>
|
|
|
|
#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");
|
|
}
|
|
|
|
printf("frame_parse: %d/%d ok\n", checks - failures, checks);
|
|
return failures != 0;
|
|
}
|