diff --git a/lib/hash/hash.ww b/lib/hash/hash.ww new file mode 100644 index 00000000..49fee331 --- /dev/null +++ b/lib/hash/hash.ww @@ -0,0 +1,89 @@ +// hash — the general-purpose hashing-function interface. Port of +// ref/hare/hash/hash.ha. A hash is an io-write-only stream you feed +// data to, plus sum()/reset()/sz()/bsz(). Concrete hashes (crypto/sha256 +// and, eventually, the lib/hash/* checksums) embed [[hash]] as their +// first field so a `*state` is castable to `*hash` for these dispatchers. +// +// The existing lib/hash/* submodules (crc32, siphash, …) are reduced +// one-shot forms WITHOUT this interface; this is the first module to +// ship it, riding the proven io.stream + inline-vtable shape. +// +// DIVERGENCE (inline vtable). Hare's `hash` carries `stream: io::stream` +// — and Hare's `io::stream` is itself `*vtable` (ref/hare/io/stream.ha:33), +// pointing at a separate module-level const vtable. ww's io collapse +// (#94) makes the io dispatchers take the vtable pointer directly +// (handle's stream arm is `stream` = `*vtable`, NOT `*stream`; see +// lib/io/types.ww:53), so the dispatch arg the callbacks receive must BE +// the outer state pointer. That requires the vtable embedded INLINE at +// offset 0, exactly as lib/encoding/base64, lib/memio, and lib/io's own +// callers do it. So `hash` opens with `vt: io.vtable` (inline) rather +// than a `*vtable` field; otherwise the port is 1:1. +package hash; + +import io; +import os; + +// ref/hare/hash/hash.ha:8-25. `vt` (inline io.vtable) replaces Hare's +// `stream: io::stream` per the header divergence — it MUST stay the +// first field so `*state -> *hash -> &h.vt` all alias offset 0. `reset` +// is nullable: Hare's `nullable *fn(...)`; ww spells it `(*fn(...) | void)` +// (#192, the Nullable-kept ruling). `sum`/`reset` are inline `*fn(...)` +// fields, the same shape as lib/errors `strerror` (errors.ww:66). +export type hash = struct { + vt: io.vtable, + sum: *fn(h: *hash, buf: []u8) void, + reset: (*fn(h: *hash) void | void), + sz: size, + bsz: size, +}; + +// write — feed `buf` to the hash. ref/hare/hash/hash.ha:28-29. Hare's +// `io::write(h, buf) as size`; ww has the same `as` but the io.error arm +// is impossible by contract (a hash stream never errors — its writer +// only buffers), so the mismatch aborts rather than propagating. +export fn write(h: *hash, buf: []u8) size = { + match (io.write(&h.vt, buf)) { + case let n: size => return n; + case let e: io.error => + abort("hash.write: hash stream returned an io error"); + }; +}; + +// close — discard the running checksum and release resources. +// ref/hare/hash/hash.ha:32. Hare's `io::close(h)!`. +export fn close(h: *hash) void = { + match (io.close(&h.vt)) { + case void => void; + case let e: io.error => + abort("hash.close: hash stream returned an io error"); + }; +}; + +// sum — write the current digest into `buf`, which must be at least +// [[sz]] bytes. ref/hare/hash/hash.ha:35-38. ww spells Hare's `h.sum(...)` +// as the explicit fn-ptr call `(*h.sum)(...)` (#193). +export fn sum(h: *hash, buf: []u8) void = { + os.assert((buf.len: size) >= h.sz, + "hash.sum buffer does not meet minimum required size for this hash function"); + (*h.sum)(h, buf); +}; + +// reset — return the hash to its initial state. +// ref/hare/hash/hash.ha:41-48. +export fn reset(h: *hash) void = { + match (h.reset) { + case let f: *fn(h: *hash) void => (*f)(h); + case void => abort("this hash cannot be reset"); + }; +}; + +// sz — digest size in bytes, independent of hash state. +// ref/hare/hash/hash.ha:52. +export fn sz(h: *hash) size = { + return h.sz; +}; + +// bsz — internal block size in bytes. ref/hare/hash/hash.ha:55. +export fn bsz(h: *hash) size = { + return h.bsz; +};