1#[cfg(any(feature = "jpeg", feature = "zstd"))]
4use std::io::Cursor;
5use std::io::Read;
6#[cfg(feature = "jpeg")]
7use std::panic::{self, AssertUnwindSafe};
8
9use crate::error::{Error, Result};
10use crate::header::ByteOrder;
11use tiff_core::{Compression, Predictor};
12
13pub fn decompress(
15 compression: u16,
16 data: &[u8],
17 index: usize,
18 _jpeg_tables: Option<&[u8]>,
19 decoded_len_limit: usize,
20) -> Result<Vec<u8>> {
21 match Compression::from_code(compression) {
22 Some(Compression::None) => {
23 if data.len() > decoded_len_limit {
24 return Err(decoded_block_too_large(
25 index,
26 "uncompressed",
27 decoded_len_limit,
28 ));
29 }
30 Ok(data.to_vec())
31 }
32 Some(Compression::Deflate | Compression::DeflateOld) => {
33 decompress_deflate(data, index, decoded_len_limit)
34 }
35 Some(Compression::Lzw) => decompress_lzw(data, index, decoded_len_limit),
36 Some(Compression::PackBits) => decompress_packbits(data, index, decoded_len_limit),
37 Some(Compression::Lerc) => Err(Error::UnsupportedCompression(compression)),
38 #[cfg(feature = "jpeg")]
39 Some(Compression::OldJpeg) => Err(Error::UnsupportedCompression(compression)),
40 #[cfg(feature = "jpeg")]
41 Some(Compression::Jpeg) => decompress_jpeg(data, index, _jpeg_tables, decoded_len_limit),
42 #[cfg(not(feature = "jpeg"))]
43 Some(Compression::OldJpeg | Compression::Jpeg) => {
44 Err(Error::UnsupportedCompression(compression))
45 }
46 #[cfg(feature = "zstd")]
47 Some(Compression::Zstd) => decompress_zstd(data, index, decoded_len_limit),
48 #[cfg(not(feature = "zstd"))]
49 Some(Compression::Zstd) => Err(Error::UnsupportedCompression(compression)),
50 None => Err(Error::UnsupportedCompression(compression)),
51 }
52}
53
54pub fn fix_endianness_and_predict(
56 row: &mut [u8],
57 bit_depth: u16,
58 samples: u16,
59 byte_order: ByteOrder,
60 predictor: u16,
61) -> Result<()> {
62 match Predictor::from_code(predictor) {
63 Some(Predictor::None) => {
64 fix_endianness(row, byte_order, bit_depth);
65 Ok(())
66 }
67 Some(Predictor::Horizontal) => {
68 fix_endianness(row, byte_order, bit_depth);
69 reverse_horizontal_predictor(row, bit_depth, samples);
70 Ok(())
71 }
72 Some(Predictor::FloatingPoint) => match bit_depth {
73 16 => {
74 let mut encoded = row.to_vec();
75 predict_f16(&mut encoded, row, samples);
76 Ok(())
77 }
78 32 => {
79 let mut encoded = row.to_vec();
80 predict_f32(&mut encoded, row, samples);
81 Ok(())
82 }
83 64 => {
84 let mut encoded = row.to_vec();
85 predict_f64(&mut encoded, row, samples);
86 Ok(())
87 }
88 _ => Err(Error::UnsupportedPredictor(3)),
89 },
90 None => Err(Error::UnsupportedPredictor(predictor)),
91 }
92}
93
94fn decompress_deflate(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
95 use flate2::read::ZlibDecoder;
96
97 let decoder = ZlibDecoder::new(data);
98 read_bounded_to_end(decoder, index, "deflate", decoded_len_limit)
99}
100
101fn decompress_lzw(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
102 use weezl::decode::Configuration;
103 use weezl::{BitOrder, LzwStatus};
104
105 let mut decoder = Configuration::with_tiff_size_switch(BitOrder::Msb, 8)
106 .with_yield_on_full_buffer(true)
107 .build();
108 let probe_limit = decoded_len_probe_limit(index, "LZW", decoded_len_limit)?;
109 let mut out = Vec::with_capacity(decoded_len_limit.min(8192));
110 let mut input_offset = 0usize;
111 let mut scratch = [0u8; 8192];
112
113 loop {
114 let remaining = probe_limit.saturating_sub(out.len());
115 if remaining == 0 {
116 return Err(decoded_block_too_large(index, "LZW", decoded_len_limit));
117 }
118
119 let output_len = remaining.min(scratch.len());
120 let result = decoder.decode_bytes(&data[input_offset..], &mut scratch[..output_len]);
121 input_offset += result.consumed_in;
122 out.extend_from_slice(&scratch[..result.consumed_out]);
123 if out.len() > decoded_len_limit {
124 return Err(decoded_block_too_large(index, "LZW", decoded_len_limit));
125 }
126
127 match result.status {
128 Err(e) => {
129 return Err(Error::DecompressionFailed {
130 index,
131 reason: format!("LZW: {e}"),
132 })
133 }
134 Ok(LzwStatus::Done) => return Ok(out),
135 Ok(LzwStatus::Ok) => {
136 if result.consumed_in == 0 && result.consumed_out == 0 {
137 return Err(Error::DecompressionFailed {
138 index,
139 reason: "LZW: decoder made no progress".into(),
140 });
141 }
142 }
143 Ok(LzwStatus::NoProgress) => {
144 if result.consumed_out == output_len {
145 continue;
146 }
147 return Err(Error::DecompressionFailed {
148 index,
149 reason: "LZW: stream ended before end marker".into(),
150 });
151 }
152 }
153 }
154}
155
156fn decompress_packbits(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
157 let probe_limit = decoded_len_probe_limit(index, "PackBits", decoded_len_limit)?;
158 let mut out = Vec::new();
159 let mut cursor = 0usize;
160
161 while cursor < data.len() {
162 let header = data[cursor] as i8;
163 cursor += 1;
164
165 if header >= 0 {
166 let count = header as usize + 1;
167 let end = cursor + count;
168 if end > data.len() {
169 return Err(Error::DecompressionFailed {
170 index,
171 reason: "PackBits literal run is truncated".into(),
172 });
173 }
174 append_bounded_bytes(
175 &mut out,
176 &data[cursor..end],
177 index,
178 "PackBits",
179 decoded_len_limit,
180 probe_limit,
181 )?;
182 cursor = end;
183 } else if header != -128 {
184 if cursor >= data.len() {
185 return Err(Error::DecompressionFailed {
186 index,
187 reason: "PackBits repeat run is truncated".into(),
188 });
189 }
190 let count = (1i16 - header as i16) as usize;
191 let byte = data[cursor];
192 cursor += 1;
193 append_bounded_repeat(
194 &mut out,
195 byte,
196 count,
197 index,
198 "PackBits",
199 decoded_len_limit,
200 probe_limit,
201 )?;
202 }
203 }
204
205 Ok(out)
206}
207
208#[cfg(feature = "jpeg")]
209fn decompress_jpeg(
210 data: &[u8],
211 index: usize,
212 jpeg_tables: Option<&[u8]>,
213 decoded_len_limit: usize,
214) -> Result<Vec<u8>> {
215 let stream = merge_jpeg_stream(jpeg_tables, data);
216 panic::catch_unwind(AssertUnwindSafe(|| {
217 let mut decoder = jpeg_decoder::Decoder::new(Cursor::new(stream));
218 decoder.set_max_decoding_buffer_size(decoded_len_limit);
219 decoder.read_info()?;
220 validate_jpeg_metadata_budget(&decoder, decoded_len_limit)?;
221 decoder.decode()
222 }))
223 .map_err(|payload| Error::DecompressionFailed {
224 index,
225 reason: format!(
226 "JPEG decoder panicked: {}",
227 panic_payload_message(payload.as_ref())
228 ),
229 })?
230 .map_err(|e| Error::DecompressionFailed {
231 index,
232 reason: format!("JPEG: {e}"),
233 })
234}
235
236#[cfg(feature = "jpeg")]
237fn validate_jpeg_metadata_budget<R: std::io::Read>(
238 decoder: &jpeg_decoder::Decoder<R>,
239 decoded_len_limit: usize,
240) -> std::result::Result<(), jpeg_decoder::Error> {
241 let info = decoder.info().ok_or_else(|| {
242 jpeg_decoder::Error::Format("JPEG metadata missing after read_info".into())
243 })?;
244 let decoded_len = usize::from(info.width)
245 .checked_mul(usize::from(info.height))
246 .and_then(|pixels| pixels.checked_mul(info.pixel_format.pixel_bytes()))
247 .ok_or_else(|| jpeg_decoder::Error::Format("JPEG decoded size overflow".into()))?;
248 if decoded_len > decoded_len_limit {
249 return Err(jpeg_decoder::Error::Format(format!(
250 "JPEG decoded size {decoded_len} exceeds TIFF block budget {decoded_len_limit}"
251 )));
252 }
253 Ok(())
254}
255
256#[cfg(feature = "zstd")]
257fn decompress_zstd(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
258 let decoder = zstd::stream::read::Decoder::new(Cursor::new(data)).map_err(|e| {
259 Error::DecompressionFailed {
260 index,
261 reason: format!("ZSTD: {e}"),
262 }
263 })?;
264 read_bounded_to_end(decoder, index, "ZSTD", decoded_len_limit)
265}
266
267fn read_bounded_to_end<R: Read>(
268 reader: R,
269 index: usize,
270 codec: &'static str,
271 decoded_len_limit: usize,
272) -> Result<Vec<u8>> {
273 let probe_limit = decoded_len_probe_limit(index, codec, decoded_len_limit)?;
274 let mut reader = reader.take(probe_limit as u64);
275 let mut out = Vec::with_capacity(decoded_len_limit.min(8192));
276 let mut scratch = [0u8; 8192];
277
278 loop {
279 let remaining = probe_limit.saturating_sub(out.len());
280 if remaining == 0 {
281 break;
282 }
283 let read_len = remaining.min(scratch.len());
284 let bytes_read =
285 reader
286 .read(&mut scratch[..read_len])
287 .map_err(|e| Error::DecompressionFailed {
288 index,
289 reason: format!("{codec}: {e}"),
290 })?;
291 if bytes_read == 0 {
292 break;
293 }
294 out.extend_from_slice(&scratch[..bytes_read]);
295 if out.len() > decoded_len_limit {
296 return Err(decoded_block_too_large(index, codec, decoded_len_limit));
297 }
298 }
299
300 Ok(out)
301}
302
303fn decoded_len_probe_limit(
304 index: usize,
305 codec: &'static str,
306 decoded_len_limit: usize,
307) -> Result<usize> {
308 decoded_len_limit
309 .checked_add(1)
310 .ok_or_else(|| Error::DecompressionFailed {
311 index,
312 reason: format!("{codec}: TIFF block budget is too large to probe safely"),
313 })
314}
315
316fn decoded_block_too_large(index: usize, codec: &'static str, decoded_len_limit: usize) -> Error {
317 Error::DecompressionFailed {
318 index,
319 reason: format!("{codec}: decoded block exceeds TIFF block budget {decoded_len_limit}"),
320 }
321}
322
323fn append_bounded_bytes(
324 out: &mut Vec<u8>,
325 bytes: &[u8],
326 index: usize,
327 codec: &'static str,
328 decoded_len_limit: usize,
329 probe_limit: usize,
330) -> Result<()> {
331 let remaining = probe_limit.saturating_sub(out.len());
332 let copy_len = bytes.len().min(remaining);
333 out.extend_from_slice(&bytes[..copy_len]);
334 if copy_len < bytes.len() || out.len() > decoded_len_limit {
335 return Err(decoded_block_too_large(index, codec, decoded_len_limit));
336 }
337 Ok(())
338}
339
340fn append_bounded_repeat(
341 out: &mut Vec<u8>,
342 byte: u8,
343 count: usize,
344 index: usize,
345 codec: &'static str,
346 decoded_len_limit: usize,
347 probe_limit: usize,
348) -> Result<()> {
349 let remaining = probe_limit.saturating_sub(out.len());
350 let copy_len = count.min(remaining);
351 out.resize(out.len() + copy_len, byte);
352 if copy_len < count || out.len() > decoded_len_limit {
353 return Err(decoded_block_too_large(index, codec, decoded_len_limit));
354 }
355 Ok(())
356}
357
358#[cfg(feature = "jpeg")]
359fn merge_jpeg_stream(jpeg_tables: Option<&[u8]>, scan_data: &[u8]) -> Vec<u8> {
360 if jpeg_tables.is_none() {
361 return scan_data.to_vec();
362 }
363
364 let tables = jpeg_tables.unwrap_or_default();
365 let table_body = match tables.strip_suffix(&[0xff, 0xd9]) {
366 Some(without_eoi) => without_eoi,
367 None => tables,
368 };
369 let scan_body = match scan_data.strip_prefix(&[0xff, 0xd8]) {
370 Some(without_soi) => without_soi,
371 None => scan_data,
372 };
373
374 let mut merged = Vec::with_capacity(table_body.len() + scan_body.len() + 2);
375 if table_body.starts_with(&[0xff, 0xd8]) {
376 merged.extend_from_slice(table_body);
377 } else {
378 merged.extend_from_slice(&[0xff, 0xd8]);
379 merged.extend_from_slice(table_body);
380 }
381 merged.extend_from_slice(scan_body);
382 if !merged.ends_with(&[0xff, 0xd9]) {
383 merged.extend_from_slice(&[0xff, 0xd9]);
384 }
385 merged
386}
387
388#[cfg(feature = "jpeg")]
389fn panic_payload_message(payload: &(dyn std::any::Any + Send)) -> String {
390 if let Some(message) = payload.downcast_ref::<&'static str>() {
391 (*message).to_string()
392 } else if let Some(message) = payload.downcast_ref::<String>() {
393 message.clone()
394 } else {
395 "unknown panic payload".into()
396 }
397}
398
399fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u16) {
400 let host_is_little_endian = cfg!(target_endian = "little");
401 let data_is_little_endian = matches!(byte_order, ByteOrder::LittleEndian);
402 if host_is_little_endian == data_is_little_endian {
403 return;
404 }
405
406 let chunk = match bit_depth {
407 0..=8 => 1,
408 9..=16 => 2,
409 17..=32 => 4,
410 _ => 8,
411 };
412 if chunk == 1 {
413 return;
414 }
415
416 for value in buf.chunks_exact_mut(chunk) {
417 value.reverse();
418 }
419}
420
421fn reverse_horizontal_predictor(buf: &mut [u8], bit_depth: u16, samples: u16) {
422 let bytes_per_value = match bit_depth {
423 0..=8 => 1,
424 9..=16 => 2,
425 17..=32 => 4,
426 _ => 8,
427 };
428 let lookback = usize::from(samples) * bytes_per_value;
429
430 match bytes_per_value {
431 1 => {
432 for index in lookback..buf.len() {
433 buf[index] = buf[index].wrapping_add(buf[index - lookback]);
434 }
435 }
436 2 => {
437 for index in (lookback..buf.len()).step_by(2) {
438 let current = u16::from_ne_bytes(buf[index..index + 2].try_into().unwrap());
439 let previous = u16::from_ne_bytes(
440 buf[index - lookback..index - lookback + 2]
441 .try_into()
442 .unwrap(),
443 );
444 buf[index..index + 2]
445 .copy_from_slice(¤t.wrapping_add(previous).to_ne_bytes());
446 }
447 }
448 4 => {
449 for index in (lookback..buf.len()).step_by(4) {
450 let current = u32::from_ne_bytes(buf[index..index + 4].try_into().unwrap());
451 let previous = u32::from_ne_bytes(
452 buf[index - lookback..index - lookback + 4]
453 .try_into()
454 .unwrap(),
455 );
456 buf[index..index + 4]
457 .copy_from_slice(¤t.wrapping_add(previous).to_ne_bytes());
458 }
459 }
460 _ => {
461 for index in (lookback..buf.len()).step_by(8) {
462 let current = u64::from_ne_bytes(buf[index..index + 8].try_into().unwrap());
463 let previous = u64::from_ne_bytes(
464 buf[index - lookback..index - lookback + 8]
465 .try_into()
466 .unwrap(),
467 );
468 buf[index..index + 8]
469 .copy_from_slice(¤t.wrapping_add(previous).to_ne_bytes());
470 }
471 }
472 }
473}
474
475fn predict_f16(input: &mut [u8], output: &mut [u8], samples: u16) {
476 let samples = usize::from(samples);
477 for i in samples..input.len() {
478 input[i] = input[i].wrapping_add(input[i - samples]);
479 }
480 for (i, chunk) in output.chunks_mut(2).enumerate() {
481 chunk.copy_from_slice(&u16::to_ne_bytes(u16::from_be_bytes([
482 input[i],
483 input[input.len() / 2 + i],
484 ])));
485 }
486}
487
488fn predict_f32(input: &mut [u8], output: &mut [u8], samples: u16) {
489 let samples = usize::from(samples);
490 for i in samples..input.len() {
491 input[i] = input[i].wrapping_add(input[i - samples]);
492 }
493 for (i, chunk) in output.chunks_mut(4).enumerate() {
494 chunk.copy_from_slice(&u32::to_ne_bytes(u32::from_be_bytes([
495 input[i],
496 input[input.len() / 4 + i],
497 input[input.len() / 2 + i],
498 input[input.len() / 4 * 3 + i],
499 ])));
500 }
501}
502
503fn predict_f64(input: &mut [u8], output: &mut [u8], samples: u16) {
504 let samples = usize::from(samples);
505 for i in samples..input.len() {
506 input[i] = input[i].wrapping_add(input[i - samples]);
507 }
508 for (i, chunk) in output.chunks_mut(8).enumerate() {
509 chunk.copy_from_slice(&u64::to_ne_bytes(u64::from_be_bytes([
510 input[i],
511 input[input.len() / 8 + i],
512 input[input.len() / 8 * 2 + i],
513 input[input.len() / 8 * 3 + i],
514 input[input.len() / 8 * 4 + i],
515 input[input.len() / 8 * 5 + i],
516 input[input.len() / 8 * 6 + i],
517 input[input.len() / 8 * 7 + i],
518 ])));
519 }
520}
521
522#[cfg(test)]
523mod tests {
524 use std::path::Path;
525
526 #[cfg(not(feature = "jpeg"))]
527 use super::decompress;
528 #[cfg(feature = "jpeg")]
529 use super::{decompress, merge_jpeg_stream};
530 use super::{decompress_lzw, decompress_packbits, fix_endianness_and_predict};
531 use crate::header::ByteOrder;
532 use std::io::Write;
533 use tiff_core::Compression;
534
535 #[test]
536 fn horizontal_predictor_restores_u16_rows() {
537 let mut row = vec![1, 0, 1, 0, 2, 0];
538 fix_endianness_and_predict(&mut row, 16, 1, ByteOrder::LittleEndian, 2).unwrap();
539 assert_eq!(row, vec![1, 0, 2, 0, 4, 0]);
540 }
541
542 #[test]
543 fn packbits_decoder_rejects_truncated_repeat_run() {
544 let err = decompress_packbits(&[0xff], 0, 1).unwrap_err();
545 assert!(err.to_string().contains("PackBits"));
546 }
547
548 #[test]
549 fn deflate_decoder_rejects_blocks_that_exceed_budget() {
550 let payload = [0x2a; 128];
551 let mut encoder =
552 flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
553 encoder.write_all(&payload).unwrap();
554 let compressed = encoder.finish().unwrap();
555
556 let err =
557 decompress(Compression::Deflate.to_code(), &compressed, 0, None, 127).unwrap_err();
558 assert!(err.to_string().contains("block budget"));
559 }
560
561 #[test]
562 fn lzw_decoder_rejects_blocks_that_exceed_budget() {
563 let payload = [0x2a; 128];
564 let compressed = weezl::encode::Encoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8)
565 .encode(&payload)
566 .unwrap();
567
568 let err = decompress_lzw(&compressed, 0, 127).unwrap_err();
569 assert!(err.to_string().contains("block budget"));
570 }
571
572 #[test]
573 fn packbits_decoder_rejects_blocks_that_exceed_budget() {
574 let err = decompress_packbits(&[0x81, 0x2a], 0, 127).unwrap_err();
575 assert!(err.to_string().contains("block budget"));
576 }
577
578 #[test]
579 fn uncompressed_decoder_rejects_blocks_that_exceed_budget() {
580 let err =
581 decompress(Compression::None.to_code(), &[1, 2, 3, 4, 5], 0, None, 4).unwrap_err();
582 assert!(err.to_string().contains("block budget"));
583 }
584
585 #[test]
586 fn lzw_real_cog_tile_requires_repeated_trailer_bytes() {
587 let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
588 .join("../testdata/interoperability/gdal/gcore/data/cog/byte_little_endian_golden.tif");
589 let bytes = std::fs::read(fixture).unwrap();
590
591 let without_trailer = &bytes[570..570 + 1223];
592 let with_trailer = &bytes[570..570 + 1227];
593
594 assert!(decompress_lzw(without_trailer, 0, 1_000_000).is_ok());
595 assert!(decompress_lzw(with_trailer, 0, 1_000_000).is_ok());
596 }
597
598 #[cfg(feature = "zstd")]
599 #[test]
600 fn zstd_decoder_rejects_blocks_that_exceed_budget() {
601 let payload = [0x2a; 128];
602 let compressed = zstd::stream::encode_all(&payload[..], 0).unwrap();
603
604 let err = decompress(Compression::Zstd.to_code(), &compressed, 0, None, 127).unwrap_err();
605 assert!(err.to_string().contains("block budget"));
606 }
607
608 #[cfg(feature = "jpeg")]
609 #[test]
610 fn merges_jpeg_tables_with_abbreviated_scan() {
611 let merged = merge_jpeg_stream(
612 Some(&[0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0xff, 0xd9]),
613 &[0xff, 0xda, 0x00, 0x08, 0x00],
614 );
615 assert_eq!(&merged[..6], &[0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43]);
616 assert!(merged.ends_with(&[0xff, 0xd9]));
617 }
618
619 #[cfg(feature = "jpeg")]
620 #[test]
621 fn jpeg_decoder_rejects_frame_sizes_that_exceed_tiff_budget() {
622 let mut jpeg = vec![
623 0xff, 0xd8, 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x14, 0x00, 0x14, 0x01, 0x01, 0x11,
624 0x00, 0xff, 0xc4, 0x00, 0x17, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
625 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x06, 0xff, 0xc4,
626 0x00, 0x2a, 0x10, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x05, 0x05, 0x00, 0x00, 0x00,
627 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x11, 0x03, 0x04, 0x00, 0x18, 0x31, 0x41,
628 0x13, 0x21, 0x51, 0x71, 0x05, 0x22, 0x61, 0x91, 0xb1, 0x14, 0x42, 0x62, 0xc1, 0xf0,
629 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x75, 0xc5, 0xb7, 0xd2,
630 0x31, 0x4a, 0x75, 0x51, 0xe0, 0x65, 0xf2, 0x19, 0xd8, 0x8d, 0x7d, 0xfe, 0x71, 0x19,
631 0x2b, 0x94, 0x54, 0x2c, 0x33, 0x38, 0x20, 0x2f, 0x7d, 0xf5, 0xd2, 0x40, 0x18, 0x6b,
632 0xdc, 0x3d, 0xa0, 0x44, 0x15, 0xc9, 0x2c, 0xa1, 0xc8, 0x5c, 0xa4, 0x2c, 0xed, 0xcc,
633 0x74, 0x83, 0xcb, 0xaf, 0x59, 0xc2, 0xaf, 0x0f, 0x02, 0xb3, 0x2e, 0x57, 0xfc, 0x79,
634 0x15, 0x9f, 0x58, 0xee, 0x3f, 0x7b, 0xe0, 0x59, 0x95, 0x84, 0x26, 0x56, 0xac, 0xc2,
635 0x62, 0xa0, 0x8c, 0xa4, 0x91, 0xc9, 0x44, 0xed, 0xa4, 0x9e, 0x9a, 0x08, 0xc1, 0x8a,
636 0x54, 0x9d, 0x41, 0xe3, 0xa4, 0xe8, 0x65, 0x01, 0xe7, 0xdc, 0xff, 0x00, 0x6d, 0x8d,
637 0x2f, 0x89, 0x5b, 0x50, 0xbe, 0xb9, 0x4a, 0x0d, 0x4c, 0x53, 0x51, 0x01, 0x8a, 0x31,
638 0x9a, 0x92, 0x22, 0x5a, 0x49, 0xe7, 0xda, 0x37, 0xeb, 0x8c, 0xc5, 0xc7, 0x0a, 0xd5,
639 0x87, 0x0a, 0x85, 0x30, 0xc7, 0xee, 0x69, 0x27, 0x40, 0x77, 0x3e, 0xbf, 0x18, 0x99,
640 0xae, 0x1c, 0xb6, 0xc0, 0x0d, 0x02, 0xf9, 0x47, 0xb0, 0x81, 0x8f, 0xff, 0xd9,
641 ];
642 jpeg[7] = 0x9b;
643 jpeg[8] = 0x43;
644 jpeg[9] = 0xee;
645 jpeg[10] = 0x23;
646
647 let error = decompress(Compression::Jpeg.to_code(), &jpeg, 0, None, 20 * 20).unwrap_err();
648 assert!(error.to_string().contains("block budget"));
649 }
650}