1use std::collections::BTreeMap;
12
13use broadcast_common::{Parse, Serialize};
14use dvb_ci::resource::ResourceId;
15use dvb_ci::spdu::{
16 CloseSessionRequest, CloseSessionResponse, CreateSessionResponse, OpenSessionRequest,
17 OpenSessionResponse, SessionNumber, SessionStatus, tags,
18};
19
20fn ser<S: Serialize>(s: &S) -> Vec<u8> {
21 let mut b = vec![0u8; s.serialized_len()];
22 match s.serialize_into(&mut b) {
25 Ok(n) => b.truncate(n),
26 Err(_) => b.clear(),
27 }
28 b
29}
30
31#[derive(Debug, Default, Clone, PartialEq, Eq)]
33pub struct SessionOut {
34 pub spdus: Vec<Vec<u8>>,
36 pub apdus: Vec<(u16, Vec<u8>)>,
38 pub opened: Vec<(u16, ResourceId)>,
40 pub closed: Vec<u16>,
42}
43
44#[derive(Debug, Default)]
46pub struct SessionLayer {
47 sessions: BTreeMap<u16, ResourceId>,
48 next: u16,
49}
50
51impl SessionLayer {
52 #[must_use]
54 pub fn new() -> Self {
55 Self {
56 sessions: BTreeMap::new(),
57 next: 1, }
59 }
60
61 #[must_use]
63 pub fn resource_of(&self, session_nb: u16) -> Option<ResourceId> {
64 self.sessions.get(&session_nb).copied()
65 }
66
67 #[must_use]
69 pub fn sessions(&self) -> Vec<(u16, ResourceId)> {
70 self.sessions.iter().map(|(&n, &r)| (n, r)).collect()
71 }
72
73 #[must_use]
75 pub fn len(&self) -> usize {
76 self.sessions.len()
77 }
78
79 #[must_use]
81 pub fn is_empty(&self) -> bool {
82 self.sessions.is_empty()
83 }
84
85 fn alloc(&mut self) -> u16 {
86 let nb = self.next;
87 self.next = self.next.checked_add(1).filter(|&n| n != 0).unwrap_or(1);
88 nb
89 }
90
91 pub fn create_session(&mut self, resource: ResourceId) -> Vec<u8> {
100 ser(&OpenSessionRequest { resource })
101 }
102
103 #[must_use]
105 pub fn send_apdu(&self, session_nb: u16, apdu: &[u8]) -> Vec<u8> {
106 let mut v = ser(&SessionNumber { session_nb });
107 v.extend_from_slice(apdu);
108 v
109 }
110
111 pub fn close(&mut self, session_nb: u16) -> Vec<u8> {
113 self.sessions.remove(&session_nb);
114 ser(&CloseSessionRequest { session_nb })
115 }
116
117 pub fn on_spdu(&mut self, spdu: &[u8], provides: impl Fn(ResourceId) -> bool) -> SessionOut {
120 let mut out = SessionOut::default();
121 match spdu.first().copied() {
122 Some(tags::OPEN_SESSION_REQUEST) if let Ok(req) = OpenSessionRequest::parse(spdu) => {
124 if provides(req.resource) {
125 let session_nb = self.alloc();
126 self.sessions.insert(session_nb, req.resource);
127 out.spdus.push(ser(&OpenSessionResponse {
128 status: SessionStatus::Ok,
129 resource: req.resource,
130 session_nb,
131 }));
132 out.opened.push((session_nb, req.resource));
133 } else {
134 out.spdus.push(ser(&OpenSessionResponse {
135 status: SessionStatus::ResourceNonExistent,
136 resource: req.resource,
137 session_nb: 0,
138 }));
139 }
140 }
141 Some(tags::OPEN_SESSION_RESPONSE)
144 if let Ok(resp) = OpenSessionResponse::parse(spdu)
145 && resp.status == SessionStatus::Ok =>
146 {
147 self.sessions.insert(resp.session_nb, resp.resource);
148 out.opened.push((resp.session_nb, resp.resource));
149 }
150 Some(tags::CREATE_SESSION_RESPONSE)
152 if let Ok(resp) = CreateSessionResponse::parse(spdu)
153 && resp.status == SessionStatus::Ok =>
154 {
155 self.sessions.insert(resp.session_nb, resp.resource);
156 out.opened.push((resp.session_nb, resp.resource));
157 }
158 Some(tags::CLOSE_SESSION_REQUEST) if let Ok(req) = CloseSessionRequest::parse(spdu) => {
160 self.sessions.remove(&req.session_nb);
161 out.spdus.push(ser(&CloseSessionResponse {
162 status: SessionStatus::Ok,
163 session_nb: req.session_nb,
164 }));
165 out.closed.push(req.session_nb);
166 }
167 Some(tags::CLOSE_SESSION_RESPONSE)
169 if let Ok(resp) = CloseSessionResponse::parse(spdu) =>
170 {
171 self.sessions.remove(&resp.session_nb);
172 out.closed.push(resp.session_nb);
173 }
174 Some(tags::SESSION_NUMBER)
176 if let Ok(sn) = SessionNumber::parse(spdu)
177 && spdu.len() > SessionNumber::HEADER_LEN =>
178 {
179 out.apdus
180 .push((sn.session_nb, spdu[SessionNumber::HEADER_LEN..].to_vec()));
181 }
182 _ => {}
183 }
184 out
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use dvb_ci::resource::{APPLICATION_INFORMATION, RESOURCE_MANAGER};
192
193 fn provides_rm(r: ResourceId) -> bool {
194 r == RESOURCE_MANAGER
195 }
196
197 #[test]
198 fn open_request_for_provided_resource_grants_and_tracks() {
199 let mut s = SessionLayer::new();
200 let req = ser(&OpenSessionRequest {
201 resource: RESOURCE_MANAGER,
202 });
203 let out = s.on_spdu(&req, provides_rm);
204 assert_eq!(out.opened.len(), 1);
205 let (nb, res) = out.opened[0];
206 assert_eq!(res, RESOURCE_MANAGER);
207 assert_eq!(s.resource_of(nb), Some(RESOURCE_MANAGER));
208 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
210 assert_eq!(resp.status, SessionStatus::Ok);
211 assert_eq!(resp.session_nb, nb);
212 }
213
214 #[test]
215 fn open_request_for_absent_resource_denied() {
216 let mut s = SessionLayer::new();
217 let req = ser(&OpenSessionRequest {
218 resource: APPLICATION_INFORMATION,
219 });
220 let out = s.on_spdu(&req, provides_rm);
221 assert!(out.opened.is_empty());
222 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
223 assert_eq!(resp.status, SessionStatus::ResourceNonExistent);
224 assert!(s.is_empty());
225 }
226
227 #[test]
228 fn create_session_tracked_on_ok_response() {
229 let mut s = SessionLayer::new();
230 let _spdu = s.create_session(APPLICATION_INFORMATION);
231 let resp = ser(&CreateSessionResponse {
233 status: SessionStatus::Ok,
234 resource: APPLICATION_INFORMATION,
235 session_nb: 1,
236 });
237 let out = s.on_spdu(&resp, |_| false);
238 assert_eq!(out.opened, vec![(1, APPLICATION_INFORMATION)]);
239 assert_eq!(s.resource_of(1), Some(APPLICATION_INFORMATION));
240 }
241
242 #[test]
243 fn session_number_routes_apdu_up() {
244 let mut s = SessionLayer::new();
245 let apdu = [0x9F, 0x80, 0x21, 0x00];
246 let mut spdu = ser(&SessionNumber { session_nb: 7 });
247 spdu.extend_from_slice(&apdu);
248 let out = s.on_spdu(&spdu, |_| false);
249 assert_eq!(out.apdus, vec![(7, apdu.to_vec())]);
250 }
251
252 #[test]
253 fn close_request_acks_and_removes() {
254 let mut s = SessionLayer::new();
255 let req = ser(&OpenSessionRequest {
257 resource: RESOURCE_MANAGER,
258 });
259 let nb = s.on_spdu(&req, provides_rm).opened[0].0;
260 let close = ser(&CloseSessionRequest { session_nb: nb });
262 let out = s.on_spdu(&close, |_| false);
263 assert_eq!(out.closed, vec![nb]);
264 assert!(s.is_empty());
265 assert_eq!(out.spdus[0][0], tags::CLOSE_SESSION_RESPONSE);
267 }
268
269 #[test]
270 fn send_apdu_prefixes_session_number() {
271 let s = SessionLayer::new();
272 let wire = s.send_apdu(3, &[0xAA, 0xBB]);
273 let sn = SessionNumber::parse(&wire).unwrap();
274 assert_eq!(sn.session_nb, 3);
275 assert_eq!(&wire[SessionNumber::HEADER_LEN..], &[0xAA, 0xBB]);
276 }
277}