use crate::error::{Error, Result};
use crate::resource::ResourceId;
use crate::tag::{self, ApduTag};
use crate::traits::ApduDef;
use alloc::vec::Vec;
use dvb_common::{Parse, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ProfileEnq;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ProfileChange;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Profile {
pub resources: Vec<ResourceId>,
}
impl<'a> Parse<'a> for ProfileEnq {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
super::parse_empty_apdu(bytes, tag::PROFILE_ENQ, "profile_enq")?;
Ok(Self)
}
}
impl Serialize for ProfileEnq {
type Error = Error;
fn serialized_len(&self) -> usize {
super::empty_apdu_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
super::serialize_empty_apdu(tag::PROFILE_ENQ, buf)
}
}
impl<'a> ApduDef<'a> for ProfileEnq {
const TAG: ApduTag = tag::PROFILE_ENQ;
const NAME: &'static str = "PROFILE_ENQ";
}
impl<'a> Parse<'a> for ProfileChange {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
super::parse_empty_apdu(bytes, tag::PROFILE_CHANGE, "profile_change")?;
Ok(Self)
}
}
impl Serialize for ProfileChange {
type Error = Error;
fn serialized_len(&self) -> usize {
super::empty_apdu_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
super::serialize_empty_apdu(tag::PROFILE_CHANGE, buf)
}
}
impl<'a> ApduDef<'a> for ProfileChange {
const TAG: ApduTag = tag::PROFILE_CHANGE;
const NAME: &'static str = "PROFILE_CHANGE";
}
impl<'a> Parse<'a> for Profile {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = super::parse_apdu_header(bytes, tag::PROFILE, "profile")?;
if body.len() % ResourceId::LEN != 0 {
return Err(Error::InvalidObject {
what: "profile",
reason: "body length is not a multiple of 4",
});
}
let mut resources = Vec::with_capacity(body.len() / ResourceId::LEN);
for chunk in body.chunks_exact(ResourceId::LEN) {
resources.push(ResourceId::parse(chunk)?);
}
Ok(Self { resources })
}
}
impl Serialize for Profile {
type Error = Error;
fn serialized_len(&self) -> usize {
let body = self.resources.len() * ResourceId::LEN;
super::apdu_len(body)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let body_len = self.resources.len() * ResourceId::LEN;
let mut pos = super::write_apdu_header(tag::PROFILE, body_len, buf)?;
for r in &self.resources {
pos += r.serialize_into(&mut buf[pos..])?;
}
Ok(pos)
}
}
impl<'a> ApduDef<'a> for Profile {
const TAG: ApduTag = tag::PROFILE;
const NAME: &'static str = "PROFILE";
}
#[cfg(test)]
mod tests {
use super::*;
use crate::resource::{APPLICATION_INFORMATION, CONDITIONAL_ACCESS_SUPPORT, RESOURCE_MANAGER};
#[test]
fn profile_enq_round_trip() {
let bytes = ProfileEnq.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x10, 0x00]);
assert_eq!(ProfileEnq::parse(&bytes).unwrap(), ProfileEnq);
}
#[test]
fn profile_change_round_trip() {
let bytes = ProfileChange.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x12, 0x00]);
assert_eq!(ProfileChange::parse(&bytes).unwrap(), ProfileChange);
}
#[test]
fn profile_reply_multi_resource_round_trips() {
let p = Profile {
resources: alloc::vec![
RESOURCE_MANAGER,
APPLICATION_INFORMATION,
CONDITIONAL_ACCESS_SUPPORT,
],
};
let bytes = p.to_bytes();
assert_eq!(bytes.len(), 16);
assert_eq!(&bytes[..4], &[0x9F, 0x80, 0x11, 0x0C]); let parsed = Profile::parse(&bytes).unwrap();
assert_eq!(parsed, p);
assert_eq!(parsed.resources.len(), 3);
}
#[test]
fn mutating_resource_changes_bytes() {
let p = Profile {
resources: alloc::vec![RESOURCE_MANAGER],
};
let bytes = p.to_bytes();
let mut other = p.clone();
other.resources[0] = MMI_RES;
assert_ne!(bytes, other.to_bytes());
}
const MMI_RES: ResourceId = crate::resource::MMI;
}