| ... | @@ -26,6 +26,8 @@ pub const Error = error{ | ... | @@ -26,6 +26,8 @@ pub const Error = error{ |
| 26 | WrongChecksum, | 26 | WrongChecksum, |
| 27 | Unsupported, | 27 | Unsupported, |
| 28 | Overflow, | 28 | Overflow, |
| | 29 | InvalidRangeCode, |
| | 30 | DecompressedSizeMismatch, |
| 29 | }; | 31 | }; |
| 30 | | 32 | |
| 31 | pub const Check = enum(u4) { | 33 | pub const Check = enum(u4) { |
| ... | @@ -55,14 +57,14 @@ pub fn init( | ... | @@ -55,14 +57,14 @@ pub fn init( |
| 55 | gpa: Allocator, | 57 | gpa: Allocator, |
| 56 | /// Decompress takes ownership of this buffer and resizes it with `gpa`. | 58 | /// Decompress takes ownership of this buffer and resizes it with `gpa`. |
| 57 | buffer: []u8, | 59 | buffer: []u8, |
| 58 | ) Decompress { | 60 | ) !Decompress { |
| 59 | const magic = try input.takeBytes(6); | 61 | const magic = try input.takeArray(6); |
| 60 | if (!std.mem.eql(u8, &magic, &.{ 0xFD, '7', 'z', 'X', 'Z', 0x00 })) | 62 | if (!std.mem.eql(u8, magic, &.{ 0xFD, '7', 'z', 'X', 'Z', 0x00 })) |
| 61 | return error.NotXzStream; | 63 | return error.NotXzStream; |
| 62 | | 64 | |
| 63 | const actual_hash = Crc32.hash(try input.peek(@sizeOf(StreamFlags))); | 65 | const actual_hash = Crc32.hash(try input.peek(@sizeOf(StreamFlags))); |
| 64 | const stream_flags = input.takeStruct(StreamFlags, .little) catch unreachable; | 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 | if (actual_hash != stored_hash) return error.WrongChecksum; | 68 | if (actual_hash != stored_hash) return error.WrongChecksum; |
| 67 | | 69 | |
| 68 | return .{ | 70 | return .{ |
| ... | @@ -71,6 +73,7 @@ pub fn init( | ... | @@ -71,6 +73,7 @@ pub fn init( |
| 71 | .vtable = &.{ | 73 | .vtable = &.{ |
| 72 | .stream = stream, | 74 | .stream = stream, |
| 73 | .readVec = readVec, | 75 | .readVec = readVec, |
| | 76 | .discard = discard, |
| 74 | }, | 77 | }, |
| 75 | .buffer = buffer, | 78 | .buffer = buffer, |
| 76 | .seek = 0, | 79 | .seek = 0, |
| ... | @@ -83,206 +86,232 @@ pub fn init( | ... | @@ -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 | fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize { | 107 | fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize { |
| 87 | _ = w; | 108 | _ = w; |
| 88 | _ = limit; | 109 | _ = limit; |
| | 110 | return readIndirect(r); |
| | 111 | } |
| | 112 | |
| | 113 | fn discard(r: *Reader, limit: std.Io.Limit) Reader.Error!usize { |
| 89 | const d: *Decompress = @alignCast(@fieldParentPtr("reader", r)); | 114 | const d: *Decompress = @alignCast(@fieldParentPtr("reader", r)); |
| 90 | _ = d; | 115 | _ = d; |
| | 116 | _ = limit; |
| 91 | @panic("TODO"); | 117 | @panic("TODO"); |
| 92 | } | 118 | } |
| 93 | | 119 | |
| 94 | fn readVec(r: *Reader, data: [][]u8) Reader.Error!usize { | 120 | fn readIndirect(r: *Reader) Reader.Error!usize { |
| 95 | _ = data; | | |
| 96 | const d: *Decompress = @alignCast(@fieldParentPtr("reader", r)); | 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 | _ = d; | 265 | _ = d; |
| 98 | @panic("TODO"); | 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) | 295 | //const hash_b = blk: { |
| 102 | // return 0; | 296 | // var hasher = hashedReader(d.in_reader, Crc32.init()); |
| 103 | // | 297 | // const hashed_reader = hasher.reader(); |
| 104 | // const r = try self.block_decode.read(buffer); | 298 | |
| 105 | // if (r != 0) | 299 | // const backward_size = (@as(u64, try hashed_reader.takeInt(u32, .little)) + 1) * 4; |
| 106 | // return r; | 300 | // if (backward_size != index_size) |
| 107 | // | 301 | // return error.CorruptInput; |
| 108 | // const index_size = blk: { | 302 | |
| 109 | // var hasher = hashedReader(self.in_reader, Crc32.init()); | 303 | // var check: Check = undefined; |
| 110 | // hasher.hasher.update(&[1]u8{0x00}); | 304 | // try readStreamFlags(hashed_reader, &check); |
| 111 | // | 305 | |
| 112 | // var counter = std.io.countingReader(hasher.reader()); | 306 | // break :blk hasher.hasher.final(); |
| 113 | // counter.bytes_read += 1; | 307 | //}; |
| 114 | // | 308 | |
| 115 | // const counting_reader = counter.reader(); | 309 | //if (hash_a != hash_b) |
| 116 | // | 310 | // return error.WrongChecksum; |
| 117 | // const record_count = try std.leb.readUleb128(u64, counting_reader); | 311 | |
| 118 | // if (record_count != self.block_decode.block_count) | 312 | //const magic = try d.in_reader.takeBytesNoEof(2); |
| 119 | // return error.CorruptInput; | 313 | //if (!std.mem.eql(u8, &magic, &.{ 'Y', 'Z' })) |
| 120 | // | 314 | // return error.CorruptInput; |
| 121 | // var i: usize = 0; | 315 | |
| 122 | // while (i < record_count) : (i += 1) { | 316 | //return 0; |
| 123 | // // TODO: validate records | 317 | } |
| 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 | //} | | |