Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass image data directly to ZlibStream, bypassing ChunkState::raw_bytes #424

Merged
merged 6 commits into from
Nov 14, 2023
190 changes: 100 additions & 90 deletions src/decoder/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,39 @@ pub const CHUNCK_BUFFER_SIZE: usize = 32 * 1024;
/// be used to detect that build.
const CHECKSUM_DISABLED: bool = cfg!(fuzzing);

/// Kind of `u32` value that is being read via `State::U32Byte...`.
#[derive(Debug)]
enum U32Value {
// CHUNKS
enum U32ValueKind {
/// Chunk length - see
/// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#Chunk-layout
Length,
Type(u32),
/// Chunk type - see
/// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#Chunk-layout
Type { length: u32 },
/// Chunk checksum - see
/// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#Chunk-layout
Crc(ChunkType),
/// Sequence number from an `fdAT` chunk - see
/// https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk
ApngSequenceNumber,
}

#[derive(Debug)]
enum State {
Signature(u8, [u8; 7]),
U32Byte3(U32Value, u32),
U32Byte2(U32Value, u32),
U32Byte1(U32Value, u32),
U32(U32Value),
ReadChunk(ChunkType),
PartialChunk(ChunkType),
DecodeData(ChunkType, usize),
U32Byte3(U32ValueKind, u32),
U32Byte2(U32ValueKind, u32),
U32Byte1(U32ValueKind, u32),
U32(U32ValueKind),
/// In this state we are reading chunk data from external input, and appending it to
/// `ChunkState::raw_bytes`.
ReadChunkData(ChunkType),
/// In this state we check if all chunk data has been already read into `ChunkState::raw_bytes`
/// and if so then we parse the chunk. Otherwise, we go back to the `ReadChunkData` state.
ParseChunkData(ChunkType),
/// In this state we are reading image data from external input and feeding it directly into
/// `StreamingDecoder::inflater`.
ImageData(ChunkType),
}

#[derive(Debug)]
Expand Down Expand Up @@ -423,8 +438,8 @@ pub struct StreamingDecoder {
pub(crate) info: Option<Info<'static>>,
/// The animation chunk sequence number.
current_seq_no: Option<u32>,
/// Stores where in decoding an `fdAT` chunk we are.
apng_seq_handled: bool,
/// Whether we have already seen a start of an IDAT chunk. (Used to validate chunk ordering -
/// some chunk types can only appear before or after an IDAT chunk.)
have_idat: bool,
decode_options: DecodeOptions,
}
Expand Down Expand Up @@ -462,7 +477,6 @@ impl StreamingDecoder {
inflater,
info: None,
current_seq_no: None,
apng_seq_handled: false,
have_idat: false,
decode_options,
}
Expand All @@ -477,7 +491,6 @@ impl StreamingDecoder {
self.inflater.reset();
self.info = None;
self.current_seq_no = None;
self.apng_seq_handled = false;
self.have_idat = false;
}

Expand Down Expand Up @@ -559,21 +572,21 @@ impl StreamingDecoder {
Signature(_, signature)
if signature == [137, 80, 78, 71, 13, 10, 26] && current_byte == 10 =>
{
self.state = Some(U32(U32Value::Length));
self.state = Some(U32(U32ValueKind::Length));
Ok((1, Decoded::Nothing))
}
Signature(..) => Err(DecodingError::Format(
FormatErrorInner::InvalidSignature.into(),
)),
U32Byte3(type_, mut val) => {
use self::U32Value::*;
use self::U32ValueKind::*;
val |= u32::from(current_byte);
match type_ {
Length => {
self.state = Some(U32(Type(val)));
self.state = Some(U32(Type { length: val }));
Ok((1, Decoded::Nothing))
}
Type(length) => {
Type { length } => {
let type_str = ChunkType([
(val >> 24) as u8,
(val >> 16) as u8,
Expand All @@ -587,7 +600,7 @@ impl StreamingDecoder {
self.current_chunk.type_ = type_str;
self.inflater.finish_compressed_chunks(image_data)?;
self.inflater.reset();
self.state = Some(U32Byte3(Type(length), val & !0xff));
self.state = Some(U32Byte3(Type { length }, val & !0xff));
return Ok((0, Decoded::ImageDataFlushed));
}
self.current_chunk.type_ = type_str;
Expand All @@ -596,9 +609,15 @@ impl StreamingDecoder {
self.current_chunk.crc.update(&type_str.0);
}
self.current_chunk.remaining = length;
self.apng_seq_handled = false;
self.current_chunk.raw_bytes.clear();
self.state = Some(ReadChunk(type_str));
self.state = match type_str {
chunk::fdAT => Some(U32(ApngSequenceNumber)),
fintelia marked this conversation as resolved.
Show resolved Hide resolved
IDAT => {
self.have_idat = true;
Some(ImageData(type_str))
}
_ => Some(ReadChunkData(type_str)),
};
Ok((1, Decoded::ChunkBegin(length, type_str)))
}
Crc(type_str) => {
Expand All @@ -612,7 +631,7 @@ impl StreamingDecoder {
};

if val == sum || CHECKSUM_DISABLED {
self.state = Some(State::U32(U32Value::Length));
self.state = Some(State::U32(U32ValueKind::Length));
if type_str == IEND {
Ok((1, Decoded::ImageEnd))
} else {
Expand All @@ -629,6 +648,36 @@ impl StreamingDecoder {
))
}
}
ApngSequenceNumber => {
debug_assert_eq!(self.current_chunk.type_, chunk::fdAT);
let next_seq_no = val;
self.current_chunk.remaining -= 4;

if let Some(seq_no) = self.current_seq_no {
if next_seq_no != seq_no + 1 {
return Err(DecodingError::Format(
FormatErrorInner::ApngOrder {
present: next_seq_no,
expected: seq_no + 1,
}
.into(),
));
}
self.current_seq_no = Some(next_seq_no);
} else {
return Err(DecodingError::Format(
FormatErrorInner::MissingFctl.into(),
));
}

if !self.decode_options.ignore_crc {
let data = next_seq_no.to_be_bytes();
self.current_chunk.crc.update(&data);
}

self.state = Some(ImageData(chunk::fdAT));
Ok((1, Decoded::PartialChunk(chunk::fdAT)))
}
}
}
U32Byte2(type_, val) => {
Expand All @@ -643,63 +692,24 @@ impl StreamingDecoder {
self.state = Some(U32Byte1(type_, u32::from(current_byte) << 24));
Ok((1, Decoded::Nothing))
}
PartialChunk(type_str) => {
match type_str {
IDAT => {
self.have_idat = true;
self.state = Some(DecodeData(type_str, 0));
Ok((0, Decoded::PartialChunk(type_str)))
}
chunk::fdAT => {
let data_start;
if let Some(seq_no) = self.current_seq_no {
if !self.apng_seq_handled {
data_start = 4;
let mut buf = &self.current_chunk.raw_bytes[..];
let next_seq_no = buf.read_be()?;
if next_seq_no != seq_no + 1 {
return Err(DecodingError::Format(
FormatErrorInner::ApngOrder {
present: next_seq_no,
expected: seq_no + 1,
}
.into(),
));
}
self.current_seq_no = Some(next_seq_no);
self.apng_seq_handled = true;
} else {
data_start = 0;
}
} else {
return Err(DecodingError::Format(
FormatErrorInner::MissingFctl.into(),
));
}
self.state = Some(DecodeData(type_str, data_start));
Ok((0, Decoded::PartialChunk(type_str)))
}
// Handle other chunks
_ => {
if self.current_chunk.remaining == 0 {
// complete chunk
Ok((0, self.parse_chunk(type_str)?))
} else {
// Make sure we have room to read more of the chunk.
// We need it fully before parsing.
self.reserve_current_chunk()?;
ParseChunkData(type_str) => {
debug_assert!(type_str != IDAT && type_str != chunk::fdAT);
if self.current_chunk.remaining == 0 {
// Got complete chunk.
Ok((0, self.parse_chunk(type_str)?))
} else {
// Make sure we have room to read more of the chunk.
// We need it fully before parsing.
self.reserve_current_chunk()?;

self.state = Some(ReadChunk(type_str));
Ok((0, Decoded::PartialChunk(type_str)))
}
}
self.state = Some(ReadChunkData(type_str));
Ok((0, Decoded::PartialChunk(type_str)))
}
}
ReadChunk(type_str) => {
// The _previous_ event wanted to return the contents of raw_bytes, and let the
// caller consume it,
ReadChunkData(type_str) => {
debug_assert!(type_str != IDAT && type_str != chunk::fdAT);
if self.current_chunk.remaining == 0 {
self.state = Some(U32(U32Value::Crc(type_str)));
self.state = Some(U32(U32ValueKind::Crc(type_str)));
Ok((0, Decoded::Nothing))
} else {
let ChunkState {
Expand All @@ -713,7 +723,7 @@ impl StreamingDecoder {
let bytes_avail = min(buf.len(), buf_avail);
let n = min(*remaining, bytes_avail as u32);
if buf_avail == 0 {
self.state = Some(PartialChunk(type_str));
self.state = Some(ParseChunkData(type_str));
Ok((0, Decoded::Nothing))
} else {
let buf = &buf[..n as usize];
Expand All @@ -724,27 +734,27 @@ impl StreamingDecoder {

*remaining -= n;
if *remaining == 0 {
self.state = Some(PartialChunk(type_str));
self.state = Some(ParseChunkData(type_str));
} else {
self.state = Some(ReadChunk(type_str));
self.state = Some(ReadChunkData(type_str));
}
Ok((n as usize, Decoded::Nothing))
}
}
}
DecodeData(type_str, mut n) => {
let chunk_len = self.current_chunk.raw_bytes.len();
let chunk_data = &self.current_chunk.raw_bytes[n..];
let c = self.inflater.decompress(chunk_data, image_data)?;
n += c;
if n == chunk_len && c == 0 {
self.current_chunk.raw_bytes.clear();
self.state = Some(ReadChunk(type_str));
Ok((0, Decoded::ImageData))
ImageData(type_str) => {
debug_assert!(type_str == IDAT || type_str == chunk::fdAT);
let len = std::cmp::min(buf.len(), self.current_chunk.remaining as usize);
let buf = &buf[..len];
let consumed = self.inflater.decompress(buf, image_data)?;
self.current_chunk.crc.update(&buf[..consumed]);
self.current_chunk.remaining -= consumed as u32;
if self.current_chunk.remaining == 0 {
self.state = Some(U32(U32ValueKind::Crc(type_str)));
} else {
self.state = Some(DecodeData(type_str, n));
Ok((0, Decoded::ImageData))
self.state = Some(ImageData(type_str));
}
Ok((consumed, Decoded::ImageData))
}
}
}
Expand All @@ -766,7 +776,7 @@ impl StreamingDecoder {
}

fn parse_chunk(&mut self, type_str: ChunkType) -> Result<Decoded, DecodingError> {
self.state = Some(State::U32(U32Value::Crc(type_str)));
self.state = Some(State::U32(U32ValueKind::Crc(type_str)));
if self.info.is_none() && type_str != IHDR {
return Err(DecodingError::Format(
FormatErrorInner::ChunkBeforeIhdr { kind: type_str }.into(),
Expand Down
Loading