1use crate::error::{Error, Result};
4use broadcast_common::len::{need_in, need_out};
5use broadcast_common::{Parse, Serialize};
6
7pub const SEGMENT_TYPE: u8 = 0x10;
9pub const HEADER_LEN: usize = 6;
11pub const FIXED_LEN: usize = 2;
13pub const REGION_ENTRY_LEN: usize = 6;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize))]
19#[repr(u8)]
20#[non_exhaustive]
21pub enum PageState {
22 Normal = 0x00,
24 Acquisition = 0x01,
26 ModeChange = 0x02,
28 Reserved(u8),
30}
31
32impl PageState {
33 #[must_use]
35 pub fn name(&self) -> &'static str {
36 match self {
37 Self::Normal => "normal",
38 Self::Acquisition => "acquisition",
39 Self::ModeChange => "mode_change",
40 Self::Reserved(_) => "reserved",
41 }
42 }
43
44 fn to_bits(self) -> u8 {
45 match self {
46 Self::Normal => 0x00,
47 Self::Acquisition => 0x01,
48 Self::ModeChange => 0x02,
49 Self::Reserved(v) => v,
50 }
51 }
52}
53
54broadcast_common::impl_spec_display!(PageState, Reserved);
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize))]
59pub struct PageRegionEntry {
60 pub region_id: u8,
62 pub reserved: u8,
64 pub region_horizontal_address: u16,
66 pub region_vertical_address: u16,
68}
69
70impl PageRegionEntry {
71 fn serialize_into(&self, buf: &mut [u8]) {
72 buf[0] = self.region_id;
73 buf[1] = self.reserved;
74 buf[2..4].copy_from_slice(&self.region_horizontal_address.to_be_bytes());
75 buf[4..6].copy_from_slice(&self.region_vertical_address.to_be_bytes());
76 }
77
78 fn parse(bytes: &[u8]) -> Result<Self> {
79 need_in(bytes, REGION_ENTRY_LEN, "page_region_entry")?;
80 let reserved = bytes[1];
81 Ok(PageRegionEntry {
82 region_id: bytes[0],
83 reserved,
84 region_horizontal_address: u16::from_be_bytes([bytes[2], bytes[3]]),
85 region_vertical_address: u16::from_be_bytes([bytes[4], bytes[5]]),
86 })
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize))]
93pub struct PageCompositionSegment {
94 pub page_id: u16,
96 pub page_time_out: u8,
98 pub page_version_number: u8,
100 pub page_state: PageState,
102 pub reserved: u8,
104 pub regions: alloc::vec::Vec<PageRegionEntry>,
106}
107
108impl<'a> Parse<'a> for PageCompositionSegment {
109 type Error = Error;
110
111 fn parse(bytes: &'a [u8]) -> Result<Self> {
112 need_in(bytes, HEADER_LEN + FIXED_LEN, "page_composition_segment")?;
113 if bytes[0] != crate::pes_data_field::SYNC_BYTE {
114 return Err(Error::InvalidSync(broadcast_common::error::InvalidSync {
115 what: "sync_byte",
116 expected: u32::from(crate::pes_data_field::SYNC_BYTE),
117 found: u32::from(bytes[0]),
118 }));
119 }
120 if bytes[1] != SEGMENT_TYPE {
121 return Err(Error::UnknownSegmentType(bytes[1]));
122 }
123 let page_id = u16::from_be_bytes([bytes[2], bytes[3]]);
124 let segment_length = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
125 let total = HEADER_LEN + segment_length;
126 need_in(bytes, total, "page_composition_segment data")?;
127 let body = &bytes[HEADER_LEN..HEADER_LEN + segment_length];
128 need_in(body, FIXED_LEN, "page_composition_segment body")?;
129 let page_time_out = body[0];
130 let page_version_number = body[1] >> 4;
131 let page_state_bits = (body[1] >> 2) & 0x03;
132 let reserved = body[1] & 0x03;
133 let page_state = match page_state_bits {
134 0x00 => PageState::Normal,
135 0x01 => PageState::Acquisition,
136 0x02 => PageState::ModeChange,
137 v => PageState::Reserved(v),
138 };
139
140 let region_data = &body[FIXED_LEN..];
141 let region_count = region_data.len() / REGION_ENTRY_LEN;
142 if !region_data.len().is_multiple_of(REGION_ENTRY_LEN) {
149 return Err(Error::BufferTooShort(
150 broadcast_common::error::BufferTooShort {
151 need: (region_count + 1) * REGION_ENTRY_LEN,
152 have: region_data.len(),
153 what: "page_composition_segment regions",
154 },
155 ));
156 }
157 let mut regions = alloc::vec::Vec::with_capacity(region_count);
158 for i in 0..region_count {
159 let entry_bytes = ®ion_data[i * REGION_ENTRY_LEN..][..REGION_ENTRY_LEN];
160 regions.push(PageRegionEntry::parse(entry_bytes)?);
161 }
162
163 Ok(PageCompositionSegment {
164 page_id,
165 page_time_out,
166 page_version_number,
167 page_state,
168 reserved,
169 regions,
170 })
171 }
172}
173
174impl Serialize for PageCompositionSegment {
175 type Error = Error;
176
177 fn serialized_len(&self) -> usize {
178 HEADER_LEN + FIXED_LEN + self.regions.len() * REGION_ENTRY_LEN
179 }
180
181 fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
182 let len = self.serialized_len();
183 need_out(buf, len)?;
184 buf[0] = 0x0F;
185 buf[1] = SEGMENT_TYPE;
186 buf[2..4].copy_from_slice(&self.page_id.to_be_bytes());
187 let seg_len = crate::segments::check_segment_length(len - HEADER_LEN)?;
188 buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
189
190 buf[6] = self.page_time_out;
191 buf[7] = (broadcast_common::len::fit_mask(
192 self.page_version_number,
193 0x0F,
194 "page_version_number",
195 )? << 4)
196 | (broadcast_common::len::fit_mask(self.page_state.to_bits(), 0x03, "page_state")?
197 << 2)
198 | broadcast_common::len::fit_mask(self.reserved, 0x03, "reserved")?;
199
200 for (i, region) in self.regions.iter().enumerate() {
201 let off = HEADER_LEN + FIXED_LEN + i * REGION_ENTRY_LEN;
202 region.serialize_into(&mut buf[off..off + REGION_ENTRY_LEN]);
203 }
204 Ok(len)
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use broadcast_common::{Parse, Serialize};
212
213 #[test]
214 fn round_trip() {
215 let bytes = [
216 0x0F, 0x10, 0x00, 0x01, 0x00, 0x0E, 0x0A, 0x04, 0x01, 0x00, 0x00, 0x64, 0x00, 0x32,
217 0x02, 0x00, 0x00, 0xC8, 0x00, 0x96,
218 ];
219 let seg = PageCompositionSegment::parse(&bytes).unwrap();
220 assert_eq!(seg.page_time_out, 10);
221 assert_eq!(seg.page_version_number, 0);
222 assert_eq!(seg.page_state, PageState::Acquisition);
223 assert_eq!(seg.regions.len(), 2);
224 assert_eq!(seg.regions[0].region_id, 1);
225 assert_eq!(seg.regions[0].region_horizontal_address, 100);
226 assert_eq!(seg.regions[0].region_vertical_address, 50);
227 let out = seg.try_to_bytes().unwrap();
228 assert_eq!(out, bytes);
229
230 let mut seg2 = seg.clone();
232 seg2.page_time_out = 20;
233 let out2 = seg2.try_to_bytes().unwrap();
234 assert_ne!(out2, bytes);
235 let reparse = PageCompositionSegment::parse(&out2).unwrap();
236 assert_eq!(reparse.page_time_out, 20);
237 }
238
239 #[test]
241 fn rejects_bad_sync_byte() {
242 let bytes = [
243 0x00, 0x10, 0x00, 0x01, 0x00, 0x0E, 0x0A, 0x04, 0x01, 0x00, 0x00, 0x64, 0x00, 0x32,
244 0x02, 0x00, 0x00, 0xC8, 0x00, 0x96,
245 ];
246 assert!(matches!(
247 PageCompositionSegment::parse(&bytes),
248 Err(Error::InvalidSync(broadcast_common::error::InvalidSync {
249 found: 0x00,
250 ..
251 }))
252 ));
253 }
254}