authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-25 21:33:57-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-26 21:00:58-07:00
logd87eb7d4e4f2ea606a18640fcc019b60cc435cdd
tree437c0e00c1b9a98e92b3045613de39ad36710429
parenta8ae6c2f4265a66c7a63d788a13549c48a1dd8c0

std.compress.xz: skeleton in place

missing these things: - implementation of finish() - detect packed bytes read for check and block padding - implementation of discard() - implementation of block stream checksum

2 files changed, 234 insertions(+), 201 deletions(-)

lib/std/compress/xz/Decompress.zig+224-195
......@@ -26,6 +26,8 @@ pub const Error = error{
2626 WrongChecksum,
2727 Unsupported,
2828 Overflow,
29 InvalidRangeCode,
30 DecompressedSizeMismatch,
2931};
3032
3133pub const Check = enum(u4) {
......@@ -55,14 +57,14 @@ pub fn init(
5557 gpa: Allocator,
5658 /// Decompress takes ownership of this buffer and resizes it with `gpa`.
5759 buffer: []u8,
58) Decompress {
59 const magic = try input.takeBytes(6);
60 if (!std.mem.eql(u8, &magic, &.{ 0xFD, '7', 'z', 'X', 'Z', 0x00 }))
60) !Decompress {
61 const magic = try input.takeArray(6);
62 if (!std.mem.eql(u8, magic, &.{ 0xFD, '7', 'z', 'X', 'Z', 0x00 }))
6163 return error.NotXzStream;
6264
6365 const actual_hash = Crc32.hash(try input.peek(@sizeOf(StreamFlags)));
6466 const stream_flags = input.takeStruct(StreamFlags, .little) catch unreachable;
65 const stored_hash = try input.readInt(u32, .little);
67 const stored_hash = try input.takeInt(u32, .little);
6668 if (actual_hash != stored_hash) return error.WrongChecksum;
6769
6870 return .{
......@@ -71,6 +73,7 @@ pub fn init(
7173 .vtable = &.{
7274 .stream = stream,
7375 .readVec = readVec,
76 .discard = discard,
7477 },
7578 .buffer = buffer,
7679 .seek = 0,
......@@ -83,206 +86,232 @@ pub fn init(
8386 };
8487}
8588
89/// Reclaim ownership of the buffer passed to `init`.
90pub fn takeBuffer(d: *Decompress) []u8 {
91 const buffer = d.reader.buffer;
92 d.reader.buffer = &.{};
93 return buffer;
94}
95
96pub fn deinit(d: *Decompress) void {
97 const gpa = d.gpa;
98 gpa.free(d.reader.buffer);
99 d.* = undefined;
100}
101
102fn readVec(r: *Reader, data: [][]u8) Reader.Error!usize {
103 _ = data;
104 return readIndirect(r);
105}
106
86107fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize {
87108 _ = w;
88109 _ = limit;
110 return readIndirect(r);
111}
112
113fn discard(r: *Reader, limit: std.Io.Limit) Reader.Error!usize {
89114 const d: *Decompress = @alignCast(@fieldParentPtr("reader", r));
90115 _ = d;
116 _ = limit;
91117 @panic("TODO");
92118}
93119
94fn readVec(r: *Reader, data: [][]u8) Reader.Error!usize {
95 _ = data;
120fn readIndirect(r: *Reader) Reader.Error!usize {
96121 const d: *Decompress = @alignCast(@fieldParentPtr("reader", r));
122 const gpa = d.gpa;
123 const input = d.input;
124
125 var allocating = Writer.Allocating.initOwnedSlice(gpa, r.buffer);
126 allocating.writer.end = r.end;
127 defer {
128 r.buffer = allocating.writer.buffer;
129 r.end = allocating.writer.end;
130 }
131
132 if (d.block_count == std.math.maxInt(usize)) return error.EndOfStream;
133
134 readBlock(input, &allocating) catch |err| switch (err) {
135 error.WriteFailed => {
136 d.err = error.OutOfMemory;
137 return error.ReadFailed;
138 },
139 error.SuccessfulEndOfStream => {
140 finish(d);
141 d.block_count = std.math.maxInt(usize);
142 return error.EndOfStream;
143 },
144 else => |e| {
145 d.err = e;
146 return error.ReadFailed;
147 },
148 };
149 switch (d.check) {
150 .none => {},
151 .crc32 => {
152 const declared_checksum = try input.takeInt(u32, .little);
153 // TODO
154 //const hash_a = Crc32.hash(unpacked_bytes);
155 //if (hash_a != hash_b) return error.WrongChecksum;
156 _ = declared_checksum;
157 },
158 .crc64 => {
159 const declared_checksum = try input.takeInt(u64, .little);
160 // TODO
161 //const hash_a = Crc64.hash(unpacked_bytes);
162 //if (hash_a != hash_b) return error.WrongChecksum;
163 _ = declared_checksum;
164 },
165 .sha256 => {
166 const declared_hash = try input.take(Sha256.digest_length);
167 // TODO
168 //var hash_a: [Sha256.digest_length]u8 = undefined;
169 //Sha256.hash(unpacked_bytes, &hash_a, .{});
170 //if (!std.mem.eql(u8, &hash_a, &hash_b))
171 // return error.WrongChecksum;
172 _ = declared_hash;
173 },
174 else => {
175 d.err = error.Unsupported;
176 return error.ReadFailed;
177 },
178 }
179 d.block_count += 1;
180 return 0;
181}
182
183fn readBlock(input: *Reader, allocating: *Writer.Allocating) !void {
184 var packed_size: ?u64 = null;
185 var unpacked_size: ?u64 = null;
186
187 {
188 // Read the block header via peeking so that we can hash the whole thing too.
189 const first_byte: usize = try input.peekByte();
190 if (first_byte == 0) return error.SuccessfulEndOfStream;
191
192 const declared_header_size = first_byte * 4;
193 try input.fill(declared_header_size);
194 const header_seek_start = input.seek;
195 input.toss(1);
196
197 const Flags = packed struct(u8) {
198 last_filter_index: u2,
199 reserved: u4,
200 has_packed_size: bool,
201 has_unpacked_size: bool,
202 };
203 const flags = try input.takeStruct(Flags, .little);
204
205 const filter_count = @as(u3, flags.last_filter_index) + 1;
206 if (filter_count > 1) return error.Unsupported;
207
208 if (flags.has_packed_size) packed_size = try input.takeLeb128(u64);
209 if (flags.has_unpacked_size) unpacked_size = try input.takeLeb128(u64);
210
211 const FilterId = enum(u64) {
212 lzma2 = 0x21,
213 _,
214 };
215
216 const filter_id: FilterId = @enumFromInt(try input.takeLeb128(u64));
217 if (filter_id != .lzma2) return error.Unsupported;
218
219 const properties_size = try input.takeLeb128(u64);
220 if (properties_size != 1) return error.CorruptInput;
221 // TODO: use filter properties
222 _ = try input.takeByte();
223
224 const actual_header_size = input.seek - header_seek_start;
225 if (actual_header_size > declared_header_size) return error.CorruptInput;
226 var remaining_bytes = declared_header_size - actual_header_size;
227 while (remaining_bytes != 0) {
228 if (try input.takeByte() != 0) return error.CorruptInput;
229 remaining_bytes -= 1;
230 }
231
232 const header_slice = input.buffer[header_seek_start..][0..declared_header_size];
233 const actual_hash = Crc32.hash(header_slice);
234 const declared_hash = try input.takeInt(u32, .little);
235 if (actual_hash != declared_hash) return error.WrongChecksum;
236 }
237
238 // Compressed Data
239
240 var lzma2_decode = try lzma2.Decode.init(allocating.allocator);
241 const before_size = allocating.writer.end;
242 try lzma2_decode.decompress(input, allocating);
243 const unpacked_bytes = allocating.writer.end - before_size;
244
245 // TODO restore this check
246 //if (packed_size) |s| {
247 // if (s != packed_counter.bytes_read)
248 // return error.CorruptInput;
249 //}
250
251 if (unpacked_size) |s| {
252 if (s != unpacked_bytes) return error.CorruptInput;
253 }
254
255 // Block Padding
256 if (true) @panic("TODO account for block padding");
257 //while (block_counter.bytes_read % 4 != 0) {
258 // if (try block_reader.takeByte() != 0)
259 // return error.CorruptInput;
260 //}
261
262}
263
264fn finish(d: *Decompress) void {
97265 _ = d;
98266 @panic("TODO");
99}
267 //const input = d.input;
268 //const index_size = blk: {
269 // const record_count = try input.takeLeb128(u64);
270 // if (record_count != d.block_decode.block_count)
271 // return error.CorruptInput;
272
273 // var i: usize = 0;
274 // while (i < record_count) : (i += 1) {
275 // // TODO: validate records
276 // _ = try std.leb.readUleb128(u64, counting_reader);
277 // _ = try std.leb.readUleb128(u64, counting_reader);
278 // }
279
280 // while (counter.bytes_read % 4 != 0) {
281 // if (try counting_reader.takeByte() != 0)
282 // return error.CorruptInput;
283 // }
284
285 // const hash_a = hasher.hasher.final();
286 // const hash_b = try counting_reader.takeInt(u32, .little);
287 // if (hash_a != hash_b)
288 // return error.WrongChecksum;
289
290 // break :blk counter.bytes_read;
291 //};
292
293 //const hash_a = try d.in_reader.takeInt(u32, .little);
100294
101// if (buffer.len == 0)
102// return 0;
103//
104// const r = try self.block_decode.read(buffer);
105// if (r != 0)
106// return r;
107//
108// const index_size = blk: {
109// var hasher = hashedReader(self.in_reader, Crc32.init());
110// hasher.hasher.update(&[1]u8{0x00});
111//
112// var counter = std.io.countingReader(hasher.reader());
113// counter.bytes_read += 1;
114//
115// const counting_reader = counter.reader();
116//
117// const record_count = try std.leb.readUleb128(u64, counting_reader);
118// if (record_count != self.block_decode.block_count)
119// return error.CorruptInput;
120//
121// var i: usize = 0;
122// while (i < record_count) : (i += 1) {
123// // TODO: validate records
124// _ = try std.leb.readUleb128(u64, counting_reader);
125// _ = try std.leb.readUleb128(u64, counting_reader);
126// }
127//
128// while (counter.bytes_read % 4 != 0) {
129// if (try counting_reader.readByte() != 0)
130// return error.CorruptInput;
131// }
132//
133// const hash_a = hasher.hasher.final();
134// const hash_b = try counting_reader.readInt(u32, .little);
135// if (hash_a != hash_b)
136// return error.WrongChecksum;
137//
138// break :blk counter.bytes_read;
139// };
140//
141// const hash_a = try self.in_reader.readInt(u32, .little);
142//
143// const hash_b = blk: {
144// var hasher = hashedReader(self.in_reader, Crc32.init());
145// const hashed_reader = hasher.reader();
146//
147// const backward_size = (@as(u64, try hashed_reader.readInt(u32, .little)) + 1) * 4;
148// if (backward_size != index_size)
149// return error.CorruptInput;
150//
151// var check: Check = undefined;
152// try readStreamFlags(hashed_reader, &check);
153//
154// break :blk hasher.hasher.final();
155// };
156//
157// if (hash_a != hash_b)
158// return error.WrongChecksum;
159//
160// const magic = try self.in_reader.readBytesNoEof(2);
161// if (!std.mem.eql(u8, &magic, &.{ 'Y', 'Z' }))
162// return error.CorruptInput;
163//
164// return 0;
165//}
166
167//fn readBlock(self: *BlockDecode) Error!void {
168// var block_counter = std.io.countingReader(self.inner_reader);
169// const block_reader = block_counter.reader();
170//
171// var packed_size: ?u64 = null;
172// var unpacked_size: ?u64 = null;
173//
174// // Block Header
175// {
176// var header_hasher = hashedReader(block_reader, Crc32.init());
177// const header_reader = header_hasher.reader();
178//
179// const header_size = @as(u64, try header_reader.readByte()) * 4;
180// if (header_size == 0)
181// return error.EndOfStreamWithNoError;
182//
183// const Flags = packed struct(u8) {
184// last_filter_index: u2,
185// reserved: u4,
186// has_packed_size: bool,
187// has_unpacked_size: bool,
188// };
189//
190// const flags = @as(Flags, @bitCast(try header_reader.readByte()));
191// const filter_count = @as(u3, flags.last_filter_index) + 1;
192// if (filter_count > 1)
193// return error.Unsupported;
194//
195// if (flags.has_packed_size)
196// packed_size = try std.leb.readUleb128(u64, header_reader);
197//
198// if (flags.has_unpacked_size)
199// unpacked_size = try std.leb.readUleb128(u64, header_reader);
200//
201// const FilterId = enum(u64) {
202// lzma2 = 0x21,
203// _,
204// };
205//
206// const filter_id = @as(
207// FilterId,
208// @enumFromInt(try std.leb.readUleb128(u64, header_reader)),
209// );
210//
211// if (@intFromEnum(filter_id) >= 0x4000_0000_0000_0000)
212// return error.CorruptInput;
213//
214// if (filter_id != .lzma2)
215// return error.Unsupported;
216//
217// const properties_size = try std.leb.readUleb128(u64, header_reader);
218// if (properties_size != 1)
219// return error.CorruptInput;
220//
221// // TODO: use filter properties
222// _ = try header_reader.readByte();
223//
224// while (block_counter.bytes_read != header_size) {
225// if (try header_reader.readByte() != 0)
226// return error.CorruptInput;
227// }
228//
229// const hash_a = header_hasher.hasher.final();
230// const hash_b = try header_reader.readInt(u32, .little);
231// if (hash_a != hash_b)
232// return error.WrongChecksum;
233// }
234//
235// // Compressed Data
236// var packed_counter = std.io.countingReader(block_reader);
237// try lzma2.decompress(
238// self.allocator,
239// packed_counter.reader(),
240// self.to_read.writer(self.allocator),
241// );
242//
243// if (packed_size) |s| {
244// if (s != packed_counter.bytes_read)
245// return error.CorruptInput;
246// }
247//
248// const unpacked_bytes = self.to_read.items;
249// if (unpacked_size) |s| {
250// if (s != unpacked_bytes.len)
251// return error.CorruptInput;
252// }
253//
254// // Block Padding
255// while (block_counter.bytes_read % 4 != 0) {
256// if (try block_reader.readByte() != 0)
257// return error.CorruptInput;
258// }
259//
260// switch (self.check) {
261// .none => {},
262// .crc32 => {
263// const hash_a = Crc32.hash(unpacked_bytes);
264// const hash_b = try self.inner_reader.readInt(u32, .little);
265// if (hash_a != hash_b)
266// return error.WrongChecksum;
267// },
268// .crc64 => {
269// const hash_a = Crc64.hash(unpacked_bytes);
270// const hash_b = try self.inner_reader.readInt(u64, .little);
271// if (hash_a != hash_b)
272// return error.WrongChecksum;
273// },
274// .sha256 => {
275// var hash_a: [Sha256.digest_length]u8 = undefined;
276// Sha256.hash(unpacked_bytes, &hash_a, .{});
277//
278// var hash_b: [Sha256.digest_length]u8 = undefined;
279// try self.inner_reader.readNoEof(&hash_b);
280//
281// if (!std.mem.eql(u8, &hash_a, &hash_b))
282// return error.WrongChecksum;
283// },
284// else => return error.Unsupported,
285// }
286//
287// self.block_count += 1;
288//}
295 //const hash_b = blk: {
296 // var hasher = hashedReader(d.in_reader, Crc32.init());
297 // const hashed_reader = hasher.reader();
298
299 // const backward_size = (@as(u64, try hashed_reader.takeInt(u32, .little)) + 1) * 4;
300 // if (backward_size != index_size)
301 // return error.CorruptInput;
302
303 // var check: Check = undefined;
304 // try readStreamFlags(hashed_reader, &check);
305
306 // break :blk hasher.hasher.final();
307 //};
308
309 //if (hash_a != hash_b)
310 // return error.WrongChecksum;
311
312 //const magic = try d.in_reader.takeBytesNoEof(2);
313 //if (!std.mem.eql(u8, &magic, &.{ 'Y', 'Z' }))
314 // return error.CorruptInput;
315
316 //return 0;
317}
lib/std/compress/xz/test.zig+10-6
......@@ -3,19 +3,23 @@ const testing = std.testing;
33const xz = std.compress.xz;
44
55fn decompress(data: []const u8) ![]u8 {
6 var in_stream = std.io.fixedBufferStream(data);
6 const gpa = testing.allocator;
77
8 var xz_stream = try xz.decompress(testing.allocator, in_stream.reader());
8 var in_stream: std.Io.Reader = .fixed(data);
9
10 var xz_stream = try xz.Decompress.init(&in_stream, gpa, &.{});
911 defer xz_stream.deinit();
1012
11 return xz_stream.reader().readAllAlloc(testing.allocator, std.math.maxInt(usize));
13 return xz_stream.reader.allocRemaining(gpa, .unlimited);
1214}
1315
1416fn testReader(data: []const u8, comptime expected: []const u8) !void {
15 const buf = try decompress(data);
16 defer testing.allocator.free(buf);
17 const gpa = testing.allocator;
18
19 const result = try decompress(data);
20 defer gpa.free(result);
1721
18 try testing.expectEqualSlices(u8, expected, buf);
22 try testing.expectEqualSlices(u8, expected, result);
1923}
2024
2125test "compressed data" {