package base64; import base64; 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; }; fn encodevec(input: str, expect: str) void = { let inbuf: [128]u8; let outbuf: [128]u8; let n: i32 = putstr(input, inbuf[0:128], 0); let m: i32 = base64.encode(outbuf[0:128], inbuf[0:n]); if (m != expect.len) { let _: i32 = 1/0; }; if (!streq(outbuf[0:m], expect)) { let _: i32 = 1/0; }; }; @test fn rfc4648_vectors() void = { encodevec("", ""); encodevec("f", "Zg=="); encodevec("fo", "Zm8="); encodevec("foo", "Zm9v"); encodevec("foob", "Zm9vYg=="); encodevec("fooba", "Zm9vYmE="); encodevec("foobar", "Zm9vYmFy"); }; fn decodevec(input: str, expect: str) void = { let inbuf: [128]u8; let outbuf: [128]u8; let n: i32 = putstr(input, inbuf[0:128], 0); let r: (i32 | base64.invalid) = base64.decode(outbuf[0:128], inbuf[0:n]); match (r) { case let m: i32 => { if (m != expect.len) { let _: i32 = 1/0; }; if (!streq(outbuf[0:m], expect)) { let _: i32 = 1/0; }; }; case let e: base64.invalid => { let _: i32 = 1/0; }; }; }; @test fn rfc4648_decode() void = { decodevec("", ""); decodevec("Zg==", "f"); decodevec("Zm8=", "fo"); decodevec("Zm9v", "foo"); decodevec("Zm9vYg==", "foob"); decodevec("Zm9vYmE=", "fooba"); decodevec("Zm9vYmFy", "foobar"); }; @test fn alphabet_full() void = { // Round-trip every 6-bit value (0..63) by encoding three bytes that // expose b0=0x00, b1=AA, b2=FF — the encoded chars depend on all // four positions including the >>2 path. let i: i32 = 0; for (i < 64) { let bits: u8 = i: u8; // Construct a triple [bits<<2, 0, 0] so the first encoded // char encodes `bits`. The other three chars are derivable // from the remaining bytes; we only check the first here. let inbuf: [3]u8; inbuf[0] = bits << 2u8; inbuf[1] = 0u8; inbuf[2] = 0u8; let outbuf: [4]u8; let m: i32 = base64.encode(outbuf[0:4], inbuf[0:3]); if (m != 4) { let _: i32 = 1/0; }; // Decoding back must give us `bits` in the high 6 bits of [0]. let r: (i32 | base64.invalid) = base64.decode(inbuf[0:3], outbuf[0:4]); match (r) { case let n: i32 => { if (n != 3) { let _: i32 = 1/0; }; if ((inbuf[0] >> 2u8) != bits) { let _: i32 = 1/0; }; }; case let e: base64.invalid => { let _: i32 = 1/0; }; }; i += 1; }; }; @test fn invalid_inputs() void = { let inbuf: [16]u8; let outbuf: [16]u8; // Length not a multiple of 4. let n: i32 = putstr("abc", inbuf[0:16], 0); let r1: (i32 | base64.invalid) = base64.decode(outbuf[0:16], inbuf[0:n]); match (r1) { case let m: i32 => { let _: i32 = 1/0; }; case let e: base64.invalid => void; }; // Bad char ('@' is not in the std alphabet). let n2: i32 = putstr("Z@==", inbuf[0:16], 0); let r2: (i32 | base64.invalid) = base64.decode(outbuf[0:16], inbuf[0:n2]); match (r2) { case let m: i32 => { let _: i32 = 1/0; }; case let e: base64.invalid => void; }; }; @test fn urlsafe_roundtrip() void = { // Byte sequence chosen so the std alphabet would use '+' and '/', // while url-safe replaces them with '-' and '_'. 0xFB = 11111011 // hits index 62 in some quad, and 0xFF hits 63. let raw: [3]u8; raw[0] = 0xFBu8; raw[1] = 0xFFu8; raw[2] = 0xBFu8; let std: [8]u8; let url: [8]u8; let dec: [3]u8; let m1: i32 = base64.encode(std[0:8], raw[0:3]); let m2: i32 = base64.encodeurl(url[0:8], raw[0:3]); if (m1 != 4) { let _: i32 = 1/0; }; if (m2 != 4) { let _: i32 = 1/0; }; // Round-trip both ways. let r1: (i32 | base64.invalid) = base64.decode(dec[0:3], std[0:m1]); match (r1) { case let n: i32 => { if (n != 3) { let _: i32 = 1/0; }; if (dec[0] != raw[0]) { let _: i32 = 1/0; }; if (dec[1] != raw[1]) { let _: i32 = 1/0; }; if (dec[2] != raw[2]) { let _: i32 = 1/0; }; }; case let e: base64.invalid => { let _: i32 = 1/0; }; }; let r2: (i32 | base64.invalid) = base64.decodeurl(dec[0:3], url[0:m2]); match (r2) { case let n: i32 => { if (n != 3) { let _: i32 = 1/0; }; if (dec[0] != raw[0]) { let _: i32 = 1/0; }; if (dec[1] != raw[1]) { let _: i32 = 1/0; }; if (dec[2] != raw[2]) { let _: i32 = 1/0; }; }; case let e: base64.invalid => { let _: i32 = 1/0; }; }; }; @test fn sizes() void = { if (base64.encodedsize(0) != 0) { let _: i32 = 1/0; }; if (base64.encodedsize(1) != 4) { let _: i32 = 1/0; }; if (base64.encodedsize(2) != 4) { let _: i32 = 1/0; }; if (base64.encodedsize(3) != 4) { let _: i32 = 1/0; }; if (base64.encodedsize(4) != 8) { let _: i32 = 1/0; }; if (base64.encodedsize(6) != 8) { let _: i32 = 1/0; }; if (base64.encodedsize(7) != 12) { let _: i32 = 1/0; }; if (base64.decodedsize(4) != 3) { let _: i32 = 1/0; }; if (base64.decodedsize(8) != 6) { let _: i32 = 1/0; }; }; export fn main() i32 = { rfc4648_vectors(); rfc4648_decode(); alphabet_full(); invalid_inputs(); urlsafe_roundtrip(); sizes(); return 0; };