use std::collections::BTreeMap;
use dvb_ci::resource::ResourceId;
use dvb_ci::spdu::{
tags, CloseSessionRequest, CloseSessionResponse, CreateSession, CreateSessionResponse,
OpenSessionRequest, OpenSessionResponse, SessionNumber, SessionStatus,
};
use dvb_common::{Parse, Serialize};
fn ser<S: Serialize>(s: &S) -> Vec<u8> {
let mut b = vec![0u8; s.serialized_len()];
match s.serialize_into(&mut b) {
Ok(n) => b.truncate(n),
Err(_) => b.clear(),
}
b
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct SessionOut {
pub spdus: Vec<Vec<u8>>,
pub apdus: Vec<(u16, Vec<u8>)>,
pub opened: Vec<(u16, ResourceId)>,
pub closed: Vec<u16>,
}
#[derive(Debug, Default)]
pub struct SessionLayer {
sessions: BTreeMap<u16, ResourceId>,
next: u16,
}
impl SessionLayer {
#[must_use]
pub fn new() -> Self {
Self {
sessions: BTreeMap::new(),
next: 1, }
}
#[must_use]
pub fn resource_of(&self, session_nb: u16) -> Option<ResourceId> {
self.sessions.get(&session_nb).copied()
}
#[must_use]
pub fn sessions(&self) -> Vec<(u16, ResourceId)> {
self.sessions.iter().map(|(&n, &r)| (n, r)).collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.sessions.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.sessions.is_empty()
}
fn alloc(&mut self) -> u16 {
let nb = self.next;
self.next = self.next.checked_add(1).filter(|&n| n != 0).unwrap_or(1);
nb
}
pub fn create_session(&mut self, resource: ResourceId) -> Vec<u8> {
let session_nb = self.alloc();
ser(&CreateSession {
resource,
session_nb,
})
}
#[must_use]
pub fn send_apdu(&self, session_nb: u16, apdu: &[u8]) -> Vec<u8> {
let mut v = ser(&SessionNumber { session_nb });
v.extend_from_slice(apdu);
v
}
pub fn close(&mut self, session_nb: u16) -> Vec<u8> {
self.sessions.remove(&session_nb);
ser(&CloseSessionRequest { session_nb })
}
pub fn on_spdu(&mut self, spdu: &[u8], provides: impl Fn(ResourceId) -> bool) -> SessionOut {
let mut out = SessionOut::default();
match spdu.first().copied() {
Some(tags::OPEN_SESSION_REQUEST) => {
if let Ok(req) = OpenSessionRequest::parse(spdu) {
if provides(req.resource) {
let session_nb = self.alloc();
self.sessions.insert(session_nb, req.resource);
out.spdus.push(ser(&OpenSessionResponse {
status: SessionStatus::Ok,
resource: req.resource,
session_nb,
}));
out.opened.push((session_nb, req.resource));
} else {
out.spdus.push(ser(&OpenSessionResponse {
status: SessionStatus::ResourceNonExistent,
resource: req.resource,
session_nb: 0,
}));
}
}
}
Some(tags::CREATE_SESSION_RESPONSE) => {
if let Ok(resp) = CreateSessionResponse::parse(spdu) {
if resp.status == SessionStatus::Ok {
self.sessions.insert(resp.session_nb, resp.resource);
out.opened.push((resp.session_nb, resp.resource));
}
}
}
Some(tags::CLOSE_SESSION_REQUEST) => {
if let Ok(req) = CloseSessionRequest::parse(spdu) {
self.sessions.remove(&req.session_nb);
out.spdus.push(ser(&CloseSessionResponse {
status: SessionStatus::Ok,
session_nb: req.session_nb,
}));
out.closed.push(req.session_nb);
}
}
Some(tags::CLOSE_SESSION_RESPONSE) => {
if let Ok(resp) = CloseSessionResponse::parse(spdu) {
self.sessions.remove(&resp.session_nb);
out.closed.push(resp.session_nb);
}
}
Some(tags::SESSION_NUMBER) => {
if let Ok(sn) = SessionNumber::parse(spdu) {
if spdu.len() > SessionNumber::HEADER_LEN {
out.apdus
.push((sn.session_nb, spdu[SessionNumber::HEADER_LEN..].to_vec()));
}
}
}
_ => {}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use dvb_ci::resource::{APPLICATION_INFORMATION, RESOURCE_MANAGER};
fn provides_rm(r: ResourceId) -> bool {
r == RESOURCE_MANAGER
}
#[test]
fn open_request_for_provided_resource_grants_and_tracks() {
let mut s = SessionLayer::new();
let req = ser(&OpenSessionRequest {
resource: RESOURCE_MANAGER,
});
let out = s.on_spdu(&req, provides_rm);
assert_eq!(out.opened.len(), 1);
let (nb, res) = out.opened[0];
assert_eq!(res, RESOURCE_MANAGER);
assert_eq!(s.resource_of(nb), Some(RESOURCE_MANAGER));
let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
assert_eq!(resp.status, SessionStatus::Ok);
assert_eq!(resp.session_nb, nb);
}
#[test]
fn open_request_for_absent_resource_denied() {
let mut s = SessionLayer::new();
let req = ser(&OpenSessionRequest {
resource: APPLICATION_INFORMATION,
});
let out = s.on_spdu(&req, provides_rm);
assert!(out.opened.is_empty());
let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
assert_eq!(resp.status, SessionStatus::ResourceNonExistent);
assert!(s.is_empty());
}
#[test]
fn create_session_tracked_on_ok_response() {
let mut s = SessionLayer::new();
let _spdu = s.create_session(APPLICATION_INFORMATION);
let resp = ser(&CreateSessionResponse {
status: SessionStatus::Ok,
resource: APPLICATION_INFORMATION,
session_nb: 1,
});
let out = s.on_spdu(&resp, |_| false);
assert_eq!(out.opened, vec![(1, APPLICATION_INFORMATION)]);
assert_eq!(s.resource_of(1), Some(APPLICATION_INFORMATION));
}
#[test]
fn session_number_routes_apdu_up() {
let mut s = SessionLayer::new();
let apdu = [0x9F, 0x80, 0x21, 0x00];
let mut spdu = ser(&SessionNumber { session_nb: 7 });
spdu.extend_from_slice(&apdu);
let out = s.on_spdu(&spdu, |_| false);
assert_eq!(out.apdus, vec![(7, apdu.to_vec())]);
}
#[test]
fn close_request_acks_and_removes() {
let mut s = SessionLayer::new();
let req = ser(&OpenSessionRequest {
resource: RESOURCE_MANAGER,
});
let nb = s.on_spdu(&req, provides_rm).opened[0].0;
let close = ser(&CloseSessionRequest { session_nb: nb });
let out = s.on_spdu(&close, |_| false);
assert_eq!(out.closed, vec![nb]);
assert!(s.is_empty());
assert_eq!(out.spdus[0][0], tags::CLOSE_SESSION_RESPONSE);
}
#[test]
fn send_apdu_prefixes_session_number() {
let s = SessionLayer::new();
let wire = s.send_apdu(3, &[0xAA, 0xBB]);
let sn = SessionNumber::parse(&wire).unwrap();
assert_eq!(sn.session_nb, 3);
assert_eq!(&wire[SessionNumber::HEADER_LEN..], &[0xAA, 0xBB]);
}
}