use crate::huffman::HuffmanTable;
use otf_pixels_core::{PixelsError, Result, Source};
const BUFFER: usize = 16 * 1024;
pub struct Reader<S: Source> {
source: S,
buffer: Box<[u8]>,
position: usize,
filled: usize,
drained: bool,
accumulator: u32,
bits: u32,
marker: Option<u8>,
}
impl<S: Source> std::fmt::Debug for Reader<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Reader")
.field("buffered", &(self.filled - self.position))
.field("bits", &self.bits)
.field("marker", &self.marker)
.finish_non_exhaustive()
}
}
impl<S: Source> Reader<S> {
pub fn new(source: S) -> Self {
Self {
source,
buffer: vec![0_u8; BUFFER].into_boxed_slice(),
position: 0,
filled: 0,
drained: false,
accumulator: 0,
bits: 0,
marker: None,
}
}
fn pull(&mut self) -> Result<Option<u8>> {
if self.position >= self.filled {
if self.drained {
return Ok(None);
}
let read = self.source.read(&mut self.buffer)?;
if read == 0 {
self.drained = true;
return Ok(None);
}
self.position = 0;
self.filled = read.min(self.buffer.len());
}
let byte = self.buffer.get(self.position).copied();
self.position += 1;
Ok(byte)
}
pub fn read_exact(&mut self, out: &mut [u8]) -> Result<()> {
for (index, slot) in out.iter_mut().enumerate() {
match self.pull()? {
Some(byte) => *slot = byte,
None => {
return Err(PixelsError::malformed(
"jpeg",
format!("stream ended after {index} of {} expected bytes", out.len()),
));
}
}
}
Ok(())
}
pub fn read_u16(&mut self) -> Result<u16> {
let mut bytes = [0_u8; 2];
self.read_exact(&mut bytes)?;
Ok(u16::from_be_bytes(bytes))
}
pub fn next_marker(&mut self) -> Result<u8> {
if let Some(code) = self.marker.take() {
return Ok(code);
}
let mut seen_prefix = false;
loop {
let Some(byte) = self.pull()? else {
return Err(PixelsError::malformed(
"jpeg",
"stream ended while looking for a marker",
));
};
match byte {
0xFF => seen_prefix = true,
0x00 => seen_prefix = false,
code if seen_prefix => return Ok(code),
_ => {}
}
}
}
pub fn skip_segment(&mut self) -> Result<()> {
let length = self.read_u16()?;
let Some(payload) = length.checked_sub(2) else {
return Err(PixelsError::malformed(
"jpeg",
format!("segment declares length {length}, below its own length field"),
));
};
for _ in 0..payload {
if self.pull()?.is_none() {
return Err(PixelsError::malformed(
"jpeg",
"stream ended inside a skipped segment",
));
}
}
Ok(())
}
pub fn read_segment(&mut self) -> Result<Vec<u8>> {
let length = self.read_u16()?;
let Some(payload) = length.checked_sub(2) else {
return Err(PixelsError::malformed(
"jpeg",
format!("segment declares length {length}, below its own length field"),
));
};
let mut bytes = vec![0_u8; payload as usize];
self.read_exact(&mut bytes)?;
Ok(bytes)
}
fn entropy_byte(&mut self) -> Result<Option<u8>> {
if self.marker.is_some() {
return Ok(None);
}
let Some(byte) = self.pull()? else {
self.marker = Some(crate::format::marker::EOI);
return Ok(None);
};
if byte != 0xFF {
return Ok(Some(byte));
}
loop {
match self.pull()? {
Some(0x00) => return Ok(Some(0xFF)),
Some(0xFF) => {}
Some(code) => {
self.marker = Some(code);
return Ok(None);
}
None => {
self.marker = Some(crate::format::marker::EOI);
return Ok(None);
}
}
}
}
fn fill(&mut self, wanted: u32) -> Result<()> {
while self.bits < wanted {
let byte = self.entropy_byte()?.unwrap_or(0);
self.accumulator = (self.accumulator << 8) | u32::from(byte);
self.bits += 8;
}
Ok(())
}
fn peek(&self, count: u32) -> u32 {
if count == 0 || count > self.bits {
return 0;
}
(self.accumulator >> (self.bits - count)) & ((1 << count) - 1)
}
fn consume(&mut self, count: u32) {
self.bits = self.bits.saturating_sub(count);
self.accumulator &= (1_u32 << self.bits) - 1;
}
pub fn get_bits(&mut self, count: u32) -> Result<u32> {
if count == 0 {
return Ok(0);
}
self.fill(count)?;
let value = self.peek(count);
self.consume(count);
Ok(value)
}
pub fn decode(&mut self, table: &HuffmanTable) -> Result<u8> {
self.fill(16)?;
if let Some((length, symbol)) = table.lookup(self.peek(8) as u8) {
self.consume(length);
return Ok(symbol);
}
let mut code = self.peek(8) as i32;
for length in 9..=table.max_length() {
code = (code << 1) | ((self.peek(length as u32) & 1) as i32);
if let Some(symbol) = table.resolve(length, code) {
self.consume(length as u32);
return Ok(symbol);
}
}
Err(PixelsError::malformed(
"jpeg",
"entropy data contains a code no Huffman table defines",
))
}
pub fn receive_extend(&mut self, magnitude: u32) -> Result<i32> {
if magnitude == 0 {
return Ok(0);
}
let raw = self.get_bits(magnitude)? as i32;
let threshold = 1_i32 << (magnitude - 1);
Ok(if raw < threshold {
raw - (1_i32 << magnitude) + 1
} else {
raw
})
}
pub fn align(&mut self) {
self.accumulator = 0;
self.bits = 0;
}
pub fn restart(&mut self) -> Result<bool> {
self.align();
if self.marker.is_none() {
while self.entropy_byte()?.is_some() {}
}
match self.marker {
Some(code) if crate::format::marker::is_restart(code) => {
self.marker = None;
Ok(true)
}
_ => Ok(false),
}
}
#[must_use]
pub const fn pending_marker(&self) -> Option<u8> {
self.marker
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
reason = "tests operate on known-good values and assert shapes directly"
)]
mod tests {
use super::*;
use crate::format::marker;
use otf_pixels_core::ErrorCode;
fn reader(bytes: &[u8]) -> Reader<std::io::Cursor<Vec<u8>>> {
Reader::new(std::io::Cursor::new(bytes.to_vec()))
}
#[test]
fn bits_are_read_most_significant_first() {
let mut r = reader(&[0b1011_0010, 0b0100_0000]);
assert_eq!(r.get_bits(1).unwrap(), 1);
assert_eq!(r.get_bits(3).unwrap(), 0b011);
assert_eq!(r.get_bits(4).unwrap(), 0b0010);
assert_eq!(r.get_bits(2).unwrap(), 0b01);
}
#[test]
fn stuffed_ff_bytes_are_data_and_bare_ones_are_markers() {
let mut r = reader(&[0xFF, 0x00, 0x55]);
assert_eq!(r.get_bits(8).unwrap(), 0xFF);
assert_eq!(r.get_bits(8).unwrap(), 0x55);
assert_eq!(r.pending_marker(), None);
let mut r = reader(&[0x55, 0xFF, 0xD9]);
assert_eq!(r.get_bits(8).unwrap(), 0x55);
assert_eq!(r.get_bits(8).unwrap(), 0);
assert_eq!(r.pending_marker(), Some(marker::EOI));
}
#[test]
fn padding_before_a_marker_is_skipped() {
let mut r = reader(&[0x55, 0xFF, 0xFF, 0xFF, 0xD0]);
assert_eq!(r.get_bits(8).unwrap(), 0x55);
assert_eq!(r.get_bits(1).unwrap(), 0);
assert_eq!(r.pending_marker(), Some(marker::RST0));
}
#[test]
fn a_truncated_scan_reads_as_zeros_rather_than_failing() {
let mut r = reader(&[0b1111_0000]);
assert_eq!(r.get_bits(8).unwrap(), 0b1111_0000);
assert_eq!(r.get_bits(16).unwrap(), 0);
assert_eq!(r.pending_marker(), Some(marker::EOI));
}
#[test]
fn receive_extend_recovers_negative_coefficients() {
let mut r = reader(&[0b1110_0010, 0b0011_0000]);
assert_eq!(r.receive_extend(3).unwrap(), 7);
assert_eq!(r.receive_extend(3).unwrap(), -7);
assert_eq!(r.receive_extend(3).unwrap(), 4);
assert_eq!(r.receive_extend(3).unwrap(), -4);
assert_eq!(r.receive_extend(0).unwrap(), 0);
}
#[test]
fn huffman_symbols_decode_through_both_paths() {
let mut counts = [0_u8; 16];
counts[0] = 1;
counts[1] = 1;
counts[2] = 1;
let table = HuffmanTable::new(&counts, vec![b'a', b'b', b'c']).unwrap();
let mut r = reader(&[0b0101_1000]);
assert_eq!(r.decode(&table).unwrap(), b'a');
assert_eq!(r.decode(&table).unwrap(), b'b');
assert_eq!(r.decode(&table).unwrap(), b'c');
assert_eq!(r.decode(&table).unwrap(), b'a');
let deep = HuffmanTable::new(&[1_u8; 16], (0..16).collect()).unwrap();
let mut r = reader(&[0xFF, 0x00, 0xFE]);
assert_eq!(r.decode(&deep).unwrap(), 15);
}
#[test]
fn an_undefined_code_is_malformed_rather_than_a_wrong_symbol() {
let mut counts = [0_u8; 16];
counts[0] = 1;
let table = HuffmanTable::new(&counts, vec![b'a']).unwrap();
let mut r = reader(&[0b1111_1111, 0x00]);
assert_eq!(r.decode(&table).unwrap_err().code(), ErrorCode::Malformed);
}
#[test]
fn markers_are_found_across_stuffed_data() {
let mut r = reader(&[0x12, 0xFF, 0x00, 0x34, 0xFF, 0xDA]);
assert_eq!(r.next_marker().unwrap(), marker::SOS);
let mut r = reader(&[0x55, 0xFF, 0xD9, 0xFF, 0xD8]);
assert_eq!(r.get_bits(16).unwrap(), 0x5500);
assert_eq!(r.next_marker().unwrap(), marker::EOI);
assert_eq!(r.next_marker().unwrap(), marker::SOI);
}
#[test]
fn restart_markers_are_consumed_at_interval_boundaries() {
let mut r = reader(&[0xAA, 0xFF, 0xD0, 0xBB]);
assert_eq!(r.get_bits(4).unwrap(), 0xA);
assert!(r.restart().unwrap());
assert_eq!(r.pending_marker(), None);
assert_eq!(r.get_bits(8).unwrap(), 0xBB);
let mut r = reader(&[0xAA, 0xFF, 0xD9]);
assert_eq!(r.get_bits(8).unwrap(), 0xAA);
assert!(!r.restart().unwrap());
assert_eq!(r.pending_marker(), Some(marker::EOI));
}
#[test]
fn segments_are_read_and_skipped_by_their_declared_length() {
let mut r = reader(&[0x00, 0x05, 1, 2, 3, 0xFF, 0xD9]);
assert_eq!(r.read_segment().unwrap(), vec![1, 2, 3]);
assert_eq!(r.next_marker().unwrap(), marker::EOI);
let mut r = reader(&[0x00, 0x05, 1, 2, 3, 0xFF, 0xD9]);
r.skip_segment().unwrap();
assert_eq!(r.next_marker().unwrap(), marker::EOI);
let mut r = reader(&[0x00, 0x01, 1, 2]);
assert_eq!(r.read_segment().unwrap_err().code(), ErrorCode::Malformed);
let mut r = reader(&[0x00, 0x40, 1, 2]);
assert_eq!(r.read_segment().unwrap_err().code(), ErrorCode::Malformed);
}
}