From 01aa731cce2a5623812493762dd981c3ab51b744 Mon Sep 17 00:00:00 2001 From: Kornel Date: Sat, 4 Jan 2025 21:30:46 +0000 Subject: [PATCH 1/2] Parse control extension from a buffer --- src/reader/decoder.rs | 96 +++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 62 deletions(-) diff --git a/src/reader/decoder.rs b/src/reader/decoder.rs index 6bc8788..41c9698 100644 --- a/src/reader/decoder.rs +++ b/src/reader/decoder.rs @@ -164,7 +164,7 @@ enum State { GlobalPalette(usize), BlockStart(u8), BlockEnd, - ExtensionBlock(AnyExtension), + ExtensionBlockStart, /// Collects data in ext.data ExtensionDataBlock(usize), ApplicationExtension, @@ -188,8 +188,6 @@ enum U16Value { ScreenWidth, /// Logical screen descriptor height ScreenHeight, - /// Delay time - Delay, /// Left frame offset ImageLeft, /// Top frame offset @@ -206,9 +204,7 @@ enum ByteValue { GlobalFlags, Background { global_flags: u8 }, AspectRatio { global_flags: u8 }, - ControlFlags, ImageFlags, - TransparentIdx, } /// Decoder for `Frame::make_lzw_pre_encoded` @@ -575,31 +571,6 @@ impl StreamingDecoder { }; goto!(GlobalPalette(table_size)) }, - ControlFlags => { - self.ext.data.push(b); - let frame = self.try_current_frame()?; - let control_flags = b; - if control_flags & 1 != 0 { - // Set to Some(...), gets overwritten later - frame.transparent = Some(0); - } - frame.needs_user_input = - control_flags & 0b10 != 0; - frame.dispose = match DisposalMethod::from_u8( - (control_flags & 0b11100) >> 2 - ) { - Some(method) => method, - None => DisposalMethod::Any - }; - goto!(U16(U16Value::Delay)) - } - TransparentIdx => { - self.ext.data.push(b); - if let Some(ref mut idx) = self.try_current_frame()?.transparent { - *idx = b; - } - goto!(ExtensionDataBlock(0)) - } ImageFlags => { let local_table = (b & 0b1000_0000) != 0; let interlaced = (b & 0b0100_0000) != 0; @@ -656,7 +627,12 @@ impl StreamingDecoder { goto!(U16Byte1(U16Value::ImageLeft, b), emit Decoded::BlockStart(Block::Image)) } Some(Block::Extension) => { - goto!(ExtensionBlock(AnyExtension(b)), emit Decoded::BlockStart(Block::Extension)) + self.ext.data.clear(); + self.ext.id = AnyExtension(b); + if self.ext.id.into_known().is_none() { + return Err(DecodingError::format("unknown block type encountered")); + } + goto!(ExtensionBlockStart, emit Decoded::BlockStart(Block::Extension)) } Some(Block::Trailer) => { // The `Trailer` is the final state, and isn't reachable without extraneous data after the end of file @@ -680,23 +656,9 @@ impl StreamingDecoder { goto!(BlockStart(b)) } } - ExtensionBlock(id) => { - use Extension::*; - self.ext.id = id; - self.ext.data.clear(); + ExtensionBlockStart => { self.ext.data.push(b); - if let Some(ext) = Extension::from_u8(id.0) { - match ext { - Control => { - goto!(self.read_control_extension(b)?) - } - Text | Comment | Application => { - goto!(ExtensionDataBlock(b as usize)) - } - } - } else { - Err(DecodingError::format("unknown block type encountered")) - } + goto!(ExtensionDataBlock(b as usize)) } ExtensionDataBlock(left) => { if left > 0 { @@ -707,10 +669,17 @@ impl StreamingDecoder { goto!(n, ExtensionDataBlock(left - n)) } else if b == 0 { self.ext.is_block_end = true; - if self.ext.id.into_known() == Some(Extension::Application) { - goto!(0, ApplicationExtension, emit Decoded::BlockFinished(self.ext.id)) - } else { - goto!(BlockEnd, emit Decoded::BlockFinished(self.ext.id)) + match self.ext.id.into_known() { + Some(Extension::Application) => { + goto!(0, ApplicationExtension, emit Decoded::BlockFinished(self.ext.id)) + } + Some(Extension::Control) => { + self.read_control_extension()?; + goto!(BlockEnd, emit Decoded::BlockFinished(self.ext.id)) + }, + _ => { + goto!(BlockEnd, emit Decoded::BlockFinished(self.ext.id)) + } } } else { self.ext.is_block_end = false; @@ -825,12 +794,6 @@ impl StreamingDecoder { self.height = height; Byte(ByteValue::GlobalFlags) }, - (Delay, delay) => { - self.try_current_frame()?.delay = delay; - self.ext.data.push(value as u8); - self.ext.data.push(b); - Byte(ByteValue::TransparentIdx) - }, (ImageLeft, left) => { self.try_current_frame()?.left = left; U16(U16Value::ImageTop) @@ -850,13 +813,22 @@ impl StreamingDecoder { }) } - fn read_control_extension(&mut self, b: u8) -> Result { - self.add_frame(); - self.ext.data.push(b); - if b != 4 { + fn read_control_extension(&mut self) -> Result<(), DecodingError> { + if self.ext.data.len() != 5 { return Err(DecodingError::format("control extension has wrong length")); } - Ok(Byte(ByteValue::ControlFlags)) + let control = &self.ext.data[1..]; + + let frame = self.current.get_or_insert_with(Frame::default); + let control_flags = control[0]; + frame.needs_user_input = control_flags & 0b10 != 0; + frame.dispose = match DisposalMethod::from_u8((control_flags & 0b11100) >> 2) { + Some(method) => method, + None => DisposalMethod::Any, + }; + frame.delay = u16::from_le_bytes(control[1..3].try_into().unwrap()); + frame.transparent = (control_flags & 1 != 0).then_some(control[3]); + Ok(()) } fn add_frame(&mut self) { From 70eadfbc6b60dab9d2fb879837e98689b334b168 Mon Sep 17 00:00:00 2001 From: Kornel Date: Sat, 4 Jan 2025 20:14:21 +0000 Subject: [PATCH 2/2] Use a buffer guaranteeing minimum update size --- src/reader/decoder.rs | 228 ++++++++++++++++++------------------------ 1 file changed, 98 insertions(+), 130 deletions(-) diff --git a/src/reader/decoder.rs b/src/reader/decoder.rs index 41c9698..6a323c7 100644 --- a/src/reader/decoder.rs +++ b/src/reader/decoder.rs @@ -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, @@ -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, @@ -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, @@ -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, @@ -532,75 +510,100 @@ impl StreamingDecoder { }) ); + macro_rules! ensure_min_length_buffer ( + ($required:expr) => ({ + let required: usize = $required; + if buf.len() >= required && self.unused_internal_buffer_len == 0 { + (required, &buf[..required]) + } else { + let has = usize::from(self.unused_internal_buffer_len); + let mut consumed = 0; + if has < required { + let to_copy = buf.len().min(required - has); + let new_len = has + to_copy; + self.internal_buffer[has .. new_len].copy_from_slice(&buf[..to_copy]); + consumed += to_copy; + if new_len < required { + self.unused_internal_buffer_len = new_len as u8; + return Ok((consumed, Decoded::Nothing)); + } else { + self.unused_internal_buffer_len = 0; + } + } + (consumed, &self.internal_buffer[..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 (consumed, 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!(consumed, 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 (consumed, 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!( + consumed, + GlobalPalette(table_size), + emit Decoded::BackgroundColor(background_color) + ) + }, + ImageBlockStart => { + let (consumed, 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!(consumed, LocalPalette(pal_len)) + } else { + goto!(consumed, LocalPalette(0)) + } + }, GlobalPalette(left) => { // the global_color_table is guaranteed to have the exact capacity required if left > 0 { @@ -624,7 +627,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(); @@ -698,11 +701,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); } @@ -768,10 +771,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; @@ -782,37 +781,6 @@ impl StreamingDecoder { } } - fn read_second_byte(&mut self, next: U16Value, value: u8, b: u8) -> Result { - 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"));