hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 31ec76f3816769c34b84cbac52bc7242f1585b73
parent a620fbddc597c066fd59f22998a2257dac973104
Author: Ajay R <ar324@protonmail.com>
Date:   Wed, 23 Feb 2022 09:21:42 +0000

encoding::base32: place encode above decode for consistency with base64

Signed-off-by: Ajay R <ar324@protonmail.com>

Diffstat:
Mencoding/base32/base32.ha | 360++++++++++++++++++++++++++++++++++++++++----------------------------------------
1 file changed, 180 insertions(+), 180 deletions(-)

diff --git a/encoding/base32/base32.ha b/encoding/base32/base32.ha @@ -43,6 +43,186 @@ export fn encoding_init(enc: *encoding, alphabet: str) void = { encoding_init(&hex_encoding, hex_alpha); }; +export type encoder = struct { + io::stream, + out: io::handle, + enc: *encoding, + buf: [4]u8, // leftover input + avail: size, // bytes available in buf + err: (void | io::error), +}; + +// Creates a stream that encodes writes as base-32 before writing them to a +// secondary stream. The encoder stream must be closed to finalize any unwritten +// bytes. Closing this stream will not close the underlying stream. +export fn new_encoder( + enc: *encoding, + out: io::handle, +) encoder = { + return encoder { + writer = &encode_writer, + closer = &encode_closer, + out = out, + enc = enc, + err = void, + ... + }; +}; + +fn encode_writer( + s: *io::stream, + in: const []u8 +) (size | io::error) = { + let s = s: *encoder; + match(s.err) { + case let err: io::error => + return err; + case void => + yield; + }; + let n = 0z; // number of bytes processed + let l = len(in); + let i = 0z; + for (i + 4 < l + s.avail; i += 5) { + static let b: [5]u8 = [0...]; // 5 bytes -> (enc) 8 bytes + if (i < s.avail) { + for (let j = 0z; j < s.avail; j += 1) { + b[j] = s.buf[i]; + }; + for (let j = s.avail; j < 5; j += 1) { + b[j] = in[j - s.avail]; + }; + } else { + for (let j = 0z; j < 5; j += 1) { + b[j] = in[j - s.avail + i]; + }; + }; + let encb: [8]u8 = [ + s.enc.encmap[b[0] >> 3], + s.enc.encmap[(b[0] & 0x7) << 2 | (b[1] & 0xC0) >> 6], + s.enc.encmap[(b[1] & 0x3E) >> 1], + s.enc.encmap[(b[1] & 0x1) << 4 | (b[2] & 0xF0) >> 4], + s.enc.encmap[(b[2] & 0xF) << 1 | (b[3] & 0x80) >> 7], + s.enc.encmap[(b[3] & 0x7C) >> 2], + s.enc.encmap[(b[3] & 0x3) << 3 | (b[4] & 0xE0) >> 5], + s.enc.encmap[b[4] & 0x1F], + ]; + match(io::write(s.out, encb)) { + case let err: io::error => + s.err = err; + return err; + case size => + yield; + }; + n += 5; + }; + // storing leftover bytes + if (l + s.avail < 5) { + for (let j = s.avail; j < s.avail + l; j += 1) { + s.buf[j] = in[j - s.avail]; + }; + } else { + const begin = (l + s.avail) / 5 * 5; + for (let j = begin; j < l + s.avail; j += 1) { + s.buf[j - begin] = in[j - s.avail]; + }; + }; + s.avail = (l + s.avail) % 5; + return n; +}; + +fn encode_closer(s: *io::stream) void = { + let s = s: *encoder; + if (s.avail == 0) { + return; + }; + static let b: [5]u8 = [0...]; // the 5 bytes that will be encoded into 8 bytes + for (let i = 0z; i < 5; i += 1) { + b[i] = if (i < s.avail) s.buf[i] else 0; + }; + let encb: [8]u8 = [ + s.enc.encmap[b[0] >> 3], + s.enc.encmap[(b[0] & 0x7) << 2 | (b[1] & 0xC0) >> 6], + s.enc.encmap[(b[1] & 0x3E) >> 1], + s.enc.encmap[(b[1] & 0x1) << 4 | (b[2] & 0xF0) >> 4], + s.enc.encmap[(b[2] & 0xF) << 1 | (b[3] & 0x80) >> 7], + s.enc.encmap[(b[3] & 0x7C) >> 2], + s.enc.encmap[(b[3] & 0x3) << 3 | (b[4] & 0xE0) >> 5], + s.enc.encmap[b[4] & 0x1F], + ]; + // adding padding as input length was not a multiple of 5 + // 0 1 2 3 4 + static const npa: []u8 = [0, 6, 4, 3, 1]; + const np = npa[s.avail]; + for (let i = 0z; i < np; i += 1) { + encb[7 - i] = PADDING; + }; + io::write(s.out, encb)!; // TODO https://todo.sr.ht/~sircmpwn/hare/568 +}; + +// Encodes a byte slice in base-32, using the given encoding, returning a slice +// of ASCII bytes. The caller must free the return value. +export fn encodeslice(enc: *encoding, in: []u8) []u8 = { + let out = bufio::dynamic(io::mode::WRITE); + let encoder = new_encoder(enc, &out); + io::write(&encoder, in)!; + io::close(&encoder); + return bufio::buffer(&out); +}; + +// Encodes a byte slice in base-32, using the given encoding, returning a +// string. The caller must free the return value. +export fn encodestr(enc: *encoding, in: []u8) str = { + return strings::fromutf8(encodeslice(enc, in)); +}; + +@test fn encode() void = { + // RFC 4648 test vectors + const in: [_]u8 = ['f', 'o', 'o', 'b', 'a', 'r']; + const expect: [_]str = [ + "", + "MY======", + "MZXQ====", + "MZXW6===", + "MZXW6YQ=", + "MZXW6YTB", + "MZXW6YTBOI======", + ]; + const expect_hex: [_]str = [ + "", + "CO======", + "CPNG====", + "CPNMU===", + "CPNMUOG=", + "CPNMUOJ1", + "CPNMUOJ1E8======", + ]; + for (let i = 0z; i <= len(in); i += 1) { + let out = bufio::dynamic(io::mode::RDWR); + let enc = new_encoder(&std_encoding, &out); + io::write(&enc, in[..i]) as size; + io::close(&enc); + let outb = bufio::buffer(&out); + assert(bytes::equal(outb, strings::toutf8(expect[i]))); + free(outb); + // Testing encodestr should cover encodeslice too + let s = encodestr(&std_encoding, in[..i]); + defer free(s); + assert(s == expect[i]); + + out = bufio::dynamic(io::mode::RDWR); + enc = new_encoder(&hex_encoding, &out); + io::write(&enc, in[..i]) as size; + io::close(&enc); + let outb = bufio::buffer(&out); + assert(bytes::equal(outb, strings::toutf8(expect_hex[i]))); + free(outb); + let s = encodestr(&hex_encoding, in[..i]); + defer free(s); + assert(s == expect_hex[i]); + }; +}; + export type decoder = struct { io::stream, in: io::handle, @@ -284,183 +464,3 @@ export fn decodestr(enc: *encoding, in: str) ([]u8 | errors::invalid) = { assert(decodestr(invalid[i].1, invalid[i].0) is errors::invalid); }; }; - -export type encoder = struct { - io::stream, - out: io::handle, - enc: *encoding, - buf: [4]u8, // leftover input - avail: size, // bytes available in buf - err: (void | io::error), -}; - -// Creates a stream that encodes writes as base-32 before writing them to a -// secondary stream. The encoder stream must be closed to finalize any unwritten -// bytes. Closing this stream will not close the underlying stream. -export fn new_encoder( - enc: *encoding, - out: io::handle, -) encoder = { - return encoder { - writer = &encode_writer, - closer = &encode_closer, - out = out, - enc = enc, - err = void, - ... - }; -}; - -fn encode_writer( - s: *io::stream, - in: const []u8 -) (size | io::error) = { - let s = s: *encoder; - match(s.err) { - case let err: io::error => - return err; - case void => - yield; - }; - let n = 0z; // number of bytes processed - let l = len(in); - let i = 0z; - for (i + 4 < l + s.avail; i += 5) { - static let b: [5]u8 = [0...]; // 5 bytes -> (enc) 8 bytes - if (i < s.avail) { - for (let j = 0z; j < s.avail; j += 1) { - b[j] = s.buf[i]; - }; - for (let j = s.avail; j < 5; j += 1) { - b[j] = in[j - s.avail]; - }; - } else { - for (let j = 0z; j < 5; j += 1) { - b[j] = in[j - s.avail + i]; - }; - }; - let encb: [8]u8 = [ - s.enc.encmap[b[0] >> 3], - s.enc.encmap[(b[0] & 0x7) << 2 | (b[1] & 0xC0) >> 6], - s.enc.encmap[(b[1] & 0x3E) >> 1], - s.enc.encmap[(b[1] & 0x1) << 4 | (b[2] & 0xF0) >> 4], - s.enc.encmap[(b[2] & 0xF) << 1 | (b[3] & 0x80) >> 7], - s.enc.encmap[(b[3] & 0x7C) >> 2], - s.enc.encmap[(b[3] & 0x3) << 3 | (b[4] & 0xE0) >> 5], - s.enc.encmap[b[4] & 0x1F], - ]; - match(io::write(s.out, encb)) { - case let err: io::error => - s.err = err; - return err; - case size => - yield; - }; - n += 5; - }; - // storing leftover bytes - if (l + s.avail < 5) { - for (let j = s.avail; j < s.avail + l; j += 1) { - s.buf[j] = in[j - s.avail]; - }; - } else { - const begin = (l + s.avail) / 5 * 5; - for (let j = begin; j < l + s.avail; j += 1) { - s.buf[j - begin] = in[j - s.avail]; - }; - }; - s.avail = (l + s.avail) % 5; - return n; -}; - -fn encode_closer(s: *io::stream) void = { - let s = s: *encoder; - if (s.avail == 0) { - return; - }; - static let b: [5]u8 = [0...]; // the 5 bytes that will be encoded into 8 bytes - for (let i = 0z; i < 5; i += 1) { - b[i] = if (i < s.avail) s.buf[i] else 0; - }; - let encb: [8]u8 = [ - s.enc.encmap[b[0] >> 3], - s.enc.encmap[(b[0] & 0x7) << 2 | (b[1] & 0xC0) >> 6], - s.enc.encmap[(b[1] & 0x3E) >> 1], - s.enc.encmap[(b[1] & 0x1) << 4 | (b[2] & 0xF0) >> 4], - s.enc.encmap[(b[2] & 0xF) << 1 | (b[3] & 0x80) >> 7], - s.enc.encmap[(b[3] & 0x7C) >> 2], - s.enc.encmap[(b[3] & 0x3) << 3 | (b[4] & 0xE0) >> 5], - s.enc.encmap[b[4] & 0x1F], - ]; - // adding padding as input length was not a multiple of 5 - // 0 1 2 3 4 - static const npa: []u8 = [0, 6, 4, 3, 1]; - const np = npa[s.avail]; - for (let i = 0z; i < np; i += 1) { - encb[7 - i] = PADDING; - }; - io::write(s.out, encb)!; // TODO https://todo.sr.ht/~sircmpwn/hare/568 -}; - -// Encodes a byte slice in base-32, using the given encoding, returning a slice -// of ASCII bytes. The caller must free the return value. -export fn encodeslice(enc: *encoding, in: []u8) []u8 = { - let out = bufio::dynamic(io::mode::WRITE); - let encoder = new_encoder(enc, &out); - io::write(&encoder, in)!; - io::close(&encoder); - return bufio::buffer(&out); -}; - -// Encodes a byte slice in base-32, using the given encoding, returning a -// string. The caller must free the return value. -export fn encodestr(enc: *encoding, in: []u8) str = { - return strings::fromutf8(encodeslice(enc, in)); -}; - -@test fn encode() void = { - // RFC 4648 test vectors - const in: [_]u8 = ['f', 'o', 'o', 'b', 'a', 'r']; - const expect: [_]str = [ - "", - "MY======", - "MZXQ====", - "MZXW6===", - "MZXW6YQ=", - "MZXW6YTB", - "MZXW6YTBOI======", - ]; - const expect_hex: [_]str = [ - "", - "CO======", - "CPNG====", - "CPNMU===", - "CPNMUOG=", - "CPNMUOJ1", - "CPNMUOJ1E8======", - ]; - for (let i = 0z; i <= len(in); i += 1) { - let out = bufio::dynamic(io::mode::RDWR); - let enc = new_encoder(&std_encoding, &out); - io::write(&enc, in[..i]) as size; - io::close(&enc); - let outb = bufio::buffer(&out); - assert(bytes::equal(outb, strings::toutf8(expect[i]))); - free(outb); - // Testing encodestr should cover encodeslice too - let s = encodestr(&std_encoding, in[..i]); - defer free(s); - assert(s == expect[i]); - - out = bufio::dynamic(io::mode::RDWR); - enc = new_encoder(&hex_encoding, &out); - io::write(&enc, in[..i]) as size; - io::close(&enc); - let outb = bufio::buffer(&out); - assert(bytes::equal(outb, strings::toutf8(expect_hex[i]))); - free(outb); - let s = encodestr(&hex_encoding, in[..i]); - defer free(s); - assert(s == expect_hex[i]); - }; -};