use super::MAX_PGS_OBJECT_DATA_LEN;
use crate::utils::BigEndianReader;
#[derive(Debug, Clone)]
pub struct ObjectDefinitionSegment {
pub id: u16,
pub version: u8,
pub sequence_flag: u8,
pub data_length: u32,
pub width: u16,
pub height: u16,
pub data: Vec<u8>,
}
impl ObjectDefinitionSegment {
pub fn parse(reader: &mut BigEndianReader, length: usize) -> Option<Self> {
let id = reader.read_u16()?;
let version = reader.read_u8()?;
let sequence_flag = reader.read_u8()?;
let is_first = (sequence_flag & 0x80) != 0;
let (data_length, width, height, data) = if is_first {
if length < 11 {
return None;
}
let data_length = reader.read_u24()?;
if data_length == 0 || (data_length as usize) > MAX_PGS_OBJECT_DATA_LEN {
return None;
}
let width = reader.read_u16()?;
let height = reader.read_u16()?;
let data = reader.read_bytes(length - 11)?;
(data_length, width, height, data)
} else {
if length < 4 {
return None;
}
let data = reader.read_bytes(length - 4)?;
(0, 0, 0, data)
};
Some(Self {
id,
version,
sequence_flag,
data_length,
width,
height,
data,
})
}
#[inline]
pub fn is_first_in_sequence(&self) -> bool {
(self.sequence_flag & 0x80) != 0
}
#[inline]
pub fn is_last_in_sequence(&self) -> bool {
(self.sequence_flag & 0x40) != 0
}
}
#[derive(Debug, Clone)]
pub struct AssembledObject {
pub id: u16,
pub version: u8,
pub width: u16,
pub height: u16,
pub data: Vec<u8>,
}
impl AssembledObject {
pub fn from_segments(segments: &[ObjectDefinitionSegment]) -> Option<Self> {
if segments.is_empty() {
return None;
}
let first = &segments[0];
if !first.is_first_in_sequence() {
return None;
}
let id = first.id;
let version = first.version;
let width = first.width;
let height = first.height;
if width == 0 || height == 0 {
return None;
}
let declared_size = first.data_length as usize;
if declared_size == 0 || declared_size > MAX_PGS_OBJECT_DATA_LEN {
return None;
}
let payload_size = declared_size.saturating_sub(4);
let total_size = segments
.iter()
.try_fold(0usize, |acc, segment| acc.checked_add(segment.data.len()))?;
if total_size == 0 || total_size > payload_size {
return None;
}
let mut data = Vec::with_capacity(total_size);
for segment in segments {
data.extend_from_slice(&segment.data);
}
if data.len() != payload_size {
return None;
}
Some(Self {
id,
version,
width,
height,
data,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_first_segment_rejects_short_length() {
let data = [0x00, 0x01, 0x00, 0x80, 0x00];
let mut reader = BigEndianReader::new(&data);
assert!(ObjectDefinitionSegment::parse(&mut reader, 5).is_none());
}
#[test]
fn test_from_segments_rejects_declared_length_mismatch() {
let segment = ObjectDefinitionSegment {
id: 1,
version: 0,
sequence_flag: 0xC0,
data_length: 2,
width: 32,
height: 32,
data: vec![1, 2, 3],
};
assert!(AssembledObject::from_segments(&[segment]).is_none());
}
#[test]
fn test_from_segments_accepts_matching_payload() {
let segment = ObjectDefinitionSegment {
id: 1,
version: 0,
sequence_flag: 0xC0, data_length: 7, width: 32,
height: 32,
data: vec![1, 2, 3],
};
let obj = AssembledObject::from_segments(&[segment]).expect("should assemble");
assert_eq!(obj.data, vec![1, 2, 3]);
assert_eq!(obj.width, 32);
assert_eq!(obj.height, 32);
}
}