mirror of
https://github.com/ziglang/zig.git
synced 2026-02-16 06:18:32 +00:00
std.compress.zstd: rewrite
WIP - passing AstGen but not yet Sema
This commit is contained in:
parent
689a70663a
commit
685d55c1a4
@ -1,230 +0,0 @@
|
||||
//! This ring buffer stores read and write indices while being able to utilise
|
||||
//! the full backing slice by incrementing the indices modulo twice the slice's
|
||||
//! length and reducing indices modulo the slice's length on slice access. This
|
||||
//! means that whether the ring buffer is full or empty can be distinguished by
|
||||
//! looking at the difference between the read and write indices without adding
|
||||
//! an extra boolean flag or having to reserve a slot in the buffer.
|
||||
//!
|
||||
//! This ring buffer has not been implemented with thread safety in mind, and
|
||||
//! therefore should not be assumed to be suitable for use cases involving
|
||||
//! separate reader and writer threads.
|
||||
|
||||
const Allocator = @import("std").mem.Allocator;
|
||||
const assert = @import("std").debug.assert;
|
||||
const copyForwards = @import("std").mem.copyForwards;
|
||||
|
||||
const RingBuffer = @This();
|
||||
|
||||
data: []u8,
|
||||
read_index: usize,
|
||||
write_index: usize,
|
||||
|
||||
pub const Error = error{ Full, ReadLengthInvalid };
|
||||
|
||||
/// Allocate a new `RingBuffer`; `deinit()` should be called to free the buffer.
|
||||
pub fn init(allocator: Allocator, capacity: usize) Allocator.Error!RingBuffer {
|
||||
const bytes = try allocator.alloc(u8, capacity);
|
||||
return RingBuffer{
|
||||
.data = bytes,
|
||||
.write_index = 0,
|
||||
.read_index = 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Free the data backing a `RingBuffer`; must be passed the same `Allocator` as
|
||||
/// `init()`.
|
||||
pub fn deinit(self: *RingBuffer, allocator: Allocator) void {
|
||||
allocator.free(self.data);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Returns `index` modulo the length of the backing slice.
|
||||
pub fn mask(self: RingBuffer, index: usize) usize {
|
||||
return index % self.data.len;
|
||||
}
|
||||
|
||||
/// Returns `index` modulo twice the length of the backing slice.
|
||||
pub fn mask2(self: RingBuffer, index: usize) usize {
|
||||
return index % (2 * self.data.len);
|
||||
}
|
||||
|
||||
/// Write `byte` into the ring buffer. Returns `error.Full` if the ring
|
||||
/// buffer is full.
|
||||
pub fn write(self: *RingBuffer, byte: u8) Error!void {
|
||||
if (self.isFull()) return error.Full;
|
||||
self.writeAssumeCapacity(byte);
|
||||
}
|
||||
|
||||
/// Write `byte` into the ring buffer. If the ring buffer is full, the
|
||||
/// oldest byte is overwritten.
|
||||
pub fn writeAssumeCapacity(self: *RingBuffer, byte: u8) void {
|
||||
self.data[self.mask(self.write_index)] = byte;
|
||||
self.write_index = self.mask2(self.write_index + 1);
|
||||
}
|
||||
|
||||
/// Write `bytes` into the ring buffer. Returns `error.Full` if the ring
|
||||
/// buffer does not have enough space, without writing any data.
|
||||
/// Uses memcpy and so `bytes` must not overlap ring buffer data.
|
||||
pub fn writeSlice(self: *RingBuffer, bytes: []const u8) Error!void {
|
||||
if (self.len() + bytes.len > self.data.len) return error.Full;
|
||||
self.writeSliceAssumeCapacity(bytes);
|
||||
}
|
||||
|
||||
/// Write `bytes` into the ring buffer. If there is not enough space, older
|
||||
/// bytes will be overwritten.
|
||||
/// Uses memcpy and so `bytes` must not overlap ring buffer data.
|
||||
pub fn writeSliceAssumeCapacity(self: *RingBuffer, bytes: []const u8) void {
|
||||
assert(bytes.len <= self.data.len);
|
||||
const data_start = self.mask(self.write_index);
|
||||
const part1_data_end = @min(data_start + bytes.len, self.data.len);
|
||||
const part1_len = part1_data_end - data_start;
|
||||
@memcpy(self.data[data_start..part1_data_end], bytes[0..part1_len]);
|
||||
|
||||
const remaining = bytes.len - part1_len;
|
||||
const to_write = @min(remaining, remaining % self.data.len + self.data.len);
|
||||
const part2_bytes_start = bytes.len - to_write;
|
||||
const part2_bytes_end = @min(part2_bytes_start + self.data.len, bytes.len);
|
||||
const part2_len = part2_bytes_end - part2_bytes_start;
|
||||
@memcpy(self.data[0..part2_len], bytes[part2_bytes_start..part2_bytes_end]);
|
||||
if (part2_bytes_end != bytes.len) {
|
||||
const part3_len = bytes.len - part2_bytes_end;
|
||||
@memcpy(self.data[0..part3_len], bytes[part2_bytes_end..bytes.len]);
|
||||
}
|
||||
self.write_index = self.mask2(self.write_index + bytes.len);
|
||||
}
|
||||
|
||||
/// Write `bytes` into the ring buffer. Returns `error.Full` if the ring
|
||||
/// buffer does not have enough space, without writing any data.
|
||||
/// Uses copyForwards and can write slices from this RingBuffer into itself.
|
||||
pub fn writeSliceForwards(self: *RingBuffer, bytes: []const u8) Error!void {
|
||||
if (self.len() + bytes.len > self.data.len) return error.Full;
|
||||
self.writeSliceForwardsAssumeCapacity(bytes);
|
||||
}
|
||||
|
||||
/// Write `bytes` into the ring buffer. If there is not enough space, older
|
||||
/// bytes will be overwritten.
|
||||
/// Uses copyForwards and can write slices from this RingBuffer into itself.
|
||||
pub fn writeSliceForwardsAssumeCapacity(self: *RingBuffer, bytes: []const u8) void {
|
||||
assert(bytes.len <= self.data.len);
|
||||
const data_start = self.mask(self.write_index);
|
||||
const part1_data_end = @min(data_start + bytes.len, self.data.len);
|
||||
const part1_len = part1_data_end - data_start;
|
||||
copyForwards(u8, self.data[data_start..], bytes[0..part1_len]);
|
||||
|
||||
const remaining = bytes.len - part1_len;
|
||||
const to_write = @min(remaining, remaining % self.data.len + self.data.len);
|
||||
const part2_bytes_start = bytes.len - to_write;
|
||||
const part2_bytes_end = @min(part2_bytes_start + self.data.len, bytes.len);
|
||||
copyForwards(u8, self.data[0..], bytes[part2_bytes_start..part2_bytes_end]);
|
||||
if (part2_bytes_end != bytes.len)
|
||||
copyForwards(u8, self.data[0..], bytes[part2_bytes_end..bytes.len]);
|
||||
self.write_index = self.mask2(self.write_index + bytes.len);
|
||||
}
|
||||
|
||||
/// Consume a byte from the ring buffer and return it. Returns `null` if the
|
||||
/// ring buffer is empty.
|
||||
pub fn read(self: *RingBuffer) ?u8 {
|
||||
if (self.isEmpty()) return null;
|
||||
return self.readAssumeLength();
|
||||
}
|
||||
|
||||
/// Consume a byte from the ring buffer and return it; asserts that the buffer
|
||||
/// is not empty.
|
||||
pub fn readAssumeLength(self: *RingBuffer) u8 {
|
||||
assert(!self.isEmpty());
|
||||
const byte = self.data[self.mask(self.read_index)];
|
||||
self.read_index = self.mask2(self.read_index + 1);
|
||||
return byte;
|
||||
}
|
||||
|
||||
/// Reads first `length` bytes written to the ring buffer into `dest`; Returns
|
||||
/// Error.ReadLengthInvalid if length greater than ring or dest length
|
||||
/// Uses memcpy and so `dest` must not overlap ring buffer data.
|
||||
pub fn readFirst(self: *RingBuffer, dest: []u8, length: usize) Error!void {
|
||||
if (length > self.len() or length > dest.len) return error.ReadLengthInvalid;
|
||||
self.readFirstAssumeLength(dest, length);
|
||||
}
|
||||
|
||||
/// Reads first `length` bytes written to the ring buffer into `dest`;
|
||||
/// Asserts that length not greater than ring buffer or dest length
|
||||
/// Uses memcpy and so `dest` must not overlap ring buffer data.
|
||||
pub fn readFirstAssumeLength(self: *RingBuffer, dest: []u8, length: usize) void {
|
||||
assert(length <= self.len() and length <= dest.len);
|
||||
const slice = self.sliceAt(self.read_index, length);
|
||||
slice.copyTo(dest);
|
||||
self.read_index = self.mask2(self.read_index + length);
|
||||
}
|
||||
|
||||
/// Reads last `length` bytes written to the ring buffer into `dest`; Returns
|
||||
/// Error.ReadLengthInvalid if length greater than ring or dest length
|
||||
/// Uses memcpy and so `dest` must not overlap ring buffer data.
|
||||
/// Reduces write index by `length`.
|
||||
pub fn readLast(self: *RingBuffer, dest: []u8, length: usize) Error!void {
|
||||
if (length > self.len() or length > dest.len) return error.ReadLengthInvalid;
|
||||
self.readLastAssumeLength(dest, length);
|
||||
}
|
||||
|
||||
/// Reads last `length` bytes written to the ring buffer into `dest`;
|
||||
/// Asserts that length not greater than ring buffer or dest length
|
||||
/// Uses memcpy and so `dest` must not overlap ring buffer data.
|
||||
/// Reduces write index by `length`.
|
||||
pub fn readLastAssumeLength(self: *RingBuffer, dest: []u8, length: usize) void {
|
||||
assert(length <= self.len() and length <= dest.len);
|
||||
const slice = self.sliceLast(length);
|
||||
slice.copyTo(dest);
|
||||
self.write_index = if (self.write_index >= self.data.len)
|
||||
self.write_index - length
|
||||
else
|
||||
self.mask(self.write_index + self.data.len - length);
|
||||
}
|
||||
|
||||
/// Returns `true` if the ring buffer is empty and `false` otherwise.
|
||||
pub fn isEmpty(self: RingBuffer) bool {
|
||||
return self.write_index == self.read_index;
|
||||
}
|
||||
|
||||
/// Returns `true` if the ring buffer is full and `false` otherwise.
|
||||
pub fn isFull(self: RingBuffer) bool {
|
||||
return self.mask2(self.write_index + self.data.len) == self.read_index;
|
||||
}
|
||||
|
||||
/// Returns the length of data available for reading
|
||||
pub fn len(self: RingBuffer) usize {
|
||||
const wrap_offset = 2 * self.data.len * @intFromBool(self.write_index < self.read_index);
|
||||
const adjusted_write_index = self.write_index + wrap_offset;
|
||||
return adjusted_write_index - self.read_index;
|
||||
}
|
||||
|
||||
/// A `Slice` represents a region of a ring buffer. The region is split into two
|
||||
/// sections as the ring buffer data will not be contiguous if the desired
|
||||
/// region wraps to the start of the backing slice.
|
||||
pub const Slice = struct {
|
||||
first: []u8,
|
||||
second: []u8,
|
||||
|
||||
/// Copy data from `self` into `dest`
|
||||
pub fn copyTo(self: Slice, dest: []u8) void {
|
||||
@memcpy(dest[0..self.first.len], self.first);
|
||||
@memcpy(dest[self.first.len..][0..self.second.len], self.second);
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns a `Slice` for the region of the ring buffer starting at
|
||||
/// `self.mask(start_unmasked)` with the specified length.
|
||||
pub fn sliceAt(self: RingBuffer, start_unmasked: usize, length: usize) Slice {
|
||||
assert(length <= self.data.len);
|
||||
const slice1_start = self.mask(start_unmasked);
|
||||
const slice1_end = @min(self.data.len, slice1_start + length);
|
||||
const slice1 = self.data[slice1_start..slice1_end];
|
||||
const slice2 = self.data[0 .. length - slice1.len];
|
||||
return Slice{
|
||||
.first = slice1,
|
||||
.second = slice2,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns a `Slice` for the last `length` bytes written to the ring buffer.
|
||||
/// Does not check that any bytes have been written into the region.
|
||||
pub fn sliceLast(self: RingBuffer, length: usize) Slice {
|
||||
return self.sliceAt(self.write_index + self.data.len - length, length);
|
||||
}
|
||||
@ -6,7 +6,7 @@ pub const lzma = @import("compress/lzma.zig");
|
||||
pub const lzma2 = @import("compress/lzma2.zig");
|
||||
pub const xz = @import("compress/xz.zig");
|
||||
pub const zlib = @import("compress/zlib.zig");
|
||||
pub const zstd = @import("compress/zstandard.zig");
|
||||
pub const zstd = @import("compress/zstd.zig");
|
||||
|
||||
test {
|
||||
_ = flate;
|
||||
|
||||
@ -1,334 +0,0 @@
|
||||
const std = @import("../std.zig");
|
||||
const RingBuffer = std.RingBuffer;
|
||||
|
||||
const types = @import("zstandard/types.zig");
|
||||
|
||||
/// Recommended amount by the standard. Lower than this may result in inability
|
||||
/// to decompress common streams.
|
||||
pub const default_window_len = 8 * 1024 * 1024;
|
||||
|
||||
pub const frame = types.frame;
|
||||
pub const compressed_block = types.compressed_block;
|
||||
|
||||
pub const decompress = @import("zstandard/decompress.zig");
|
||||
|
||||
pub const Decompressor = struct {
|
||||
const table_size_max = types.compressed_block.table_size_max;
|
||||
|
||||
input: *std.io.BufferedReader,
|
||||
bytes_read: usize,
|
||||
state: enum { NewFrame, InFrame, LastBlock },
|
||||
decode_state: decompress.block.DecodeState,
|
||||
frame_context: decompress.FrameContext,
|
||||
buffer: WindowBuffer,
|
||||
literal_fse_buffer: [table_size_max.literal]types.compressed_block.Table.Fse,
|
||||
match_fse_buffer: [table_size_max.match]types.compressed_block.Table.Fse,
|
||||
offset_fse_buffer: [table_size_max.offset]types.compressed_block.Table.Fse,
|
||||
literals_buffer: [types.block_size_max]u8,
|
||||
sequence_buffer: [types.block_size_max]u8,
|
||||
verify_checksum: bool,
|
||||
checksum: ?u32,
|
||||
current_frame_decompressed_size: usize,
|
||||
err: ?Error = null,
|
||||
|
||||
pub const Options = struct {
|
||||
verify_checksum: bool = true,
|
||||
/// See `default_window_len`.
|
||||
window_buffer: []u8,
|
||||
};
|
||||
|
||||
const WindowBuffer = struct {
|
||||
data: []u8 = undefined,
|
||||
read_index: usize = 0,
|
||||
write_index: usize = 0,
|
||||
};
|
||||
|
||||
pub const Error = std.io.Reader.Error || error{
|
||||
ChecksumFailure,
|
||||
DictionaryIdFlagUnsupported,
|
||||
MalformedBlock,
|
||||
MalformedFrame,
|
||||
OutOfMemory,
|
||||
EndOfStream,
|
||||
};
|
||||
|
||||
pub fn init(input: *std.io.BufferedReader, options: Options) Decompressor {
|
||||
return .{
|
||||
.input = input,
|
||||
.bytes_read = 0,
|
||||
.state = .NewFrame,
|
||||
.decode_state = undefined,
|
||||
.frame_context = undefined,
|
||||
.buffer = .{ .data = options.window_buffer },
|
||||
.literal_fse_buffer = undefined,
|
||||
.match_fse_buffer = undefined,
|
||||
.offset_fse_buffer = undefined,
|
||||
.literals_buffer = undefined,
|
||||
.sequence_buffer = undefined,
|
||||
.verify_checksum = options.verify_checksum,
|
||||
.checksum = undefined,
|
||||
.current_frame_decompressed_size = undefined,
|
||||
};
|
||||
}
|
||||
|
||||
fn frameInit(d: *Decompressor) !void {
|
||||
const in = d.input;
|
||||
switch (try decompress.decodeFrameHeader(in, &d.bytes_read)) {
|
||||
.skippable => |header| {
|
||||
try in.discardAll(header.frame_size);
|
||||
d.bytes_read += header.frame_size;
|
||||
d.state = .NewFrame;
|
||||
},
|
||||
.zstandard => |header| {
|
||||
const frame_context = try decompress.FrameContext.init(
|
||||
header,
|
||||
d.buffer.data.len,
|
||||
d.verify_checksum,
|
||||
);
|
||||
|
||||
const decode_state = decompress.block.DecodeState.init(
|
||||
&d.literal_fse_buffer,
|
||||
&d.match_fse_buffer,
|
||||
&d.offset_fse_buffer,
|
||||
);
|
||||
|
||||
d.decode_state = decode_state;
|
||||
d.frame_context = frame_context;
|
||||
|
||||
d.checksum = null;
|
||||
d.current_frame_decompressed_size = 0;
|
||||
|
||||
d.state = .InFrame;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reader(self: *Decompressor) std.io.Reader {
|
||||
return .{
|
||||
.context = self,
|
||||
.vtable = &.{
|
||||
.read = read,
|
||||
.readVec = readVec,
|
||||
.discard = discard,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn read(context: ?*anyopaque, bw: *std.io.BufferedWriter, limit: std.io.Reader.Limit) std.io.Reader.RwError!usize {
|
||||
const buf = limit.slice(try bw.writableSliceGreedy(1));
|
||||
const n = try readVec(context, &.{buf});
|
||||
bw.advance(n);
|
||||
return n;
|
||||
}
|
||||
|
||||
fn discard(context: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize {
|
||||
var trash: [128]u8 = undefined;
|
||||
const buf = limit.slice(&trash);
|
||||
return readVec(context, &.{buf});
|
||||
}
|
||||
|
||||
fn readVec(context: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize {
|
||||
const d: *Decompressor = @ptrCast(@alignCast(context));
|
||||
if (data.len == 0) return 0;
|
||||
const buffer = data[0];
|
||||
while (d.state == .NewFrame) {
|
||||
const initial_count = d.bytes_read;
|
||||
d.frameInit() catch |err| switch (err) {
|
||||
error.DictionaryIdFlagUnsupported => {
|
||||
d.err = error.DictionaryIdFlagUnsupported;
|
||||
return error.ReadFailed;
|
||||
},
|
||||
error.EndOfStream => {
|
||||
if (d.bytes_read == initial_count) return error.EndOfStream;
|
||||
d.err = error.MalformedFrame;
|
||||
return error.ReadFailed;
|
||||
},
|
||||
else => {
|
||||
d.err = error.MalformedFrame;
|
||||
return error.ReadFailed;
|
||||
},
|
||||
};
|
||||
}
|
||||
return d.readInner(buffer) catch |err| {
|
||||
d.err = err;
|
||||
return error.ReadFailed;
|
||||
};
|
||||
}
|
||||
|
||||
fn readInner(d: *Decompressor, buffer: []u8) Error!usize {
|
||||
std.debug.assert(d.state != .NewFrame);
|
||||
|
||||
var ring_buffer = RingBuffer{
|
||||
.data = d.buffer.data,
|
||||
.read_index = d.buffer.read_index,
|
||||
.write_index = d.buffer.write_index,
|
||||
};
|
||||
defer {
|
||||
d.buffer.read_index = ring_buffer.read_index;
|
||||
d.buffer.write_index = ring_buffer.write_index;
|
||||
}
|
||||
|
||||
const in = d.input;
|
||||
while (ring_buffer.isEmpty() and d.state != .LastBlock) {
|
||||
const header_bytes = try in.takeArray(3);
|
||||
d.bytes_read += header_bytes.len;
|
||||
const block_header = decompress.block.decodeBlockHeader(header_bytes);
|
||||
|
||||
decompress.block.decodeBlockReader(
|
||||
&ring_buffer,
|
||||
in,
|
||||
&d.bytes_read,
|
||||
block_header,
|
||||
&d.decode_state,
|
||||
d.frame_context.block_size_max,
|
||||
&d.literals_buffer,
|
||||
&d.sequence_buffer,
|
||||
) catch return error.MalformedBlock;
|
||||
|
||||
if (d.frame_context.content_size) |size| {
|
||||
if (d.current_frame_decompressed_size > size) return error.MalformedFrame;
|
||||
}
|
||||
|
||||
const size = ring_buffer.len();
|
||||
d.current_frame_decompressed_size += size;
|
||||
|
||||
if (d.frame_context.hasher_opt) |*hasher| {
|
||||
if (size > 0) {
|
||||
const written_slice = ring_buffer.sliceLast(size);
|
||||
hasher.update(written_slice.first);
|
||||
hasher.update(written_slice.second);
|
||||
}
|
||||
}
|
||||
if (block_header.last_block) {
|
||||
d.state = .LastBlock;
|
||||
if (d.frame_context.has_checksum) {
|
||||
const checksum = in.readInt(u32, .little) catch return error.MalformedFrame;
|
||||
d.bytes_read += 4;
|
||||
if (d.verify_checksum) {
|
||||
if (d.frame_context.hasher_opt) |*hasher| {
|
||||
if (checksum != decompress.computeChecksum(hasher))
|
||||
return error.ChecksumFailure;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (d.frame_context.content_size) |content_size| {
|
||||
if (content_size != d.current_frame_decompressed_size) {
|
||||
return error.MalformedFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const size = @min(ring_buffer.len(), buffer.len);
|
||||
if (size > 0) {
|
||||
ring_buffer.readFirstAssumeLength(buffer, size);
|
||||
}
|
||||
if (d.state == .LastBlock and ring_buffer.len() == 0) {
|
||||
d.state = .NewFrame;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
fn testDecompress(data: []const u8) ![]u8 {
|
||||
const window_buffer = try std.testing.allocator.alloc(u8, 1 << 23);
|
||||
defer std.testing.allocator.free(window_buffer);
|
||||
|
||||
var in_stream = std.io.fixedBufferStream(data);
|
||||
var zstd_stream: Decompressor = .init(in_stream.reader(), .{ .window_buffer = window_buffer });
|
||||
const result = zstd_stream.reader().readAllAlloc(std.testing.allocator, std.math.maxInt(usize));
|
||||
return result;
|
||||
}
|
||||
|
||||
fn testReader(data: []const u8, comptime expected: []const u8) !void {
|
||||
const buf = try testDecompress(data);
|
||||
defer std.testing.allocator.free(buf);
|
||||
try std.testing.expectEqualSlices(u8, expected, buf);
|
||||
}
|
||||
|
||||
test "decompression" {
|
||||
const uncompressed = @embedFile("testdata/rfc8478.txt");
|
||||
const compressed3 = @embedFile("testdata/rfc8478.txt.zst.3");
|
||||
const compressed19 = @embedFile("testdata/rfc8478.txt.zst.19");
|
||||
|
||||
const buffer = try std.testing.allocator.alloc(u8, uncompressed.len);
|
||||
defer std.testing.allocator.free(buffer);
|
||||
|
||||
const res3 = try decompress.decode(buffer, compressed3, true);
|
||||
try std.testing.expectEqual(uncompressed.len, res3);
|
||||
try std.testing.expectEqualSlices(u8, uncompressed, buffer);
|
||||
|
||||
@memset(buffer, undefined);
|
||||
const res19 = try decompress.decode(buffer, compressed19, true);
|
||||
try std.testing.expectEqual(uncompressed.len, res19);
|
||||
try std.testing.expectEqualSlices(u8, uncompressed, buffer);
|
||||
|
||||
try testReader(compressed3, uncompressed);
|
||||
try testReader(compressed19, uncompressed);
|
||||
}
|
||||
|
||||
fn expectEqualDecoded(expected: []const u8, input: []const u8) !void {
|
||||
{
|
||||
const result = try decompress.decodeAlloc(std.testing.allocator, input, false, 1 << 23);
|
||||
defer std.testing.allocator.free(result);
|
||||
try std.testing.expectEqualStrings(expected, result);
|
||||
}
|
||||
|
||||
{
|
||||
var buffer = try std.testing.allocator.alloc(u8, 2 * expected.len);
|
||||
defer std.testing.allocator.free(buffer);
|
||||
|
||||
const size = try decompress.decode(buffer, input, false);
|
||||
try std.testing.expectEqualStrings(expected, buffer[0..size]);
|
||||
}
|
||||
}
|
||||
|
||||
fn expectEqualDecodedStreaming(expected: []const u8, input: []const u8) !void {
|
||||
const window_buffer = try std.testing.allocator.alloc(u8, 1 << 23);
|
||||
defer std.testing.allocator.free(window_buffer);
|
||||
|
||||
var in_stream = std.io.fixedBufferStream(input);
|
||||
var stream: Decompressor = .init(in_stream.reader(), .{ .window_buffer = window_buffer });
|
||||
|
||||
const result = try stream.reader().readAllAlloc(std.testing.allocator, std.math.maxInt(usize));
|
||||
defer std.testing.allocator.free(result);
|
||||
|
||||
try std.testing.expectEqualStrings(expected, result);
|
||||
}
|
||||
|
||||
test "zero sized block" {
|
||||
const input_raw =
|
||||
"\x28\xb5\x2f\xfd" ++ // zstandard frame magic number
|
||||
"\x20\x00" ++ // frame header: only single_segment_flag set, frame_content_size zero
|
||||
"\x01\x00\x00"; // block header with: last_block set, block_type raw, block_size zero
|
||||
|
||||
const input_rle =
|
||||
"\x28\xb5\x2f\xfd" ++ // zstandard frame magic number
|
||||
"\x20\x00" ++ // frame header: only single_segment_flag set, frame_content_size zero
|
||||
"\x03\x00\x00" ++ // block header with: last_block set, block_type rle, block_size zero
|
||||
"\xaa"; // block_content
|
||||
|
||||
try expectEqualDecoded("", input_raw);
|
||||
try expectEqualDecoded("", input_rle);
|
||||
try expectEqualDecodedStreaming("", input_raw);
|
||||
try expectEqualDecodedStreaming("", input_rle);
|
||||
}
|
||||
|
||||
test "declared raw literals size too large" {
|
||||
const input_raw =
|
||||
"\x28\xb5\x2f\xfd" ++ // zstandard frame magic number
|
||||
"\x00\x00" ++ // frame header: everything unset, window descriptor zero
|
||||
"\x95\x00\x00" ++ // block header with: last_block set, block_type compressed, block_size 18
|
||||
"\xbc\xf3\xae" ++ // literals section header with: type raw, size_format 3, regenerated_size 716603
|
||||
"\xa5\x9f\xe3"; // some bytes of literal content - the content is shorter than regenerated_size
|
||||
|
||||
// Note that the regenerated_size in the above input is larger than block maximum size, so the
|
||||
// block can't be valid as it is a raw literals block.
|
||||
|
||||
var fbs = std.io.fixedBufferStream(input_raw);
|
||||
var window: [1024]u8 = undefined;
|
||||
var stream: Decompressor = .init(fbs.reader(), .{ .window_buffer = &window });
|
||||
|
||||
var buf: [1024]u8 = undefined;
|
||||
try std.testing.expectError(error.MalformedBlock, stream.read(&buf));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,153 +0,0 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const types = @import("../types.zig");
|
||||
const Table = types.compressed_block.Table;
|
||||
|
||||
pub fn decodeFseTable(
|
||||
bit_reader: anytype,
|
||||
expected_symbol_count: usize,
|
||||
max_accuracy_log: u4,
|
||||
entries: []Table.Fse,
|
||||
) !usize {
|
||||
const accuracy_log_biased = try bit_reader.readBitsNoEof(u4, 4);
|
||||
if (accuracy_log_biased > max_accuracy_log -| 5) return error.MalformedAccuracyLog;
|
||||
const accuracy_log = accuracy_log_biased + 5;
|
||||
|
||||
var values: [256]u16 = undefined;
|
||||
var value_count: usize = 0;
|
||||
|
||||
const total_probability = @as(u16, 1) << accuracy_log;
|
||||
var accumulated_probability: u16 = 0;
|
||||
|
||||
while (accumulated_probability < total_probability) {
|
||||
// WARNING: The RFC is poorly worded, and would suggest std.math.log2_int_ceil is correct here,
|
||||
// but power of two (remaining probabilities + 1) need max bits set to 1 more.
|
||||
const max_bits = std.math.log2_int(u16, total_probability - accumulated_probability + 1) + 1;
|
||||
const small = try bit_reader.readBitsNoEof(u16, max_bits - 1);
|
||||
|
||||
const cutoff = (@as(u16, 1) << max_bits) - 1 - (total_probability - accumulated_probability + 1);
|
||||
|
||||
const value = if (small < cutoff)
|
||||
small
|
||||
else value: {
|
||||
const value_read = small + (try bit_reader.readBitsNoEof(u16, 1) << (max_bits - 1));
|
||||
break :value if (value_read < @as(u16, 1) << (max_bits - 1))
|
||||
value_read
|
||||
else
|
||||
value_read - cutoff;
|
||||
};
|
||||
|
||||
accumulated_probability += if (value != 0) value - 1 else 1;
|
||||
|
||||
values[value_count] = value;
|
||||
value_count += 1;
|
||||
|
||||
if (value == 1) {
|
||||
while (true) {
|
||||
const repeat_flag = try bit_reader.readBitsNoEof(u2, 2);
|
||||
if (repeat_flag + value_count > 256) return error.MalformedFseTable;
|
||||
for (0..repeat_flag) |_| {
|
||||
values[value_count] = 1;
|
||||
value_count += 1;
|
||||
}
|
||||
if (repeat_flag < 3) break;
|
||||
}
|
||||
}
|
||||
if (value_count == 256) break;
|
||||
}
|
||||
bit_reader.alignToByte();
|
||||
|
||||
if (value_count < 2) return error.MalformedFseTable;
|
||||
if (accumulated_probability != total_probability) return error.MalformedFseTable;
|
||||
if (value_count > expected_symbol_count) return error.MalformedFseTable;
|
||||
|
||||
const table_size = total_probability;
|
||||
|
||||
try buildFseTable(values[0..value_count], entries[0..table_size]);
|
||||
return table_size;
|
||||
}
|
||||
|
||||
fn buildFseTable(values: []const u16, entries: []Table.Fse) !void {
|
||||
const total_probability = @as(u16, @intCast(entries.len));
|
||||
const accuracy_log = std.math.log2_int(u16, total_probability);
|
||||
assert(total_probability <= 1 << 9);
|
||||
|
||||
var less_than_one_count: usize = 0;
|
||||
for (values, 0..) |value, i| {
|
||||
if (value == 0) {
|
||||
entries[entries.len - 1 - less_than_one_count] = Table.Fse{
|
||||
.symbol = @as(u8, @intCast(i)),
|
||||
.baseline = 0,
|
||||
.bits = accuracy_log,
|
||||
};
|
||||
less_than_one_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
var position: usize = 0;
|
||||
var temp_states: [1 << 9]u16 = undefined;
|
||||
for (values, 0..) |value, symbol| {
|
||||
if (value == 0 or value == 1) continue;
|
||||
const probability = value - 1;
|
||||
|
||||
const state_share_dividend = std.math.ceilPowerOfTwo(u16, probability) catch
|
||||
return error.MalformedFseTable;
|
||||
const share_size = @divExact(total_probability, state_share_dividend);
|
||||
const double_state_count = state_share_dividend - probability;
|
||||
const single_state_count = probability - double_state_count;
|
||||
const share_size_log = std.math.log2_int(u16, share_size);
|
||||
|
||||
for (0..probability) |i| {
|
||||
temp_states[i] = @as(u16, @intCast(position));
|
||||
position += (entries.len >> 1) + (entries.len >> 3) + 3;
|
||||
position &= entries.len - 1;
|
||||
while (position >= entries.len - less_than_one_count) {
|
||||
position += (entries.len >> 1) + (entries.len >> 3) + 3;
|
||||
position &= entries.len - 1;
|
||||
}
|
||||
}
|
||||
std.mem.sort(u16, temp_states[0..probability], {}, std.sort.asc(u16));
|
||||
for (0..probability) |i| {
|
||||
entries[temp_states[i]] = if (i < double_state_count) Table.Fse{
|
||||
.symbol = @as(u8, @intCast(symbol)),
|
||||
.bits = share_size_log + 1,
|
||||
.baseline = single_state_count * share_size + @as(u16, @intCast(i)) * 2 * share_size,
|
||||
} else Table.Fse{
|
||||
.symbol = @as(u8, @intCast(symbol)),
|
||||
.bits = share_size_log,
|
||||
.baseline = (@as(u16, @intCast(i)) - double_state_count) * share_size,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test buildFseTable {
|
||||
const literals_length_default_values = [36]u16{
|
||||
5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2,
|
||||
0, 0, 0, 0,
|
||||
};
|
||||
|
||||
const match_lengths_default_values = [53]u16{
|
||||
2, 5, 4, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0,
|
||||
0, 0, 0, 0, 0,
|
||||
};
|
||||
|
||||
const offset_codes_default_values = [29]u16{
|
||||
2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0,
|
||||
};
|
||||
|
||||
var entries: [64]Table.Fse = undefined;
|
||||
try buildFseTable(&literals_length_default_values, &entries);
|
||||
try std.testing.expectEqualSlices(Table.Fse, types.compressed_block.predefined_literal_fse_table.fse, &entries);
|
||||
|
||||
try buildFseTable(&match_lengths_default_values, &entries);
|
||||
try std.testing.expectEqualSlices(Table.Fse, types.compressed_block.predefined_match_fse_table.fse, &entries);
|
||||
|
||||
try buildFseTable(&offset_codes_default_values, entries[0..32]);
|
||||
try std.testing.expectEqualSlices(Table.Fse, types.compressed_block.predefined_offset_fse_table.fse, entries[0..32]);
|
||||
}
|
||||
@ -1,234 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const types = @import("../types.zig");
|
||||
const LiteralsSection = types.compressed_block.LiteralsSection;
|
||||
const Table = types.compressed_block.Table;
|
||||
|
||||
const readers = @import("../readers.zig");
|
||||
|
||||
const decodeFseTable = @import("fse.zig").decodeFseTable;
|
||||
|
||||
pub const Error = error{
|
||||
MalformedHuffmanTree,
|
||||
MalformedFseTable,
|
||||
MalformedAccuracyLog,
|
||||
EndOfStream,
|
||||
};
|
||||
|
||||
fn decodeFseHuffmanTree(
|
||||
source: anytype,
|
||||
compressed_size: usize,
|
||||
buffer: []u8,
|
||||
weights: *[256]u4,
|
||||
) !usize {
|
||||
var stream = std.io.limitedReader(source, compressed_size);
|
||||
var bit_reader = readers.bitReader(stream.reader());
|
||||
|
||||
var entries: [1 << 6]Table.Fse = undefined;
|
||||
const table_size = decodeFseTable(&bit_reader, 256, 6, &entries) catch |err| switch (err) {
|
||||
error.MalformedAccuracyLog, error.MalformedFseTable => |e| return e,
|
||||
error.EndOfStream => return error.MalformedFseTable,
|
||||
else => |e| return e,
|
||||
};
|
||||
const accuracy_log = std.math.log2_int_ceil(usize, table_size);
|
||||
|
||||
const amount = try stream.reader().readAll(buffer);
|
||||
var huff_bits: readers.ReverseBitReader = undefined;
|
||||
huff_bits.init(buffer[0..amount]) catch return error.MalformedHuffmanTree;
|
||||
|
||||
return assignWeights(&huff_bits, accuracy_log, &entries, weights);
|
||||
}
|
||||
|
||||
fn decodeFseHuffmanTreeSlice(src: []const u8, compressed_size: usize, weights: *[256]u4) !usize {
|
||||
if (src.len < compressed_size) return error.MalformedHuffmanTree;
|
||||
var stream: std.io.FixedBufferStream = .{ .buffer = src[0..compressed_size] };
|
||||
var counting_reader = std.io.countingReader(stream.reader());
|
||||
var bit_reader = readers.bitReader(counting_reader.reader());
|
||||
|
||||
var entries: [1 << 6]Table.Fse = undefined;
|
||||
const table_size = decodeFseTable(&bit_reader, 256, 6, &entries) catch |err| switch (err) {
|
||||
error.MalformedAccuracyLog, error.MalformedFseTable => |e| return e,
|
||||
error.EndOfStream => return error.MalformedFseTable,
|
||||
};
|
||||
const accuracy_log = std.math.log2_int_ceil(usize, table_size);
|
||||
|
||||
const start_index = std.math.cast(usize, counting_reader.bytes_read) orelse
|
||||
return error.MalformedHuffmanTree;
|
||||
const huff_data = src[start_index..compressed_size];
|
||||
var huff_bits: readers.ReverseBitReader = undefined;
|
||||
huff_bits.init(huff_data) catch return error.MalformedHuffmanTree;
|
||||
|
||||
return assignWeights(&huff_bits, accuracy_log, &entries, weights);
|
||||
}
|
||||
|
||||
fn assignWeights(
|
||||
huff_bits: *readers.ReverseBitReader,
|
||||
accuracy_log: u16,
|
||||
entries: *[1 << 6]Table.Fse,
|
||||
weights: *[256]u4,
|
||||
) !usize {
|
||||
var i: usize = 0;
|
||||
var even_state: u32 = huff_bits.readBitsNoEof(u32, accuracy_log) catch return error.MalformedHuffmanTree;
|
||||
var odd_state: u32 = huff_bits.readBitsNoEof(u32, accuracy_log) catch return error.MalformedHuffmanTree;
|
||||
|
||||
while (i < 254) {
|
||||
const even_data = entries[even_state];
|
||||
var read_bits: u16 = 0;
|
||||
const even_bits = huff_bits.readBits(u32, even_data.bits, &read_bits) catch unreachable;
|
||||
weights[i] = std.math.cast(u4, even_data.symbol) orelse return error.MalformedHuffmanTree;
|
||||
i += 1;
|
||||
if (read_bits < even_data.bits) {
|
||||
weights[i] = std.math.cast(u4, entries[odd_state].symbol) orelse return error.MalformedHuffmanTree;
|
||||
i += 1;
|
||||
break;
|
||||
}
|
||||
even_state = even_data.baseline + even_bits;
|
||||
|
||||
read_bits = 0;
|
||||
const odd_data = entries[odd_state];
|
||||
const odd_bits = huff_bits.readBits(u32, odd_data.bits, &read_bits) catch unreachable;
|
||||
weights[i] = std.math.cast(u4, odd_data.symbol) orelse return error.MalformedHuffmanTree;
|
||||
i += 1;
|
||||
if (read_bits < odd_data.bits) {
|
||||
if (i == 255) return error.MalformedHuffmanTree;
|
||||
weights[i] = std.math.cast(u4, entries[even_state].symbol) orelse return error.MalformedHuffmanTree;
|
||||
i += 1;
|
||||
break;
|
||||
}
|
||||
odd_state = odd_data.baseline + odd_bits;
|
||||
} else return error.MalformedHuffmanTree;
|
||||
|
||||
if (!huff_bits.isEmpty()) {
|
||||
return error.MalformedHuffmanTree;
|
||||
}
|
||||
|
||||
return i + 1; // stream contains all but the last symbol
|
||||
}
|
||||
|
||||
fn decodeDirectHuffmanTree(source: anytype, encoded_symbol_count: usize, weights: *[256]u4) !usize {
|
||||
const weights_byte_count = (encoded_symbol_count + 1) / 2;
|
||||
for (0..weights_byte_count) |i| {
|
||||
const byte = try source.readByte();
|
||||
weights[2 * i] = @as(u4, @intCast(byte >> 4));
|
||||
weights[2 * i + 1] = @as(u4, @intCast(byte & 0xF));
|
||||
}
|
||||
return encoded_symbol_count + 1;
|
||||
}
|
||||
|
||||
fn assignSymbols(weight_sorted_prefixed_symbols: []LiteralsSection.HuffmanTree.PrefixedSymbol, weights: [256]u4) usize {
|
||||
for (0..weight_sorted_prefixed_symbols.len) |i| {
|
||||
weight_sorted_prefixed_symbols[i] = .{
|
||||
.symbol = @as(u8, @intCast(i)),
|
||||
.weight = undefined,
|
||||
.prefix = undefined,
|
||||
};
|
||||
}
|
||||
|
||||
std.mem.sort(
|
||||
LiteralsSection.HuffmanTree.PrefixedSymbol,
|
||||
weight_sorted_prefixed_symbols,
|
||||
weights,
|
||||
lessThanByWeight,
|
||||
);
|
||||
|
||||
var prefix: u16 = 0;
|
||||
var prefixed_symbol_count: usize = 0;
|
||||
var sorted_index: usize = 0;
|
||||
const symbol_count = weight_sorted_prefixed_symbols.len;
|
||||
while (sorted_index < symbol_count) {
|
||||
var symbol = weight_sorted_prefixed_symbols[sorted_index].symbol;
|
||||
const weight = weights[symbol];
|
||||
if (weight == 0) {
|
||||
sorted_index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
while (sorted_index < symbol_count) : ({
|
||||
sorted_index += 1;
|
||||
prefixed_symbol_count += 1;
|
||||
prefix += 1;
|
||||
}) {
|
||||
symbol = weight_sorted_prefixed_symbols[sorted_index].symbol;
|
||||
if (weights[symbol] != weight) {
|
||||
prefix = ((prefix - 1) >> (weights[symbol] - weight)) + 1;
|
||||
break;
|
||||
}
|
||||
weight_sorted_prefixed_symbols[prefixed_symbol_count].symbol = symbol;
|
||||
weight_sorted_prefixed_symbols[prefixed_symbol_count].prefix = prefix;
|
||||
weight_sorted_prefixed_symbols[prefixed_symbol_count].weight = weight;
|
||||
}
|
||||
}
|
||||
return prefixed_symbol_count;
|
||||
}
|
||||
|
||||
fn buildHuffmanTree(weights: *[256]u4, symbol_count: usize) error{MalformedHuffmanTree}!LiteralsSection.HuffmanTree {
|
||||
var weight_power_sum_big: u32 = 0;
|
||||
for (weights[0 .. symbol_count - 1]) |value| {
|
||||
weight_power_sum_big += (@as(u16, 1) << value) >> 1;
|
||||
}
|
||||
if (weight_power_sum_big >= 1 << 11) return error.MalformedHuffmanTree;
|
||||
const weight_power_sum = @as(u16, @intCast(weight_power_sum_big));
|
||||
|
||||
// advance to next power of two (even if weight_power_sum is a power of 2)
|
||||
// TODO: is it valid to have weight_power_sum == 0?
|
||||
const max_number_of_bits = if (weight_power_sum == 0) 1 else std.math.log2_int(u16, weight_power_sum) + 1;
|
||||
const next_power_of_two = @as(u16, 1) << max_number_of_bits;
|
||||
weights[symbol_count - 1] = std.math.log2_int(u16, next_power_of_two - weight_power_sum) + 1;
|
||||
|
||||
var weight_sorted_prefixed_symbols: [256]LiteralsSection.HuffmanTree.PrefixedSymbol = undefined;
|
||||
const prefixed_symbol_count = assignSymbols(weight_sorted_prefixed_symbols[0..symbol_count], weights.*);
|
||||
const tree = LiteralsSection.HuffmanTree{
|
||||
.max_bit_count = max_number_of_bits,
|
||||
.symbol_count_minus_one = @as(u8, @intCast(prefixed_symbol_count - 1)),
|
||||
.nodes = weight_sorted_prefixed_symbols,
|
||||
};
|
||||
return tree;
|
||||
}
|
||||
|
||||
pub fn decodeHuffmanTree(
|
||||
source: anytype,
|
||||
buffer: []u8,
|
||||
) (@TypeOf(source).Error || Error)!LiteralsSection.HuffmanTree {
|
||||
const header = try source.readByte();
|
||||
var weights: [256]u4 = undefined;
|
||||
const symbol_count = if (header < 128)
|
||||
// FSE compressed weights
|
||||
try decodeFseHuffmanTree(source, header, buffer, &weights)
|
||||
else
|
||||
try decodeDirectHuffmanTree(source, header - 127, &weights);
|
||||
|
||||
return buildHuffmanTree(&weights, symbol_count);
|
||||
}
|
||||
|
||||
pub fn decodeHuffmanTreeSlice(
|
||||
src: []const u8,
|
||||
consumed_count: *usize,
|
||||
) Error!LiteralsSection.HuffmanTree {
|
||||
if (src.len == 0) return error.MalformedHuffmanTree;
|
||||
const header = src[0];
|
||||
var bytes_read: usize = 1;
|
||||
var weights: [256]u4 = undefined;
|
||||
const symbol_count = if (header < 128) count: {
|
||||
// FSE compressed weights
|
||||
bytes_read += header;
|
||||
break :count try decodeFseHuffmanTreeSlice(src[1..], header, &weights);
|
||||
} else count: {
|
||||
var fbs: std.io.FixedBufferStream = .{ .buffer = src[1..] };
|
||||
defer bytes_read += fbs.pos;
|
||||
break :count try decodeDirectHuffmanTree(fbs.reader(), header - 127, &weights);
|
||||
};
|
||||
|
||||
consumed_count.* += bytes_read;
|
||||
return buildHuffmanTree(&weights, symbol_count);
|
||||
}
|
||||
|
||||
fn lessThanByWeight(
|
||||
weights: [256]u4,
|
||||
lhs: LiteralsSection.HuffmanTree.PrefixedSymbol,
|
||||
rhs: LiteralsSection.HuffmanTree.PrefixedSymbol,
|
||||
) bool {
|
||||
// NOTE: this function relies on the use of a stable sorting algorithm,
|
||||
// otherwise a special case of if (weights[lhs] == weights[rhs]) return lhs < rhs;
|
||||
// should be added
|
||||
return weights[lhs.symbol] < weights[rhs.symbol];
|
||||
}
|
||||
@ -1,441 +0,0 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const RingBuffer = std.RingBuffer;
|
||||
|
||||
const types = @import("types.zig");
|
||||
const frame = types.frame;
|
||||
const LiteralsSection = types.compressed_block.LiteralsSection;
|
||||
const SequencesSection = types.compressed_block.SequencesSection;
|
||||
const SkippableHeader = types.frame.Skippable.Header;
|
||||
const ZstandardHeader = types.frame.Zstandard.Header;
|
||||
const Table = types.compressed_block.Table;
|
||||
|
||||
pub const block = @import("decode/block.zig");
|
||||
|
||||
const readers = @import("readers.zig");
|
||||
|
||||
/// Returns `true` is `magic` is a valid magic number for a skippable frame
|
||||
pub fn isSkippableMagic(magic: u32) bool {
|
||||
return frame.Skippable.magic_number_min <= magic and magic <= frame.Skippable.magic_number_max;
|
||||
}
|
||||
|
||||
/// Returns the kind of frame at the beginning of `source`.
|
||||
///
|
||||
/// Errors returned:
|
||||
/// - `error.BadMagic` if `source` begins with bytes not equal to the
|
||||
/// Zstandard frame magic number, or outside the range of magic numbers for
|
||||
/// skippable frames.
|
||||
/// - `error.EndOfStream` if `source` contains fewer than 4 bytes
|
||||
pub fn decodeFrameType(source: anytype) error{ BadMagic, EndOfStream }!frame.Kind {
|
||||
const magic = try source.readInt(u32, .little);
|
||||
return frameType(magic);
|
||||
}
|
||||
|
||||
/// Returns the kind of frame associated to `magic`.
|
||||
///
|
||||
/// Errors returned:
|
||||
/// - `error.BadMagic` if `magic` is not a valid magic number.
|
||||
pub fn frameType(magic: u32) error{BadMagic}!frame.Kind {
|
||||
return if (magic == frame.Zstandard.magic_number)
|
||||
.zstandard
|
||||
else if (isSkippableMagic(magic))
|
||||
.skippable
|
||||
else
|
||||
error.BadMagic;
|
||||
}
|
||||
|
||||
pub const FrameHeader = union(enum) {
|
||||
zstandard: ZstandardHeader,
|
||||
skippable: SkippableHeader,
|
||||
};
|
||||
|
||||
pub const HeaderError = error{ ReadFailed, BadMagic, EndOfStream, ReservedBitSet };
|
||||
|
||||
/// Returns the header of the frame at the beginning of `source`.
|
||||
///
|
||||
/// Errors returned:
|
||||
/// - `error.BadMagic` if `source` begins with bytes not equal to the
|
||||
/// Zstandard frame magic number, or outside the range of magic numbers for
|
||||
/// skippable frames.
|
||||
/// - `error.EndOfStream` if `source` contains fewer than 4 bytes
|
||||
/// - `error.ReservedBitSet` if the frame is a Zstandard frame and any of the
|
||||
/// reserved bits are set
|
||||
pub fn decodeFrameHeader(br: *std.io.BufferedReader, bytes_read: *usize) HeaderError!FrameHeader {
|
||||
const magic = try br.readInt(u32, .little);
|
||||
bytes_read.* += 4;
|
||||
const frame_type = try frameType(magic);
|
||||
switch (frame_type) {
|
||||
.zstandard => return .{ .zstandard = try decodeZstandardHeader(br, bytes_read) },
|
||||
.skippable => {
|
||||
const result: FrameHeader = .{
|
||||
.skippable = .{
|
||||
.magic_number = magic,
|
||||
.frame_size = try br.readInt(u32, .little),
|
||||
},
|
||||
};
|
||||
bytes_read.* += 4;
|
||||
return result;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const ReadWriteCount = struct {
|
||||
read_count: usize,
|
||||
write_count: usize,
|
||||
};
|
||||
|
||||
/// Decodes frames from `src` into `dest`; returns the length of the result.
|
||||
/// The stream should not have extra trailing bytes - either all bytes in `src`
|
||||
/// will be decoded, or an error will be returned. An error will be returned if
|
||||
/// a Zstandard frame in `src` does not declare its content size.
|
||||
///
|
||||
/// Errors returned:
|
||||
/// - `error.DictionaryIdFlagUnsupported` if a `src` contains a frame that
|
||||
/// uses a dictionary
|
||||
/// - `error.MalformedFrame` if a frame in `src` is invalid
|
||||
/// - `error.UnknownContentSizeUnsupported` if a frame in `src` does not
|
||||
/// declare its content size
|
||||
pub fn decode(dest: []u8, src: []const u8, verify_checksum: bool) error{
|
||||
MalformedFrame,
|
||||
UnknownContentSizeUnsupported,
|
||||
DictionaryIdFlagUnsupported,
|
||||
}!usize {
|
||||
var write_count: usize = 0;
|
||||
var read_count: usize = 0;
|
||||
while (read_count < src.len) {
|
||||
const counts = decodeFrame(dest, src[read_count..], verify_checksum) catch |err| {
|
||||
switch (err) {
|
||||
error.UnknownContentSizeUnsupported => return error.UnknownContentSizeUnsupported,
|
||||
error.DictionaryIdFlagUnsupported => return error.DictionaryIdFlagUnsupported,
|
||||
else => return error.MalformedFrame,
|
||||
}
|
||||
};
|
||||
read_count += counts.read_count;
|
||||
write_count += counts.write_count;
|
||||
}
|
||||
return write_count;
|
||||
}
|
||||
|
||||
/// Decodes the frame at the start of `src` into `dest`. Returns the number of
|
||||
/// bytes read from `src` and written to `dest`. This function can only decode
|
||||
/// frames that declare the decompressed content size.
|
||||
///
|
||||
/// Errors returned:
|
||||
/// - `error.BadMagic` if the first 4 bytes of `src` is not a valid magic
|
||||
/// number for a Zstandard or skippable frame
|
||||
/// - `error.UnknownContentSizeUnsupported` if the frame does not declare the
|
||||
/// uncompressed content size
|
||||
/// - `error.WindowSizeUnknown` if the frame does not have a valid window size
|
||||
/// - `error.ContentTooLarge` if `dest` is smaller than the uncompressed data
|
||||
/// size declared by the frame header
|
||||
/// - `error.ContentSizeTooLarge` if the frame header indicates a content size
|
||||
/// that is larger than `std.math.maxInt(usize)`
|
||||
/// - `error.DictionaryIdFlagUnsupported` if the frame uses a dictionary
|
||||
/// - `error.ChecksumFailure` if `verify_checksum` is true and the frame
|
||||
/// contains a checksum that does not match the checksum of the decompressed
|
||||
/// data
|
||||
/// - `error.ReservedBitSet` if any of the reserved bits of the frame header
|
||||
/// are set
|
||||
/// - `error.EndOfStream` if `src` does not contain a complete frame
|
||||
/// - `error.BadContentSize` if the content size declared by the frame does
|
||||
/// not equal the actual size of decompressed data
|
||||
/// - an error in `block.Error` if there are errors decoding a block
|
||||
/// - `error.SkippableSizeTooLarge` if the frame is skippable and reports a
|
||||
/// size greater than `src.len`
|
||||
pub fn decodeFrame(
|
||||
dest: []u8,
|
||||
src: []const u8,
|
||||
verify_checksum: bool,
|
||||
) (error{
|
||||
BadMagic,
|
||||
UnknownContentSizeUnsupported,
|
||||
ContentTooLarge,
|
||||
ContentSizeTooLarge,
|
||||
WindowSizeUnknown,
|
||||
DictionaryIdFlagUnsupported,
|
||||
SkippableSizeTooLarge,
|
||||
} || FrameError)!ReadWriteCount {
|
||||
var fbs: std.io.FixedBufferStream = .{ .buffer = src };
|
||||
switch (try decodeFrameType(fbs.reader())) {
|
||||
.zstandard => return decodeZstandardFrame(dest, src, verify_checksum),
|
||||
.skippable => {
|
||||
const content_size = try fbs.reader().readInt(u32, .little);
|
||||
if (content_size > std.math.maxInt(usize) - 8) return error.SkippableSizeTooLarge;
|
||||
const read_count = @as(usize, content_size) + 8;
|
||||
if (read_count > src.len) return error.SkippableSizeTooLarge;
|
||||
return ReadWriteCount{
|
||||
.read_count = read_count,
|
||||
.write_count = 0,
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the frame checksum corresponding to the data fed into `hasher`
|
||||
pub fn computeChecksum(hasher: *std.hash.XxHash64) u32 {
|
||||
const hash = hasher.final();
|
||||
return @as(u32, @intCast(hash & 0xFFFFFFFF));
|
||||
}
|
||||
|
||||
const FrameError = error{
|
||||
ChecksumFailure,
|
||||
BadContentSize,
|
||||
EndOfStream,
|
||||
ReservedBitSet,
|
||||
} || block.Error;
|
||||
|
||||
/// Decode a Zstandard frame from `src` into `dest`, returning the number of
|
||||
/// bytes read from `src` and written to `dest`. The first four bytes of `src`
|
||||
/// must be the magic number for a Zstandard frame.
|
||||
///
|
||||
/// Error returned:
|
||||
/// - `error.UnknownContentSizeUnsupported` if the frame does not declare the
|
||||
/// uncompressed content size
|
||||
/// - `error.ContentTooLarge` if `dest` is smaller than the uncompressed data
|
||||
/// size declared by the frame header
|
||||
/// - `error.WindowSizeUnknown` if the frame does not have a valid window size
|
||||
/// - `error.DictionaryIdFlagUnsupported` if the frame uses a dictionary
|
||||
/// - `error.ContentSizeTooLarge` if the frame header indicates a content size
|
||||
/// that is larger than `std.math.maxInt(usize)`
|
||||
/// - `error.ChecksumFailure` if `verify_checksum` is true and the frame
|
||||
/// contains a checksum that does not match the checksum of the decompressed
|
||||
/// data
|
||||
/// - `error.ReservedBitSet` if the reserved bit of the frame header is set
|
||||
/// - `error.EndOfStream` if `src` does not contain a complete frame
|
||||
/// - an error in `block.Error` if there are errors decoding a block
|
||||
/// - `error.BadContentSize` if the content size declared by the frame does
|
||||
/// not equal the actual size of decompressed data
|
||||
pub fn decodeZstandardFrame(
|
||||
dest: []u8,
|
||||
src: []const u8,
|
||||
verify_checksum: bool,
|
||||
) (error{
|
||||
UnknownContentSizeUnsupported,
|
||||
ContentTooLarge,
|
||||
ContentSizeTooLarge,
|
||||
WindowSizeUnknown,
|
||||
DictionaryIdFlagUnsupported,
|
||||
} || FrameError)!ReadWriteCount {
|
||||
assert(std.mem.readInt(u32, src[0..4], .little) == frame.Zstandard.magic_number);
|
||||
var consumed_count: usize = 4;
|
||||
|
||||
var frame_context = context: {
|
||||
var fbs: std.io.FixedBufferStream = .{ .buffer = src[consumed_count..] };
|
||||
const source = fbs.reader();
|
||||
const frame_header = try decodeZstandardHeader(source);
|
||||
consumed_count += fbs.pos;
|
||||
break :context FrameContext.init(
|
||||
frame_header,
|
||||
std.math.maxInt(usize),
|
||||
verify_checksum,
|
||||
) catch |err| switch (err) {
|
||||
error.WindowTooLarge => unreachable,
|
||||
inline else => |e| return e,
|
||||
};
|
||||
};
|
||||
const counts = try decodeZStandardFrameBlocks(
|
||||
dest,
|
||||
src[consumed_count..],
|
||||
&frame_context,
|
||||
);
|
||||
return ReadWriteCount{
|
||||
.read_count = counts.read_count + consumed_count,
|
||||
.write_count = counts.write_count,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn decodeZStandardFrameBlocks(
|
||||
dest: []u8,
|
||||
src: []const u8,
|
||||
frame_context: *FrameContext,
|
||||
) (error{ ContentTooLarge, UnknownContentSizeUnsupported } || FrameError)!ReadWriteCount {
|
||||
const content_size = frame_context.content_size orelse
|
||||
return error.UnknownContentSizeUnsupported;
|
||||
if (dest.len < content_size) return error.ContentTooLarge;
|
||||
|
||||
var consumed_count: usize = 0;
|
||||
const written_count = decodeFrameBlocksInner(
|
||||
dest[0..content_size],
|
||||
src[consumed_count..],
|
||||
&consumed_count,
|
||||
if (frame_context.hasher_opt) |*hasher| hasher else null,
|
||||
frame_context.block_size_max,
|
||||
) catch |err| switch (err) {
|
||||
error.DestTooSmall => return error.BadContentSize,
|
||||
inline else => |e| return e,
|
||||
};
|
||||
|
||||
if (written_count != content_size) return error.BadContentSize;
|
||||
if (frame_context.has_checksum) {
|
||||
if (src.len < consumed_count + 4) return error.EndOfStream;
|
||||
const checksum = std.mem.readInt(u32, src[consumed_count..][0..4], .little);
|
||||
consumed_count += 4;
|
||||
if (frame_context.hasher_opt) |*hasher| {
|
||||
if (checksum != computeChecksum(hasher)) return error.ChecksumFailure;
|
||||
}
|
||||
}
|
||||
return ReadWriteCount{ .read_count = consumed_count, .write_count = written_count };
|
||||
}
|
||||
|
||||
pub const FrameContext = struct {
|
||||
hasher_opt: ?std.hash.XxHash64,
|
||||
window_size: usize,
|
||||
has_checksum: bool,
|
||||
block_size_max: usize,
|
||||
content_size: ?usize,
|
||||
|
||||
const Error = error{
|
||||
DictionaryIdFlagUnsupported,
|
||||
WindowSizeUnknown,
|
||||
WindowTooLarge,
|
||||
ContentSizeTooLarge,
|
||||
};
|
||||
/// Validates `frame_header` and returns the associated `FrameContext`.
|
||||
///
|
||||
/// Errors returned:
|
||||
/// - `error.DictionaryIdFlagUnsupported` if the frame uses a dictionary
|
||||
/// - `error.WindowSizeUnknown` if the frame does not have a valid window
|
||||
/// size
|
||||
/// - `error.WindowTooLarge` if the window size is larger than
|
||||
/// `window_size_max` or `std.math.intMax(usize)`
|
||||
/// - `error.ContentSizeTooLarge` if the frame header indicates a content
|
||||
/// size larger than `std.math.maxInt(usize)`
|
||||
pub fn init(
|
||||
frame_header: ZstandardHeader,
|
||||
window_size_max: usize,
|
||||
verify_checksum: bool,
|
||||
) Error!FrameContext {
|
||||
if (frame_header.descriptor.dictionary_id_flag != 0)
|
||||
return error.DictionaryIdFlagUnsupported;
|
||||
|
||||
const window_size_raw = frameWindowSize(frame_header) orelse return error.WindowSizeUnknown;
|
||||
const window_size = if (window_size_raw > window_size_max)
|
||||
return error.WindowTooLarge
|
||||
else
|
||||
std.math.cast(usize, window_size_raw) orelse return error.WindowTooLarge;
|
||||
|
||||
const should_compute_checksum =
|
||||
frame_header.descriptor.content_checksum_flag and verify_checksum;
|
||||
|
||||
const content_size = if (frame_header.content_size) |size|
|
||||
std.math.cast(usize, size) orelse return error.ContentSizeTooLarge
|
||||
else
|
||||
null;
|
||||
|
||||
return .{
|
||||
.hasher_opt = if (should_compute_checksum) std.hash.XxHash64.init(0) else null,
|
||||
.window_size = window_size,
|
||||
.has_checksum = frame_header.descriptor.content_checksum_flag,
|
||||
.block_size_max = @min(types.block_size_max, window_size),
|
||||
.content_size = content_size,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn decodeFrameBlocksInner(
|
||||
dest: []u8,
|
||||
src: []const u8,
|
||||
consumed_count: *usize,
|
||||
hash: ?*std.hash.XxHash64,
|
||||
block_size_max: usize,
|
||||
) (error{ EndOfStream, DestTooSmall } || block.Error)!usize {
|
||||
// These tables take 7680 bytes
|
||||
var literal_fse_data: [types.compressed_block.table_size_max.literal]Table.Fse = undefined;
|
||||
var match_fse_data: [types.compressed_block.table_size_max.match]Table.Fse = undefined;
|
||||
var offset_fse_data: [types.compressed_block.table_size_max.offset]Table.Fse = undefined;
|
||||
|
||||
var block_header = try block.decodeBlockHeaderSlice(src);
|
||||
var bytes_read: usize = 3;
|
||||
defer consumed_count.* += bytes_read;
|
||||
var decode_state = block.DecodeState.init(&literal_fse_data, &match_fse_data, &offset_fse_data);
|
||||
var count: usize = 0;
|
||||
while (true) : ({
|
||||
block_header = try block.decodeBlockHeaderSlice(src[bytes_read..]);
|
||||
bytes_read += 3;
|
||||
}) {
|
||||
const written_size = try block.decodeBlock(
|
||||
dest,
|
||||
src[bytes_read..],
|
||||
block_header,
|
||||
&decode_state,
|
||||
&bytes_read,
|
||||
block_size_max,
|
||||
count,
|
||||
);
|
||||
if (hash) |hash_state| hash_state.update(dest[count .. count + written_size]);
|
||||
count += written_size;
|
||||
if (block_header.last_block) break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/// Decode the header of a skippable frame. The first four bytes of `src` must
|
||||
/// be a valid magic number for a skippable frame.
|
||||
pub fn decodeSkippableHeader(src: *const [8]u8) SkippableHeader {
|
||||
const magic = std.mem.readInt(u32, src[0..4], .little);
|
||||
assert(isSkippableMagic(magic));
|
||||
const frame_size = std.mem.readInt(u32, src[4..8], .little);
|
||||
return .{
|
||||
.magic_number = magic,
|
||||
.frame_size = frame_size,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the window size required to decompress a frame, or `null` if it
|
||||
/// cannot be determined (which indicates a malformed frame header).
|
||||
pub fn frameWindowSize(header: ZstandardHeader) ?u64 {
|
||||
if (header.window_descriptor) |descriptor| {
|
||||
const exponent = (descriptor & 0b11111000) >> 3;
|
||||
const mantissa = descriptor & 0b00000111;
|
||||
const window_log = 10 + exponent;
|
||||
const window_base = @as(u64, 1) << @as(u6, @intCast(window_log));
|
||||
const window_add = (window_base / 8) * mantissa;
|
||||
return window_base + window_add;
|
||||
} else return header.content_size;
|
||||
}
|
||||
|
||||
/// Decode the header of a Zstandard frame.
|
||||
///
|
||||
/// Errors returned:
|
||||
/// - `error.ReservedBitSet` if any of the reserved bits of the header are set
|
||||
/// - `error.EndOfStream` if `source` does not contain a complete header
|
||||
pub fn decodeZstandardHeader(
|
||||
source: anytype,
|
||||
) (@TypeOf(source).Error || error{ EndOfStream, ReservedBitSet })!ZstandardHeader {
|
||||
const descriptor = @as(ZstandardHeader.Descriptor, @bitCast(try source.readByte()));
|
||||
|
||||
if (descriptor.reserved) return error.ReservedBitSet;
|
||||
|
||||
var window_descriptor: ?u8 = null;
|
||||
if (!descriptor.single_segment_flag) {
|
||||
window_descriptor = try source.readByte();
|
||||
}
|
||||
|
||||
var dictionary_id: ?u32 = null;
|
||||
if (descriptor.dictionary_id_flag > 0) {
|
||||
// if flag is 3 then field_size = 4, else field_size = flag
|
||||
const field_size = (@as(u4, 1) << descriptor.dictionary_id_flag) >> 1;
|
||||
dictionary_id = try source.readVarInt(u32, .little, field_size);
|
||||
}
|
||||
|
||||
var content_size: ?u64 = null;
|
||||
if (descriptor.single_segment_flag or descriptor.content_size_flag > 0) {
|
||||
const field_size = @as(u4, 1) << descriptor.content_size_flag;
|
||||
content_size = try source.readVarInt(u64, .little, field_size);
|
||||
if (field_size == 2) content_size.? += 256;
|
||||
}
|
||||
|
||||
const header = ZstandardHeader{
|
||||
.descriptor = descriptor,
|
||||
.window_descriptor = window_descriptor,
|
||||
.dictionary_id = dictionary_id,
|
||||
.content_size = content_size,
|
||||
};
|
||||
return header;
|
||||
}
|
||||
|
||||
test {
|
||||
_ = types;
|
||||
_ = block;
|
||||
_ = readers;
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const ReversedByteReader = struct {
|
||||
remaining_bytes: usize,
|
||||
bytes: []const u8,
|
||||
|
||||
const Reader = std.io.Reader(*ReversedByteReader, error{}, readFn);
|
||||
|
||||
pub fn init(bytes: []const u8) ReversedByteReader {
|
||||
return .{
|
||||
.bytes = bytes,
|
||||
.remaining_bytes = bytes.len,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reader(self: *ReversedByteReader) Reader {
|
||||
return .{ .context = self };
|
||||
}
|
||||
|
||||
fn readFn(ctx: *ReversedByteReader, buffer: []u8) !usize {
|
||||
if (ctx.remaining_bytes == 0) return 0;
|
||||
const byte_index = ctx.remaining_bytes - 1;
|
||||
buffer[0] = ctx.bytes[byte_index];
|
||||
// buffer[0] = @bitReverse(ctx.bytes[byte_index]);
|
||||
ctx.remaining_bytes = byte_index;
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
/// A bit reader for reading the reversed bit streams used to encode
|
||||
/// FSE compressed data.
|
||||
pub const ReverseBitReader = struct {
|
||||
byte_reader: ReversedByteReader,
|
||||
bit_reader: std.io.BitReader(.big),
|
||||
|
||||
pub fn init(self: *ReverseBitReader, bytes: []const u8) error{BitStreamHasNoStartBit}!void {
|
||||
self.byte_reader = ReversedByteReader.init(bytes);
|
||||
self.bit_reader = .init(self.byte_reader.reader());
|
||||
if (bytes.len == 0) return;
|
||||
var i: usize = 0;
|
||||
while (i < 8 and 0 == self.readBitsNoEof(u1, 1) catch unreachable) : (i += 1) {}
|
||||
if (i == 8) return error.BitStreamHasNoStartBit;
|
||||
}
|
||||
|
||||
pub fn readBitsNoEof(self: *@This(), comptime U: type, num_bits: u16) error{EndOfStream}!U {
|
||||
return self.bit_reader.readBitsNoEof(U, num_bits);
|
||||
}
|
||||
|
||||
pub fn readBits(self: *@This(), comptime U: type, num_bits: u16, out_bits: *u16) error{}!U {
|
||||
return try self.bit_reader.readBits(U, num_bits, out_bits);
|
||||
}
|
||||
|
||||
pub fn alignToByte(self: *@This()) void {
|
||||
self.bit_reader.alignToByte();
|
||||
}
|
||||
|
||||
pub fn isEmpty(self: ReverseBitReader) bool {
|
||||
return self.byte_reader.remaining_bytes == 0 and self.bit_reader.count == 0;
|
||||
}
|
||||
};
|
||||
|
||||
pub const BitReader = std.io.BitReader(.little);
|
||||
@ -1,403 +0,0 @@
|
||||
pub const block_size_max = 1 << 17;
|
||||
|
||||
pub const frame = struct {
|
||||
pub const Kind = enum { zstandard, skippable };
|
||||
|
||||
pub const Zstandard = struct {
|
||||
pub const magic_number = 0xFD2FB528;
|
||||
|
||||
header: Header,
|
||||
data_blocks: []Block,
|
||||
checksum: ?u32,
|
||||
|
||||
pub const Header = struct {
|
||||
descriptor: Descriptor,
|
||||
window_descriptor: ?u8,
|
||||
dictionary_id: ?u32,
|
||||
content_size: ?u64,
|
||||
|
||||
pub const Descriptor = packed struct {
|
||||
dictionary_id_flag: u2,
|
||||
content_checksum_flag: bool,
|
||||
reserved: bool,
|
||||
unused: bool,
|
||||
single_segment_flag: bool,
|
||||
content_size_flag: u2,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Block = struct {
|
||||
pub const Header = struct {
|
||||
last_block: bool,
|
||||
block_type: Block.Type,
|
||||
block_size: u21,
|
||||
};
|
||||
|
||||
pub const Type = enum(u2) {
|
||||
raw,
|
||||
rle,
|
||||
compressed,
|
||||
reserved,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const Skippable = struct {
|
||||
pub const magic_number_min = 0x184D2A50;
|
||||
pub const magic_number_max = 0x184D2A5F;
|
||||
|
||||
pub const Header = struct {
|
||||
magic_number: u32,
|
||||
frame_size: u32,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const compressed_block = struct {
|
||||
pub const LiteralsSection = struct {
|
||||
header: Header,
|
||||
huffman_tree: ?HuffmanTree,
|
||||
streams: Streams,
|
||||
|
||||
pub const Streams = union(enum) {
|
||||
one: []const u8,
|
||||
four: [4][]const u8,
|
||||
};
|
||||
|
||||
pub const Header = struct {
|
||||
block_type: BlockType,
|
||||
size_format: u2,
|
||||
regenerated_size: u20,
|
||||
compressed_size: ?u18,
|
||||
};
|
||||
|
||||
pub const BlockType = enum(u2) {
|
||||
raw,
|
||||
rle,
|
||||
compressed,
|
||||
treeless,
|
||||
};
|
||||
|
||||
pub const HuffmanTree = struct {
|
||||
max_bit_count: u4,
|
||||
symbol_count_minus_one: u8,
|
||||
nodes: [256]PrefixedSymbol,
|
||||
|
||||
pub const PrefixedSymbol = struct {
|
||||
symbol: u8,
|
||||
prefix: u16,
|
||||
weight: u4,
|
||||
};
|
||||
|
||||
pub const Result = union(enum) {
|
||||
symbol: u8,
|
||||
index: usize,
|
||||
};
|
||||
|
||||
pub fn query(self: HuffmanTree, index: usize, prefix: u16) error{NotFound}!Result {
|
||||
var node = self.nodes[index];
|
||||
const weight = node.weight;
|
||||
var i: usize = index;
|
||||
while (node.weight == weight) {
|
||||
if (node.prefix == prefix) return Result{ .symbol = node.symbol };
|
||||
if (i == 0) return error.NotFound;
|
||||
i -= 1;
|
||||
node = self.nodes[i];
|
||||
}
|
||||
return Result{ .index = i };
|
||||
}
|
||||
|
||||
pub fn weightToBitCount(weight: u4, max_bit_count: u4) u4 {
|
||||
return if (weight == 0) 0 else ((max_bit_count + 1) - weight);
|
||||
}
|
||||
};
|
||||
|
||||
pub const StreamCount = enum { one, four };
|
||||
pub fn streamCount(size_format: u2, block_type: BlockType) StreamCount {
|
||||
return switch (block_type) {
|
||||
.raw, .rle => .one,
|
||||
.compressed, .treeless => if (size_format == 0) .one else .four,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const SequencesSection = struct {
|
||||
header: SequencesSection.Header,
|
||||
literals_length_table: Table,
|
||||
offset_table: Table,
|
||||
match_length_table: Table,
|
||||
|
||||
pub const Header = struct {
|
||||
sequence_count: u24,
|
||||
match_lengths: Mode,
|
||||
offsets: Mode,
|
||||
literal_lengths: Mode,
|
||||
|
||||
pub const Mode = enum(u2) {
|
||||
predefined,
|
||||
rle,
|
||||
fse,
|
||||
repeat,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const Table = union(enum) {
|
||||
fse: []const Fse,
|
||||
rle: u8,
|
||||
|
||||
pub const Fse = struct {
|
||||
symbol: u8,
|
||||
baseline: u16,
|
||||
bits: u8,
|
||||
};
|
||||
};
|
||||
|
||||
pub const literals_length_code_table = [36]struct { u32, u5 }{
|
||||
.{ 0, 0 }, .{ 1, 0 }, .{ 2, 0 }, .{ 3, 0 },
|
||||
.{ 4, 0 }, .{ 5, 0 }, .{ 6, 0 }, .{ 7, 0 },
|
||||
.{ 8, 0 }, .{ 9, 0 }, .{ 10, 0 }, .{ 11, 0 },
|
||||
.{ 12, 0 }, .{ 13, 0 }, .{ 14, 0 }, .{ 15, 0 },
|
||||
.{ 16, 1 }, .{ 18, 1 }, .{ 20, 1 }, .{ 22, 1 },
|
||||
.{ 24, 2 }, .{ 28, 2 }, .{ 32, 3 }, .{ 40, 3 },
|
||||
.{ 48, 4 }, .{ 64, 6 }, .{ 128, 7 }, .{ 256, 8 },
|
||||
.{ 512, 9 }, .{ 1024, 10 }, .{ 2048, 11 }, .{ 4096, 12 },
|
||||
.{ 8192, 13 }, .{ 16384, 14 }, .{ 32768, 15 }, .{ 65536, 16 },
|
||||
};
|
||||
|
||||
pub const match_length_code_table = [53]struct { u32, u5 }{
|
||||
.{ 3, 0 }, .{ 4, 0 }, .{ 5, 0 }, .{ 6, 0 }, .{ 7, 0 }, .{ 8, 0 },
|
||||
.{ 9, 0 }, .{ 10, 0 }, .{ 11, 0 }, .{ 12, 0 }, .{ 13, 0 }, .{ 14, 0 },
|
||||
.{ 15, 0 }, .{ 16, 0 }, .{ 17, 0 }, .{ 18, 0 }, .{ 19, 0 }, .{ 20, 0 },
|
||||
.{ 21, 0 }, .{ 22, 0 }, .{ 23, 0 }, .{ 24, 0 }, .{ 25, 0 }, .{ 26, 0 },
|
||||
.{ 27, 0 }, .{ 28, 0 }, .{ 29, 0 }, .{ 30, 0 }, .{ 31, 0 }, .{ 32, 0 },
|
||||
.{ 33, 0 }, .{ 34, 0 }, .{ 35, 1 }, .{ 37, 1 }, .{ 39, 1 }, .{ 41, 1 },
|
||||
.{ 43, 2 }, .{ 47, 2 }, .{ 51, 3 }, .{ 59, 3 }, .{ 67, 4 }, .{ 83, 4 },
|
||||
.{ 99, 5 }, .{ 131, 7 }, .{ 259, 8 }, .{ 515, 9 }, .{ 1027, 10 }, .{ 2051, 11 },
|
||||
.{ 4099, 12 }, .{ 8195, 13 }, .{ 16387, 14 }, .{ 32771, 15 }, .{ 65539, 16 },
|
||||
};
|
||||
|
||||
pub const literals_length_default_distribution = [36]i16{
|
||||
4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, 1, 1, 1,
|
||||
-1, -1, -1, -1,
|
||||
};
|
||||
|
||||
pub const match_lengths_default_distribution = [53]i16{
|
||||
1, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1,
|
||||
-1, -1, -1, -1, -1,
|
||||
};
|
||||
|
||||
pub const offset_codes_default_distribution = [29]i16{
|
||||
1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1,
|
||||
};
|
||||
|
||||
pub const predefined_literal_fse_table = Table{
|
||||
.fse = &[64]Table.Fse{
|
||||
.{ .symbol = 0, .bits = 4, .baseline = 0 },
|
||||
.{ .symbol = 0, .bits = 4, .baseline = 16 },
|
||||
.{ .symbol = 1, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 3, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 4, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 6, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 7, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 9, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 10, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 12, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 14, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 16, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 18, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 19, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 21, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 22, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 24, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 25, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 26, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 27, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 29, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 31, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 0, .bits = 4, .baseline = 32 },
|
||||
.{ .symbol = 1, .bits = 4, .baseline = 0 },
|
||||
.{ .symbol = 2, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 4, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 5, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 7, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 8, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 10, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 11, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 13, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 16, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 17, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 19, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 20, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 22, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 23, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 25, .bits = 4, .baseline = 0 },
|
||||
.{ .symbol = 25, .bits = 4, .baseline = 16 },
|
||||
.{ .symbol = 26, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 28, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 30, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 0, .bits = 4, .baseline = 48 },
|
||||
.{ .symbol = 1, .bits = 4, .baseline = 16 },
|
||||
.{ .symbol = 2, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 3, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 5, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 6, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 8, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 9, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 11, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 12, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 15, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 17, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 18, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 20, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 21, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 23, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 24, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 35, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 34, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 33, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 32, .bits = 6, .baseline = 0 },
|
||||
},
|
||||
};
|
||||
|
||||
pub const predefined_match_fse_table = Table{
|
||||
.fse = &[64]Table.Fse{
|
||||
.{ .symbol = 0, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 1, .bits = 4, .baseline = 0 },
|
||||
.{ .symbol = 2, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 3, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 5, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 6, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 8, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 10, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 13, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 16, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 19, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 22, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 25, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 28, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 31, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 33, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 35, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 37, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 39, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 41, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 43, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 45, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 1, .bits = 4, .baseline = 16 },
|
||||
.{ .symbol = 2, .bits = 4, .baseline = 0 },
|
||||
.{ .symbol = 3, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 4, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 6, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 7, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 9, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 12, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 15, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 18, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 21, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 24, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 27, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 30, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 32, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 34, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 36, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 38, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 40, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 42, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 44, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 1, .bits = 4, .baseline = 32 },
|
||||
.{ .symbol = 1, .bits = 4, .baseline = 48 },
|
||||
.{ .symbol = 2, .bits = 4, .baseline = 16 },
|
||||
.{ .symbol = 4, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 5, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 7, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 8, .bits = 5, .baseline = 32 },
|
||||
.{ .symbol = 11, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 14, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 17, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 20, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 23, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 26, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 29, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 52, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 51, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 50, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 49, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 48, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 47, .bits = 6, .baseline = 0 },
|
||||
.{ .symbol = 46, .bits = 6, .baseline = 0 },
|
||||
},
|
||||
};
|
||||
|
||||
pub const predefined_offset_fse_table = Table{
|
||||
.fse = &[32]Table.Fse{
|
||||
.{ .symbol = 0, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 6, .bits = 4, .baseline = 0 },
|
||||
.{ .symbol = 9, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 15, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 21, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 3, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 7, .bits = 4, .baseline = 0 },
|
||||
.{ .symbol = 12, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 18, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 23, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 5, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 8, .bits = 4, .baseline = 0 },
|
||||
.{ .symbol = 14, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 20, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 2, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 7, .bits = 4, .baseline = 16 },
|
||||
.{ .symbol = 11, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 17, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 22, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 4, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 8, .bits = 4, .baseline = 16 },
|
||||
.{ .symbol = 13, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 19, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 1, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 6, .bits = 4, .baseline = 16 },
|
||||
.{ .symbol = 10, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 16, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 28, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 27, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 26, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 25, .bits = 5, .baseline = 0 },
|
||||
.{ .symbol = 24, .bits = 5, .baseline = 0 },
|
||||
},
|
||||
};
|
||||
pub const start_repeated_offset_1 = 1;
|
||||
pub const start_repeated_offset_2 = 4;
|
||||
pub const start_repeated_offset_3 = 8;
|
||||
|
||||
pub const table_accuracy_log_max = struct {
|
||||
pub const literal = 9;
|
||||
pub const match = 9;
|
||||
pub const offset = 8;
|
||||
};
|
||||
|
||||
pub const table_symbol_count_max = struct {
|
||||
pub const literal = 36;
|
||||
pub const match = 53;
|
||||
pub const offset = 32;
|
||||
};
|
||||
|
||||
pub const default_accuracy_log = struct {
|
||||
pub const literal = 6;
|
||||
pub const match = 6;
|
||||
pub const offset = 5;
|
||||
};
|
||||
pub const table_size_max = struct {
|
||||
pub const literal = 1 << table_accuracy_log_max.literal;
|
||||
pub const match = 1 << table_accuracy_log_max.match;
|
||||
pub const offset = 1 << table_accuracy_log_max.offset;
|
||||
};
|
||||
};
|
||||
|
||||
test {
|
||||
const testing = @import("std").testing;
|
||||
testing.refAllDeclsRecursive(@This());
|
||||
}
|
||||
153
lib/std/compress/zstd.zig
Normal file
153
lib/std/compress/zstd.zig
Normal file
@ -0,0 +1,153 @@
|
||||
const std = @import("../std.zig");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
/// Recommended amount by the standard. Lower than this may result in inability
|
||||
/// to decompress common streams.
|
||||
pub const default_window_len = 8 * 1024 * 1024;
|
||||
|
||||
pub const Decompress = @import("zstd/Decompress.zig");
|
||||
|
||||
pub const block_size_max = 1 << 17;
|
||||
|
||||
pub const literals_length_default_distribution = [36]i16{
|
||||
4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, 1, 1, 1,
|
||||
-1, -1, -1, -1,
|
||||
};
|
||||
|
||||
pub const match_lengths_default_distribution = [53]i16{
|
||||
1, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1,
|
||||
-1, -1, -1, -1, -1,
|
||||
};
|
||||
|
||||
pub const offset_codes_default_distribution = [29]i16{
|
||||
1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1,
|
||||
};
|
||||
|
||||
pub const start_repeated_offset_1 = 1;
|
||||
pub const start_repeated_offset_2 = 4;
|
||||
pub const start_repeated_offset_3 = 8;
|
||||
|
||||
pub const literals_length_code_table = [36]struct { u32, u5 }{
|
||||
.{ 0, 0 }, .{ 1, 0 }, .{ 2, 0 }, .{ 3, 0 },
|
||||
.{ 4, 0 }, .{ 5, 0 }, .{ 6, 0 }, .{ 7, 0 },
|
||||
.{ 8, 0 }, .{ 9, 0 }, .{ 10, 0 }, .{ 11, 0 },
|
||||
.{ 12, 0 }, .{ 13, 0 }, .{ 14, 0 }, .{ 15, 0 },
|
||||
.{ 16, 1 }, .{ 18, 1 }, .{ 20, 1 }, .{ 22, 1 },
|
||||
.{ 24, 2 }, .{ 28, 2 }, .{ 32, 3 }, .{ 40, 3 },
|
||||
.{ 48, 4 }, .{ 64, 6 }, .{ 128, 7 }, .{ 256, 8 },
|
||||
.{ 512, 9 }, .{ 1024, 10 }, .{ 2048, 11 }, .{ 4096, 12 },
|
||||
.{ 8192, 13 }, .{ 16384, 14 }, .{ 32768, 15 }, .{ 65536, 16 },
|
||||
};
|
||||
|
||||
pub const match_length_code_table = [53]struct { u32, u5 }{
|
||||
.{ 3, 0 }, .{ 4, 0 }, .{ 5, 0 }, .{ 6, 0 }, .{ 7, 0 }, .{ 8, 0 },
|
||||
.{ 9, 0 }, .{ 10, 0 }, .{ 11, 0 }, .{ 12, 0 }, .{ 13, 0 }, .{ 14, 0 },
|
||||
.{ 15, 0 }, .{ 16, 0 }, .{ 17, 0 }, .{ 18, 0 }, .{ 19, 0 }, .{ 20, 0 },
|
||||
.{ 21, 0 }, .{ 22, 0 }, .{ 23, 0 }, .{ 24, 0 }, .{ 25, 0 }, .{ 26, 0 },
|
||||
.{ 27, 0 }, .{ 28, 0 }, .{ 29, 0 }, .{ 30, 0 }, .{ 31, 0 }, .{ 32, 0 },
|
||||
.{ 33, 0 }, .{ 34, 0 }, .{ 35, 1 }, .{ 37, 1 }, .{ 39, 1 }, .{ 41, 1 },
|
||||
.{ 43, 2 }, .{ 47, 2 }, .{ 51, 3 }, .{ 59, 3 }, .{ 67, 4 }, .{ 83, 4 },
|
||||
.{ 99, 5 }, .{ 131, 7 }, .{ 259, 8 }, .{ 515, 9 }, .{ 1027, 10 }, .{ 2051, 11 },
|
||||
.{ 4099, 12 }, .{ 8195, 13 }, .{ 16387, 14 }, .{ 32771, 15 }, .{ 65539, 16 },
|
||||
};
|
||||
|
||||
pub const table_accuracy_log_max = struct {
|
||||
pub const literal = 9;
|
||||
pub const match = 9;
|
||||
pub const offset = 8;
|
||||
};
|
||||
|
||||
pub const table_symbol_count_max = struct {
|
||||
pub const literal = 36;
|
||||
pub const match = 53;
|
||||
pub const offset = 32;
|
||||
};
|
||||
|
||||
pub const default_accuracy_log = struct {
|
||||
pub const literal = 6;
|
||||
pub const match = 6;
|
||||
pub const offset = 5;
|
||||
};
|
||||
pub const table_size_max = struct {
|
||||
pub const literal = 1 << table_accuracy_log_max.literal;
|
||||
pub const match = 1 << table_accuracy_log_max.match;
|
||||
pub const offset = 1 << table_accuracy_log_max.offset;
|
||||
};
|
||||
|
||||
fn testDecompress(gpa: std.mem.Allocator, compressed: []const u8) ![]u8 {
|
||||
var out: std.ArrayListUnmanaged(u8) = .empty;
|
||||
defer out.deinit(gpa);
|
||||
try out.ensureUnusedCapacity(gpa, default_window_len);
|
||||
|
||||
var in: std.io.BufferedReader = undefined;
|
||||
in.initFixed(@constCast(compressed));
|
||||
var zstd_stream: Decompress = .init(&in, .{});
|
||||
try zstd_stream.reader().readRemainingArrayList(gpa, null, &out, .unlimited);
|
||||
|
||||
return out.toOwnedSlice(gpa);
|
||||
}
|
||||
|
||||
fn testExpectDecompress(uncompressed: []const u8, compressed: []const u8) !void {
|
||||
const gpa = std.testing.allocator;
|
||||
const result = try testDecompress(gpa, compressed);
|
||||
defer gpa.free(result);
|
||||
try std.testing.expectEqualSlices(u8, uncompressed, result);
|
||||
}
|
||||
|
||||
fn testExpectDecompressError(err: anyerror, compressed: []const u8) !void {
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
var out: std.ArrayListUnmanaged(u8) = .empty;
|
||||
defer out.deinit(gpa);
|
||||
try out.ensureUnusedCapacity(gpa, default_window_len);
|
||||
|
||||
var in: std.io.BufferedReader = undefined;
|
||||
in.initFixed(@constCast(compressed));
|
||||
var zstd_stream: Decompress = .init(&in, .{});
|
||||
try std.testing.expectError(error.ReadFailed, zstd_stream.reader().readRemainingArrayList(gpa, null, &out, .unlimited));
|
||||
try std.testing.expectError(err, zstd_stream.err.?);
|
||||
|
||||
return error.TestFailed;
|
||||
}
|
||||
|
||||
test "decompression" {
|
||||
const uncompressed = @embedFile("testdata/rfc8478.txt");
|
||||
const compressed3 = @embedFile("testdata/rfc8478.txt.zst.3");
|
||||
const compressed19 = @embedFile("testdata/rfc8478.txt.zst.19");
|
||||
|
||||
try testExpectDecompress(uncompressed, compressed3);
|
||||
try testExpectDecompress(uncompressed, compressed19);
|
||||
}
|
||||
|
||||
test "zero sized block" {
|
||||
const input_raw =
|
||||
"\x28\xb5\x2f\xfd" ++ // zstandard frame magic number
|
||||
"\x20\x00" ++ // frame header: only single_segment_flag set, frame_content_size zero
|
||||
"\x01\x00\x00"; // block header with: last_block set, block_type raw, block_size zero
|
||||
|
||||
const input_rle =
|
||||
"\x28\xb5\x2f\xfd" ++ // zstandard frame magic number
|
||||
"\x20\x00" ++ // frame header: only single_segment_flag set, frame_content_size zero
|
||||
"\x03\x00\x00" ++ // block header with: last_block set, block_type rle, block_size zero
|
||||
"\xaa"; // block_content
|
||||
|
||||
try testExpectDecompress("", input_raw);
|
||||
try testExpectDecompress("", input_rle);
|
||||
}
|
||||
|
||||
test "declared raw literals size too large" {
|
||||
const input_raw =
|
||||
"\x28\xb5\x2f\xfd" ++ // zstandard frame magic number
|
||||
"\x00\x00" ++ // frame header: everything unset, window descriptor zero
|
||||
"\x95\x00\x00" ++ // block header with: last_block set, block_type compressed, block_size 18
|
||||
"\xbc\xf3\xae" ++ // literals section header with: type raw, size_format 3, regenerated_size 716603
|
||||
"\xa5\x9f\xe3"; // some bytes of literal content - the content is shorter than regenerated_size
|
||||
|
||||
// Note that the regenerated_size in the above input is larger than block maximum size, so the
|
||||
// block can't be valid as it is a raw literals block.
|
||||
try testExpectDecompressError(error.MalformedBlock, input_raw);
|
||||
}
|
||||
1641
lib/std/compress/zstd/Decompress.zig
Normal file
1641
lib/std/compress/zstd/Decompress.zig
Normal file
File diff suppressed because it is too large
Load Diff
@ -293,7 +293,9 @@ fn Sha2x32(comptime iv: Iv32, digest_bits: comptime_int) type {
|
||||
|
||||
var i: usize = 16;
|
||||
while (i < 64) : (i += 1) {
|
||||
s[i] = s[i - 16] +% s[i - 7] +% (math.rotr(u32, s[i - 15], @as(u32, 7)) ^ math.rotr(u32, s[i - 15], @as(u32, 18)) ^ (s[i - 15] >> 3)) +% (math.rotr(u32, s[i - 2], @as(u32, 17)) ^ math.rotr(u32, s[i - 2], @as(u32, 19)) ^ (s[i - 2] >> 10));
|
||||
s[i] = s[i - 16] +% s[i - 7] +%
|
||||
(math.rotr(u32, s[i - 15], @as(u32, 7)) ^ math.rotr(u32, s[i - 15], @as(u32, 18)) ^ (s[i - 15] >> 3)) +%
|
||||
(math.rotr(u32, s[i - 2], @as(u32, 17)) ^ math.rotr(u32, s[i - 2], @as(u32, 19)) ^ (s[i - 2] >> 10));
|
||||
}
|
||||
|
||||
var v: [8]u32 = d.s;
|
||||
|
||||
@ -825,17 +825,32 @@ pub inline fn peekStructEndian(br: *BufferedReader, comptime T: type, endian: st
|
||||
return res;
|
||||
}
|
||||
|
||||
pub const TakeEnumError = Reader.Error || error{InvalidEnumTag};
|
||||
|
||||
/// Reads an integer with the same size as the given enum's tag type. If the
|
||||
/// integer matches an enum tag, casts the integer to the enum tag and returns
|
||||
/// it. Otherwise, returns `error.InvalidEnumTag`.
|
||||
///
|
||||
/// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`.
|
||||
pub fn takeEnum(br: *BufferedReader, comptime Enum: type, endian: std.builtin.Endian) (Reader.Error || std.meta.IntToEnumError)!Enum {
|
||||
pub fn takeEnum(br: *BufferedReader, comptime Enum: type, endian: std.builtin.Endian) TakeEnumError!Enum {
|
||||
const Tag = @typeInfo(Enum).@"enum".tag_type;
|
||||
const int = try br.takeInt(Tag, endian);
|
||||
return std.meta.intToEnum(Enum, int);
|
||||
}
|
||||
|
||||
/// Reads an integer with the same size as the given nonexhaustive enum's tag type.
|
||||
///
|
||||
/// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`.
|
||||
pub fn takeEnumNonexhaustive(br: *BufferedReader, comptime Enum: type, endian: std.builtin.Endian) Reader.Error!Enum {
|
||||
const info = @typeInfo(Enum).@"enum";
|
||||
comptime assert(!info.is_exhaustive);
|
||||
comptime assert(@bitSizeOf(info.tag_type) == @sizeOf(info.tag_type) * 8);
|
||||
return takeEnum(br, Enum, endian) catch |err| switch (err) {
|
||||
error.InvalidEnumTag => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const TakeLeb128Error = Reader.Error || error{Overflow};
|
||||
|
||||
/// Read a single LEB128 value as type T, or `error.Overflow` if the value cannot fit.
|
||||
|
||||
@ -5,7 +5,7 @@ const Writer = std.io.Writer;
|
||||
|
||||
const NullWriter = @This();
|
||||
|
||||
err: Error,
|
||||
err: ?Error = null,
|
||||
|
||||
pub const Error = std.fs.File.StatError;
|
||||
|
||||
|
||||
@ -30,7 +30,6 @@ pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
|
||||
pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue;
|
||||
pub const Progress = @import("Progress.zig");
|
||||
pub const Random = @import("Random.zig");
|
||||
pub const RingBuffer = @import("RingBuffer.zig");
|
||||
pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
|
||||
pub const SemanticVersion = @import("SemanticVersion.zig");
|
||||
pub const SinglyLinkedList = @import("SinglyLinkedList.zig");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user