| ... | ... | @@ -26,6 +26,8 @@ pub const Error = error{ |
| 26 | 26 | WrongChecksum, |
| 27 | 27 | Unsupported, |
| 28 | 28 | Overflow, |
| 29 | InvalidRangeCode, |
| 30 | DecompressedSizeMismatch, |
| 29 | 31 | }; |
| 30 | 32 | |
| 31 | 33 | pub const Check = enum(u4) { |
| ... | ... | @@ -55,14 +57,14 @@ pub fn init( |
| 55 | 57 | gpa: Allocator, |
| 56 | 58 | /// Decompress takes ownership of this buffer and resizes it with `gpa`. |
| 57 | 59 | 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 })) |
| 61 | 63 | return error.NotXzStream; |
| 62 | 64 | |
| 63 | 65 | const actual_hash = Crc32.hash(try input.peek(@sizeOf(StreamFlags))); |
| 64 | 66 | 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); |
| 66 | 68 | if (actual_hash != stored_hash) return error.WrongChecksum; |
| 67 | 69 | |
| 68 | 70 | return .{ |
| ... | ... | @@ -71,6 +73,7 @@ pub fn init( |
| 71 | 73 | .vtable = &.{ |
| 72 | 74 | .stream = stream, |
| 73 | 75 | .readVec = readVec, |
| 76 | .discard = discard, |
| 74 | 77 | }, |
| 75 | 78 | .buffer = buffer, |
| 76 | 79 | .seek = 0, |
| ... | ... | @@ -83,206 +86,232 @@ pub fn init( |
| 83 | 86 | }; |
| 84 | 87 | } |
| 85 | 88 | |
| 89 | /// Reclaim ownership of the buffer passed to `init`. |
| 90 | pub fn takeBuffer(d: *Decompress) []u8 { |
| 91 | const buffer = d.reader.buffer; |
| 92 | d.reader.buffer = &.{}; |
| 93 | return buffer; |
| 94 | } |
| 95 | |
| 96 | pub fn deinit(d: *Decompress) void { |
| 97 | const gpa = d.gpa; |
| 98 | gpa.free(d.reader.buffer); |
| 99 | d.* = undefined; |
| 100 | } |
| 101 | |
| 102 | fn readVec(r: *Reader, data: [][]u8) Reader.Error!usize { |
| 103 | _ = data; |
| 104 | return readIndirect(r); |
| 105 | } |
| 106 | |
| 86 | 107 | fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize { |
| 87 | 108 | _ = w; |
| 88 | 109 | _ = limit; |
| 110 | return readIndirect(r); |
| 111 | } |
| 112 | |
| 113 | fn discard(r: *Reader, limit: std.Io.Limit) Reader.Error!usize { |
| 89 | 114 | const d: *Decompress = @alignCast(@fieldParentPtr("reader", r)); |
| 90 | 115 | _ = d; |
| 116 | _ = limit; |
| 91 | 117 | @panic("TODO"); |
| 92 | 118 | } |
| 93 | 119 | |
| 94 | | fn readVec(r: *Reader, data: [][]u8) Reader.Error!usize { |
| 95 | | _ = data; |
| 120 | fn readIndirect(r: *Reader) Reader.Error!usize { |
| 96 | 121 | 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 | |
| 183 | fn 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 | |
| 264 | fn finish(d: *Decompress) void { |
| 97 | 265 | _ = d; |
| 98 | 266 | @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); |
| 100 | 294 | |
| 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 | } |