dvb_si/descriptors/
parental_rating.rs1use super::descriptor_body;
6use crate::error::{Error, Result};
7use crate::text::LangCode;
8use alloc::vec::Vec;
9use dvb_common::{Parse, Serialize};
10
11pub const TAG: u8 = 0x55;
13const HEADER_LEN: usize = 2;
14const ENTRY_LEN: usize = 4;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize))]
19pub struct RatingEntry {
20 pub country_code: LangCode,
22 pub rating: u8,
24}
25
26impl RatingEntry {
27 #[must_use]
29 pub fn minimum_age(&self) -> Option<u8> {
30 match self.rating {
31 0x01..=0x0F => Some(self.rating + 3),
32 _ => None,
33 }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize))]
40pub struct ParentalRatingDescriptor {
41 pub entries: Vec<RatingEntry>,
43}
44
45impl<'a> Parse<'a> for ParentalRatingDescriptor {
46 type Error = crate::error::Error;
47 fn parse(bytes: &'a [u8]) -> Result<Self> {
48 let body = descriptor_body(
49 bytes,
50 TAG,
51 "ParentalRatingDescriptor",
52 "unexpected tag for parental_rating_descriptor",
53 )?;
54 if body.len() % ENTRY_LEN != 0 {
55 return Err(Error::InvalidDescriptor {
56 tag: TAG,
57 reason: "descriptor_body_length is not a multiple of 4",
58 });
59 }
60 let num_entries = body.len() / ENTRY_LEN;
61 let mut entries = Vec::with_capacity(num_entries);
62 for chunk in body.chunks_exact(ENTRY_LEN) {
63 entries.push(RatingEntry {
64 country_code: LangCode([chunk[0], chunk[1], chunk[2]]),
65 rating: chunk[3],
66 });
67 }
68 Ok(Self { entries })
69 }
70}
71
72impl Serialize for ParentalRatingDescriptor {
73 type Error = crate::error::Error;
74 fn serialized_len(&self) -> usize {
75 HEADER_LEN + self.entries.len() * ENTRY_LEN
76 }
77
78 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
79 let len = self.serialized_len();
80 if buf.len() < len {
81 return Err(Error::OutputBufferTooSmall {
82 need: len,
83 have: buf.len(),
84 });
85 }
86 buf[0] = TAG;
87 buf[1] = (len - HEADER_LEN) as u8;
88 for (i, entry) in self.entries.iter().enumerate() {
89 let entry_start = HEADER_LEN + i * ENTRY_LEN;
90 buf[entry_start..entry_start + 3].copy_from_slice(&entry.country_code.0);
91 buf[entry_start + 3] = entry.rating;
92 }
93 Ok(len)
94 }
95}
96impl<'a> crate::traits::DescriptorDef<'a> for ParentalRatingDescriptor {
97 const TAG: u8 = TAG;
98 const NAME: &'static str = "PARENTAL_RATING";
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn parse_single_entry_extracts_country_and_rating() {
107 let bytes = [TAG, 4, b'F', b'R', b'A', 0x05];
108 let d = ParentalRatingDescriptor::parse(&bytes).unwrap();
109 assert_eq!(d.entries.len(), 1);
110 assert_eq!(d.entries[0].country_code, LangCode(*b"FRA"));
111 assert_eq!(d.entries[0].rating, 0x05);
112 }
113
114 #[test]
115 fn parse_multiple_entries_preserves_order() {
116 let bytes = [TAG, 8, b'G', b'B', b'R', 0x01, b'U', b'S', b'A', 0x10];
117 let d = ParentalRatingDescriptor::parse(&bytes).unwrap();
118 assert_eq!(d.entries.len(), 2);
119 assert_eq!(d.entries[0].country_code, LangCode(*b"GBR"));
120 assert_eq!(d.entries[0].rating, 0x01);
121 assert_eq!(d.entries[1].country_code, LangCode(*b"USA"));
122 assert_eq!(d.entries[1].rating, 0x10);
123 }
124
125 #[test]
126 fn parse_rejects_wrong_tag() {
127 let err = ParentalRatingDescriptor::parse(&[0x4E, 0]).unwrap_err();
128 assert!(matches!(err, Error::InvalidDescriptor { tag: 0x4E, .. }));
129 }
130
131 #[test]
132 fn parse_rejects_length_not_multiple_of_4() {
133 let bytes = [TAG, 3, b'F', b'R', b'A'];
134 let err = ParentalRatingDescriptor::parse(&bytes).unwrap_err();
135 assert!(matches!(err, Error::InvalidDescriptor { .. }));
136 }
137
138 #[test]
139 fn parse_rejects_truncated_body() {
140 let bytes = [TAG, 4, b'F', b'R'];
141 let err = ParentalRatingDescriptor::parse(&bytes).unwrap_err();
142 assert!(matches!(err, Error::BufferTooShort { .. }));
143 }
144
145 #[test]
146 fn minimum_age_maps_0x01_to_4_years() {
147 let entry = RatingEntry {
148 country_code: LangCode(*b"FRA"),
149 rating: 0x01,
150 };
151 assert_eq!(entry.minimum_age(), Some(4));
152 }
153
154 #[test]
155 fn minimum_age_returns_none_for_rating_0x00() {
156 let entry = RatingEntry {
157 country_code: LangCode(*b"USA"),
158 rating: 0x00,
159 };
160 assert!(entry.minimum_age().is_none());
161 }
162
163 #[test]
164 fn minimum_age_returns_none_for_rating_0x10_and_above() {
165 let entry = RatingEntry {
166 country_code: LangCode(*b"GBR"),
167 rating: 0x10,
168 };
169 assert!(entry.minimum_age().is_none());
170 let entry2 = RatingEntry {
171 country_code: LangCode(*b"JPN"),
172 rating: 0xFF,
173 };
174 assert!(entry2.minimum_age().is_none());
175 }
176
177 #[test]
178 fn serialize_round_trip() {
179 let d = ParentalRatingDescriptor {
180 entries: vec![
181 RatingEntry {
182 country_code: LangCode(*b"FRA"),
183 rating: 0x05,
184 },
185 RatingEntry {
186 country_code: LangCode(*b"GBR"),
187 rating: 0x01,
188 },
189 ],
190 };
191 let mut buf = vec![0u8; d.serialized_len()];
192 d.serialize_into(&mut buf).unwrap();
193 let re = ParentalRatingDescriptor::parse(&buf).unwrap();
194 assert_eq!(d, re);
195 }
196}