| ... | ... | @@ -96,6 +96,7 @@ pub fn decodeAlloc( |
| 96 | 96 | /// - `error.UnknownContentSizeUnsupported` if the frame does not declare the |
| 97 | 97 | /// uncompressed content size |
| 98 | 98 | /// - `error.ContentTooLarge` if `dest` is smaller than the uncompressed data |
| 99 | /// size declared by the frame header |
| 99 | 100 | /// - `error.BadMagic` if the first 4 bytes of `src` is not a valid magic |
| 100 | 101 | /// number for a Zstandard or Skippable frame |
| 101 | 102 | /// - `error.DictionaryIdFlagUnsupported` if the frame uses a dictionary |
| ... | ... | @@ -180,6 +181,7 @@ pub fn computeChecksum(hasher: *std.hash.XxHash64) u32 { |
| 180 | 181 | const FrameError = error{ |
| 181 | 182 | DictionaryIdFlagUnsupported, |
| 182 | 183 | ChecksumFailure, |
| 184 | BadContentSize, |
| 183 | 185 | EndOfStream, |
| 184 | 186 | } || InvalidBit || block.Error; |
| 185 | 187 | |
| ... | ... | @@ -191,7 +193,7 @@ const FrameError = error{ |
| 191 | 193 | /// - `error.UnknownContentSizeUnsupported` if the frame does not declare the |
| 192 | 194 | /// uncompressed content size |
| 193 | 195 | /// - `error.ContentTooLarge` if `dest` is smaller than the uncompressed data |
| 194 | | /// number for a Zstandard or Skippable frame |
| 196 | /// size declared by the frame header |
| 195 | 197 | /// - `error.DictionaryIdFlagUnsupported` if the frame uses a dictionary |
| 196 | 198 | /// - `error.ChecksumFailure` if `verify_checksum` is true and the frame |
| 197 | 199 | /// contains a checksum that does not match the checksum of the decompressed |
| ... | ... | @@ -200,39 +202,51 @@ const FrameError = error{ |
| 200 | 202 | /// - `error.UnusedBitSet` if the unused bit of the frame header is set |
| 201 | 203 | /// - `error.EndOfStream` if `src` does not contain a complete frame |
| 202 | 204 | /// - an error in `block.Error` if there are errors decoding a block |
| 205 | /// - `error.BadContentSize` if the content size declared by the frame does |
| 206 | /// not equal the actual size of decompressed data |
| 203 | 207 | pub fn decodeZstandardFrame( |
| 204 | 208 | dest: []u8, |
| 205 | 209 | src: []const u8, |
| 206 | 210 | verify_checksum: bool, |
| 207 | | ) (error{ UnknownContentSizeUnsupported, ContentTooLarge } || FrameError)!ReadWriteCount { |
| 211 | ) (error{ |
| 212 | UnknownContentSizeUnsupported, |
| 213 | ContentTooLarge, |
| 214 | ContentSizeTooLarge, |
| 215 | WindowSizeUnknown, |
| 216 | } || FrameError)!ReadWriteCount { |
| 208 | 217 | assert(readInt(u32, src[0..4]) == frame.Zstandard.magic_number); |
| 209 | 218 | var consumed_count: usize = 4; |
| 210 | 219 | |
| 211 | | var fbs = std.io.fixedBufferStream(src[consumed_count..]); |
| 212 | | var source = fbs.reader(); |
| 213 | | const frame_header = try decodeZstandardHeader(source); |
| 214 | | consumed_count += fbs.pos; |
| 215 | | |
| 216 | | if (frame_header.descriptor.dictionary_id_flag != 0) return error.DictionaryIdFlagUnsupported; |
| 220 | var frame_context = context: { |
| 221 | var fbs = std.io.fixedBufferStream(src[consumed_count..]); |
| 222 | var source = fbs.reader(); |
| 223 | const frame_header = try decodeZstandardHeader(source); |
| 224 | consumed_count += fbs.pos; |
| 225 | break :context FrameContext.init(frame_header, std.math.maxInt(usize), verify_checksum) catch |err| switch (err) { |
| 226 | error.WindowTooLarge => unreachable, |
| 227 | inline else => |e| return e, |
| 228 | }; |
| 229 | }; |
| 217 | 230 | |
| 218 | | const content_size = frame_header.content_size orelse return error.UnknownContentSizeUnsupported; |
| 231 | const content_size = frame_context.content_size orelse return error.UnknownContentSizeUnsupported; |
| 219 | 232 | if (dest.len < content_size) return error.ContentTooLarge; |
| 220 | 233 | |
| 221 | | const should_compute_checksum = frame_header.descriptor.content_checksum_flag and verify_checksum; |
| 222 | | var hasher_opt = if (should_compute_checksum) std.hash.XxHash64.init(0) else null; |
| 223 | | |
| 224 | | const written_count = try decodeFrameBlocks( |
| 225 | | dest, |
| 234 | const written_count = decodeFrameBlocks( |
| 235 | dest[0..content_size], |
| 226 | 236 | src[consumed_count..], |
| 227 | 237 | &consumed_count, |
| 228 | | if (hasher_opt) |*hasher| hasher else null, |
| 229 | | ); |
| 238 | if (frame_context.hasher_opt) |*hasher| hasher else null, |
| 239 | ) catch |err| switch (err) { |
| 240 | error.DestTooSmall => return error.BadContentSize, |
| 241 | inline else => |e| return e, |
| 242 | }; |
| 230 | 243 | |
| 231 | | if (frame_header.descriptor.content_checksum_flag) { |
| 244 | if (written_count != content_size) return error.BadContentSize; |
| 245 | if (frame_context.has_checksum) { |
| 232 | 246 | if (src.len < consumed_count + 4) return error.EndOfStream; |
| 233 | 247 | const checksum = readIntSlice(u32, src[consumed_count .. consumed_count + 4]); |
| 234 | 248 | consumed_count += 4; |
| 235 | | if (hasher_opt) |*hasher| { |
| 249 | if (frame_context.hasher_opt) |*hasher| { |
| 236 | 250 | if (checksum != computeChecksum(hasher)) return error.ChecksumFailure; |
| 237 | 251 | } |
| 238 | 252 | } |
| ... | ... | @@ -244,8 +258,14 @@ pub const FrameContext = struct { |
| 244 | 258 | window_size: usize, |
| 245 | 259 | has_checksum: bool, |
| 246 | 260 | block_size_max: usize, |
| 261 | content_size: ?usize, |
| 247 | 262 | |
| 248 | | const Error = error{ DictionaryIdFlagUnsupported, WindowSizeUnknown, WindowTooLarge }; |
| 263 | const Error = error{ |
| 264 | DictionaryIdFlagUnsupported, |
| 265 | WindowSizeUnknown, |
| 266 | WindowTooLarge, |
| 267 | ContentSizeTooLarge, |
| 268 | }; |
| 249 | 269 | /// Validates `frame_header` and returns the associated `FrameContext`. |
| 250 | 270 | /// |
| 251 | 271 | /// Errors returned: |
| ... | ... | @@ -266,11 +286,18 @@ pub const FrameContext = struct { |
| 266 | 286 | @intCast(usize, window_size_raw); |
| 267 | 287 | |
| 268 | 288 | const should_compute_checksum = frame_header.descriptor.content_checksum_flag and verify_checksum; |
| 289 | |
| 290 | const content_size = if (frame_header.content_size) |size| |
| 291 | std.math.cast(usize, size) orelse return error.ContentSizeTooLarge |
| 292 | else |
| 293 | null; |
| 294 | |
| 269 | 295 | return .{ |
| 270 | 296 | .hasher_opt = if (should_compute_checksum) std.hash.XxHash64.init(0) else null, |
| 271 | 297 | .window_size = window_size, |
| 272 | 298 | .has_checksum = frame_header.descriptor.content_checksum_flag, |
| 273 | 299 | .block_size_max = @min(1 << 17, window_size), |
| 300 | .content_size = content_size, |
| 274 | 301 | }; |
| 275 | 302 | } |
| 276 | 303 | }; |
| ... | ... | @@ -294,6 +321,8 @@ pub const FrameContext = struct { |
| 294 | 321 | /// - `error.EndOfStream` if `src` does not contain a complete frame |
| 295 | 322 | /// - `error.OutOfMemory` if `allocator` cannot allocate enough memory |
| 296 | 323 | /// - an error in `block.Error` if there are errors decoding a block |
| 324 | /// - `error.BadContentSize` if the content size declared by the frame does |
| 325 | /// not equal the size of decompressed data |
| 297 | 326 | pub fn decodeZstandardFrameAlloc( |
| 298 | 327 | allocator: Allocator, |
| 299 | 328 | src: []const u8, |
| ... | ... | @@ -321,6 +350,7 @@ pub fn decodeZstandardFrameArrayList( |
| 321 | 350 | window_size_max: usize, |
| 322 | 351 | ) (error{OutOfMemory} || FrameContext.Error || FrameError)!usize { |
| 323 | 352 | assert(readInt(u32, src[0..4]) == frame.Zstandard.magic_number); |
| 353 | const initial_len = dest.items.len; |
| 324 | 354 | var consumed_count: usize = 4; |
| 325 | 355 | |
| 326 | 356 | var frame_context = context: { |
| ... | ... | @@ -364,6 +394,12 @@ pub fn decodeZstandardFrameArrayList( |
| 364 | 394 | hasher.update(written_slice.second); |
| 365 | 395 | } |
| 366 | 396 | } |
| 397 | const added_len = dest.items.len - initial_len; |
| 398 | if (frame_context.content_size) |size| { |
| 399 | if (added_len != size) { |
| 400 | return error.BadContentSize; |
| 401 | } |
| 402 | } |
| 367 | 403 | if (block_header.last_block) break; |
| 368 | 404 | } |
| 369 | 405 | |
| ... | ... | @@ -384,7 +420,7 @@ fn decodeFrameBlocks( |
| 384 | 420 | src: []const u8, |
| 385 | 421 | consumed_count: *usize, |
| 386 | 422 | hash: ?*std.hash.XxHash64, |
| 387 | | ) (error{EndOfStream} || block.Error)!usize { |
| 423 | ) (error{ EndOfStream, DestTooSmall } || block.Error)!usize { |
| 388 | 424 | // These tables take 7680 bytes |
| 389 | 425 | var literal_fse_data: [types.compressed_block.table_size_max.literal]Table.Fse = undefined; |
| 390 | 426 | var match_fse_data: [types.compressed_block.table_size_max.match]Table.Fse = undefined; |