use crate::error::{Error, Result};
use crate::objects;
use crate::tag::ApduTag;
use alloc::vec::Vec;
use dvb_common::{Parse, Serialize};
pub mod tag {
use crate::tag::ApduTag;
pub const TUNE_LCN_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x07);
pub const TUNE_IP_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x08);
pub const TUNE_TRIPLET_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x09);
pub const TUNER_STATUS_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x0A);
pub const TUNER_STATUS_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x0B);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum HostControlMode {
MultiStream,
BaseV3,
}
impl HostControlMode {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::MultiStream => "multi_stream",
Self::BaseV3 => "base_v3",
}
}
}
dvb_common::impl_spec_display!(HostControlMode);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TuneTripletReq {
pub background_tune: bool,
pub tune_quietly: bool,
pub keep_app_running: bool,
pub original_network_id: u16,
pub transport_stream_id: u16,
pub service_id: u16,
pub delivery_system_descriptor_tag: u8,
pub descriptor_tag_extension: Option<u8>,
}
const TRIPLET_BODY: usize = 1 + 2 + 2 + 2 + 1 + 1;
const DSD_TAG_EXTENSION: u8 = 0x7F;
const TRIPLET_BACKGROUND_BIT: u8 = 0x04;
const TRIPLET_QUIETLY_BIT: u8 = 0x02;
const TRIPLET_KEEP_BIT: u8 = 0x01;
impl<'a> Parse<'a> for TuneTripletReq {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::TUNE_TRIPLET_REQ, "tune_triplet_req")?;
if body.len() < TRIPLET_BODY {
return Err(Error::BufferTooShort {
need: TRIPLET_BODY,
have: body.len(),
what: "tune_triplet_req",
});
}
let flags = body[0];
let dsd_tag = body[7];
let descriptor_tag_extension = if dsd_tag == DSD_TAG_EXTENSION {
Some(body[8])
} else {
None
};
Ok(Self {
background_tune: flags & TRIPLET_BACKGROUND_BIT != 0,
tune_quietly: flags & TRIPLET_QUIETLY_BIT != 0,
keep_app_running: flags & TRIPLET_KEEP_BIT != 0,
original_network_id: u16::from_be_bytes([body[1], body[2]]),
transport_stream_id: u16::from_be_bytes([body[3], body[4]]),
service_id: u16::from_be_bytes([body[5], body[6]]),
delivery_system_descriptor_tag: dsd_tag,
descriptor_tag_extension,
})
}
}
impl Serialize for TuneTripletReq {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(TRIPLET_BODY)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(tag::TUNE_TRIPLET_REQ, TRIPLET_BODY, buf)?;
let mut flags = 0u8;
if self.background_tune {
flags |= TRIPLET_BACKGROUND_BIT;
}
if self.tune_quietly {
flags |= TRIPLET_QUIETLY_BIT;
}
if self.keep_app_running {
flags |= TRIPLET_KEEP_BIT;
}
buf[pos] = flags;
buf[pos + 1..pos + 3].copy_from_slice(&self.original_network_id.to_be_bytes());
buf[pos + 3..pos + 5].copy_from_slice(&self.transport_stream_id.to_be_bytes());
buf[pos + 5..pos + 7].copy_from_slice(&self.service_id.to_be_bytes());
buf[pos + 7] = self.delivery_system_descriptor_tag;
buf[pos + 8] = self.descriptor_tag_extension.unwrap_or(0);
Ok(pos + TRIPLET_BODY)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TuneLcnReq {
pub background_tune: bool,
pub tune_quietly: bool,
pub keep_app_running: bool,
pub logical_channel_number: u16,
}
const LCN_BODY: usize = 3;
const LCN_BACKGROUND_BIT: u8 = 0x01;
const LCN_QUIETLY_BIT: u8 = 0x80;
const LCN_KEEP_BIT: u8 = 0x40;
const LCN_MASK: u16 = 0x3FFF;
impl<'a> Parse<'a> for TuneLcnReq {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::TUNE_LCN_REQ, "tune_lcn_req")?;
if body.len() < LCN_BODY {
return Err(Error::BufferTooShort {
need: LCN_BODY,
have: body.len(),
what: "tune_lcn_req",
});
}
let background_tune = body[0] & LCN_BACKGROUND_BIT != 0;
let tune_quietly = body[1] & LCN_QUIETLY_BIT != 0;
let keep_app_running = body[1] & LCN_KEEP_BIT != 0;
let logical_channel_number = u16::from_be_bytes([body[1], body[2]]) & LCN_MASK;
Ok(Self {
background_tune,
tune_quietly,
keep_app_running,
logical_channel_number,
})
}
}
impl Serialize for TuneLcnReq {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(LCN_BODY)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(tag::TUNE_LCN_REQ, LCN_BODY, buf)?;
buf[pos] = if self.background_tune {
LCN_BACKGROUND_BIT
} else {
0
};
let mut hi = (self.logical_channel_number >> 8) as u8 & (LCN_MASK >> 8) as u8;
if self.tune_quietly {
hi |= LCN_QUIETLY_BIT;
}
if self.keep_app_running {
hi |= LCN_KEEP_BIT;
}
buf[pos + 1] = hi;
buf[pos + 2] = self.logical_channel_number as u8;
Ok(pos + LCN_BODY)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TuneIpReq<'a> {
pub mode: HostControlMode,
pub background_tune: bool,
pub tune_quietly: bool,
pub keep_app_running: bool,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub service_location_data: &'a [u8],
}
const TUNE_IP_PREFIX: usize = 2;
const IP_MS_BACKGROUND_BIT: u8 = 0x40;
const IP_MS_QUIETLY_BIT: u8 = 0x20;
const IP_MS_KEEP_BIT: u8 = 0x10;
const IP_V3_QUIETLY_BIT: u8 = 0x20;
const IP_V3_KEEP_BIT: u8 = 0x10;
const SLL_HI_MASK: u8 = 0x0F;
impl<'a> TuneIpReq<'a> {
pub fn parse_mode(bytes: &'a [u8], mode: HostControlMode) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::TUNE_IP_REQ, "tune_ip_req")?;
if body.len() < TUNE_IP_PREFIX {
return Err(Error::BufferTooShort {
need: TUNE_IP_PREFIX,
have: body.len(),
what: "tune_ip_req",
});
}
let (background_tune, tune_quietly, keep_app_running) = match mode {
HostControlMode::MultiStream => (
body[0] & IP_MS_BACKGROUND_BIT != 0,
body[0] & IP_MS_QUIETLY_BIT != 0,
body[0] & IP_MS_KEEP_BIT != 0,
),
HostControlMode::BaseV3 => (
false,
body[0] & IP_V3_QUIETLY_BIT != 0,
body[0] & IP_V3_KEEP_BIT != 0,
),
};
let sll = (((body[0] & SLL_HI_MASK) as usize) << 8) | body[1] as usize;
let data = &body[TUNE_IP_PREFIX..];
if data.len() < sll {
return Err(Error::BufferTooShort {
need: sll,
have: data.len(),
what: "tune_ip_req service_location_data",
});
}
Ok(Self {
mode,
background_tune,
tune_quietly,
keep_app_running,
service_location_data: &data[..sll],
})
}
}
impl Serialize for TuneIpReq<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(TUNE_IP_PREFIX + self.service_location_data.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let sll = self.service_location_data.len();
let body_len = TUNE_IP_PREFIX + sll;
let pos = objects::write_apdu_header(tag::TUNE_IP_REQ, body_len, buf)?;
let mut byte0 = (sll >> 8) as u8 & SLL_HI_MASK;
match self.mode {
HostControlMode::MultiStream => {
if self.background_tune {
byte0 |= IP_MS_BACKGROUND_BIT;
}
if self.tune_quietly {
byte0 |= IP_MS_QUIETLY_BIT;
}
if self.keep_app_running {
byte0 |= IP_MS_KEEP_BIT;
}
}
HostControlMode::BaseV3 => {
if self.tune_quietly {
byte0 |= IP_V3_QUIETLY_BIT;
}
if self.keep_app_running {
byte0 |= IP_V3_KEEP_BIT;
}
}
}
buf[pos] = byte0;
buf[pos + 1] = sll as u8;
buf[pos + 2..pos + 2 + sll].copy_from_slice(self.service_location_data);
Ok(pos + body_len)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TunerStatusReq;
impl<'a> Parse<'a> for TunerStatusReq {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
objects::parse_empty_apdu(bytes, tag::TUNER_STATUS_REQ, "tuner_status_req")?;
Ok(Self)
}
}
impl Serialize for TunerStatusReq {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::empty_apdu_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::serialize_empty_apdu(tag::TUNER_STATUS_REQ, buf)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TunerStatusDsd {
pub connected: bool,
pub delivery_system_descriptor_tag: u8,
pub descriptor_tag_extension: Option<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TunerStatusReply {
pub ip_tune_capable: bool,
pub dsds: Vec<TunerStatusDsd>,
}
const DSD_ENTRY_LEN: usize = 3;
const IP_TUNE_CAPABLE_BIT: u8 = 0x80;
const NUM_DSD_MASK: u8 = 0x7F;
const DSD_CONNECTED_BIT: u8 = 0x01;
impl<'a> Parse<'a> for TunerStatusReply {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body =
objects::parse_apdu_header(bytes, tag::TUNER_STATUS_REPLY, "tuner_status_reply")?;
if body.is_empty() {
return Err(Error::BufferTooShort {
need: 1,
have: 0,
what: "tuner_status_reply",
});
}
let ip_tune_capable = body[0] & IP_TUNE_CAPABLE_BIT != 0;
let num_dsd = (body[0] & NUM_DSD_MASK) as usize;
let mut rest = &body[1..];
let mut dsds = Vec::with_capacity(num_dsd);
for _ in 0..num_dsd {
if rest.len() < DSD_ENTRY_LEN {
return Err(Error::BufferTooShort {
need: DSD_ENTRY_LEN,
have: rest.len(),
what: "tuner_status_reply dsd",
});
}
let connected = rest[0] & DSD_CONNECTED_BIT != 0;
let dsd_tag = rest[1];
let descriptor_tag_extension = if dsd_tag == DSD_TAG_EXTENSION {
Some(rest[2])
} else {
None
};
dsds.push(TunerStatusDsd {
connected,
delivery_system_descriptor_tag: dsd_tag,
descriptor_tag_extension,
});
rest = &rest[DSD_ENTRY_LEN..];
}
Ok(Self {
ip_tune_capable,
dsds,
})
}
}
impl Serialize for TunerStatusReply {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(1 + self.dsds.len() * DSD_ENTRY_LEN)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let body_len = 1 + self.dsds.len() * DSD_ENTRY_LEN;
let mut pos = objects::write_apdu_header(tag::TUNER_STATUS_REPLY, body_len, buf)?;
let mut byte0 = self.dsds.len() as u8 & NUM_DSD_MASK;
if self.ip_tune_capable {
byte0 |= IP_TUNE_CAPABLE_BIT;
}
buf[pos] = byte0;
pos += 1;
for dsd in &self.dsds {
buf[pos] = if dsd.connected { DSD_CONNECTED_BIT } else { 0 };
buf[pos + 1] = dsd.delivery_system_descriptor_tag;
buf[pos + 2] = dsd.descriptor_tag_extension.unwrap_or(0);
pos += DSD_ENTRY_LEN;
}
Ok(pos)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum MultistreamHostControlApdu<'a> {
TuneTripletReq(TuneTripletReq),
TuneLcnReq(TuneLcnReq),
TuneIpReq(TuneIpReq<'a>),
TunerStatusReq(TunerStatusReq),
TunerStatusReply(TunerStatusReply),
}
impl<'a> MultistreamHostControlApdu<'a> {
pub fn parse_mode(body: &'a [u8], mode: HostControlMode) -> Result<Self> {
if body.len() < 3 {
return Err(Error::BufferTooShort {
need: 3,
have: body.len(),
what: "multistream_host_control apdu_tag",
});
}
let t = ApduTag::from_bytes(body[0], body[1], body[2]);
match t {
tag::TUNE_TRIPLET_REQ => Ok(Self::TuneTripletReq(TuneTripletReq::parse(body)?)),
tag::TUNE_LCN_REQ => Ok(Self::TuneLcnReq(TuneLcnReq::parse(body)?)),
tag::TUNE_IP_REQ => Ok(Self::TuneIpReq(TuneIpReq::parse_mode(body, mode)?)),
tag::TUNER_STATUS_REQ => Ok(Self::TunerStatusReq(TunerStatusReq::parse(body)?)),
tag::TUNER_STATUS_REPLY => Ok(Self::TunerStatusReply(TunerStatusReply::parse(body)?)),
_ => Err(Error::UnexpectedApduTag {
got: t.as_u24(),
expected: tag::TUNE_TRIPLET_REQ.as_u24(),
what: "multistream_host_control",
}),
}
}
}
impl Serialize for MultistreamHostControlApdu<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
Self::TuneTripletReq(o) => o.serialized_len(),
Self::TuneLcnReq(o) => o.serialized_len(),
Self::TuneIpReq(o) => o.serialized_len(),
Self::TunerStatusReq(o) => o.serialized_len(),
Self::TunerStatusReply(o) => o.serialized_len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
Self::TuneTripletReq(o) => o.serialize_into(buf),
Self::TuneLcnReq(o) => o.serialize_into(buf),
Self::TuneIpReq(o) => o.serialize_into(buf),
Self::TunerStatusReq(o) => o.serialize_into(buf),
Self::TunerStatusReply(o) => o.serialize_into(buf),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tune_triplet_round_trips_and_bites() {
let t = TuneTripletReq {
background_tune: true,
tune_quietly: false,
keep_app_running: true,
original_network_id: 0x1122,
transport_stream_id: 0x3344,
service_id: 0x5566,
delivery_system_descriptor_tag: 0x44,
descriptor_tag_extension: None,
};
let bytes = t.to_bytes();
assert_eq!(
bytes,
[0x9F, 0x84, 0x09, 0x09, 0x05, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x44, 0x00]
);
assert_eq!(TuneTripletReq::parse(&bytes).unwrap(), t);
let mut other = t;
other.background_tune = false;
assert_eq!(other.to_bytes()[4], 0x01);
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn tune_triplet_with_descriptor_extension() {
let t = TuneTripletReq {
background_tune: false,
tune_quietly: true,
keep_app_running: false,
original_network_id: 0x0001,
transport_stream_id: 0x0002,
service_id: 0x0003,
delivery_system_descriptor_tag: 0x7F,
descriptor_tag_extension: Some(0x79),
};
let bytes = t.to_bytes();
assert_eq!(
bytes,
[0x9F, 0x84, 0x09, 0x09, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x7F, 0x79]
);
assert_eq!(TuneTripletReq::parse(&bytes).unwrap(), t);
}
#[test]
fn tune_lcn_round_trips_and_bites() {
let t = TuneLcnReq {
background_tune: true,
tune_quietly: true,
keep_app_running: false,
logical_channel_number: 0x0123,
};
let bytes = t.to_bytes();
assert_eq!(bytes, [0x9F, 0x84, 0x07, 0x03, 0x01, 0x81, 0x23]);
assert_eq!(TuneLcnReq::parse(&bytes).unwrap(), t);
let mut other = t;
other.logical_channel_number = 0x3FFE;
assert_ne!(bytes, other.to_bytes());
assert_eq!(
TuneLcnReq::parse(&other.to_bytes())
.unwrap()
.logical_channel_number,
0x3FFE
);
}
#[test]
fn tune_ip_multistream_vs_basev3_distinct_bytes() {
let data: &[u8] = &[0xAA, 0xBB];
let ms = TuneIpReq {
mode: HostControlMode::MultiStream,
background_tune: true,
tune_quietly: true,
keep_app_running: false,
service_location_data: data,
};
let v3 = TuneIpReq {
mode: HostControlMode::BaseV3,
background_tune: false, tune_quietly: true,
keep_app_running: false,
service_location_data: data,
};
let ms_bytes = ms.to_bytes();
let v3_bytes = v3.to_bytes();
assert_eq!(ms_bytes, [0x9F, 0x84, 0x08, 0x04, 0x60, 0x02, 0xAA, 0xBB]);
assert_eq!(v3_bytes, [0x9F, 0x84, 0x08, 0x04, 0x20, 0x02, 0xAA, 0xBB]);
assert_ne!(ms_bytes, v3_bytes);
assert_eq!(
TuneIpReq::parse_mode(&ms_bytes, HostControlMode::MultiStream).unwrap(),
ms
);
assert_eq!(
TuneIpReq::parse_mode(&v3_bytes, HostControlMode::BaseV3).unwrap(),
v3
);
}
#[test]
fn tune_ip_multistream_background_bit_is_v3_reserved() {
let ms = TuneIpReq {
mode: HostControlMode::MultiStream,
background_tune: true,
tune_quietly: false,
keep_app_running: false,
service_location_data: &[],
};
let bytes = ms.to_bytes();
assert_eq!(bytes[4], 0x40);
let as_v3 = TuneIpReq::parse_mode(&bytes, HostControlMode::BaseV3).unwrap();
assert!(!as_v3.background_tune);
assert!(!as_v3.tune_quietly);
assert!(!as_v3.keep_app_running);
}
#[test]
fn tune_ip_empty_location() {
let t = TuneIpReq {
mode: HostControlMode::MultiStream,
background_tune: false,
tune_quietly: false,
keep_app_running: false,
service_location_data: &[],
};
let bytes = t.to_bytes();
assert_eq!(bytes, [0x9F, 0x84, 0x08, 0x02, 0x00, 0x00]);
assert_eq!(
TuneIpReq::parse_mode(&bytes, HostControlMode::MultiStream).unwrap(),
t
);
}
#[test]
fn tuner_status_req_round_trips() {
let bytes = TunerStatusReq.to_bytes();
assert_eq!(bytes, [0x9F, 0x84, 0x0A, 0x00]);
assert_eq!(TunerStatusReq::parse(&bytes).unwrap(), TunerStatusReq);
}
#[test]
fn tuner_status_reply_round_trips_with_two_dsds() {
let r = TunerStatusReply {
ip_tune_capable: true,
dsds: alloc::vec![
TunerStatusDsd {
connected: true,
delivery_system_descriptor_tag: 0x44, descriptor_tag_extension: None,
},
TunerStatusDsd {
connected: false,
delivery_system_descriptor_tag: 0x7F,
descriptor_tag_extension: Some(0x79),
},
],
};
let bytes = r.to_bytes();
assert_eq!(
bytes,
[0x9F, 0x84, 0x0B, 0x07, 0x82, 0x01, 0x44, 0x00, 0x00, 0x7F, 0x79]
);
assert_eq!(TunerStatusReply::parse(&bytes).unwrap(), r);
let mut other = r.clone();
other.ip_tune_capable = false;
assert_eq!(other.to_bytes()[4], 0x02);
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn tuner_status_reply_empty() {
let r = TunerStatusReply {
ip_tune_capable: false,
dsds: Vec::new(),
};
let bytes = r.to_bytes();
assert_eq!(bytes, [0x9F, 0x84, 0x0B, 0x01, 0x00]);
assert_eq!(TunerStatusReply::parse(&bytes).unwrap(), r);
}
#[test]
fn dispatch_routes_each_tag() {
let triplet = TuneTripletReq {
background_tune: false,
tune_quietly: false,
keep_app_running: false,
original_network_id: 1,
transport_stream_id: 2,
service_id: 3,
delivery_system_descriptor_tag: 0,
descriptor_tag_extension: None,
}
.to_bytes();
assert!(matches!(
MultistreamHostControlApdu::parse_mode(&triplet, HostControlMode::MultiStream).unwrap(),
MultistreamHostControlApdu::TuneTripletReq(_)
));
let ip = TuneIpReq {
mode: HostControlMode::MultiStream,
background_tune: true,
tune_quietly: false,
keep_app_running: false,
service_location_data: &[0x01],
}
.to_bytes();
let parsed =
MultistreamHostControlApdu::parse_mode(&ip, HostControlMode::MultiStream).unwrap();
assert!(matches!(parsed, MultistreamHostControlApdu::TuneIpReq(_)));
assert_eq!(parsed.to_bytes(), ip);
assert!(matches!(
MultistreamHostControlApdu::parse_mode(
&[0x9F, 0x84, 0x00, 0x00],
HostControlMode::MultiStream
),
Err(Error::UnexpectedApduTag { .. })
));
}
}