1use crate::error::{Error, Result};
10use crate::tag::{self, ApduTag};
11use crate::traits::ApduDef;
12use alloc::vec::Vec;
13use broadcast_common::{Parse, Serialize};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize))]
18#[non_exhaustive]
19pub enum CaEnable {
20 Possible,
22 PossiblePurchaseDialogue,
24 PossibleTechnicalDialogue,
26 NotPossibleNoEntitlement,
28 NotPossibleTechnical,
30 Rfu(u8),
32}
33
34impl CaEnable {
35 #[must_use]
37 pub fn from_u8(v: u8) -> Self {
38 match v & 0x7F {
39 0x01 => Self::Possible,
40 0x02 => Self::PossiblePurchaseDialogue,
41 0x03 => Self::PossibleTechnicalDialogue,
42 0x71 => Self::NotPossibleNoEntitlement,
43 0x73 => Self::NotPossibleTechnical,
44 other => Self::Rfu(other),
45 }
46 }
47 #[must_use]
49 pub const fn to_u8(self) -> u8 {
50 match self {
51 Self::Possible => 0x01,
52 Self::PossiblePurchaseDialogue => 0x02,
53 Self::PossibleTechnicalDialogue => 0x03,
54 Self::NotPossibleNoEntitlement => 0x71,
55 Self::NotPossibleTechnical => 0x73,
56 Self::Rfu(v) => v & 0x7F,
57 }
58 }
59 #[must_use]
61 pub fn name(&self) -> &'static str {
62 match self {
63 Self::Possible => "descrambling_possible",
64 Self::PossiblePurchaseDialogue => "possible_purchase_dialogue",
65 Self::PossibleTechnicalDialogue => "possible_technical_dialogue",
66 Self::NotPossibleNoEntitlement => "not_possible_no_entitlement",
67 Self::NotPossibleTechnical => "not_possible_technical",
68 Self::Rfu(_) => "reserved",
69 }
70 }
71}
72broadcast_common::impl_spec_display!(CaEnable, Rfu);
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize))]
77pub struct CaPmtReplyStream {
78 pub elementary_pid: u16,
80 pub ca_enable: Option<CaEnable>,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize))]
87pub struct CaPmtReply {
88 pub program_number: u16,
90 pub version_number: u8,
92 pub current_next_indicator: bool,
94 pub ca_enable: Option<CaEnable>,
97 pub streams: Vec<CaPmtReplyStream>,
99}
100
101const REPLY_PREFIX: usize = 4; const ES_LEN: usize = 3; impl<'a> Parse<'a> for CaPmtReply {
105 type Error = Error;
106 fn parse(bytes: &'a [u8]) -> Result<Self> {
107 let body = super::parse_apdu_header(bytes, tag::CA_PMT_REPLY, "ca_pmt_reply")?;
108 if body.len() < REPLY_PREFIX {
109 return Err(Error::BufferTooShort {
110 need: REPLY_PREFIX,
111 have: body.len(),
112 what: "ca_pmt_reply prefix",
113 });
114 }
115 let program_number = u16::from_be_bytes([body[0], body[1]]);
116 let version_number = (body[2] >> 1) & 0x1F;
117 let current_next_indicator = (body[2] & 0x01) != 0;
118 let ca_enable_flag = (body[3] & 0x80) != 0;
119 let ca_enable = if ca_enable_flag {
120 Some(CaEnable::from_u8(body[3] & 0x7F))
121 } else {
122 None
123 };
124
125 let mut pos = REPLY_PREFIX;
126 let mut streams = Vec::new();
127 while pos < body.len() {
128 if pos + ES_LEN > body.len() {
129 return Err(Error::BufferTooShort {
130 need: pos + ES_LEN,
131 have: body.len(),
132 what: "ca_pmt_reply ES",
133 });
134 }
135 let elementary_pid = (((body[pos] & 0x1F) as u16) << 8) | body[pos + 1] as u16;
136 let es_flag = (body[pos + 2] & 0x80) != 0;
137 let es_enable = if es_flag {
138 Some(CaEnable::from_u8(body[pos + 2] & 0x7F))
139 } else {
140 None
141 };
142 streams.push(CaPmtReplyStream {
143 elementary_pid,
144 ca_enable: es_enable,
145 });
146 pos += ES_LEN;
147 }
148
149 Ok(Self {
150 program_number,
151 version_number,
152 current_next_indicator,
153 ca_enable,
154 streams,
155 })
156 }
157}
158
159impl Serialize for CaPmtReply {
160 type Error = Error;
161 fn serialized_len(&self) -> usize {
162 super::apdu_len(REPLY_PREFIX + self.streams.len() * ES_LEN)
163 }
164 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
165 let body = REPLY_PREFIX + self.streams.len() * ES_LEN;
166 let mut pos = super::write_apdu_header(tag::CA_PMT_REPLY, body, buf)?;
167 buf[pos..pos + 2].copy_from_slice(&self.program_number.to_be_bytes());
168 buf[pos + 2] =
170 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
171 buf[pos + 3] = encode_enable_byte(self.ca_enable);
172 pos += REPLY_PREFIX;
173 for s in &self.streams {
174 buf[pos] = 0xE0 | ((s.elementary_pid >> 8) as u8 & 0x1F);
176 buf[pos + 1] = s.elementary_pid as u8;
177 buf[pos + 2] = encode_enable_byte(s.ca_enable);
178 pos += ES_LEN;
179 }
180 Ok(pos)
181 }
182}
183
184fn encode_enable_byte(enable: Option<CaEnable>) -> u8 {
188 match enable {
189 Some(e) => 0x80 | (e.to_u8() & 0x7F),
190 None => 0x7F,
191 }
192}
193
194impl ApduDef<'_> for CaPmtReply {
195 const TAG: ApduTag = tag::CA_PMT_REPLY;
196 const NAME: &'static str = "CA_PMT_REPLY";
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn reply_with_prog_and_es_enable_round_trips() {
205 let r = CaPmtReply {
206 program_number: 0x0001,
207 version_number: 1,
208 current_next_indicator: true,
209 ca_enable: Some(CaEnable::Possible),
210 streams: alloc::vec![
211 CaPmtReplyStream {
212 elementary_pid: 0x0200,
213 ca_enable: Some(CaEnable::Possible),
214 },
215 CaPmtReplyStream {
216 elementary_pid: 0x0201,
217 ca_enable: Some(CaEnable::NotPossibleNoEntitlement),
218 },
219 ],
220 };
221 let bytes = r.to_bytes();
222 assert_eq!(&bytes[..3], &[0x9F, 0x80, 0x33]);
223 let parsed = CaPmtReply::parse(&bytes).unwrap();
224 assert_eq!(parsed, r);
225 assert_eq!(parsed.streams.len(), 2);
226 assert_eq!(parsed.ca_enable.unwrap().name(), "descrambling_possible");
227 }
228
229 #[test]
230 fn reply_without_enable_flags() {
231 let r = CaPmtReply {
232 program_number: 9,
233 version_number: 0,
234 current_next_indicator: true,
235 ca_enable: None,
236 streams: alloc::vec![CaPmtReplyStream {
237 elementary_pid: 0x00FF,
238 ca_enable: None,
239 }],
240 };
241 let bytes = r.to_bytes();
242 let parsed = CaPmtReply::parse(&bytes).unwrap();
243 assert_eq!(parsed, r);
244 assert!(parsed.ca_enable.is_none());
245 assert!(parsed.streams[0].ca_enable.is_none());
246 }
247
248 #[test]
249 fn mutating_enable_changes_bytes() {
250 let r = CaPmtReply {
251 program_number: 1,
252 version_number: 1,
253 current_next_indicator: true,
254 ca_enable: Some(CaEnable::Possible),
255 streams: Vec::new(),
256 };
257 let a = r.to_bytes();
258 let mut other = r.clone();
259 other.ca_enable = Some(CaEnable::NotPossibleNoEntitlement);
260 assert_ne!(a, other.to_bytes());
261 }
262}