Skip to content

Commit

Permalink
Use a buffer guaranteeing minimum update size
Browse files Browse the repository at this point in the history
  • Loading branch information
kornelski committed Jan 5, 2025
1 parent 01aa731 commit 01ac8b0
Showing 1 changed file with 97 additions and 130 deletions.
227 changes: 97 additions & 130 deletions src/reader/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,9 @@ pub enum Decoded {
/// Internal state of the GIF decoder
#[derive(Debug, Copy, Clone)]
enum State {
Magic(u8, [u8; 6]),
U16Byte1(U16Value, u8),
U16(U16Value),
Byte(ByteValue),
Magic,
ScreenDescriptor,
ImageBlockStart,
GlobalPalette(usize),
BlockStart(u8),
BlockEnd,
Expand All @@ -181,32 +180,6 @@ use self::State::*;

use super::converter::PixelConverter;

/// U16 values that may occur in a GIF image
#[derive(Debug, Copy, Clone)]
enum U16Value {
/// Logical screen descriptor width
ScreenWidth,
/// Logical screen descriptor height
ScreenHeight,
/// Left frame offset
ImageLeft,
/// Top frame offset
ImageTop,
/// Frame width
ImageWidth,
/// Frame height
ImageHeight,
}

/// Single byte screen descriptor values
#[derive(Debug, Copy, Clone)]
enum ByteValue {
GlobalFlags,
Background { global_flags: u8 },
AspectRatio { global_flags: u8 },
ImageFlags,
}

/// Decoder for `Frame::make_lzw_pre_encoded`
pub struct FrameDecoder {
lzw_reader: LzwReader,
Expand Down Expand Up @@ -343,6 +316,9 @@ impl LzwReader {
/// To just get GIF frames, use [`crate::Decoder`] instead.
pub struct StreamingDecoder {
state: State,
/// Input bytes are collected here if `update` got `buf` smaller than the minimum required
internal_buffer: [u8; 9],
unused_internal_buffer_len: u8,
lzw_reader: LzwReader,
skip_frame_decoding: bool,
check_frame_consistency: bool,
Expand Down Expand Up @@ -421,7 +397,9 @@ impl StreamingDecoder {

pub(crate) fn with_options(options: &DecodeOptions) -> Self {
Self {
state: Magic(0, [0; 6]),
internal_buffer: [0; 9],
unused_internal_buffer_len: 0,
state: Magic,
lzw_reader: LzwReader::new(options.check_for_end_code),
skip_frame_decoding: options.skip_frame_decoding,
check_frame_consistency: options.check_frame_consistency,
Expand Down Expand Up @@ -532,75 +510,99 @@ impl StreamingDecoder {
})
);

macro_rules! ensure_min_length_buffer (
($required:expr) => ({
let required: usize = $required;
let new_buf = if buf.len() >= required && self.unused_internal_buffer_len == 0 {
buf
} else {
let has = usize::from(self.unused_internal_buffer_len);
if has < required {
let to_copy = buf.len().min(has - required);
let new_len = has + to_copy;
self.internal_buffer[has .. new_len].copy_from_slice(&buf[..to_copy]);
if new_len < required {
self.unused_internal_buffer_len = new_len as u8;
return Ok((to_copy, Decoded::Nothing));
} else {
self.unused_internal_buffer_len = 0;
}
}
&self.internal_buffer
};
&new_buf[..required]
})
);

let b = *buf.first().ok_or(io::ErrorKind::UnexpectedEof)?;

match self.state {
Magic(i, mut version) => if i < 6 {
version[i as usize] = b;
goto!(Magic(i+1, version))
} else if &version[..3] == b"GIF" {
self.version = match &version[3..] {
b"87a" => Version::V87a,
b"89a" => Version::V89a,
_ => return Err(DecodingError::format("malformed GIF header"))
Magic => {
let version = ensure_min_length_buffer!(6);

self.version = match version {
b"GIF87a" => Version::V87a,
b"GIF89a" => Version::V89a,
_ => return Err(DecodingError::format("malformed GIF header")),
};
goto!(U16Byte1(U16Value::ScreenWidth, b))
} else {
Err(DecodingError::format("malformed GIF header"))

goto!(version.len(), ScreenDescriptor)
},
Byte(value) => {
use self::ByteValue::*;
match value {
GlobalFlags => {
goto!(Byte(Background { global_flags: b }))
},
Background { global_flags } => {
goto!(
Byte(AspectRatio { global_flags }),
emit Decoded::BackgroundColor(b)
)
},
AspectRatio { global_flags } => {
let global_table = global_flags & 0x80 != 0;
let table_size = if global_table {
let table_size = PLTE_CHANNELS * (1 << ((global_flags & 0b111) + 1) as usize);
self.global_color_table.try_reserve_exact(table_size).map_err(|_| io::ErrorKind::OutOfMemory)?;
table_size
} else {
0usize
};
goto!(GlobalPalette(table_size))
},
ImageFlags => {
let local_table = (b & 0b1000_0000) != 0;
let interlaced = (b & 0b0100_0000) != 0;
let table_size = b & 0b0000_0111;
let check_frame_consistency = self.check_frame_consistency;
let (width, height) = (self.width, self.height);

let frame = self.try_current_frame()?;

frame.interlaced = interlaced;
if check_frame_consistency {
// Consistency checks.
if width.checked_sub(frame.width) < Some(frame.left)
|| height.checked_sub(frame.height) < Some(frame.top)
{
return Err(DecodingError::format("frame descriptor is out-of-bounds"))
}
}
ScreenDescriptor => {
let desc = ensure_min_length_buffer!(7);

self.width = u16::from_le_bytes(desc[..2].try_into().unwrap());
self.height = u16::from_le_bytes(desc[2..4].try_into().unwrap());
let global_flags = desc[4];
let background_color = desc[5];

let global_table = global_flags & 0x80 != 0;
let table_size = if global_table {
let table_size = PLTE_CHANNELS * (1 << ((global_flags & 0b111) + 1) as usize);
self.global_color_table.try_reserve_exact(table_size).map_err(|_| io::ErrorKind::OutOfMemory)?;
table_size
} else {
0usize
};

if local_table {
let pal_len = PLTE_CHANNELS * (1 << (table_size + 1));
frame.palette.get_or_insert_with(Vec::new)
.try_reserve_exact(pal_len).map_err(|_| io::ErrorKind::OutOfMemory)?;
goto!(LocalPalette(pal_len))
} else {
goto!(LocalPalette(0))
}
goto!(
desc.len(),
GlobalPalette(table_size),
emit Decoded::BackgroundColor(background_color)
)
},
ImageBlockStart => {
let header = ensure_min_length_buffer!(9);

let frame = self.current.as_mut().ok_or_else(|| DecodingError::format("bad state"))?;
frame.left = u16::from_le_bytes(header[..2].try_into().unwrap());
frame.top = u16::from_le_bytes(header[2..4].try_into().unwrap());
frame.width = u16::from_le_bytes(header[4..6].try_into().unwrap());
frame.height = u16::from_le_bytes(header[6..8].try_into().unwrap());

let flags = header[8];
frame.interlaced = (flags & 0b0100_0000) != 0;

if self.check_frame_consistency {
// Consistency checks.
if self.width.checked_sub(frame.width) < Some(frame.left)
|| self.height.checked_sub(frame.height) < Some(frame.top)
{
return Err(DecodingError::format("frame descriptor is out-of-bounds"));
}
}
}

let local_table = (flags & 0b1000_0000) != 0;
if local_table {
let table_size = flags & 0b0000_0111;
let pal_len = PLTE_CHANNELS * (1 << (table_size + 1));
frame.palette.get_or_insert_with(Vec::new)
.try_reserve_exact(pal_len).map_err(|_| io::ErrorKind::OutOfMemory)?;
goto!(header.len(), LocalPalette(pal_len))
} else {
goto!(header.len(), LocalPalette(0))
}
},
GlobalPalette(left) => {
// the global_color_table is guaranteed to have the exact capacity required
if left > 0 {
Expand All @@ -624,7 +626,7 @@ impl StreamingDecoder {
match Block::from_u8(type_) {
Some(Block::Image) => {
self.add_frame();
goto!(U16Byte1(U16Value::ImageLeft, b), emit Decoded::BlockStart(Block::Image))
goto!(0, ImageBlockStart, emit Decoded::BlockStart(Block::Image))
}
Some(Block::Extension) => {
self.ext.data.clear();
Expand Down Expand Up @@ -698,11 +700,11 @@ impl StreamingDecoder {
}
}
LocalPalette(left) => {
let n = cmp::min(left, buf.len());
if left > 0 {
let n = cmp::min(left, buf.len());
let src = &buf[..n];
if let Some(pal) = self.try_current_frame()?.palette.as_mut() {
// capacity has already been reserved in ImageFlags
// capacity has already been reserved in ImageBlockStart
if pal.capacity() - pal.len() >= src.len() {
pal.extend_from_slice(src);
}
Expand Down Expand Up @@ -768,10 +770,6 @@ impl StreamingDecoder {
}
}
}
U16(next) => goto!(U16Byte1(next, b)),
U16Byte1(next, value) => {
goto!(self.read_second_byte(next, value, b)?)
}
FrameDecoded => {
// end of image data reached
self.current = None;
Expand All @@ -782,37 +780,6 @@ impl StreamingDecoder {
}
}

fn read_second_byte(&mut self, next: U16Value, value: u8, b: u8) -> Result<State, DecodingError> {
use self::U16Value::*;
let value = (u16::from(b) << 8) | u16::from(value);
Ok(match (next, value) {
(ScreenWidth, width) => {
self.width = width;
U16(U16Value::ScreenHeight)
},
(ScreenHeight, height) => {
self.height = height;
Byte(ByteValue::GlobalFlags)
},
(ImageLeft, left) => {
self.try_current_frame()?.left = left;
U16(U16Value::ImageTop)
},
(ImageTop, top) => {
self.try_current_frame()?.top = top;
U16(U16Value::ImageWidth)
},
(ImageWidth, width) => {
self.try_current_frame()?.width = width;
U16(U16Value::ImageHeight)
},
(ImageHeight, height) => {
self.try_current_frame()?.height = height;
Byte(ByteValue::ImageFlags)
}
})
}

fn read_control_extension(&mut self) -> Result<(), DecodingError> {
if self.ext.data.len() != 5 {
return Err(DecodingError::format("control extension has wrong length"));
Expand Down

0 comments on commit 01ac8b0

Please sign in to comment.