Does not compress data
pub const Raw = struct
pub const Raw = struct {
/// After `finish` is called, all vtable calls with result in `error.WriteFailed`.
writer: Writer,
output: *Writer,
hasher: flate.Container.Hasher,
const max_block_size: u16 = 65535;
const full_header: [5]u8 = .{
BlockHeader.int(.{ .final = false, .kind = .stored }),
255,
255,
0,
0,
};
/// While there is no minimum buffer size, it is recommended
/// to be at least `flate.max_window_len` for optimal output.
pub fn init(output: *Writer, buffer: []u8, container: flate.Container) Writer.Error!Raw {
try output.writeAll(container.header());
return .{
.writer = .{
.buffer = buffer,
.vtable = &.{
.drain = Raw.drain,
.flush = Raw.flush,
.rebase = Raw.rebase,
},
},
.output = output,
.hasher = .init(container),
};
}
fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
errdefer w.* = .failing;
const r: *Raw = @fieldParentPtr("writer", w);
const min_block = @min(w.buffer.len, max_block_size);
const pattern = data[data.len - 1];
var partial_header: [5]u8 = undefined;
var vecs: [16][]const u8 = undefined;
var vecs_n: usize = 0;
const data_bytes = Writer.countSplat(data, splat);
const total_bytes = w.end + data_bytes;
var rem_bytes = total_bytes;
var rem_splat = splat;
var rem_data = data;
var rem_data_elem: []const u8 = w.buffered();
assert(rem_bytes > min_block);
while (rem_bytes > min_block) { // not >= to allow `min_block` blocks to be marked as final
// also, it handles the case of `min_block` being zero (no buffer)
const block_size: u16 = @min(rem_bytes, max_block_size);
rem_bytes -= block_size;
if (vecs_n == vecs.len) {
try r.output.writeVecAll(&vecs);
vecs_n = 0;
}
vecs[vecs_n] = if (block_size == 65535)
&full_header
else header: {
partial_header[0] = BlockHeader.int(.{ .final = false, .kind = .stored });
mem.writeInt(u16, partial_header[1..3], block_size, .little);
mem.writeInt(u16, partial_header[3..5], ~block_size, .little);
break :header &partial_header;
};
vecs_n += 1;
var block_limit: Io.Limit = .limited(block_size);
while (true) {
if (vecs_n == vecs.len) {
try r.output.writeVecAll(&vecs);
vecs_n = 0;
}
const vec = block_limit.sliceConst(rem_data_elem);
vecs[vecs_n] = vec;
vecs_n += 1;
r.hasher.update(vec);
const is_pattern = rem_splat != splat and vec.len == pattern.len;
if (is_pattern) assert(pattern.len != 0); // exceeded countSplat
if (!is_pattern or rem_splat == 0 or pattern.len > @backingInt(block_limit) / 2) {
rem_data_elem = rem_data_elem[vec.len..];
block_limit = block_limit.subtract(vec.len).?;
if (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) {
rem_splat -= 1;
} else {
// All of `data` has been consumed.
assert(block_limit == .nothing);
assert(rem_bytes == 0);
// Since `rem_bytes` and `block_limit` are zero, these won't be used.
rem_data = undefined;
rem_data_elem = undefined;
rem_splat = undefined;
}
}
if (block_limit == .nothing) break;
} else {
const out_splat = @backingInt(block_limit) / pattern.len;
assert(out_splat >= 2);
try r.output.writeSplatAll(vecs[0..vecs_n], out_splat);
for (1..out_splat) |_| r.hasher.update(vec);
vecs_n = 0;
block_limit = block_limit.subtract(pattern.len * out_splat).?;
if (rem_splat >= out_splat) {
// `out_splat` contains `rem_data`, however one more needs subtracted
// anyways since the next pattern is also being taken.
rem_splat -= out_splat;
} else {
// All of `data` has been consumed.
assert(block_limit == .nothing);
assert(rem_bytes == 0);
// Since `rem_bytes` and `block_limit` are zero, these won't be used.
rem_data = undefined;
rem_data_elem = undefined;
rem_splat = undefined;
}
if (block_limit == .nothing) break;
}
}
}
if (vecs_n != 0) { // can be the case if a splat was sent
try r.output.writeVecAll(vecs[0..vecs_n]);
}
if (rem_bytes > data_bytes) {
assert(rem_bytes - data_bytes == rem_data_elem.len);
assert(&rem_data_elem[0] == &w.buffer[total_bytes - rem_bytes]);
}
return w.consume(total_bytes - rem_bytes);
}
fn flush(w: *Writer) Writer.Error!void {
errdefer w.* = .failing;
try Raw.rebaseInner(w, 0, w.buffer.len, false);
}
pub fn finish(r: *Raw) Writer.Error!void {
defer r.writer = .failing;
try Raw.rebaseInner(&r.writer, 0, r.writer.buffer.len, true);
// The footer is written in `rebaseInner` as part of the write vector
}
fn rebase(w: *Writer, preserve: usize, capacity: usize) Writer.Error!void {
errdefer w.* = .failing;
try Raw.rebaseInner(w, preserve, capacity, false);
}
fn rebaseInner(w: *Writer, preserve: usize, capacity: usize, eos: bool) Writer.Error!void {
const r: *Raw = @fieldParentPtr("writer", w);
assert(preserve + capacity <= w.buffer.len);
if (eos) assert(capacity == w.buffer.len);
var partial_header: [5]u8 = undefined;
var footer_buf: [8]u8 = undefined;
const preserved = @min(w.end, preserve);
var remaining = w.buffer[0 .. w.end - preserved];
var vecs: [16][]const u8 = undefined;
var vecs_n: usize = 0;
while (remaining.len > max_block_size) { // not >= so there is always a block down below
if (vecs_n == vecs.len) {
try r.output.writeVecAll(&vecs);
vecs_n = 0;
}
vecs[vecs_n + 0] = &full_header;
vecs[vecs_n + 1] = remaining[0..max_block_size];
r.hasher.update(vecs[vecs_n + 1]);
vecs_n += 2;
remaining = remaining[max_block_size..];
}
// eos check required for empty block
if (w.buffer.len - (remaining.len + preserved) < capacity or eos) {
// A partial write is necessary to reclaim enough buffer space
const block_size: u16 = @intCast(remaining.len);
partial_header[0] = BlockHeader.int(.{ .final = eos, .kind = .stored });
mem.writeInt(u16, partial_header[1..3], block_size, .little);
mem.writeInt(u16, partial_header[3..5], ~block_size, .little);
if (vecs_n == vecs.len) {
try r.output.writeVecAll(&vecs);
vecs_n = 0;
}
vecs[vecs_n + 0] = &partial_header;
vecs[vecs_n + 1] = remaining[0..block_size];
r.hasher.update(vecs[vecs_n + 1]);
vecs_n += 2;
remaining = remaining[block_size..];
assert(remaining.len == 0);
if (eos and r.hasher != .raw) {
// the footer is done here instead of `flush` so it can be included in the vector
var footer_w: Writer = .fixed(&footer_buf);
r.hasher.writeFooter(&footer_w) catch unreachable;
assert(footer_w.end != 0);
if (vecs_n == vecs.len) {
try r.output.writeVecAll(&vecs);
return r.output.writeAll(footer_w.buffered());
} else {
vecs[vecs_n] = footer_w.buffered();
vecs_n += 1;
}
}
}
try r.output.writeVecAll(vecs[0..vecs_n]);
_ = w.consume(w.end - preserved - remaining.len);
}
}