| ... | @@ -1,7 +1,10 @@ | ... | @@ -1,7 +1,10 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const block = @import("xz/block.zig"); | | |
| 3 | const Allocator = std.mem.Allocator; | 2 | const Allocator = std.mem.Allocator; |
| | 3 | const ArrayList = std.ArrayList; |
| 4 | const Crc32 = std.hash.Crc32; | 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 | pub const Check = enum(u4) { | 9 | pub const Check = enum(u4) { |
| 7 | none = 0x00, | 10 | none = 0x00, |
| ... | @@ -27,11 +30,11 @@ pub fn Decompress(comptime ReaderType: type) type { | ... | @@ -27,11 +30,11 @@ pub fn Decompress(comptime ReaderType: type) type { |
| 27 | return struct { | 30 | return struct { |
| 28 | const Self = @This(); | 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 | pub const Reader = std.io.GenericReader(*Self, Error, read); | 34 | pub const Reader = std.io.GenericReader(*Self, Error, read); |
| 32 | | 35 | |
| 33 | allocator: Allocator, | 36 | allocator: Allocator, |
| 34 | block_decoder: block.Decoder(ReaderType), | 37 | block_decoder: Decoder(ReaderType), |
| 35 | in_reader: ReaderType, | 38 | in_reader: ReaderType, |
| 36 | | 39 | |
| 37 | fn init(allocator: Allocator, source: ReaderType) !Self { | 40 | fn init(allocator: Allocator, source: ReaderType) !Self { |
| ... | @@ -52,7 +55,7 @@ pub fn Decompress(comptime ReaderType: type) type { | ... | @@ -52,7 +55,7 @@ pub fn Decompress(comptime ReaderType: type) type { |
| 52 | | 55 | |
| 53 | return Self{ | 56 | return Self{ |
| 54 | .allocator = allocator, | 57 | .allocator = allocator, |
| 55 | .block_decoder = try block.decoder(allocator, source, check), | 58 | .block_decoder = try decoder(allocator, source, check), |
| 56 | .in_reader = source, | 59 | .in_reader = source, |
| 57 | }; | 60 | }; |
| 58 | } | 61 | } |
| ... | @@ -161,6 +164,206 @@ pub fn hashedReader( | ... | @@ -161,6 +164,206 @@ pub fn hashedReader( |
| 161 | return .{ .child_reader = reader, .hasher = hasher }; | 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 | test { | 367 | test { |
| 165 | _ = @import("xz/test.zig"); | 368 | _ = @import("xz/test.zig"); |
| 166 | } | 369 | } |