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