Skip to main content

libbitsub_core/pgs/
object.rs

1//! Object Definition Segment parsing.
2
3use super::MAX_PGS_OBJECT_DATA_LEN;
4use crate::utils::BigEndianReader;
5
6/// Object Definition Segment contains RLE-encoded bitmap data.
7#[derive(Debug, Clone)]
8pub struct ObjectDefinitionSegment {
9    /// Object ID (16-bit)
10    pub id: u16,
11    /// Version number
12    pub version: u8,
13    /// Sequence flags (bit 7 = first, bit 6 = last)
14    pub sequence_flag: u8,
15    /// Total data length (only valid for first segment)
16    pub data_length: u32,
17    /// Object width in pixels (only valid for first segment)
18    pub width: u16,
19    /// Object height in pixels (only valid for first segment)
20    pub height: u16,
21    /// RLE-encoded pixel data fragment
22    pub data: Vec<u8>,
23}
24
25impl ObjectDefinitionSegment {
26    /// Parse an object definition segment from binary data.
27    pub fn parse(reader: &mut BigEndianReader, length: usize) -> Option<Self> {
28        let id = reader.read_u16()?;
29        let version = reader.read_u8()?;
30        let sequence_flag = reader.read_u8()?;
31
32        let is_first = (sequence_flag & 0x80) != 0;
33
34        let (data_length, width, height, data) = if is_first {
35            if length < 11 {
36                return None;
37            }
38
39            // First segment includes dimensions
40            let data_length = reader.read_u24()?;
41            if data_length == 0 || (data_length as usize) > MAX_PGS_OBJECT_DATA_LEN {
42                return None;
43            }
44            let width = reader.read_u16()?;
45            let height = reader.read_u16()?;
46            let data = reader.read_bytes(length - 11)?;
47            (data_length, width, height, data)
48        } else {
49            if length < 4 {
50                return None;
51            }
52
53            // Continuation segment - only raw data
54            let data = reader.read_bytes(length - 4)?;
55            (0, 0, 0, data)
56        };
57
58        Some(Self {
59            id,
60            version,
61            sequence_flag,
62            data_length,
63            width,
64            height,
65            data,
66        })
67    }
68
69    /// Check if this is the first segment in a sequence.
70    #[inline]
71    pub fn is_first_in_sequence(&self) -> bool {
72        (self.sequence_flag & 0x80) != 0
73    }
74
75    /// Check if this is the last segment in a sequence.
76    #[inline]
77    pub fn is_last_in_sequence(&self) -> bool {
78        (self.sequence_flag & 0x40) != 0
79    }
80}
81
82/// Assembled object from one or more ObjectDefinitionSegments.
83#[derive(Debug, Clone)]
84pub struct AssembledObject {
85    /// Object ID
86    pub id: u16,
87    /// Object version
88    pub version: u8,
89    /// Width in pixels
90    pub width: u16,
91    /// Height in pixels
92    pub height: u16,
93    /// Complete RLE-encoded data
94    pub data: Vec<u8>,
95}
96
97impl AssembledObject {
98    /// Create from a list of object definition segments (must be in order).
99    pub fn from_segments(segments: &[ObjectDefinitionSegment]) -> Option<Self> {
100        if segments.is_empty() {
101            return None;
102        }
103
104        let first = &segments[0];
105        if !first.is_first_in_sequence() {
106            return None;
107        }
108
109        let id = first.id;
110        let version = first.version;
111        let width = first.width;
112        let height = first.height;
113
114        if width == 0 || height == 0 {
115            return None;
116        }
117
118        let declared_size = first.data_length as usize;
119        if declared_size == 0 || declared_size > MAX_PGS_OBJECT_DATA_LEN {
120            return None;
121        }
122
123        // data_length in the ODS includes 4 bytes for width (u16) + height (u16),
124        // but those were already parsed above — `data` only contains the RLE payload.
125        let payload_size = declared_size.saturating_sub(4);
126
127        // Combine all data segments
128        let total_size = segments
129            .iter()
130            .try_fold(0usize, |acc, segment| acc.checked_add(segment.data.len()))?;
131        if total_size == 0 || total_size > payload_size {
132            return None;
133        }
134
135        let mut data = Vec::with_capacity(total_size);
136
137        for segment in segments {
138            data.extend_from_slice(&segment.data);
139        }
140
141        if data.len() != payload_size {
142            return None;
143        }
144
145        Some(Self {
146            id,
147            version,
148            width,
149            height,
150            data,
151        })
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_parse_first_segment_rejects_short_length() {
161        let data = [0x00, 0x01, 0x00, 0x80, 0x00];
162        let mut reader = BigEndianReader::new(&data);
163
164        assert!(ObjectDefinitionSegment::parse(&mut reader, 5).is_none());
165    }
166
167    #[test]
168    fn test_from_segments_rejects_declared_length_mismatch() {
169        let segment = ObjectDefinitionSegment {
170            id: 1,
171            version: 0,
172            sequence_flag: 0xC0,
173            data_length: 2,
174            width: 32,
175            height: 32,
176            data: vec![1, 2, 3],
177        };
178
179        assert!(AssembledObject::from_segments(&[segment]).is_none());
180    }
181
182    #[test]
183    fn test_from_segments_accepts_matching_payload() {
184        let segment = ObjectDefinitionSegment {
185            id: 1,
186            version: 0,
187            sequence_flag: 0xC0, // first + last
188            data_length: 7,      // 4 (width + height) + 3 bytes RLE
189            width: 32,
190            height: 32,
191            data: vec![1, 2, 3],
192        };
193
194        let obj = AssembledObject::from_segments(&[segment]).expect("should assemble");
195        assert_eq!(obj.data, vec![1, 2, 3]);
196        assert_eq!(obj.width, 32);
197        assert_eq!(obj.height, 32);
198    }
199}