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 let (b, _) =
79 sogi_slice[k..]
80 .split_first_chunk::<2>()
81 .ok_or(Error::BufferTooShort {
82 need: 1 + k + 2,
83 have: sel.len(),
84 what: "service_prominence body",
85 })?;
86 let id = u16::from_be_bytes(*b);
87 k += 2;
88 Some(id)
89 } else {
90 None
91 };
92 let target_region_loop = if target_region_flag {
93 if sogi_slice.len() - k < 1 {
94 return Err(Error::BufferTooShort {
95 need: 1 + k + 1,
96 have: sel.len(),
97 what: "service_prominence body",
98 });
99 }
100 let region_len = sogi_slice[k] as usize;
101 k += 1;
102 if sogi_slice.len() - k < region_len {
103 return Err(Error::BufferTooShort {
104 need: 1 + k + region_len,
105 have: sel.len(),
106 what: "service_prominence body",
107 });
108 }
109 let region_bytes = &sogi_slice[k..k + region_len];
110 k += region_len;
111 let entries = super::target_region::parse_region_entries(region_bytes, 0)?;
112 Some(entries)
113 } else {
114 None
115 };
116 sogi_list.push(SogiEntry {
117 sogi_flag,
118 sogi_priority,
119 service_id,
120 target_region_loop,
121 });
122 }
123 Ok(ServiceProminence {
124 sogi_list,
125 private_data: &sel[1 + sogi_list_length..],
126 })
127 }
128}
129
130impl Serialize for ServiceProminence<'_> {
131 type Error = crate::error::Error;
132 fn serialized_len(&self) -> usize {
133 let sogi_list_length: usize = self
134 .sogi_list
135 .iter()
136 .map(|e| {
137 2 + if e.service_id.is_some() { 2 } else { 0 }
138 + if e.target_region_loop.is_some() {
139 1 + e.target_region_loop.as_ref().map_or(0, |entries| {
140 super::target_region::region_entries_serialized_len(entries)
141 })
142 } else {
143 0
144 }
145 })
146 .sum();
147 1 + sogi_list_length + self.private_data.len()
148 }
149 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
150 let len = self.serialized_len();
151 if buf.len() < len {
152 return Err(Error::OutputBufferTooSmall {
153 need: len,
154 have: buf.len(),
155 });
156 }
157 let sogi_len = len - 1 - self.private_data.len();
158 buf[0] = sogi_len as u8;
159 let mut p = 1;
160 for e in &self.sogi_list {
161 buf[p] = ((e.sogi_flag as u8) << 7)
162 | ((e.target_region_loop.is_some() as u8) << 6)
163 | ((e.service_id.is_some() as u8) << 5)
164 | SOGI_RESERVED_FUTURE_USE
165 | ((e.sogi_priority >> 8) as u8 & 0x0F);
166 buf[p + 1] = e.sogi_priority as u8;
167 p += 2;
168 if let Some(id) = e.service_id {
169 buf[p..p + 2].copy_from_slice(&id.to_be_bytes());
170 p += 2;
171 }
172 if let Some(entries) = &e.target_region_loop {
173 let entries_len = super::target_region::region_entries_serialized_len(entries);
174 buf[p] = entries_len as u8;
175 p += 1;
176 super::target_region::write_region_entries(entries, buf, p);
177 p += entries_len;
178 }
179 }
180 buf[p..p + self.private_data.len()].copy_from_slice(self.private_data);
181 Ok(len)
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use crate::descriptors::extension::test_support::*;
189 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
190
191 #[test]
192 fn parse_service_prominence_one_entry_service_only() {
193 let sel = [0x04, 0x21, 0x23, 0x45, 0x67, 0xAB];
196 let bytes = wrap(0x22, &sel);
197 let d = ExtensionDescriptor::parse(&bytes).unwrap();
198 assert_eq!(d.kind(), Some(ExtensionTag::ServiceProminence));
199 match &d.body {
200 ExtensionBody::ServiceProminence(b) => {
201 assert_eq!(b.sogi_list.len(), 1);
202 let e = &b.sogi_list[0];
203 assert!(!e.sogi_flag);
204 assert!(e.target_region_loop.is_none());
205 assert!(e.service_id.is_some());
206 assert_eq!(e.sogi_priority, 0x0123);
207 assert_eq!(e.service_id, Some(0x4567));
208 assert_eq!(b.private_data, &[0xAB]);
209 }
210 other => panic!("expected ServiceProminence, got {other:?}"),
211 }
212 round_trip(&d);
213 }
214
215 #[test]
216 fn parse_service_prominence_one_entry_target_region() {
217 let sel = [0x04, 0x40, 0x01, 0x01, 0xF8];
220 let bytes = wrap(0x22, &sel);
221 let d = ExtensionDescriptor::parse(&bytes).unwrap();
222 match &d.body {
223 ExtensionBody::ServiceProminence(b) => {
224 assert_eq!(b.sogi_list.len(), 1);
225 let e = &b.sogi_list[0];
226 assert!(!e.sogi_flag);
227 assert_eq!(e.sogi_priority, 0x0001);
228 assert!(e.service_id.is_none());
229 assert!(e.target_region_loop.is_some());
230 let tr = e.target_region_loop.as_ref().unwrap();
231 assert_eq!(tr.len(), 1);
232 assert_eq!(tr[0].country_code, None);
233 assert_eq!(
234 tr[0].region_codes,
235 super::super::target_region::RegionCodes::None
236 );
237 assert!(b.private_data.is_empty());
238 }
239 other => panic!("expected ServiceProminence, got {other:?}"),
240 }
241 round_trip(&d);
242 }
243
244 #[test]
245 fn parse_service_prominence_two_entries_plus_private() {
246 let sel = [0x08, 0x2A, 0xBC, 0x11, 0x11, 0x43, 0x45, 0x01, 0xF8, 0xDD];
250 let bytes = wrap(0x22, &sel);
251 let d = ExtensionDescriptor::parse(&bytes).unwrap();
252 match &d.body {
253 ExtensionBody::ServiceProminence(b) => {
254 assert_eq!(b.sogi_list.len(), 2);
255 let e0 = &b.sogi_list[0];
256 assert_eq!(e0.sogi_priority, 0x0ABC);
257 assert_eq!(e0.service_id, Some(0x1111));
258 assert!(e0.target_region_loop.is_none());
259 let e1 = &b.sogi_list[1];
260 assert!(!e1.sogi_flag);
261 assert!(e1.service_id.is_none());
262 assert_eq!(e1.sogi_priority, 0x0345);
263 assert!(e1.target_region_loop.is_some());
264 assert_eq!(e1.target_region_loop.as_ref().unwrap().len(), 1);
265 assert_eq!(b.private_data, &[0xDD]);
266 }
267 other => panic!("expected ServiceProminence, got {other:?}"),
268 }
269 round_trip(&d);
270 }
271
272 #[test]
273 fn parse_service_prominence_empty_list_private_only() {
274 let sel = [0x00, 0x01, 0x02];
276 let bytes = wrap(0x22, &sel);
277 let d = ExtensionDescriptor::parse(&bytes).unwrap();
278 match &d.body {
279 ExtensionBody::ServiceProminence(b) => {
280 assert!(b.sogi_list.is_empty());
281 assert_eq!(b.private_data, &[0x01, 0x02]);
282 }
283 other => panic!("expected ServiceProminence, got {other:?}"),
284 }
285 round_trip(&d);
286 }
287
288 #[test]
289 fn parse_service_prominence_rejects_overrun() {
290 let sel = [0x05, 0xAA, 0xBB, 0xCC];
292 let bytes = wrap(0x22, &sel);
293 assert!(matches!(
294 ExtensionDescriptor::parse(&bytes).unwrap_err(),
295 crate::error::Error::BufferTooShort { .. }
296 ));
297 }
298
299 #[test]
300 fn parse_service_prominence_rejects_entry_overrun() {
301 let sel = [0x03, 0x20, 0x00, 0x00];
303 let bytes = wrap(0x22, &sel);
304 assert!(matches!(
305 ExtensionDescriptor::parse(&bytes).unwrap_err(),
306 crate::error::Error::BufferTooShort { .. }
307 ));
308 }
309
310 #[cfg(feature = "serde")]
311 #[test]
312 fn serde_serialize_service_prominence() {
313 let d = ExtensionDescriptor {
314 tag_extension: 0x22,
315 body: ExtensionBody::ServiceProminence(ServiceProminence {
316 sogi_list: vec![SogiEntry {
317 sogi_flag: false,
318 sogi_priority: 0x123,
319 service_id: Some(0x4567),
320 target_region_loop: None,
321 }],
322 private_data: &[0xAB],
323 }),
324 };
325 let json = serde_json::to_string(&d).unwrap();
326 assert!(json.contains("\"tag_extension\":34"));
327 assert!(json.contains("\"serviceProminence\""));
328 }
329}