dvb_si/descriptors/extension/
service_prominence.rs1use super::*;
3use alloc::vec::Vec;
4
5impl<'a> ExtensionBodyDef<'a> for ServiceProminence<'a> {
6 const TAG_EXTENSION: u8 = 0x22;
7 const NAME: &'static str = "SERVICE_PROMINENCE";
8}
9
10const SOGI_RESERVED_FUTURE_USE: u8 = 0x10;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize))]
19#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
20pub struct ServiceProminence<'a> {
21 pub sogi_list: Vec<SogiEntry>,
23 pub private_data: &'a [u8],
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize))]
30pub struct SogiEntry {
31 pub sogi_flag: bool,
33 pub sogi_priority: u16,
35 pub service_id: Option<u16>,
37 pub target_region_loop: Option<Vec<super::target_region::TargetRegionEntry>>,
39}
40
41impl<'a> Parse<'a> for ServiceProminence<'a> {
42 type Error = crate::error::Error;
43 fn parse(sel: &'a [u8]) -> Result<Self> {
44 if sel.is_empty() {
45 return Err(Error::BufferTooShort {
46 need: 1,
47 have: sel.len(),
48 what: "service_prominence body",
49 });
50 }
51 let sogi_list_length = sel[0] as usize;
52 if sel.len() < 1 + sogi_list_length {
53 return Err(Error::BufferTooShort {
54 need: 1 + sogi_list_length,
55 have: sel.len(),
56 what: "service_prominence body",
57 });
58 }
59 let sogi_slice = &sel[1..1 + sogi_list_length];
60 let mut sogi_list = Vec::new();
61 let mut k = 0;
62 while k < sogi_slice.len() {
63 if sogi_slice.len() - k < 2 {
64 return Err(Error::BufferTooShort {
65 need: 1 + k + 2,
66 have: sel.len(),
67 what: "service_prominence body",
68 });
69 }
70 let byte0 = sogi_slice[k];
71 let byte1 = sogi_slice[k + 1];
72 let sogi_flag = (byte0 >> 7) != 0;
73 let target_region_flag = ((byte0 >> 6) & 0x01) != 0;
74 let service_flag = ((byte0 >> 5) & 0x01) != 0;
75 let sogi_priority = ((u16::from(byte0) & 0x0F) << 8) | u16::from(byte1);
76 k += 2;
77 let service_id = if service_flag {
78 if sogi_slice.len() - k < 2 {
79 return Err(Error::BufferTooShort {
80 need: 1 + k + 2,
81 have: sel.len(),
82 what: "service_prominence body",
83 });
84 }
85 let id = u16::from_be_bytes([sogi_slice[k], sogi_slice[k + 1]]);
86 k += 2;
87 Some(id)
88 } else {
89 None
90 };
91 let target_region_loop = if target_region_flag {
92 if sogi_slice.len() - k < 1 {
93 return Err(Error::BufferTooShort {
94 need: 1 + k + 1,
95 have: sel.len(),
96 what: "service_prominence body",
97 });
98 }
99 let region_len = sogi_slice[k] as usize;
100 k += 1;
101 if sogi_slice.len() - k < region_len {
102 return Err(Error::BufferTooShort {
103 need: 1 + k + region_len,
104 have: sel.len(),
105 what: "service_prominence body",
106 });
107 }
108 let region_bytes = &sogi_slice[k..k + region_len];
109 k += region_len;
110 let entries = super::target_region::parse_region_entries(region_bytes, 0)?;
111 Some(entries)
112 } else {
113 None
114 };
115 sogi_list.push(SogiEntry {
116 sogi_flag,
117 sogi_priority,
118 service_id,
119 target_region_loop,
120 });
121 }
122 Ok(ServiceProminence {
123 sogi_list,
124 private_data: &sel[1 + sogi_list_length..],
125 })
126 }
127}
128
129impl Serialize for ServiceProminence<'_> {
130 type Error = crate::error::Error;
131 fn serialized_len(&self) -> usize {
132 let sogi_list_length: usize = self
133 .sogi_list
134 .iter()
135 .map(|e| {
136 2 + if e.service_id.is_some() { 2 } else { 0 }
137 + if e.target_region_loop.is_some() {
138 1 + e.target_region_loop.as_ref().map_or(0, |entries| {
139 super::target_region::region_entries_serialized_len(entries)
140 })
141 } else {
142 0
143 }
144 })
145 .sum();
146 1 + sogi_list_length + self.private_data.len()
147 }
148 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
149 let len = self.serialized_len();
150 if buf.len() < len {
151 return Err(Error::OutputBufferTooSmall {
152 need: len,
153 have: buf.len(),
154 });
155 }
156 let sogi_len = len - 1 - self.private_data.len();
157 buf[0] = sogi_len as u8;
158 let mut p = 1;
159 for e in &self.sogi_list {
160 buf[p] = ((e.sogi_flag as u8) << 7)
161 | ((e.target_region_loop.is_some() as u8) << 6)
162 | ((e.service_id.is_some() as u8) << 5)
163 | SOGI_RESERVED_FUTURE_USE
164 | ((e.sogi_priority >> 8) as u8 & 0x0F);
165 buf[p + 1] = e.sogi_priority as u8;
166 p += 2;
167 if let Some(id) = e.service_id {
168 buf[p..p + 2].copy_from_slice(&id.to_be_bytes());
169 p += 2;
170 }
171 if let Some(entries) = &e.target_region_loop {
172 let entries_len = super::target_region::region_entries_serialized_len(entries);
173 buf[p] = entries_len as u8;
174 p += 1;
175 super::target_region::write_region_entries(entries, buf, p);
176 p += entries_len;
177 }
178 }
179 buf[p..p + self.private_data.len()].copy_from_slice(self.private_data);
180 Ok(len)
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::descriptors::extension::test_support::*;
188 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
189
190 #[test]
191 fn parse_service_prominence_one_entry_service_only() {
192 let sel = [0x04, 0x21, 0x23, 0x45, 0x67, 0xAB];
195 let bytes = wrap(0x22, &sel);
196 let d = ExtensionDescriptor::parse(&bytes).unwrap();
197 assert_eq!(d.kind(), Some(ExtensionTag::ServiceProminence));
198 match &d.body {
199 ExtensionBody::ServiceProminence(b) => {
200 assert_eq!(b.sogi_list.len(), 1);
201 let e = &b.sogi_list[0];
202 assert!(!e.sogi_flag);
203 assert!(e.target_region_loop.is_none());
204 assert!(e.service_id.is_some());
205 assert_eq!(e.sogi_priority, 0x0123);
206 assert_eq!(e.service_id, Some(0x4567));
207 assert_eq!(b.private_data, &[0xAB]);
208 }
209 other => panic!("expected ServiceProminence, got {other:?}"),
210 }
211 round_trip(&d);
212 }
213
214 #[test]
215 fn parse_service_prominence_one_entry_target_region() {
216 let sel = [0x04, 0x40, 0x01, 0x01, 0xF8];
219 let bytes = wrap(0x22, &sel);
220 let d = ExtensionDescriptor::parse(&bytes).unwrap();
221 match &d.body {
222 ExtensionBody::ServiceProminence(b) => {
223 assert_eq!(b.sogi_list.len(), 1);
224 let e = &b.sogi_list[0];
225 assert!(!e.sogi_flag);
226 assert_eq!(e.sogi_priority, 0x0001);
227 assert!(e.service_id.is_none());
228 assert!(e.target_region_loop.is_some());
229 let tr = e.target_region_loop.as_ref().unwrap();
230 assert_eq!(tr.len(), 1);
231 assert_eq!(tr[0].country_code, None);
232 assert_eq!(
233 tr[0].region_codes,
234 super::super::target_region::RegionCodes::None
235 );
236 assert!(b.private_data.is_empty());
237 }
238 other => panic!("expected ServiceProminence, got {other:?}"),
239 }
240 round_trip(&d);
241 }
242
243 #[test]
244 fn parse_service_prominence_two_entries_plus_private() {
245 let sel = [0x08, 0x2A, 0xBC, 0x11, 0x11, 0x43, 0x45, 0x01, 0xF8, 0xDD];
249 let bytes = wrap(0x22, &sel);
250 let d = ExtensionDescriptor::parse(&bytes).unwrap();
251 match &d.body {
252 ExtensionBody::ServiceProminence(b) => {
253 assert_eq!(b.sogi_list.len(), 2);
254 let e0 = &b.sogi_list[0];
255 assert_eq!(e0.sogi_priority, 0x0ABC);
256 assert_eq!(e0.service_id, Some(0x1111));
257 assert!(e0.target_region_loop.is_none());
258 let e1 = &b.sogi_list[1];
259 assert!(!e1.sogi_flag);
260 assert!(e1.service_id.is_none());
261 assert_eq!(e1.sogi_priority, 0x0345);
262 assert!(e1.target_region_loop.is_some());
263 assert_eq!(e1.target_region_loop.as_ref().unwrap().len(), 1);
264 assert_eq!(b.private_data, &[0xDD]);
265 }
266 other => panic!("expected ServiceProminence, got {other:?}"),
267 }
268 round_trip(&d);
269 }
270
271 #[test]
272 fn parse_service_prominence_empty_list_private_only() {
273 let sel = [0x00, 0x01, 0x02];
275 let bytes = wrap(0x22, &sel);
276 let d = ExtensionDescriptor::parse(&bytes).unwrap();
277 match &d.body {
278 ExtensionBody::ServiceProminence(b) => {
279 assert!(b.sogi_list.is_empty());
280 assert_eq!(b.private_data, &[0x01, 0x02]);
281 }
282 other => panic!("expected ServiceProminence, got {other:?}"),
283 }
284 round_trip(&d);
285 }
286
287 #[test]
288 fn parse_service_prominence_rejects_overrun() {
289 let sel = [0x05, 0xAA, 0xBB, 0xCC];
291 let bytes = wrap(0x22, &sel);
292 assert!(matches!(
293 ExtensionDescriptor::parse(&bytes).unwrap_err(),
294 crate::error::Error::BufferTooShort { .. }
295 ));
296 }
297
298 #[test]
299 fn parse_service_prominence_rejects_entry_overrun() {
300 let sel = [0x03, 0x20, 0x00, 0x00];
302 let bytes = wrap(0x22, &sel);
303 assert!(matches!(
304 ExtensionDescriptor::parse(&bytes).unwrap_err(),
305 crate::error::Error::BufferTooShort { .. }
306 ));
307 }
308
309 #[cfg(feature = "serde")]
310 #[test]
311 fn serde_serialize_service_prominence() {
312 let d = ExtensionDescriptor {
313 tag_extension: 0x22,
314 body: ExtensionBody::ServiceProminence(ServiceProminence {
315 sogi_list: vec![SogiEntry {
316 sogi_flag: false,
317 sogi_priority: 0x123,
318 service_id: Some(0x4567),
319 target_region_loop: None,
320 }],
321 private_data: &[0xAB],
322 }),
323 };
324 let json = serde_json::to_string(&d).unwrap();
325 assert!(json.contains("\"tag_extension\":34"));
326 assert!(json.contains("\"serviceProminence\""));
327 }
328}