1use crate::error::{Error, Result};
13use crate::objects;
14use crate::tag::ApduTag;
15use dvb_common::{Parse, Serialize};
16
17pub mod tag {
19 use crate::tag::ApduTag;
20 pub const CP_QUERY: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
22 pub const CP_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
24 pub const CP_COMMAND: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
26 pub const CP_RESPONSE: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize))]
33#[non_exhaustive]
34pub enum CpStatus {
35 Inactive,
37 Active,
39 IdMismatch,
41 Reserved(u8),
43}
44
45impl CpStatus {
46 #[must_use]
48 pub fn from_u8(v: u8) -> Self {
49 match v {
50 0x01 => Self::Inactive,
51 0x02 => Self::Active,
52 0xFF => Self::IdMismatch,
53 other => Self::Reserved(other),
54 }
55 }
56 #[must_use]
58 pub const fn to_u8(self) -> u8 {
59 match self {
60 Self::Inactive => 0x01,
61 Self::Active => 0x02,
62 Self::IdMismatch => 0xFF,
63 Self::Reserved(v) => v,
64 }
65 }
66 #[must_use]
68 pub fn name(&self) -> &'static str {
69 match self {
70 Self::Inactive => "Copy Protection Inactive",
71 Self::Active => "Copy Protection Active",
72 Self::IdMismatch => "ID mismatch",
73 Self::Reserved(_) => "reserved",
74 }
75 }
76}
77dvb_common::impl_spec_display!(CpStatus, Reserved);
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize))]
82pub struct CpQuery {
83 pub copy_protection_id: u32,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89#[cfg_attr(feature = "serde", derive(serde::Serialize))]
90pub struct CpReply {
91 pub copy_protection_id: u32,
93 pub status: CpStatus,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize))]
101pub struct CpCommand<'a> {
102 pub copy_protection_id: u32,
104 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
106 pub command_bytes: &'a [u8],
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize))]
113pub struct CpResponse<'a> {
114 pub copy_protection_id: u32,
116 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
118 pub response_bytes: &'a [u8],
119}
120
121const CP_ID_LEN: usize = 3;
123
124fn read_cp_id(body: &[u8]) -> u32 {
125 ((body[0] as u32) << 16) | ((body[1] as u32) << 8) | body[2] as u32
126}
127
128fn write_cp_id(id: u32, buf: &mut [u8]) {
129 buf[0] = (id >> 16) as u8;
130 buf[1] = (id >> 8) as u8;
131 buf[2] = id as u8;
132}
133
134impl<'a> Parse<'a> for CpQuery {
137 type Error = Error;
138 fn parse(bytes: &'a [u8]) -> Result<Self> {
139 let body = objects::parse_apdu_header(bytes, tag::CP_QUERY, "cp_query")?;
140 if body.len() < CP_ID_LEN {
141 return Err(Error::BufferTooShort {
142 need: CP_ID_LEN,
143 have: body.len(),
144 what: "cp_query",
145 });
146 }
147 Ok(Self {
148 copy_protection_id: read_cp_id(body),
149 })
150 }
151}
152impl Serialize for CpQuery {
153 type Error = Error;
154 fn serialized_len(&self) -> usize {
155 objects::apdu_len(CP_ID_LEN)
156 }
157 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
158 let pos = objects::write_apdu_header(tag::CP_QUERY, CP_ID_LEN, buf)?;
159 write_cp_id(self.copy_protection_id, &mut buf[pos..]);
160 Ok(pos + CP_ID_LEN)
161 }
162}
163
164const CP_REPLY_BODY: usize = CP_ID_LEN + 1;
168
169impl<'a> Parse<'a> for CpReply {
170 type Error = Error;
171 fn parse(bytes: &'a [u8]) -> Result<Self> {
172 let body = objects::parse_apdu_header(bytes, tag::CP_REPLY, "cp_reply")?;
173 if body.len() < CP_REPLY_BODY {
174 return Err(Error::BufferTooShort {
175 need: CP_REPLY_BODY,
176 have: body.len(),
177 what: "cp_reply",
178 });
179 }
180 Ok(Self {
181 copy_protection_id: read_cp_id(body),
182 status: CpStatus::from_u8(body[CP_ID_LEN]),
183 })
184 }
185}
186impl Serialize for CpReply {
187 type Error = Error;
188 fn serialized_len(&self) -> usize {
189 objects::apdu_len(CP_REPLY_BODY)
190 }
191 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
192 let pos = objects::write_apdu_header(tag::CP_REPLY, CP_REPLY_BODY, buf)?;
193 write_cp_id(self.copy_protection_id, &mut buf[pos..]);
194 buf[pos + CP_ID_LEN] = self.status.to_u8();
195 Ok(pos + CP_REPLY_BODY)
196 }
197}
198
199impl<'a> Parse<'a> for CpCommand<'a> {
202 type Error = Error;
203 fn parse(bytes: &'a [u8]) -> Result<Self> {
204 let body = objects::parse_apdu_header(bytes, tag::CP_COMMAND, "cp_command")?;
205 if body.len() < CP_ID_LEN {
206 return Err(Error::BufferTooShort {
207 need: CP_ID_LEN,
208 have: body.len(),
209 what: "cp_command",
210 });
211 }
212 Ok(Self {
213 copy_protection_id: read_cp_id(body),
214 command_bytes: &body[CP_ID_LEN..],
215 })
216 }
217}
218impl Serialize for CpCommand<'_> {
219 type Error = Error;
220 fn serialized_len(&self) -> usize {
221 objects::apdu_len(CP_ID_LEN + self.command_bytes.len())
222 }
223 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
224 let body_len = CP_ID_LEN + self.command_bytes.len();
225 let mut pos = objects::write_apdu_header(tag::CP_COMMAND, body_len, buf)?;
226 write_cp_id(self.copy_protection_id, &mut buf[pos..]);
227 pos += CP_ID_LEN;
228 buf[pos..pos + self.command_bytes.len()].copy_from_slice(self.command_bytes);
229 Ok(pos + self.command_bytes.len())
230 }
231}
232
233impl<'a> Parse<'a> for CpResponse<'a> {
236 type Error = Error;
237 fn parse(bytes: &'a [u8]) -> Result<Self> {
238 let body = objects::parse_apdu_header(bytes, tag::CP_RESPONSE, "cp_response")?;
239 if body.len() < CP_ID_LEN {
240 return Err(Error::BufferTooShort {
241 need: CP_ID_LEN,
242 have: body.len(),
243 what: "cp_response",
244 });
245 }
246 Ok(Self {
247 copy_protection_id: read_cp_id(body),
248 response_bytes: &body[CP_ID_LEN..],
249 })
250 }
251}
252impl Serialize for CpResponse<'_> {
253 type Error = Error;
254 fn serialized_len(&self) -> usize {
255 objects::apdu_len(CP_ID_LEN + self.response_bytes.len())
256 }
257 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
258 let body_len = CP_ID_LEN + self.response_bytes.len();
259 let mut pos = objects::write_apdu_header(tag::CP_RESPONSE, body_len, buf)?;
260 write_cp_id(self.copy_protection_id, &mut buf[pos..]);
261 pos += CP_ID_LEN;
262 buf[pos..pos + self.response_bytes.len()].copy_from_slice(self.response_bytes);
263 Ok(pos + self.response_bytes.len())
264 }
265}
266
267#[derive(Debug, Clone, PartialEq, Eq)]
269#[cfg_attr(feature = "serde", derive(serde::Serialize))]
270#[non_exhaustive]
271pub enum CopyProtectionApdu<'a> {
272 CpQuery(CpQuery),
274 CpReply(CpReply),
276 CpCommand(CpCommand<'a>),
278 CpResponse(CpResponse<'a>),
280}
281
282impl<'a> CopyProtectionApdu<'a> {
283 pub fn parse(body: &'a [u8]) -> Result<Self> {
285 if body.len() < 3 {
286 return Err(Error::BufferTooShort {
287 need: 3,
288 have: body.len(),
289 what: "copy_protection apdu_tag",
290 });
291 }
292 let t = ApduTag::from_bytes(body[0], body[1], body[2]);
293 match t {
294 tag::CP_QUERY => Ok(Self::CpQuery(CpQuery::parse(body)?)),
295 tag::CP_REPLY => Ok(Self::CpReply(CpReply::parse(body)?)),
296 tag::CP_COMMAND => Ok(Self::CpCommand(CpCommand::parse(body)?)),
297 tag::CP_RESPONSE => Ok(Self::CpResponse(CpResponse::parse(body)?)),
298 _ => Err(Error::UnexpectedApduTag {
299 got: t.as_u24(),
300 expected: tag::CP_QUERY.as_u24(),
301 what: "copy_protection",
302 }),
303 }
304 }
305}
306
307impl Serialize for CopyProtectionApdu<'_> {
308 type Error = Error;
309 fn serialized_len(&self) -> usize {
310 match self {
311 Self::CpQuery(o) => o.serialized_len(),
312 Self::CpReply(o) => o.serialized_len(),
313 Self::CpCommand(o) => o.serialized_len(),
314 Self::CpResponse(o) => o.serialized_len(),
315 }
316 }
317 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
318 match self {
319 Self::CpQuery(o) => o.serialize_into(buf),
320 Self::CpReply(o) => o.serialize_into(buf),
321 Self::CpCommand(o) => o.serialize_into(buf),
322 Self::CpResponse(o) => o.serialize_into(buf),
323 }
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn cp_query_round_trips_and_bites() {
333 let q = CpQuery {
334 copy_protection_id: 0xAABBCC,
335 };
336 let bytes = q.to_bytes();
337 assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x03, 0xAA, 0xBB, 0xCC]);
338 assert_eq!(CpQuery::parse(&bytes).unwrap(), q);
339 let other = CpQuery {
340 copy_protection_id: 0xAABBCD,
341 };
342 assert_ne!(bytes, other.to_bytes());
343 }
344
345 #[test]
346 fn cp_reply_round_trips_and_bites() {
347 let r = CpReply {
348 copy_protection_id: 0x010203,
349 status: CpStatus::Active,
350 };
351 let bytes = r.to_bytes();
352 assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x04, 0x01, 0x02, 0x03, 0x02]);
353 assert_eq!(CpReply::parse(&bytes).unwrap(), r);
354 assert_eq!(r.status.name(), "Copy Protection Active");
355 let mut other = r;
356 other.status = CpStatus::IdMismatch;
357 assert_ne!(bytes, other.to_bytes());
358 assert_eq!(other.to_bytes()[7], 0xFF);
359 }
360
361 #[test]
362 fn cp_command_round_trips_and_bites() {
363 let c = CpCommand {
364 copy_protection_id: 0x112233,
365 command_bytes: &[0xDE, 0xAD, 0xBE, 0xEF],
366 };
367 let bytes = c.to_bytes();
368 assert_eq!(
370 bytes,
371 [0x9F, 0x80, 0x02, 0x07, 0x11, 0x22, 0x33, 0xDE, 0xAD, 0xBE, 0xEF]
372 );
373 assert_eq!(CpCommand::parse(&bytes).unwrap(), c);
374 let other = CpCommand {
375 copy_protection_id: 0x112233,
376 command_bytes: &[0xDE, 0xAD, 0xBE, 0x00],
377 };
378 assert_ne!(bytes, other.to_bytes());
379 }
380
381 #[test]
382 fn cp_response_round_trips() {
383 let r = CpResponse {
384 copy_protection_id: 0x445566,
385 response_bytes: &[0x01, 0x02],
386 };
387 let bytes = r.to_bytes();
388 assert_eq!(
389 bytes,
390 [0x9F, 0x80, 0x03, 0x05, 0x44, 0x55, 0x66, 0x01, 0x02]
391 );
392 assert_eq!(CpResponse::parse(&bytes).unwrap(), r);
393 }
394
395 #[test]
396 fn dispatch_routes_each_tag() {
397 let q = CpQuery {
398 copy_protection_id: 0,
399 }
400 .to_bytes();
401 assert!(matches!(
402 CopyProtectionApdu::parse(&q).unwrap(),
403 CopyProtectionApdu::CpQuery(_)
404 ));
405 let resp = CpResponse {
406 copy_protection_id: 0x1,
407 response_bytes: &[0xFF],
408 }
409 .to_bytes();
410 let parsed = CopyProtectionApdu::parse(&resp).unwrap();
411 assert!(matches!(parsed, CopyProtectionApdu::CpResponse(_)));
412 assert_eq!(parsed.to_bytes(), resp);
413 }
414}