Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

testFuzzedRawInput

Compress.testFuzzedRawInput
fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, smith: *std.testing.Smith) !void

File

lib/std/compress/flate/Compress.zig:1835

Code

fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, smith: *std.testing.Smith) !void {
    @disableInstrumentation();
    const HashedStoreWriter = struct {
        writer: Writer,
        state: enum {
            header,
            block_header,
            block_body,
            final_block_body,
            footer,
            end,
        },
        block_remaining: u16,
        container: flate.Container,
        data_hash: flate.Container.Hasher,
        data_size: usize,
        footer_hash: u32,
        footer_size: u32,

        pub fn init(buf: []u8, container: flate.Container) @This() {
            return .{
                .writer = .{
                    .vtable = &.{
                        .drain = @This().drain,
                        .flush = @This().flush,
                    },
                    .buffer = buf,
                },
                .state = .header,
                .block_remaining = 0,
                .container = container,
                .data_hash = .init(container),
                .data_size = 0,
                .footer_hash = undefined,
                .footer_size = undefined,
            };
        }

        /// Note that this implementation is somewhat dependent on the implementation of
        /// `Raw` by expecting headers / footers to be continous in data elements. It
        /// also expects the header to be the same as `flate.Container.header` and for
        /// multiple streams to not be concatenated.
        fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
            errdefer w.* = .failing;
            var h: *@This() = @fieldParentPtr("writer", w);

            var rem_splat = splat;
            var rem_data = data;
            var rem_data_elem: []const u8 = w.buffered();

            data_loop: while (true) {
                const wanted = switch (h.state) {
                    .header => h.container.headerSize(),
                    .block_header => 5,
                    .block_body, .final_block_body => h.block_remaining,
                    .footer => h.container.footerSize(),
                    .end => 1,
                };

                if (wanted != 0) {
                    while (rem_data_elem.len == 0) {
                        rem_data_elem = rem_data[0];
                        if (rem_data.len != 1) {
                            rem_data = rem_data[1..];
                        } else {
                            if (rem_splat == 0) {
                                break :data_loop;
                            } else {
                                rem_splat -= 1;
                            }
                        }
                    }
                }

                const bytes = Io.Limit.limited(wanted).sliceConst(rem_data_elem);
                rem_data_elem = rem_data_elem[bytes.len..];

                switch (h.state) {
                    .header => {
                        if (bytes.len < wanted)
                            return error.WriteFailed; // header eos
                        if (!mem.eql(u8, bytes, h.container.header()))
                            return error.WriteFailed; // wrong header
                        h.state = .block_header;
                    },
                    .block_header => {
                        if (bytes.len < wanted)
                            return error.WriteFailed; // store block header eos
                        const header: BlockHeader = @bitCast(@as(u3, @truncate(bytes[0])));
                        if (header.kind != .stored)
                            return error.WriteFailed; // non-store block
                        const len = mem.readInt(u16, bytes[1..3], .little);
                        const nlen = mem.readInt(u16, bytes[3..5], .little);
                        if (nlen != ~len)
                            return error.WriteFailed; // wrong nlen
                        h.block_remaining = len;
                        h.state = if (!header.final) .block_body else .final_block_body;
                    },
                    .block_body, .final_block_body => {
                        h.data_hash.update(bytes);
                        h.data_size += bytes.len;
                        h.block_remaining -= @intCast(bytes.len);
                        if (h.block_remaining == 0) {
                            h.state = if (h.state != .final_block_body) .block_header else .footer;
                        }
                    },
                    .footer => {
                        if (bytes.len < wanted)
                            return error.WriteFailed; // footer eos
                        switch (h.container) {
                            .raw => {},
                            .gzip => {
                                h.footer_hash = mem.readInt(u32, bytes[0..4], .little);
                                h.footer_size = mem.readInt(u32, bytes[4..8], .little);
                            },
                            .zlib => {
                                h.footer_hash = mem.readInt(u32, bytes[0..4], .big);
                            },
                        }
                        h.state = .end;
                    },
                    .end => return error.WriteFailed, // data past end
                }
            }

            w.end = 0;
            return Writer.countSplat(data, splat);
        }

        fn flush(w: *Writer) Writer.Error!void {
            defer w.* = .failing; // Empties buffer even if state hasn't reached `end`
            _ = try @This().drain(w, &.{""}, 0);
        }
    };

    const container = smith.value(flate.Container);
    var output: HashedStoreWriter = .init(&.{}, container);
    var expected_hash: flate.Container.Hasher = .init(container);
    var expected_size: u32 = 0;
    // 10 maximum blocks is the choosen limit since it is two more
    // than the maximum the implementation can output in one drain.
    const max_size = 10 * @as(u32, Raw.max_block_size);

    var raw_buf: [2 * @as(usize, Raw.max_block_size)]u8 = undefined;
    const raw_buf_len = smith.valueWeighted(u32, &.{
        .value(u32, 0, @intCast(raw_buf.len)), // unbuffered
        .rangeAtMost(u32, 0, @intCast(raw_buf.len), 1),
    });
    var raw: Raw = try .init(&output.writer, raw_buf[0..raw_buf_len], container);

    const data_buf_len: u32 = @intCast(data_buf.len);
    var vecs: [32][]const u8 = undefined;
    var vecs_n: usize = 0;

    while (true) {
        const Op = packed struct {
            drain: bool = false,
            add_vec: bool = false,
            rebase: enum(u2) { none, rebase, flush } = .none,

            pub const drain_only: @This() = .{ .drain = true };
            pub const add_vec_only: @This() = .{ .add_vec = true };
            pub const add_vec_and_drain: @This() = .{ .add_vec = true, .drain = true };
            pub const drain_and_rebase: @This() = .{ .drain = true, .rebase = .rebase };
            pub const drain_and_flush: @This() = .{ .drain = true, .rebase = .flush };
        };

        const is_eos = expected_size == max_size or smith.eosWeightedSimple(7, 1);
        var op: Op = if (!is_eos) smith.valueWeighted(Op, &.{
            .value(Op, .add_vec_only, 5),
            .value(Op, .add_vec_and_drain, 1),
            .value(Op, .drain_and_rebase, 1),
            .value(Op, .drain_and_flush, 1),
        }) else .drain_only;

        if (op.add_vec) {
            const max_write = max_size - expected_size;
            const buffered: u32 = @intCast(raw.writer.buffered().len + countVec(vecs[0..vecs_n]));
            const to_align = Raw.max_block_size - buffered % Raw.max_block_size;
            assert(to_align != 0); // otherwise, not helpful.

            const max_data = @min(data_buf_len, max_write);
            const len = smith.valueWeighted(u32, &.{
                .rangeAtMost(u32, 0, max_data, 1),
                .rangeAtMost(u32, 0, @min(Raw.max_block_size, max_data), 4),
                .value(u32, @min(to_align, max_data), max_data), // @min 2nd arg is an edge-case
            });
            const off = smith.valueRangeAtMost(u32, 0, data_buf_len - len);

            expected_size += len;
            vecs[vecs_n] = data_buf[off..][0..len];
            vecs_n += 1;
            op.drain |= vecs_n == vecs.len;
        }

        op.drain |= is_eos;
        op.drain &= vecs_n != 0;
        if (op.drain) {
            const pattern_len: u32 = @intCast(vecs[vecs_n - 1].len);
            const pattern_len_z = @max(pattern_len, 1);

            const max_write = max_size - (expected_size - pattern_len);
            const buffered: u32 = @intCast(raw.writer.buffered().len + countVec(vecs[0 .. vecs_n - 1]));
            const to_align = Raw.max_block_size - buffered % Raw.max_block_size;
            assert(to_align != 0); // otherwise, not helpful.

            const max_splat = max_write / pattern_len_z;
            const weights: [3]std.testing.Smith.Weight = .{
                .rangeAtMost(u32, 0, max_splat, 1),
                .rangeAtMost(u32, 0, @min(
                    Raw.max_block_size + pattern_len_z,
                    max_write,
                ) / pattern_len_z, 4),
                .value(u32, to_align / pattern_len_z, max_splat * 4),
            };
            const align_weight = to_align % pattern_len_z == 0 and to_align <= max_write;
            const n_weights = @as(u8, 2) + @intFromBool(align_weight);
            const splat = smith.valueWeighted(u32, weights[0..n_weights]);

            expected_size = expected_size - pattern_len + pattern_len * splat; // splat may be zero
            for (vecs[0 .. vecs_n - 1]) |v| expected_hash.update(v);
            for (0..splat) |_| expected_hash.update(vecs[vecs_n - 1]);
            try raw.writer.writeSplatAll(vecs[0..vecs_n], splat);
            vecs_n = 0;
        }

        switch (op.rebase) {
            .none => {},
            .rebase => {
                const capacity = smith.valueRangeAtMost(u32, 0, raw_buf_len);
                const preserve = smith.valueRangeAtMost(u32, 0, raw_buf_len - capacity);
                try raw.writer.rebase(preserve, capacity);
            },
            .flush => try raw.writer.flush(),
        }

        if (is_eos) break;
    }

    try raw.finish();
    try output.writer.flush();

    try std.testing.expectEqual(.end, output.state);
    try std.testing.expectEqual(expected_size, output.data_size);
    switch (output.data_hash) {
        .raw => {},
        .gzip => |gz| {
            const expected_crc = expected_hash.gzip.crc.final();
            try std.testing.expectEqual(expected_crc, gz.crc.final());
            try std.testing.expectEqual(expected_crc, output.footer_hash);
            try std.testing.expectEqual(expected_size, output.footer_size);
        },
        .zlib => |zl| {
            const expected_adler = expected_hash.zlib.adler;
            try std.testing.expectEqual(expected_adler, zl.adler);
            try std.testing.expectEqual(expected_adler, output.footer_hash);
        },
    }
}