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:
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 { ... };