1use super::BitSlice;
10use bitvec::prelude::*;
11use num_enum::TryFromPrimitive;
12use std::fmt::{Display, Formatter};
13
14#[derive(Debug, Clone, Eq, PartialEq, Hash)]
19pub struct BBHeader<'a>(&'a [u8; BBHeader::LEN]);
20
21lazy_static::lazy_static! {
22 static ref CRC8: crc::Crc<u8> = crc::Crc::<u8>::new(&crc::CRC_8_DVB_S2);
23}
24
25impl BBHeader<'_> {
26 pub const LEN: usize = 10;
28
29 pub fn new(data: &[u8; BBHeader::LEN]) -> BBHeader {
31 BBHeader(data)
32 }
33
34 fn matype1(&self) -> &BitSlice {
35 BitSlice::from_slice(&self.0[..1])
36 }
37
38 pub fn tsgs(&self) -> TsGs {
41 TsGs::try_from(self.matype1()[..2].load_be::<u8>()).unwrap()
42 }
43
44 pub fn is_gse_hem(&self) -> bool {
48 matches!(self.tsgs(), TsGs::GseHem)
49 }
50
51 pub fn sismis(&self) -> SisMis {
54 SisMis::try_from(self.matype1()[2..3].load_be::<u8>()).unwrap()
55 }
56
57 pub fn ccmacm(&self) -> CcmAcm {
60 CcmAcm::try_from(self.matype1()[3..4].load_be::<u8>()).unwrap()
61 }
62
63 pub fn issyi(&self) -> bool {
65 self.matype1()[4]
66 }
67
68 pub fn npd(&self) -> bool {
70 self.matype1()[5]
71 }
72
73 pub fn gse_lite(&self) -> bool {
77 self.npd()
78 }
79
80 pub fn rolloff(&self) -> RollOff {
82 RollOff::try_from_primitive(self.matype1()[6..8].load_be::<u8>()).unwrap()
83 }
84
85 pub fn isi(&self) -> u8 {
90 self.0[1]
91 }
92
93 pub fn upl(&self) -> Option<u16> {
98 if self.is_gse_hem() {
99 None
100 } else {
101 Some(u16::from_be_bytes(self.0[2..4].try_into().unwrap()))
102 }
103 }
104
105 pub fn issy(&self) -> Option<[u8; 3]> {
113 if self.is_gse_hem() && self.issyi() {
114 let mut field = [0; 3];
115 field[0] = self.0[2];
116 field[1] = self.0[3];
117 field[2] = self.0[6];
118 Some(field)
119 } else {
120 None
121 }
122 }
123
124 pub fn dfl(&self) -> u16 {
126 u16::from_be_bytes(self.0[4..6].try_into().unwrap())
127 }
128
129 pub fn sync(&self) -> Option<u8> {
134 if self.is_gse_hem() {
135 None
136 } else {
137 Some(self.0[6])
138 }
139 }
140
141 pub fn syncd(&self) -> u16 {
143 u16::from_be_bytes(self.0[7..9].try_into().unwrap())
144 }
145
146 pub fn crc8(&self) -> u8 {
148 self.0[BBHeader::LEN - 1]
149 }
150
151 pub fn compute_crc8(&self) -> u8 {
153 let crc = CRC8.checksum(&self.0[..BBHeader::LEN - 1]);
154 if self.is_gse_hem() {
155 crc ^ 1
161 } else {
162 crc
163 }
164 }
165
166 pub fn crc_is_valid(&self) -> bool {
168 self.crc8() == self.compute_crc8()
169 }
170}
171
172impl Display for BBHeader<'_> {
173 fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
174 write!(
175 f,
176 "BBHEADER(TS/GS = {}, SIS/MIS = {}, CCM/ACM = {}, ISSYI = {}, \
177 NPD/GSE-Lite = {}, {}, ISI = {}, ",
178 self.tsgs(),
179 self.sismis(),
180 self.ccmacm(),
181 self.issyi(),
182 self.npd(),
183 self.rolloff(),
184 self.isi(),
185 )?;
186 if let Some(upl) = self.upl() {
187 write!(f, "UPL = {} bits, ", upl)?;
188 }
189 if let Some(issy) = self.issy() {
190 let issy = (u32::from(issy[0]) << 16) | (u32::from(issy[1]) << 8) | u32::from(issy[2]);
191 write!(f, "ISSY = {:#06x}, ", issy)?;
192 }
193 write!(f, "DFL = {} bits, ", self.dfl())?;
194 if let Some(sync) = self.sync() {
195 write!(f, "SYNC = {:#04x}, ", sync)?;
196 }
197 write!(f, "SYNCD = {:#06x})", self.syncd())
198 }
199}
200
201#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, TryFromPrimitive)]
203#[repr(u8)]
204pub enum TsGs {
205 Transport = 0b11,
207 GenericPacketized = 0b00,
209 GenericContinuous = 0b01,
211 GseHem = 0b10,
215}
216
217impl Display for TsGs {
218 fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
219 write!(
220 f,
221 "{}",
222 match self {
223 TsGs::Transport => "Transport",
224 TsGs::GenericPacketized => "Generic packetized",
225 TsGs::GenericContinuous => "Generic continuous",
226 TsGs::GseHem => "GSE-HEM",
227 }
228 )
229 }
230}
231
232#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, TryFromPrimitive)]
234#[repr(u8)]
235pub enum SisMis {
236 Sis = 0b1,
238 Mis = 0b0,
240}
241
242impl Display for SisMis {
243 fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
244 write!(
245 f,
246 "{}",
247 match self {
248 SisMis::Sis => "single",
249 SisMis::Mis => "multiple",
250 }
251 )
252 }
253}
254
255#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, TryFromPrimitive)]
258#[repr(u8)]
259pub enum CcmAcm {
260 Ccm = 0b1,
262 Acm = 0b0,
266}
267
268impl Display for CcmAcm {
269 fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
270 write!(
271 f,
272 "{}",
273 match self {
274 CcmAcm::Ccm => "CCM",
275 CcmAcm::Acm => "ACM",
276 }
277 )
278 }
279}
280
281#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, TryFromPrimitive)]
289#[repr(u8)]
290pub enum RollOff {
292 Ro0_35 = 0b00,
294 Ro0_25 = 0b01,
296 Ro0_20 = 0b10,
298 Reserved = 0b11,
304}
305
306impl Display for RollOff {
307 fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
308 write!(
309 f,
310 "α = {}",
311 match self {
312 RollOff::Ro0_35 => "0.35",
313 RollOff::Ro0_25 => "0.25",
314 RollOff::Ro0_20 => "0.20",
315 RollOff::Reserved => "reserved",
316 }
317 )
318 }
319}
320
321#[cfg(test)]
322mod test {
323 use super::*;
324 use hex_literal::hex;
325
326 const CONTINUOUS_GSE_HEADER: [u8; 10] = hex!("72 00 00 00 02 f0 00 00 00 15");
327 const GSE_HEM_HEADER: [u8; 10] = hex!("b2 00 00 00 02 f0 00 00 00 87");
328 const GSE_HEM_HEADER_ISSY: [u8; 10] = hex!("ba 00 12 34 02 f0 56 02 11 7c");
329
330 #[test]
331 fn continuous_gse_header() {
332 let header = BBHeader::new(&CONTINUOUS_GSE_HEADER);
333 assert_eq!(
334 format!("{}", header),
335 "BBHEADER(TS/GS = Generic continuous, SIS/MIS = single, CCM/ACM = CCM, \
336 ISSYI = false, NPD/GSE-Lite = false, α = 0.20, ISI = 0, UPL = 0 bits, \
337 DFL = 752 bits, SYNC = 0x00, SYNCD = 0x0000)"
338 );
339 assert_eq!(header.tsgs(), TsGs::GenericContinuous);
340 assert_eq!(header.sismis(), SisMis::Sis);
341 assert_eq!(header.ccmacm(), CcmAcm::Ccm);
342 assert!(!header.issyi());
343 assert!(!header.npd());
344 assert!(!header.gse_lite());
345 assert_eq!(header.rolloff(), RollOff::Ro0_20);
346 assert_eq!(header.isi(), 0);
347 assert_eq!(header.issy(), None);
348 assert_eq!(header.upl(), Some(0));
349 assert_eq!(header.dfl(), 752);
350 assert_eq!(header.sync(), Some(0));
351 assert_eq!(header.syncd(), 0);
352 assert_eq!(header.crc8(), CONTINUOUS_GSE_HEADER[9]);
353 assert_eq!(header.compute_crc8(), header.crc8());
354 assert!(header.crc_is_valid());
355 }
356
357 #[test]
358 fn gse_hem_header() {
359 let header = BBHeader::new(&GSE_HEM_HEADER);
360 assert_eq!(
361 format!("{}", header),
362 "BBHEADER(TS/GS = GSE-HEM, SIS/MIS = single, CCM/ACM = CCM, \
363 ISSYI = false, NPD/GSE-Lite = false, α = 0.20, ISI = 0, \
364 DFL = 752 bits, SYNCD = 0x0000)"
365 );
366 assert_eq!(header.tsgs(), TsGs::GseHem);
367 assert_eq!(header.sismis(), SisMis::Sis);
368 assert_eq!(header.ccmacm(), CcmAcm::Ccm);
369 assert!(!header.issyi());
370 assert!(!header.npd());
371 assert!(!header.gse_lite());
372 assert_eq!(header.rolloff(), RollOff::Ro0_20);
373 assert_eq!(header.isi(), 0);
374 assert_eq!(header.issy(), None);
375 assert_eq!(header.upl(), None);
376 assert_eq!(header.dfl(), 752);
377 assert_eq!(header.sync(), None);
378 assert_eq!(header.syncd(), 0);
379 assert_eq!(header.crc8(), GSE_HEM_HEADER[9]);
380 assert_eq!(header.compute_crc8(), header.crc8());
381 assert!(header.crc_is_valid());
382 }
383
384 #[test]
385 fn gse_hem_header_issy() {
386 let header = BBHeader::new(&GSE_HEM_HEADER_ISSY);
387 assert_eq!(
388 format!("{}", header),
389 "BBHEADER(TS/GS = GSE-HEM, SIS/MIS = single, CCM/ACM = CCM, \
390 ISSYI = true, NPD/GSE-Lite = false, α = 0.20, ISI = 0, \
391 ISSY = 0x123456, DFL = 752 bits, SYNCD = 0x0211)"
392 );
393 assert_eq!(header.tsgs(), TsGs::GseHem);
394 assert_eq!(header.sismis(), SisMis::Sis);
395 assert_eq!(header.ccmacm(), CcmAcm::Ccm);
396 assert!(header.issyi());
397 assert!(!header.npd());
398 assert!(!header.gse_lite());
399 assert_eq!(header.rolloff(), RollOff::Ro0_20);
400 assert_eq!(header.isi(), 0);
401 assert_eq!(header.issy(), Some([0x12, 0x34, 0x56]));
402 assert_eq!(header.upl(), None);
403 assert_eq!(header.dfl(), 752);
404 assert_eq!(header.sync(), None);
405 assert_eq!(header.syncd(), 0x211);
406 assert_eq!(header.crc8(), GSE_HEM_HEADER_ISSY[9]);
407 assert_eq!(header.compute_crc8(), header.crc8());
408 assert!(header.crc_is_valid());
409 }
410}
411
412#[cfg(test)]
413mod proptests {
414 use super::*;
415 use proptest::prelude::*;
416
417 proptest! {
418 #[test]
419 fn doesnt_panic(header: [u8; 10]) {
420 let header = BBHeader::new(&header);
421 header.tsgs();
422 header.sismis();
423 header.ccmacm();
424 header.issyi();
425 header.npd();
426 header.gse_lite();
427 header.rolloff();
428 header.isi();
429 header.upl();
430 header.dfl();
431 header.sync();
432 header.syncd();
433 header.crc8();
434 header.compute_crc8();
435 header.crc_is_valid();
436 let _ = format!("{header}");
437 }
438 }
439}