188 lines
6.2 KiB
Plaintext
188 lines
6.2 KiB
Plaintext
// Mirrors Hare's base64 @test fns (ref/hare/encoding/base64/base64.ha:
|
|
// 315,514,601) over the RFC 4648 §10 vectors, table-driven (parallel
|
|
// arrays; tuple-row arrays are blocked by #111). A failing row aborts
|
|
// via the assert/abort builtin (task #5 @test conversion).
|
|
//
|
|
// The streaming decoder (newdecoder) is deferred (#247-sibling), so the
|
|
// decode side is exercised through decodestr only.
|
|
|
|
package base64_test;
|
|
|
|
import encoding.base64;
|
|
import bytes;
|
|
import errors;
|
|
import io;
|
|
import memio;
|
|
import test;
|
|
|
|
|
|
fn streq(a: str, b: str) bool = {
|
|
if (a.len != b.len) { return false; };
|
|
let i: i32 = 0;
|
|
for (i < a.len) {
|
|
if (a[i] != b[i]) { return false; };
|
|
i += 1;
|
|
};
|
|
return true;
|
|
};
|
|
|
|
// enc_check — encode `raw` two ways (the io.handle sink via base64.encode
|
|
// and the string form via base64.encodestr) and assert both equal
|
|
// `expect`. ref/hare/encoding/base64/base64.ha:315.
|
|
fn enc_check(enc: *base64.encoding, raw: []u8, expect: str) void = {
|
|
let out: memio.stream = memio.dynamic();
|
|
match (base64.encode(&out.vt, enc, raw)) {
|
|
case let n: size => { assert(!(n: i32 != raw.len)); };
|
|
case let e: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&out), expect)));
|
|
assert(!(!streq(base64.encodestr(enc, raw), expect)));
|
|
};
|
|
|
|
// dec_check — decodestr(`encoded`) must round-trip back to `raw`.
|
|
// ref/hare/encoding/base64/base64.ha:514.
|
|
fn dec_check(enc: *base64.encoding, encoded: str, raw: []u8) void = {
|
|
match (base64.decodestr(enc, encoded)) {
|
|
case let b: []u8 => { assert(!(!bytes.equal(b, raw))); };
|
|
case let e: errors.invalid => abort();
|
|
};
|
|
};
|
|
|
|
// inval_check — decodestr(`encoded`) must report errors.invalid.
|
|
// ref/hare/encoding/base64/base64.ha:525.
|
|
fn inval_check(enc: *base64.encoding, encoded: str) void = {
|
|
match (base64.decodestr(enc, encoded)) {
|
|
case let b: []u8 => abort();
|
|
case let e: errors.invalid => void;
|
|
};
|
|
};
|
|
|
|
// Inputs are the prefixes of "foobar". The §10 expected encodings
|
|
// contain no '+' / '/', so std and base64url agree on these vectors —
|
|
// both alphabets are driven over the same table here; the std-vs-url
|
|
// distinctness chars are covered separately by urlsafe_distinct().
|
|
|
|
@test fn rfc4648_std() void = {
|
|
let foobar: [6]u8 = ['f', 'o', 'o', 'b', 'a', 'r'];
|
|
let exp: [7]str = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==",
|
|
"Zm9vYmE=", "Zm9vYmFy"];
|
|
let i: i32 = 0;
|
|
for (i <= 6) {
|
|
enc_check(&base64.std_encoding, foobar[0:i], exp[i]);
|
|
dec_check(&base64.std_encoding, exp[i], foobar[0:i]);
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
@test fn rfc4648_url() void = {
|
|
let foobar: [6]u8 = ['f', 'o', 'o', 'b', 'a', 'r'];
|
|
let exp: [7]str = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==",
|
|
"Zm9vYmE=", "Zm9vYmFy"];
|
|
let i: i32 = 0;
|
|
for (i <= 6) {
|
|
enc_check(&base64.url_encoding, foobar[0:i], exp[i]);
|
|
dec_check(&base64.url_encoding, exp[i], foobar[0:i]);
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
// [0xFB, 0xFF, 0xBF] hits the 62/63 alphabet slots: std emits '+'/'/',
|
|
// url emits '-'/'_'. The two encodings must differ, each round-trips
|
|
// under its own alphabet, and each is INVALID under the other (std
|
|
// decmap marks '-'/'_' 0xff and url marks '+'/'/' 0xff).
|
|
|
|
@test fn urlsafe_distinct() void = {
|
|
let raw: [3]u8 = [0xFBu8, 0xFFu8, 0xBFu8];
|
|
let s_std: str = base64.encodestr(&base64.std_encoding, raw[0:3]);
|
|
let s_url: str = base64.encodestr(&base64.url_encoding, raw[0:3]);
|
|
assert(!(streq(s_std, s_url)));
|
|
|
|
dec_check(&base64.std_encoding, s_std, raw[0:3]);
|
|
dec_check(&base64.url_encoding, s_url, raw[0:3]);
|
|
|
|
// cross-alphabet decode must reject the foreign chars.
|
|
inval_check(&base64.std_encoding, s_url);
|
|
inval_check(&base64.url_encoding, s_std);
|
|
};
|
|
|
|
// Table-driven (parallel-array idiom; tuple rows blocked by #111). Each
|
|
// row exercises one malformed class the hand-written validation in
|
|
// decodestr must reject. Cross-alphabet chars are covered separately by
|
|
// urlsafe_distinct(); Hare's all-padding-but-not-multiple-of-4 rows
|
|
// ("=", "===", "=====", base64.ha:527-531) collapse onto the length
|
|
// guard here, so this table pins the multiple-of-4 survivors of every
|
|
// class instead.
|
|
|
|
@test fn decode_invalid() void = {
|
|
let bad: [10]str = [
|
|
"Z", // len % 4 == 1 (lone trailing char)
|
|
"Zg", // len % 4 == 2
|
|
"Zm9", // len % 4 == 3
|
|
"Z@==", // non-alphabet char in data region
|
|
"@Zg=", // bad leading char
|
|
"====", // all padding (no data)
|
|
"A===", // excess '=' (3 pads in the final quad)
|
|
"Z=g=", // '=' not in the trailing position
|
|
"Zg==Zg==", // data after padding (embedded '=')
|
|
"Zm8=Zm8=", // data after padding (embedded '=')
|
|
];
|
|
let i: i32 = 0;
|
|
for (i < len(bad)) {
|
|
inval_check(&base64.std_encoding, bad[i]);
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
@test fn sizes() void = {
|
|
assert(!(base64.encodedsize(0) != 0));
|
|
assert(!(base64.encodedsize(1) != 4));
|
|
assert(!(base64.encodedsize(2) != 4));
|
|
assert(!(base64.encodedsize(3) != 4));
|
|
assert(!(base64.encodedsize(4) != 8));
|
|
assert(!(base64.encodedsize(10) != 16));
|
|
assert(!(base64.encodedsize(119) != 160));
|
|
assert(!(base64.encodedsize(120) != 160));
|
|
assert(!(base64.encodedsize(121) != 164));
|
|
assert(!(base64.encodedsize(122) != 164));
|
|
assert(!(base64.encodedsize(123) != 164));
|
|
assert(!(base64.decodedsize(0) != 0));
|
|
assert(!(base64.decodedsize(4) != 3));
|
|
assert(!(base64.decodedsize(8) != 6));
|
|
assert(!(base64.decodedsize(160) != 120));
|
|
assert(!(base64.decodedsize(164) != 123));
|
|
};
|
|
|
|
// A non-multiple-of-4 input is out of domain and must abort loudly,
|
|
// never return garbage. Pins the restored Hare precondition
|
|
// (ref/hare/encoding/base64/base64.ha:597, assert(sz % 4 == 0)).
|
|
|
|
@test fn decodedsize_unaligned_aborts() void = {
|
|
test.expectabort();
|
|
base64.decodedsize(5i32);
|
|
};
|
|
|
|
@test fn roundtrip_all_bytes() void = {
|
|
let src: [256]u8;
|
|
let i: i32 = 0;
|
|
for (i < 256) {
|
|
src[i] = i: u8;
|
|
i += 1;
|
|
};
|
|
let s: str = base64.encodestr(&base64.std_encoding, src[0:256]);
|
|
match (base64.decodestr(&base64.std_encoding, s)) {
|
|
case let b: []u8 => {
|
|
assert(!(b.len != 256));
|
|
assert(!(!bytes.equal(b, src[0:256])));
|
|
};
|
|
case let e: errors.invalid => abort();
|
|
};
|
|
let u: str = base64.encodestr(&base64.url_encoding, src[0:256]);
|
|
match (base64.decodestr(&base64.url_encoding, u)) {
|
|
case let b: []u8 => {
|
|
assert(!(b.len != 256));
|
|
assert(!(!bytes.equal(b, src[0:256])));
|
|
};
|
|
case let e: errors.invalid => abort();
|
|
};
|
|
};
|