1use super::descriptor_body;
22use crate::error::{Error, Result};
23use dvb_common::{Parse, Serialize};
24
25pub const TAG: u8 = 0x6D;
27pub const HEADER_LEN: usize = 2;
29pub const OUTER_FIXED_LEN: usize = 7;
31pub const SUBCELL_LEN: usize = 5;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize))]
37pub struct CellFrequencyLinkSubcell {
38 pub cell_id_extension: u8,
40 pub transposer_frequency: u32,
42}
43
44impl CellFrequencyLinkSubcell {
45 #[must_use]
47 pub fn transposer_frequency_hz(&self) -> u64 {
48 u64::from(self.transposer_frequency) * 10
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize))]
55pub struct CellFrequencyLinkEntry {
56 pub cell_id: u16,
58 pub frequency: u32,
60 pub subcells: Vec<CellFrequencyLinkSubcell>,
62}
63
64impl CellFrequencyLinkEntry {
65 #[must_use]
67 pub fn frequency_hz(&self) -> u64 {
68 u64::from(self.frequency) * 10
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize))]
75pub struct CellFrequencyLinkDescriptor {
76 pub entries: Vec<CellFrequencyLinkEntry>,
78}
79
80impl<'a> Parse<'a> for CellFrequencyLinkDescriptor {
81 type Error = crate::error::Error;
82 fn parse(bytes: &'a [u8]) -> Result<Self> {
83 let body = descriptor_body(
84 bytes,
85 TAG,
86 "CellFrequencyLinkDescriptor",
87 "unexpected tag for cell_frequency_link_descriptor",
88 )?;
89 let mut entries = Vec::new();
90 let mut pos = 0;
91 while pos < body.len() {
92 if pos + OUTER_FIXED_LEN > body.len() {
93 return Err(Error::InvalidDescriptor {
94 tag: TAG,
95 reason: "cell_frequency_link outer entry truncated",
96 });
97 }
98 let cell_id = u16::from_be_bytes([body[pos], body[pos + 1]]);
99 let frequency =
100 u32::from_be_bytes([body[pos + 2], body[pos + 3], body[pos + 4], body[pos + 5]]);
101 let subcell_info_loop_length = body[pos + 6] as usize;
102 pos += OUTER_FIXED_LEN;
103 if subcell_info_loop_length % SUBCELL_LEN != 0 {
104 return Err(Error::InvalidDescriptor {
105 tag: TAG,
106 reason: "subcell_info_loop_length must be a multiple of 5",
107 });
108 }
109 if pos + subcell_info_loop_length > body.len() {
110 return Err(Error::InvalidDescriptor {
111 tag: TAG,
112 reason: "subcell_info_loop_length exceeds descriptor body",
113 });
114 }
115 let subcell_count = subcell_info_loop_length / SUBCELL_LEN;
116 let mut subcells = Vec::with_capacity(subcell_count);
117 for _ in 0..subcell_count {
118 let cell_id_extension = body[pos];
119 let transposer_frequency = u32::from_be_bytes([
120 body[pos + 1],
121 body[pos + 2],
122 body[pos + 3],
123 body[pos + 4],
124 ]);
125 subcells.push(CellFrequencyLinkSubcell {
126 cell_id_extension,
127 transposer_frequency,
128 });
129 pos += SUBCELL_LEN;
130 }
131 entries.push(CellFrequencyLinkEntry {
132 cell_id,
133 frequency,
134 subcells,
135 });
136 }
137 Ok(Self { entries })
138 }
139}
140
141impl CellFrequencyLinkDescriptor {
142 fn body_len(&self) -> usize {
143 self.entries
144 .iter()
145 .map(|e| OUTER_FIXED_LEN + e.subcells.len() * SUBCELL_LEN)
146 .sum()
147 }
148}
149
150impl Serialize for CellFrequencyLinkDescriptor {
151 type Error = crate::error::Error;
152 fn serialized_len(&self) -> usize {
153 HEADER_LEN + self.body_len()
154 }
155
156 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
157 let body_len = self.body_len();
158 if body_len > u8::MAX as usize {
159 return Err(Error::InvalidDescriptor {
160 tag: TAG,
161 reason: "cell_frequency_link_descriptor body exceeds 255 bytes",
162 });
163 }
164 for e in &self.entries {
165 if e.subcells.len() * SUBCELL_LEN > u8::MAX as usize {
166 return Err(Error::InvalidDescriptor {
167 tag: TAG,
168 reason: "subcell_info_loop_length exceeds 255 bytes",
169 });
170 }
171 }
172 let len = self.serialized_len();
173 if buf.len() < len {
174 return Err(Error::OutputBufferTooSmall {
175 need: len,
176 have: buf.len(),
177 });
178 }
179 buf[0] = TAG;
180 buf[1] = body_len as u8;
181 let mut pos = HEADER_LEN;
182 for e in &self.entries {
183 buf[pos..pos + 2].copy_from_slice(&e.cell_id.to_be_bytes());
184 buf[pos + 2..pos + 6].copy_from_slice(&e.frequency.to_be_bytes());
185 buf[pos + 6] = (e.subcells.len() * SUBCELL_LEN) as u8;
186 pos += OUTER_FIXED_LEN;
187 for sc in &e.subcells {
188 buf[pos] = sc.cell_id_extension;
189 buf[pos + 1..pos + 5].copy_from_slice(&sc.transposer_frequency.to_be_bytes());
190 pos += SUBCELL_LEN;
191 }
192 }
193 Ok(len)
194 }
195}
196impl<'a> crate::traits::DescriptorDef<'a> for CellFrequencyLinkDescriptor {
197 const TAG: u8 = TAG;
198 const NAME: &'static str = "CELL_FREQUENCY_LINK";
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn frequency_hz_computes_correctly() {
207 let entry = CellFrequencyLinkEntry {
208 cell_id: 0,
209 frequency: 100_000,
210 subcells: vec![],
211 };
212 assert_eq!(entry.frequency_hz(), 1_000_000);
213 }
214
215 #[test]
216 fn transposer_frequency_hz_computes_correctly() {
217 let sc = CellFrequencyLinkSubcell {
218 cell_id_extension: 0,
219 transposer_frequency: 500_000,
220 };
221 assert_eq!(sc.transposer_frequency_hz(), 5_000_000);
222 }
223
224 #[test]
225 fn parse_entry_with_subcells() {
226 let bytes = [
227 TAG, 17, 0x12, 0x34, 0x00, 0x11, 0x22, 0x33, 10, 0x01, 0x0A, 0xAB, 0xBC, 0xCD, 0x02, 0x0D, 0xDE, 0xEF, 0xF0,
232 ];
233 let d = CellFrequencyLinkDescriptor::parse(&bytes).unwrap();
234 assert_eq!(d.entries.len(), 1);
235 assert_eq!(d.entries[0].cell_id, 0x1234);
236 assert_eq!(d.entries[0].frequency, 0x0011_2233);
237 assert_eq!(d.entries[0].subcells.len(), 2);
238 assert_eq!(d.entries[0].subcells[0].cell_id_extension, 0x01);
239 assert_eq!(d.entries[0].subcells[0].transposer_frequency, 0x0AAB_BCCD);
240 assert_eq!(d.entries[0].subcells[1].cell_id_extension, 0x02);
241 }
242
243 #[test]
244 fn parse_entry_no_subcells() {
245 let bytes = [TAG, 7, 0x00, 0x05, 0x00, 0x00, 0x10, 0x00, 0x00];
246 let d = CellFrequencyLinkDescriptor::parse(&bytes).unwrap();
247 assert_eq!(d.entries.len(), 1);
248 assert_eq!(d.entries[0].cell_id, 0x0005);
249 assert!(d.entries[0].subcells.is_empty());
250 }
251
252 #[test]
253 fn empty_body_is_valid() {
254 let d = CellFrequencyLinkDescriptor::parse(&[TAG, 0]).unwrap();
255 assert!(d.entries.is_empty());
256 }
257
258 #[test]
259 fn parse_rejects_wrong_tag() {
260 assert!(matches!(
261 CellFrequencyLinkDescriptor::parse(&[0x6E, 0]).unwrap_err(),
262 Error::InvalidDescriptor { tag: 0x6E, .. }
263 ));
264 }
265
266 #[test]
267 fn parse_rejects_truncated_outer() {
268 let bytes = [TAG, 5, 0x00, 0x01, 0x00, 0x00, 0x10];
270 assert!(matches!(
271 CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
272 Error::InvalidDescriptor { tag: TAG, .. }
273 ));
274 }
275
276 #[test]
277 fn parse_rejects_subcell_loop_overrun() {
278 let bytes = [TAG, 7, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 10];
280 assert!(matches!(
281 CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
282 Error::InvalidDescriptor { tag: TAG, .. }
283 ));
284 }
285
286 #[test]
287 fn parse_rejects_buffer_shorter_than_length() {
288 let bytes = [TAG, 7, 0x00, 0x01, 0x00];
289 assert!(matches!(
290 CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
291 Error::BufferTooShort { .. }
292 ));
293 }
294
295 #[test]
296 fn serialize_round_trip() {
297 let d = CellFrequencyLinkDescriptor {
298 entries: vec![
299 CellFrequencyLinkEntry {
300 cell_id: 0x1234,
301 frequency: 0x0011_2233,
302 subcells: vec![
303 CellFrequencyLinkSubcell {
304 cell_id_extension: 0x01,
305 transposer_frequency: 0x0AAB_BCCD,
306 },
307 CellFrequencyLinkSubcell {
308 cell_id_extension: 0x02,
309 transposer_frequency: 0x0DDE_EFF0,
310 },
311 ],
312 },
313 CellFrequencyLinkEntry {
314 cell_id: 0x9999,
315 frequency: 0x4455_6677,
316 subcells: vec![],
317 },
318 ],
319 };
320 let mut buf = vec![0u8; d.serialized_len()];
321 d.serialize_into(&mut buf).unwrap();
322 assert_eq!(CellFrequencyLinkDescriptor::parse(&buf).unwrap(), d);
323 }
324
325 #[test]
326 fn serialize_rejects_too_small_buffer() {
327 let d = CellFrequencyLinkDescriptor {
328 entries: vec![CellFrequencyLinkEntry {
329 cell_id: 0,
330 frequency: 0,
331 subcells: vec![],
332 }],
333 };
334 let mut buf = vec![0u8; 3];
335 assert!(matches!(
336 d.serialize_into(&mut buf).unwrap_err(),
337 Error::OutputBufferTooSmall { .. }
338 ));
339 }
340
341 #[test]
342 fn serialize_rejects_over_range_body() {
343 let d = CellFrequencyLinkDescriptor {
345 entries: (0..37)
346 .map(|_| CellFrequencyLinkEntry {
347 cell_id: 0,
348 frequency: 0,
349 subcells: vec![],
350 })
351 .collect(),
352 };
353 let mut buf = vec![0u8; d.serialized_len()];
354 assert!(matches!(
355 d.serialize_into(&mut buf).unwrap_err(),
356 Error::InvalidDescriptor { tag: TAG, .. }
357 ));
358 }
359
360 #[cfg(feature = "serde")]
361 #[test]
362 fn serde_round_trip() {
363 let d = CellFrequencyLinkDescriptor {
364 entries: vec![CellFrequencyLinkEntry {
365 cell_id: 0x1234,
366 frequency: 0x0011_2233,
367 subcells: vec![CellFrequencyLinkSubcell {
368 cell_id_extension: 0x01,
369 transposer_frequency: 0x0AAB_BCCD,
370 }],
371 }],
372 };
373 let json = serde_json::to_string(&d).unwrap();
374 let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
376 }
377}