hare

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

commit 4f4edab4ed674ee54e49130f221b73dd05f2b9ba
parent 232ee41422b9c27b0e189f570a64ab85b731c55f
Author: Ajay R <ar324@disroot.org>
Date:   Thu,  5 May 2022 18:49:47 +0530

encoding::hex: make stream oriented

Signed-off-by: Ajay R <ar324@disroot.org>

Diffstat:
Mcrypto/blake2b/+test.ha | 9+++++----
Mcrypto/ed25519/+test.ha | 6+++---
Mencoding/hex/hex.ha | 161++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mhare/module/manifest.ha | 6+++---
Mtemp/+linux.ha | 3++-
5 files changed, 144 insertions(+), 41 deletions(-)

diff --git a/crypto/blake2b/+test.ha b/crypto/blake2b/+test.ha @@ -13,11 +13,11 @@ use strio; @test fn blake2b() void = { for (let i = 0z; i < len(vectors); i += 1) { - let key = hex::decode(vectors[i].key)!; + let key = hex::decodestr(vectors[i].key)!; defer free(key); - let out = hex::decode(vectors[i].out)!; + let out = hex::decodestr(vectors[i].out)!; defer free(out); - let in = hex::decode(vectors[i].in)!; + let in = hex::decodestr(vectors[i].in)!; defer free(in); let blake = blake2b(key, len(out)); defer hash::close(&blake); @@ -30,7 +30,8 @@ use strio; hash::sum(&blake, sum); let out = strio::dynamic(); defer io::close(&out)!; - hex::encode(&out, sum)!; + let enc = hex::newencoder(&out); + io::write(&enc, sum)!; assert(strio::string(&out) == vectors[i].out); }; diff --git a/crypto/ed25519/+test.ha b/crypto/ed25519/+test.ha @@ -28,16 +28,16 @@ use strings; @test fn golden() void = { // TODO https://todo.sr.ht/~sircmpwn/hare/596 let priv_key: privatekey = [0...]; - let priv_key_u8 = hex::decode("8ed7a797b9cea8a8370d419136bcdf683b759d2e3c6947f17e13e2485aa9d420b49f3a78b1c6a7fca8f3466f33bc0e929f01fba04306c2a7465f46c3759316d9") as []u8; + let priv_key_u8 = hex::decodestr("8ed7a797b9cea8a8370d419136bcdf683b759d2e3c6947f17e13e2485aa9d420b49f3a78b1c6a7fca8f3466f33bc0e929f01fba04306c2a7465f46c3759316d9") as []u8; priv_key[..] = priv_key_u8[..]; free(priv_key_u8); const pub_key = skey_getpublic(&priv_key); - let msg = hex::decode("a750c232933dc14b1184d86d8b4ce72e16d69744ba69818b6ac33b1d823bb2c3") as []u8; + let msg = hex::decodestr("a750c232933dc14b1184d86d8b4ce72e16d69744ba69818b6ac33b1d823bb2c3") as []u8; defer free(msg); let good_sig: privatekey = [0...]; - let sig_u8 = hex::decode("04266c033b91c1322ceb3446c901ffcf3cc40c4034e887c9597ca1893ba7330becbbd8b48142ef35c012c6ba51a66df9308cb6268ad6b1e4b03e70102495790b") as []u8; + let sig_u8 = hex::decodestr("04266c033b91c1322ceb3446c901ffcf3cc40c4034e887c9597ca1893ba7330becbbd8b48142ef35c012c6ba51a66df9308cb6268ad6b1e4b03e70102495790b") as []u8; good_sig[..] = sig_u8[..]; free(sig_u8); diff --git a/encoding/hex/hex.ha b/encoding/hex/hex.ha @@ -4,36 +4,76 @@ // (c) 2021 Eyal Sawady <ecs@d2evs.net> // (c) 2022 Sebastian <sebastian@sebsite.pw> use ascii; +use bufio; use bytes; +use errors; use fmt; use io; +use os; use strconv; use strings; use strio; -// Returned when attempting to decode a string which contains invalid hex -// characters. -export type invalid = !void; +export type encoder = struct { + stream: io::stream, + out: io::handle, + err: (void | io::error), +}; + +const encoder_vtable: io::vtable = io::vtable { + writer = &encode_writer, + ... +}; + +// Creates a stream that encodes writes as lowercase hexadecimal before writing +// them to a secondary stream. Closing this stream will not close the underlying +// stream. +export fn newencoder(out: io::handle) encoder = { + return encoder { + stream = &encoder_vtable, + out = out, + err = void, + }; +}; -// Encodes a byte slice as a hexadecimal string and writes it to an I/O handle. -export fn encode(sink: io::handle, b: []u8) (size | io::error) = { +fn encode_writer(s: *io::stream, in: const []u8) (size | io::error) = { + const s = s: *encoder; + match(s.err) { + case let err: io::error => + return err; + case void => + yield; + }; let z = 0z; - for (let i = 0z; i < len(b); i += 1) { - let s = strconv::u8tosb(b[i], strconv::base::HEX_LOWER); - if (len(s) == 1) { - z += fmt::fprint(sink, "0")?; + for (let i = 0z; i < len(in); i += 1) { + const r = strconv::u8tosb(in[i], strconv::base::HEX_LOWER); + if (len(r) == 1) { + match(fmt::fprint(s.out, "0")) { + case let b: size => + z += b; + case let err: io::error => + s.err = err; + return err; + }; + }; + match(fmt::fprint(s.out, r)) { + case let b: size => + z += b; + case let err: io::error => + s.err = err; + return err; }; - z += fmt::fprint(sink, s)?; }; return z; }; // Encodes a byte slice as a hexadecimal string and returns it. The caller must // free the return value. -export fn encodestr(b: []u8) str = { - let sink = strio::dynamic(); - encode(&sink, b) as size; - return strio::string(&sink); +export fn encodestr(in: []u8) str = { + const out = strio::dynamic(); + const enc = newencoder(&out); + io::writeall(&enc, in)!; + return strio::string(&out); }; @test fn encode() void = { @@ -43,34 +83,95 @@ export fn encodestr(b: []u8) str = { assert(s == "cafebabedeadf00d"); }; -// Decodes a string of hexadecimal bytes into a byte slice. The caller must free -// the return value. -export fn decode(s: str) ([]u8 | invalid) = { - if (len(s) % 2 != 0) { - return invalid; +export type decoder = struct { + stream: io::stream, + in: io::handle, + state: (void | io::EOF | io::error), +}; + +const decoder_vtable: io::vtable = io::vtable { + reader = &decode_reader, + ... +}; + +// Creates a stream that reads and decodes hexadecimal data from a secondary +// stream. This stream does not need to be closed, and closing it will not +// close the underlying stream. +export fn newdecoder(in: io::handle) decoder = { + return decoder { + stream = &decoder_vtable, + in = in, + state = void, + ... }; - let buf: []u8 = alloc([], len(s) / 2); - let s = strings::toutf8(s); - for (let i = 0z; i < len(s) / 2; i += 1) { - let oct = strings::fromutf8_unsafe(s[i * 2..i * 2 + 2]); - let u = match (strconv::stou8b(oct, 16)) { +}; + +fn decode_reader(s: *io::stream, out: []u8) (size | io::EOF | io::error) = { + const s = s: *decoder; + match(s.state) { + case let err: (io::EOF | io::error) => + return err; + case void => + yield; + }; + static let buf: [os::BUFSIZ]u8 = [0...]; + let n = len(out) * 2; + if (n > os::BUFSIZ) { + n = os::BUFSIZ; + }; + let nr = 0z; + for (nr < n) { + match(io::read(s.in, buf[nr..n])) { + case let n: size => + nr += n; + case io::EOF => + s.state = io::EOF; + break; + case let err: io::error => + s.state = err; + return err; + }; + }; + if (nr % 2 != 0) { + s.state = errors::invalid; + return errors::invalid; + }; + const l = nr / 2; + for (let i = 0z; i < l; i += 1) { + const oct = strings::fromutf8_unsafe(buf[i * 2..i * 2 + 2]); + const u = match (strconv::stou8b(oct, 16)) { case (strconv::invalid | strconv::overflow) => - free(buf); - return invalid; + s.state = errors::invalid; + return errors::invalid; case let u: u8 => yield u; }; - append(buf, u); + out[i] = u; + }; + return l; +}; + +// Decodes a string of hexadecimal bytes into a byte slice. The caller must free +// the return value. +export fn decodestr(s: str) ([]u8 | io::error) = { + let s = strings::toutf8(s); + const in = bufio::fixed(s, io::mode::READ); + const decoder = newdecoder(&in); + const out = bufio::dynamic(io::mode::WRITE); + match(io::copy(&out, &decoder)) { + case size => + return bufio::buffer(&out); + case let err: io::error => + return err; }; - return buf; }; @test fn decode() void = { - let s = decode("cafebabedeadf00d") as []u8; + let s = decodestr("cafebabedeadf00d") as []u8; defer free(s); assert(bytes::equal(s, [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xF0, 0x0D])); - decode("this is not hex") as invalid: void; + decodestr("this is not hex") as errors::invalid: void; }; // Outputs a dump of hex data alongside the offset and an ASCII representation diff --git a/hare/module/manifest.ha b/hare/module/manifest.ha @@ -142,7 +142,7 @@ export fn manifest_load(ctx: *context, ident: ast::ident) (manifest | error) = { yield s; }; - let hash = match (hex::decode(hash)) { + let hash = match (hex::decodestr(hash)) { case let b: []u8 => yield b; case => @@ -189,7 +189,7 @@ export fn manifest_load(ctx: *context, ident: ast::ident) (manifest | error) = { case let s: str => yield s; }; - let modhash = match (hex::decode(modhash)) { + let modhash = match (hex::decodestr(modhash)) { case let b: []u8 => yield b; case => @@ -204,7 +204,7 @@ export fn manifest_load(ctx: *context, ident: ast::ident) (manifest | error) = { case let s: str => yield s; }; - let hash = match (hex::decode(hash)) { + let hash = match (hex::decodestr(hash)) { case let b: []u8 => yield b; case => diff --git a/temp/+linux.ha b/temp/+linux.ha @@ -108,7 +108,8 @@ export fn dir() str = { random::buffer(buf[..]); const sink = strio::fixed(name); - hex::encode(&sink, buf) as size; + let enc = hex::newencoder(&sink); + io::write(&enc, buf) as size; const name = strio::string(&sink); static let buf = path::buffer { ... };