feature. See also
. The project being documented here (as the example) is the Zig library itself.
Compress.testFuzzedRawInput
fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, smith: *std.testing.Smith) !void
File
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,
};
}
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;
if (!mem.eql(u8, bytes, h.container.header()))
return error.WriteFailed;
h.state = .block_header;
},
.block_header => {
if (bytes.len < wanted)
return error.WriteFailed;
const header: BlockHeader = @bitCast(@as(u3, @truncate(bytes[0])));
if (header.kind != .stored)
return error.WriteFailed;
const len = mem.readInt(u16, bytes[1..3], .little);
const nlen = mem.readInt(u16, bytes[3..5], .little);
if (nlen != ~len)
return error.WriteFailed;
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;
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,
}
}
w.end = 0;
return Writer.countSplat(data, splat);
}
fn flush(w: *Writer) Writer.Error!void {
defer w.* = .failing;
_ = 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;
// 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)),
.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);
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),
});
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);
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;
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);
},
}
}