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