use crate::error::{Error, Result};
use crate::objects;
use crate::tag::ApduTag;
use dvb_common::{Parse, Serialize};
pub mod tag {
use crate::tag::ApduTag;
pub const CP_QUERY: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
pub const CP_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
pub const CP_COMMAND: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
pub const CP_RESPONSE: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum CpStatus {
Inactive,
Active,
IdMismatch,
Reserved(u8),
}
impl CpStatus {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0x01 => Self::Inactive,
0x02 => Self::Active,
0xFF => Self::IdMismatch,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::Inactive => 0x01,
Self::Active => 0x02,
Self::IdMismatch => 0xFF,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Inactive => "Copy Protection Inactive",
Self::Active => "Copy Protection Active",
Self::IdMismatch => "ID mismatch",
Self::Reserved(_) => "reserved",
}
}
}
dvb_common::impl_spec_display!(CpStatus, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CpQuery {
pub copy_protection_id: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CpReply {
pub copy_protection_id: u32,
pub status: CpStatus,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CpCommand<'a> {
pub copy_protection_id: u32,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub command_bytes: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CpResponse<'a> {
pub copy_protection_id: u32,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub response_bytes: &'a [u8],
}
const CP_ID_LEN: usize = 3;
fn read_cp_id(body: &[u8]) -> u32 {
((body[0] as u32) << 16) | ((body[1] as u32) << 8) | body[2] as u32
}
fn write_cp_id(id: u32, buf: &mut [u8]) {
buf[0] = (id >> 16) as u8;
buf[1] = (id >> 8) as u8;
buf[2] = id as u8;
}
impl<'a> Parse<'a> for CpQuery {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::CP_QUERY, "cp_query")?;
if body.len() < CP_ID_LEN {
return Err(Error::BufferTooShort {
need: CP_ID_LEN,
have: body.len(),
what: "cp_query",
});
}
Ok(Self {
copy_protection_id: read_cp_id(body),
})
}
}
impl Serialize for CpQuery {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(CP_ID_LEN)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(tag::CP_QUERY, CP_ID_LEN, buf)?;
write_cp_id(self.copy_protection_id, &mut buf[pos..]);
Ok(pos + CP_ID_LEN)
}
}
const CP_REPLY_BODY: usize = CP_ID_LEN + 1;
impl<'a> Parse<'a> for CpReply {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::CP_REPLY, "cp_reply")?;
if body.len() < CP_REPLY_BODY {
return Err(Error::BufferTooShort {
need: CP_REPLY_BODY,
have: body.len(),
what: "cp_reply",
});
}
Ok(Self {
copy_protection_id: read_cp_id(body),
status: CpStatus::from_u8(body[CP_ID_LEN]),
})
}
}
impl Serialize for CpReply {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(CP_REPLY_BODY)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(tag::CP_REPLY, CP_REPLY_BODY, buf)?;
write_cp_id(self.copy_protection_id, &mut buf[pos..]);
buf[pos + CP_ID_LEN] = self.status.to_u8();
Ok(pos + CP_REPLY_BODY)
}
}
impl<'a> Parse<'a> for CpCommand<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::CP_COMMAND, "cp_command")?;
if body.len() < CP_ID_LEN {
return Err(Error::BufferTooShort {
need: CP_ID_LEN,
have: body.len(),
what: "cp_command",
});
}
Ok(Self {
copy_protection_id: read_cp_id(body),
command_bytes: &body[CP_ID_LEN..],
})
}
}
impl Serialize for CpCommand<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(CP_ID_LEN + self.command_bytes.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let body_len = CP_ID_LEN + self.command_bytes.len();
let mut pos = objects::write_apdu_header(tag::CP_COMMAND, body_len, buf)?;
write_cp_id(self.copy_protection_id, &mut buf[pos..]);
pos += CP_ID_LEN;
buf[pos..pos + self.command_bytes.len()].copy_from_slice(self.command_bytes);
Ok(pos + self.command_bytes.len())
}
}
impl<'a> Parse<'a> for CpResponse<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::CP_RESPONSE, "cp_response")?;
if body.len() < CP_ID_LEN {
return Err(Error::BufferTooShort {
need: CP_ID_LEN,
have: body.len(),
what: "cp_response",
});
}
Ok(Self {
copy_protection_id: read_cp_id(body),
response_bytes: &body[CP_ID_LEN..],
})
}
}
impl Serialize for CpResponse<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(CP_ID_LEN + self.response_bytes.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let body_len = CP_ID_LEN + self.response_bytes.len();
let mut pos = objects::write_apdu_header(tag::CP_RESPONSE, body_len, buf)?;
write_cp_id(self.copy_protection_id, &mut buf[pos..]);
pos += CP_ID_LEN;
buf[pos..pos + self.response_bytes.len()].copy_from_slice(self.response_bytes);
Ok(pos + self.response_bytes.len())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum CopyProtectionApdu<'a> {
CpQuery(CpQuery),
CpReply(CpReply),
CpCommand(CpCommand<'a>),
CpResponse(CpResponse<'a>),
}
impl<'a> CopyProtectionApdu<'a> {
pub fn parse(body: &'a [u8]) -> Result<Self> {
if body.len() < 3 {
return Err(Error::BufferTooShort {
need: 3,
have: body.len(),
what: "copy_protection apdu_tag",
});
}
let t = ApduTag::from_bytes(body[0], body[1], body[2]);
match t {
tag::CP_QUERY => Ok(Self::CpQuery(CpQuery::parse(body)?)),
tag::CP_REPLY => Ok(Self::CpReply(CpReply::parse(body)?)),
tag::CP_COMMAND => Ok(Self::CpCommand(CpCommand::parse(body)?)),
tag::CP_RESPONSE => Ok(Self::CpResponse(CpResponse::parse(body)?)),
_ => Err(Error::UnexpectedApduTag {
got: t.as_u24(),
expected: tag::CP_QUERY.as_u24(),
what: "copy_protection",
}),
}
}
}
impl Serialize for CopyProtectionApdu<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
Self::CpQuery(o) => o.serialized_len(),
Self::CpReply(o) => o.serialized_len(),
Self::CpCommand(o) => o.serialized_len(),
Self::CpResponse(o) => o.serialized_len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
Self::CpQuery(o) => o.serialize_into(buf),
Self::CpReply(o) => o.serialize_into(buf),
Self::CpCommand(o) => o.serialize_into(buf),
Self::CpResponse(o) => o.serialize_into(buf),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cp_query_round_trips_and_bites() {
let q = CpQuery {
copy_protection_id: 0xAABBCC,
};
let bytes = q.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x03, 0xAA, 0xBB, 0xCC]);
assert_eq!(CpQuery::parse(&bytes).unwrap(), q);
let other = CpQuery {
copy_protection_id: 0xAABBCD,
};
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn cp_reply_round_trips_and_bites() {
let r = CpReply {
copy_protection_id: 0x010203,
status: CpStatus::Active,
};
let bytes = r.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x04, 0x01, 0x02, 0x03, 0x02]);
assert_eq!(CpReply::parse(&bytes).unwrap(), r);
assert_eq!(r.status.name(), "Copy Protection Active");
let mut other = r;
other.status = CpStatus::IdMismatch;
assert_ne!(bytes, other.to_bytes());
assert_eq!(other.to_bytes()[7], 0xFF);
}
#[test]
fn cp_command_round_trips_and_bites() {
let c = CpCommand {
copy_protection_id: 0x112233,
command_bytes: &[0xDE, 0xAD, 0xBE, 0xEF],
};
let bytes = c.to_bytes();
assert_eq!(
bytes,
[0x9F, 0x80, 0x02, 0x07, 0x11, 0x22, 0x33, 0xDE, 0xAD, 0xBE, 0xEF]
);
assert_eq!(CpCommand::parse(&bytes).unwrap(), c);
let other = CpCommand {
copy_protection_id: 0x112233,
command_bytes: &[0xDE, 0xAD, 0xBE, 0x00],
};
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn cp_response_round_trips() {
let r = CpResponse {
copy_protection_id: 0x445566,
response_bytes: &[0x01, 0x02],
};
let bytes = r.to_bytes();
assert_eq!(
bytes,
[0x9F, 0x80, 0x03, 0x05, 0x44, 0x55, 0x66, 0x01, 0x02]
);
assert_eq!(CpResponse::parse(&bytes).unwrap(), r);
}
#[test]
fn dispatch_routes_each_tag() {
let q = CpQuery {
copy_protection_id: 0,
}
.to_bytes();
assert!(matches!(
CopyProtectionApdu::parse(&q).unwrap(),
CopyProtectionApdu::CpQuery(_)
));
let resp = CpResponse {
copy_protection_id: 0x1,
response_bytes: &[0xFF],
}
.to_bytes();
let parsed = CopyProtectionApdu::parse(&resp).unwrap();
assert!(matches!(parsed, CopyProtectionApdu::CpResponse(_)));
assert_eq!(parsed.to_bytes(), resp);
}
}