| ... | ... | @@ -1,7 +1,10 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | | const block = @import("xz/block.zig"); |
| 3 | 2 | const Allocator = std.mem.Allocator; |
| 3 | const ArrayList = std.ArrayList; |
| 4 | 4 | const Crc32 = std.hash.Crc32; |
| 5 | const Crc64 = std.hash.crc.Crc64Xz; |
| 6 | const Sha256 = std.crypto.hash.sha2.Sha256; |
| 7 | const lzma2 = std.compress.lzma2; |
| 5 | 8 | |
| 6 | 9 | pub const Check = enum(u4) { |
| 7 | 10 | none = 0x00, |
| ... | ... | @@ -27,11 +30,11 @@ pub fn Decompress(comptime ReaderType: type) type { |
| 27 | 30 | return struct { |
| 28 | 31 | const Self = @This(); |
| 29 | 32 | |
| 30 | | pub const Error = ReaderType.Error || block.Decoder(ReaderType).Error; |
| 33 | pub const Error = ReaderType.Error || Decoder(ReaderType).Error; |
| 31 | 34 | pub const Reader = std.io.GenericReader(*Self, Error, read); |
| 32 | 35 | |
| 33 | 36 | allocator: Allocator, |
| 34 | | block_decoder: block.Decoder(ReaderType), |
| 37 | block_decoder: Decoder(ReaderType), |
| 35 | 38 | in_reader: ReaderType, |
| 36 | 39 | |
| 37 | 40 | fn init(allocator: Allocator, source: ReaderType) !Self { |
| ... | ... | @@ -52,7 +55,7 @@ pub fn Decompress(comptime ReaderType: type) type { |
| 52 | 55 | |
| 53 | 56 | return Self{ |
| 54 | 57 | .allocator = allocator, |
| 55 | | .block_decoder = try block.decoder(allocator, source, check), |
| 58 | .block_decoder = try decoder(allocator, source, check), |
| 56 | 59 | .in_reader = source, |
| 57 | 60 | }; |
| 58 | 61 | } |
| ... | ... | @@ -161,6 +164,206 @@ pub fn hashedReader( |
| 161 | 164 | return .{ .child_reader = reader, .hasher = hasher }; |
| 162 | 165 | } |
| 163 | 166 | |
| 167 | const DecodeError = error{ |
| 168 | CorruptInput, |
| 169 | EndOfStream, |
| 170 | EndOfStreamWithNoError, |
| 171 | WrongChecksum, |
| 172 | Unsupported, |
| 173 | Overflow, |
| 174 | }; |
| 175 | |
| 176 | pub fn decoder(allocator: Allocator, reader: anytype, check: Check) !Decoder(@TypeOf(reader)) { |
| 177 | return Decoder(@TypeOf(reader)).init(allocator, reader, check); |
| 178 | } |
| 179 | |
| 180 | pub fn Decoder(comptime ReaderType: type) type { |
| 181 | return struct { |
| 182 | const Self = @This(); |
| 183 | pub const Error = |
| 184 | ReaderType.Error || |
| 185 | DecodeError || |
| 186 | Allocator.Error; |
| 187 | pub const Reader = std.io.GenericReader(*Self, Error, read); |
| 188 | |
| 189 | allocator: Allocator, |
| 190 | inner_reader: ReaderType, |
| 191 | check: Check, |
| 192 | err: ?Error, |
| 193 | to_read: ArrayList(u8), |
| 194 | read_pos: usize, |
| 195 | block_count: usize, |
| 196 | |
| 197 | fn init(allocator: Allocator, in_reader: ReaderType, check: Check) !Self { |
| 198 | return Self{ |
| 199 | .allocator = allocator, |
| 200 | .inner_reader = in_reader, |
| 201 | .check = check, |
| 202 | .err = null, |
| 203 | .to_read = .{}, |
| 204 | .read_pos = 0, |
| 205 | .block_count = 0, |
| 206 | }; |
| 207 | } |
| 208 | |
| 209 | pub fn deinit(self: *Self) void { |
| 210 | self.to_read.deinit(self.allocator); |
| 211 | } |
| 212 | |
| 213 | pub fn reader(self: *Self) Reader { |
| 214 | return .{ .context = self }; |
| 215 | } |
| 216 | |
| 217 | pub fn read(self: *Self, output: []u8) Error!usize { |
| 218 | while (true) { |
| 219 | const unread_len = self.to_read.items.len - self.read_pos; |
| 220 | if (unread_len > 0) { |
| 221 | const n = @min(unread_len, output.len); |
| 222 | @memcpy(output[0..n], self.to_read.items[self.read_pos..][0..n]); |
| 223 | self.read_pos += n; |
| 224 | return n; |
| 225 | } |
| 226 | if (self.err) |e| { |
| 227 | if (e == DecodeError.EndOfStreamWithNoError) { |
| 228 | return 0; |
| 229 | } |
| 230 | return e; |
| 231 | } |
| 232 | if (self.read_pos > 0) { |
| 233 | self.to_read.shrinkRetainingCapacity(0); |
| 234 | self.read_pos = 0; |
| 235 | } |
| 236 | self.readBlock() catch |e| { |
| 237 | self.err = e; |
| 238 | }; |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | fn readBlock(self: *Self) Error!void { |
| 243 | var block_counter = std.io.countingReader(self.inner_reader); |
| 244 | const block_reader = block_counter.reader(); |
| 245 | |
| 246 | var packed_size: ?u64 = null; |
| 247 | var unpacked_size: ?u64 = null; |
| 248 | |
| 249 | // Block Header |
| 250 | { |
| 251 | var header_hasher = hashedReader(block_reader, Crc32.init()); |
| 252 | const header_reader = header_hasher.reader(); |
| 253 | |
| 254 | const header_size = @as(u64, try header_reader.readByte()) * 4; |
| 255 | if (header_size == 0) |
| 256 | return error.EndOfStreamWithNoError; |
| 257 | |
| 258 | const Flags = packed struct(u8) { |
| 259 | last_filter_index: u2, |
| 260 | reserved: u4, |
| 261 | has_packed_size: bool, |
| 262 | has_unpacked_size: bool, |
| 263 | }; |
| 264 | |
| 265 | const flags = @as(Flags, @bitCast(try header_reader.readByte())); |
| 266 | const filter_count = @as(u3, flags.last_filter_index) + 1; |
| 267 | if (filter_count > 1) |
| 268 | return error.Unsupported; |
| 269 | |
| 270 | if (flags.has_packed_size) |
| 271 | packed_size = try std.leb.readUleb128(u64, header_reader); |
| 272 | |
| 273 | if (flags.has_unpacked_size) |
| 274 | unpacked_size = try std.leb.readUleb128(u64, header_reader); |
| 275 | |
| 276 | const FilterId = enum(u64) { |
| 277 | lzma2 = 0x21, |
| 278 | _, |
| 279 | }; |
| 280 | |
| 281 | const filter_id = @as( |
| 282 | FilterId, |
| 283 | @enumFromInt(try std.leb.readUleb128(u64, header_reader)), |
| 284 | ); |
| 285 | |
| 286 | if (@intFromEnum(filter_id) >= 0x4000_0000_0000_0000) |
| 287 | return error.CorruptInput; |
| 288 | |
| 289 | if (filter_id != .lzma2) |
| 290 | return error.Unsupported; |
| 291 | |
| 292 | const properties_size = try std.leb.readUleb128(u64, header_reader); |
| 293 | if (properties_size != 1) |
| 294 | return error.CorruptInput; |
| 295 | |
| 296 | // TODO: use filter properties |
| 297 | _ = try header_reader.readByte(); |
| 298 | |
| 299 | while (block_counter.bytes_read != header_size) { |
| 300 | if (try header_reader.readByte() != 0) |
| 301 | return error.CorruptInput; |
| 302 | } |
| 303 | |
| 304 | const hash_a = header_hasher.hasher.final(); |
| 305 | const hash_b = try header_reader.readInt(u32, .little); |
| 306 | if (hash_a != hash_b) |
| 307 | return error.WrongChecksum; |
| 308 | } |
| 309 | |
| 310 | // Compressed Data |
| 311 | var packed_counter = std.io.countingReader(block_reader); |
| 312 | try lzma2.decompress( |
| 313 | self.allocator, |
| 314 | packed_counter.reader(), |
| 315 | self.to_read.writer(self.allocator), |
| 316 | ); |
| 317 | |
| 318 | if (packed_size) |s| { |
| 319 | if (s != packed_counter.bytes_read) |
| 320 | return error.CorruptInput; |
| 321 | } |
| 322 | |
| 323 | const unpacked_bytes = self.to_read.items; |
| 324 | if (unpacked_size) |s| { |
| 325 | if (s != unpacked_bytes.len) |
| 326 | return error.CorruptInput; |
| 327 | } |
| 328 | |
| 329 | // Block Padding |
| 330 | while (block_counter.bytes_read % 4 != 0) { |
| 331 | if (try block_reader.readByte() != 0) |
| 332 | return error.CorruptInput; |
| 333 | } |
| 334 | |
| 335 | switch (self.check) { |
| 336 | .none => {}, |
| 337 | .crc32 => { |
| 338 | const hash_a = Crc32.hash(unpacked_bytes); |
| 339 | const hash_b = try self.inner_reader.readInt(u32, .little); |
| 340 | if (hash_a != hash_b) |
| 341 | return error.WrongChecksum; |
| 342 | }, |
| 343 | .crc64 => { |
| 344 | const hash_a = Crc64.hash(unpacked_bytes); |
| 345 | const hash_b = try self.inner_reader.readInt(u64, .little); |
| 346 | if (hash_a != hash_b) |
| 347 | return error.WrongChecksum; |
| 348 | }, |
| 349 | .sha256 => { |
| 350 | var hash_a: [Sha256.digest_length]u8 = undefined; |
| 351 | Sha256.hash(unpacked_bytes, &hash_a, .{}); |
| 352 | |
| 353 | var hash_b: [Sha256.digest_length]u8 = undefined; |
| 354 | try self.inner_reader.readNoEof(&hash_b); |
| 355 | |
| 356 | if (!std.mem.eql(u8, &hash_a, &hash_b)) |
| 357 | return error.WrongChecksum; |
| 358 | }, |
| 359 | else => return error.Unsupported, |
| 360 | } |
| 361 | |
| 362 | self.block_count += 1; |
| 363 | } |
| 364 | }; |
| 365 | } |
| 366 | |
| 164 | 367 | test { |
| 165 | 368 | _ = @import("xz/test.zig"); |
| 166 | 369 | } |