DoxigAlpha

Hashed

Provides a Writer implementation based on calling Hasher.update, sending all data also to an underlying Writer.

When using this, the underlying writer is best unbuffered because all writes are passed on directly to it.

This implementation makes suboptimal buffering decisions due to being generic. A better solution will involve creating a writer for each hash function, where the splat buffer can be tailored to the hash implementation details.

Contrast with Hashing which terminates the stream pipeline.

Fields of this type

Fields

#
out:*Writer
hasher:Hasher

Functions in this namespace

Functions

#

Source

Implementation

#
pub fn Hashed(comptime Hasher: type) type {
    return struct {
        out: *Writer,
        hasher: Hasher,
        writer: Writer,

        pub fn init(out: *Writer, buffer: []u8) @This() {
            return .initHasher(out, .{}, buffer);
        }

        pub fn initHasher(out: *Writer, hasher: Hasher, buffer: []u8) @This() {
            return .{
                .out = out,
                .hasher = hasher,
                .writer = .{
                    .buffer = buffer,
                    .vtable = &.{ .drain = @This().drain },
                },
            };
        }

        fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
            const this: *@This() = @alignCast(@fieldParentPtr("writer", w));
            const aux = w.buffered();
            const aux_n = try this.out.writeSplatHeader(aux, data, splat);
            if (aux_n < w.end) {
                this.hasher.update(w.buffer[0..aux_n]);
                const remaining = w.buffer[aux_n..w.end];
                @memmove(w.buffer[0..remaining.len], remaining);
                w.end = remaining.len;
                return 0;
            }
            this.hasher.update(aux);
            const n = aux_n - w.end;
            w.end = 0;
            var remaining: usize = n;
            for (data[0 .. data.len - 1]) |slice| {
                if (remaining <= slice.len) {
                    this.hasher.update(slice[0..remaining]);
                    return n;
                }
                remaining -= slice.len;
                this.hasher.update(slice);
            }
            const pattern = data[data.len - 1];
            assert(remaining <= splat * pattern.len);
            switch (pattern.len) {
                0 => {
                    assert(remaining == 0);
                },
                1 => {
                    var buffer: [64]u8 = undefined;
                    @memset(&buffer, pattern[0]);
                    while (remaining > 0) {
                        const update_len = @min(remaining, buffer.len);
                        this.hasher.update(buffer[0..update_len]);
                        remaining -= update_len;
                    }
                },
                else => {
                    while (remaining > 0) {
                        const update_len = @min(remaining, pattern.len);
                        this.hasher.update(pattern[0..update_len]);
                        remaining -= update_len;
                    }
                },
            }
            return n;
        }
    };
}