// hextest — exercises lib/encoding/hex. Run with // `out/bin/ww run lib/encoding/hex/hextest.ww`. Same // signalled-then-fail()-with-+10 pattern as the rest of the 9xx // stdlib tests; non-zero exit pinpoints the failing scenario. use bytes; use hex; use os; let signalled: i32 = 0; fn fail() void = { os.exit(signalled + 10); }; fn putstr(s: str, into: []u8, off: i32) i32 = { let i: i32 = 0; for (i < s.len) { into[off + i] = s[i]; i += 1; }; return off + s.len; }; fn streq(buf: []u8, expect: str) bool = { if (buf.len != expect.len) { return false; }; let i: i32 = 0; for (i < buf.len) { if (buf[i] != expect[i]) { return false; }; i += 1; }; return true; }; // ---- encodedsize / decodedsize ----------------------------------------- @test fn sizes() void = { if (hex.encodedsize(0) != 0) { fail(); }; if (hex.encodedsize(1) != 2) { fail(); }; if (hex.encodedsize(8) != 16) { fail(); }; if (hex.decodedsize(0) != 0) { fail(); }; if (hex.decodedsize(2) != 1) { fail(); }; if (hex.decodedsize(16) != 8) { fail(); }; }; // ---- encode: lowercase, all-bytes coverage ----------------------------- // // Hare test vector ref/hare/encoding/hex/hex.ha:82. @test fn encode_basic() void = { let src: [8]u8; src[0] = 0xCAu8; src[1] = 0xFEu8; src[2] = 0xBAu8; src[3] = 0xBEu8; src[4] = 0xDEu8; src[5] = 0xADu8; src[6] = 0xF0u8; src[7] = 0x0Du8; let dst: [16]u8; let n: i32 = hex.encode(dst[0:16], src[0:8]); if (n != 16) { fail(); }; if (!streq(dst[0:16], "cafebabedeadf00d")) { fail(); }; }; // 0x00 in / "00" out catches a sign-extend / signed-shift miscompile // on the high nibble. @test fn encode_zero() void = { let src: [1]u8; src[0] = 0u8; let dst: [2]u8; let n: i32 = hex.encode(dst[0:2], src[0:1]); if (n != 2) { fail(); }; if (!streq(dst[0:2], "00")) { fail(); }; }; // 0xFF in / "ff" out catches an off-by-one in the nibble lookup or // a wrong-width shift. @test fn encode_ff() void = { let src: [1]u8; src[0] = 0xFFu8; let dst: [2]u8; let n: i32 = hex.encode(dst[0:2], src[0:1]); if (n != 2) { fail(); }; if (!streq(dst[0:2], "ff")) { fail(); }; }; // Empty input is a no-op encode. @test fn encode_empty() void = { let src: [1]u8; let dst: [1]u8; let n: i32 = hex.encode(dst[0:0], src[0:0]); if (n != 0) { fail(); }; }; // ---- decode: lowercase, uppercase, mixed ------------------------------- @test fn decode_lower() void = { let inbuf: [16]u8; let n: i32 = putstr("cafebabedeadf00d", inbuf[0:16], 0); let dst: [8]u8; let r: (i32 | hex.invalid) = hex.decode(dst[0:8], inbuf[0:n]); match (r) { case let m: i32 => { if (m != 8) { fail(); }; let want: [8]u8; want[0] = 0xCAu8; want[1] = 0xFEu8; want[2] = 0xBAu8; want[3] = 0xBEu8; want[4] = 0xDEu8; want[5] = 0xADu8; want[6] = 0xF0u8; want[7] = 0x0Du8; if (!bytes.equal(dst[0:8], want[0:8])) { fail(); }; }; case let e: hex.invalid => { fail(); }; }; }; @test fn decode_upper() void = { let inbuf: [16]u8; let n: i32 = putstr("CAFEBABEDEADF00D", inbuf[0:16], 0); let dst: [8]u8; let r: (i32 | hex.invalid) = hex.decode(dst[0:8], inbuf[0:n]); match (r) { case let m: i32 => { if (m != 8) { fail(); }; let want: [8]u8; want[0] = 0xCAu8; want[1] = 0xFEu8; want[2] = 0xBAu8; want[3] = 0xBEu8; want[4] = 0xDEu8; want[5] = 0xADu8; want[6] = 0xF0u8; want[7] = 0x0Du8; if (!bytes.equal(dst[0:8], want[0:8])) { fail(); }; }; case let e: hex.invalid => { fail(); }; }; }; // Mixed-case must decode too; Hare's encoder is lowercase-only but // the decoder accepts both per ref/hare/encoding/hex/README:13. @test fn decode_mixed() void = { let inbuf: [16]u8; let n: i32 = putstr("CaFeBaBeDeAdF00d", inbuf[0:16], 0); let dst: [8]u8; let r: (i32 | hex.invalid) = hex.decode(dst[0:8], inbuf[0:n]); match (r) { case let m: i32 => { if (m != 8) { fail(); }; let want: [8]u8; want[0] = 0xCAu8; want[1] = 0xFEu8; want[2] = 0xBAu8; want[3] = 0xBEu8; want[4] = 0xDEu8; want[5] = 0xADu8; want[6] = 0xF0u8; want[7] = 0x0Du8; if (!bytes.equal(dst[0:8], want[0:8])) { fail(); }; }; case let e: hex.invalid => { fail(); }; }; }; @test fn decode_empty() void = { let inbuf: [1]u8; let dst: [1]u8; let r: (i32 | hex.invalid) = hex.decode(dst[0:0], inbuf[0:0]); match (r) { case let m: i32 => { if (m != 0) { fail(); }; }; case let e: hex.invalid => { fail(); }; }; }; // ---- decode: error cases ----------------------------------------------- // // Odd length and non-hex chars both return invalid. Hare's // decode_reader at ref/hare/encoding/hex/hex.ha:154 returns // errors::invalid for both. @test fn decode_odd_length() void = { let inbuf: [3]u8; let n: i32 = putstr("abc", inbuf[0:3], 0); let dst: [2]u8; let r: (i32 | hex.invalid) = hex.decode(dst[0:2], inbuf[0:n]); match (r) { case let m: i32 => { fail(); }; case let e: hex.invalid => void; }; }; @test fn decode_bad_char() void = { let inbuf: [4]u8; let n: i32 = putstr("zz00", inbuf[0:4], 0); // 'z' isn't hex let dst: [2]u8; let r: (i32 | hex.invalid) = hex.decode(dst[0:2], inbuf[0:n]); match (r) { case let m: i32 => { fail(); }; case let e: hex.invalid => void; }; }; @test fn decode_bad_char_mid() void = { let inbuf: [6]u8; let n: i32 = putstr("aabbgg", inbuf[0:6], 0); // 'g' isn't hex let dst: [3]u8; let r: (i32 | hex.invalid) = hex.decode(dst[0:3], inbuf[0:n]); match (r) { case let m: i32 => { fail(); }; case let e: hex.invalid => void; }; }; // ---- roundtrip: every byte value 0..255 -------------------------------- @test fn roundtrip_all_bytes() void = { let src: [256]u8; let i: i32 = 0; for (i < 256) { src[i] = i: u8; i += 1; }; let enc: [512]u8; let n: i32 = hex.encode(enc[0:512], src[0:256]); if (n != 512) { fail(); }; let dec: [256]u8; let r: (i32 | hex.invalid) = hex.decode(dec[0:256], enc[0:n]); match (r) { case let m: i32 => { if (m != 256) { fail(); }; if (!bytes.equal(src[0:256], dec[0:256])) { fail(); }; }; case let e: hex.invalid => { fail(); }; }; }; export fn main() i32 = { signalled = 1; sizes(); signalled = 2; encode_basic(); signalled = 3; encode_zero(); signalled = 4; encode_ff(); signalled = 5; encode_empty(); signalled = 6; decode_lower(); signalled = 7; decode_upper(); signalled = 8; decode_mixed(); signalled = 9; decode_empty(); signalled = 10; decode_odd_length(); signalled = 11; decode_bad_char(); signalled = 12; decode_bad_char_mid(); signalled = 13; roundtrip_all_bytes(); return 0; };