libbitsub_core/pgs/
object.rs1use super::MAX_PGS_OBJECT_DATA_LEN;
4use crate::utils::BigEndianReader;
5
6#[derive(Debug, Clone)]
8pub struct ObjectDefinitionSegment {
9 pub id: u16,
11 pub version: u8,
13 pub sequence_flag: u8,
15 pub data_length: u32,
17 pub width: u16,
19 pub height: u16,
21 pub data: Vec<u8>,
23}
24
25impl ObjectDefinitionSegment {
26 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 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 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 #[inline]
71 pub fn is_first_in_sequence(&self) -> bool {
72 (self.sequence_flag & 0x80) != 0
73 }
74
75 #[inline]
77 pub fn is_last_in_sequence(&self) -> bool {
78 (self.sequence_flag & 0x40) != 0
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct AssembledObject {
85 pub id: u16,
87 pub version: u8,
89 pub width: u16,
91 pub height: u16,
93 pub data: Vec<u8>,
95}
96
97impl AssembledObject {
98 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 let payload_size = declared_size.saturating_sub(4);
126
127 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, data_length: 7, 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}