// encoding/base64 — RFC 4648 base64 encode/decode, buffer-based. // // Mirrors Hare's encoding::base64 surface, modulo Hare's stream-based // encoder/decoder. ww ships the in-memory subset only: `encode(dst, // src)` writes the encoded bytes into `dst`, returning the count; // `decode(dst, src)` writes the decoded bytes into `dst`, returning a // count or invalid. // // std uses '+' and '/' for indexes 62 and 63 (the RFC 4648 §4 // alphabet); url uses '-' and '_' (the §5 url-safe alphabet). Both // pad encoded output with '=' to a multiple of 4 bytes. // invalid — input was not well-formed base64 (bad char, wrong length, // padding error). Payload is the byte index of the first offending // position. Matches Hare's errors::invalid pairing with strconv. export type invalid = !i32; // encodedsize — bytes required to encode `n` source bytes (including // '=' padding). Hare names it the same. export fn encodedsize(n: i32) i32 = { if (n == 0) { return 0; }; return ((n - 1) / 3 + 1) * 4; }; // decodedsize — upper bound on the number of bytes decoded from `n` // encoded bytes. The exact count depends on padding; callers consult // the i32 returned by `decode`. export fn decodedsize(n: i32) i32 = { return (n / 4) * 3; }; // encchar — map a 6-bit value to its alphabet character. `urlsafe` // chooses '-'/'_' instead of '+'/'/' for 62/63. fn encchar(v: u8, urlsafe: bool) u8 = { if (v < 26u8) { return v + 65u8; }; // 'A' + v if (v < 52u8) { return v + 71u8; }; // 'a' + (v - 26) = v + 71 if (v < 62u8) { return v - 4u8; }; // '0' + (v - 52) = v - 4 if (v == 62u8) { if (urlsafe) { return 45u8; }; // '-' return 43u8; // '+' }; if (urlsafe) { return 95u8; }; // '_' return 47u8; // '/' }; // decchar — inverse of encchar. Returns 0..63 on success or 255 on // invalid char. '=' is handled in the decode loop, not here. fn decchar(c: u8, urlsafe: bool) u8 = { if (c >= 65u8) { if (c <= 90u8) { return c - 65u8; }; }; // 'A'..'Z' if (c >= 97u8) { if (c <= 122u8) { return c - 71u8; }; }; // 'a'..'z' if (c >= 48u8) { if (c <= 57u8) { return c + 4u8; }; }; // '0'..'9' if (urlsafe) { if (c == 45u8) { return 62u8; }; // '-' if (c == 95u8) { return 63u8; }; // '_' } else { if (c == 43u8) { return 62u8; }; // '+' if (c == 47u8) { return 63u8; }; // '/' }; return 255u8; }; // encodeinto — encode `src` into `dst` using the std (`urlsafe=false`) // or url-safe (`urlsafe=true`) alphabet. `dst` must hold at least // encodedsize(src.len) bytes. Returns the number of bytes written. fn encodeinto(dst: []u8, src: []u8, urlsafe: bool) i32 = { let i: i32 = 0; let j: i32 = 0; for (i + 2 < src.len) { let b0: u8 = src[i]; let b1: u8 = src[i + 1]; let b2: u8 = src[i + 2]; dst[j] = encchar(b0 >> 2u8, urlsafe); dst[j + 1] = encchar(((b0 & 3u8) << 4u8) | (b1 >> 4u8), urlsafe); dst[j + 2] = encchar(((b1 & 15u8) << 2u8) | (b2 >> 6u8), urlsafe); dst[j + 3] = encchar(b2 & 63u8, urlsafe); i += 3; j += 4; }; let rem: i32 = src.len - i; if (rem == 1) { let b0: u8 = src[i]; dst[j] = encchar(b0 >> 2u8, urlsafe); dst[j + 1] = encchar((b0 & 3u8) << 4u8, urlsafe); dst[j + 2] = 61u8; // '=' dst[j + 3] = 61u8; // '=' j += 4; }; if (rem == 2) { let b0: u8 = src[i]; let b1: u8 = src[i + 1]; dst[j] = encchar(b0 >> 2u8, urlsafe); dst[j + 1] = encchar(((b0 & 3u8) << 4u8) | (b1 >> 4u8), urlsafe); dst[j + 2] = encchar((b1 & 15u8) << 2u8, urlsafe); dst[j + 3] = 61u8; // '=' j += 4; }; return j; }; // encode — encode `src` into `dst` using the std alphabet. Returns // the number of bytes written. `dst` must hold at least // encodedsize(src.len) bytes. export fn encode(dst: []u8, src: []u8) i32 = { return encodeinto(dst, src, false); }; // encodeurl — same as encode but uses the url-safe alphabet ('-'/'_' // for 62/63). export fn encodeurl(dst: []u8, src: []u8) i32 = { return encodeinto(dst, src, true); }; // decodeinto — decode base64 `src` into `dst`. `dst` must hold at // least decodedsize(src.len) bytes. Returns the number of bytes // written, or invalid with the offending source index. fn decodeinto(dst: []u8, src: []u8, urlsafe: bool) (i32 | invalid) = { if (src.len == 0) { return 0; }; if ((src.len & 3) != 0) { return src.len: invalid; }; let i: i32 = 0; let j: i32 = 0; let end: i32 = src.len; for (i < end) { let c0: u8 = src[i]; let c1: u8 = src[i + 1]; let c2: u8 = src[i + 2]; let c3: u8 = src[i + 3]; let v0: u8 = decchar(c0, urlsafe); let v1: u8 = decchar(c1, urlsafe); if (v0 == 255u8) { return i: invalid; }; if (v1 == 255u8) { return (i + 1): invalid; }; // Last quad may carry '=' padding. if (i + 4 == end) { if (c2 == 61u8) { // "XX==" if (c3 != 61u8) { return (i + 3): invalid; }; dst[j] = (v0 << 2u8) | (v1 >> 4u8); j += 1; i += 4; return j; }; let v2: u8 = decchar(c2, urlsafe); if (v2 == 255u8) { return (i + 2): invalid; }; if (c3 == 61u8) { // "XXX=" dst[j] = (v0 << 2u8) | (v1 >> 4u8); dst[j + 1] = (v1 << 4u8) | (v2 >> 2u8); j += 2; i += 4; return j; }; let v3: u8 = decchar(c3, urlsafe); if (v3 == 255u8) { return (i + 3): invalid; }; dst[j] = (v0 << 2u8) | (v1 >> 4u8); dst[j + 1] = (v1 << 4u8) | (v2 >> 2u8); dst[j + 2] = (v2 << 6u8) | v3; j += 3; i += 4; return j; }; let v2: u8 = decchar(c2, urlsafe); let v3: u8 = decchar(c3, urlsafe); if (v2 == 255u8) { return (i + 2): invalid; }; if (v3 == 255u8) { return (i + 3): invalid; }; dst[j] = (v0 << 2u8) | (v1 >> 4u8); dst[j + 1] = (v1 << 4u8) | (v2 >> 2u8); dst[j + 2] = (v2 << 6u8) | v3; i += 4; j += 3; }; return j; }; // decode — decode std-alphabet base64 from `src` into `dst`. Returns // the count of decoded bytes, or invalid on a malformed input. export fn decode(dst: []u8, src: []u8) (i32 | invalid) = { return decodeinto(dst, src, false); }; // decodeurl — same as decode but accepts the url-safe alphabet. export fn decodeurl(dst: []u8, src: []u8) (i32 | invalid) = { return decodeinto(dst, src, true); };