1use std::collections::BTreeMap;
12
13use crate::resource::ser;
14use broadcast_common::Parse;
15use dvb_ci::resource::ResourceId;
16use dvb_ci::spdu::{
17 CloseSessionRequest, CloseSessionResponse, CreateSessionResponse, OpenSessionRequest,
18 OpenSessionResponse, SessionNumber, SessionStatus, tags,
19};
20
21#[derive(Debug, Default, Clone, PartialEq, Eq)]
23pub struct SessionOut {
24 pub spdus: Vec<Vec<u8>>,
26 pub apdus: Vec<(u16, Vec<u8>)>,
28 pub opened: Vec<(u16, ResourceId)>,
30 pub closed: Vec<u16>,
32}
33
34#[derive(Debug, Default)]
36pub struct SessionLayer {
37 sessions: BTreeMap<u16, ResourceId>,
38 next: u16,
39}
40
41impl SessionLayer {
42 #[must_use]
44 pub fn new() -> Self {
45 Self {
46 sessions: BTreeMap::new(),
47 next: 1, }
49 }
50
51 #[must_use]
53 pub fn resource_of(&self, session_nb: u16) -> Option<ResourceId> {
54 self.sessions.get(&session_nb).copied()
55 }
56
57 #[must_use]
63 pub fn session_for(&self, resource: ResourceId) -> Option<u16> {
64 self.sessions
65 .iter()
66 .find(|&(_, &r)| r == resource)
67 .map(|(&n, _)| n)
68 }
69
70 #[must_use]
72 pub fn sessions(&self) -> Vec<(u16, ResourceId)> {
73 self.sessions.iter().map(|(&n, &r)| (n, r)).collect()
74 }
75
76 #[must_use]
78 pub fn len(&self) -> usize {
79 self.sessions.len()
80 }
81
82 #[must_use]
84 pub fn is_empty(&self) -> bool {
85 self.sessions.is_empty()
86 }
87
88 fn alloc(&mut self) -> u16 {
94 let start = self.next;
95 loop {
96 let nb = self.next;
97 self.next = self.next.checked_add(1).filter(|&n| n != 0).unwrap_or(1);
98 if !self.sessions.contains_key(&nb) {
99 return nb;
100 }
101 if self.next == start {
106 return nb;
107 }
108 }
109 }
110
111 pub fn create_session(&mut self, resource: ResourceId) -> dvb_ci::Result<Vec<u8>> {
120 ser(&OpenSessionRequest { resource })
121 }
122
123 pub fn send_apdu(&self, session_nb: u16, apdu: &[u8]) -> dvb_ci::Result<Vec<u8>> {
125 let mut v = ser(&SessionNumber { session_nb })?;
126 v.extend_from_slice(apdu);
127 Ok(v)
128 }
129
130 pub fn close(&mut self, session_nb: u16) -> dvb_ci::Result<Vec<u8>> {
132 self.sessions.remove(&session_nb);
133 ser(&CloseSessionRequest { session_nb })
134 }
135
136 fn try_bind_module_chosen(&mut self, session_nb: u16, resource: ResourceId) -> bool {
149 if session_nb == 0 {
150 return false;
151 }
152 match self.sessions.get(&session_nb) {
153 Some(&existing) => existing == resource,
154 None => {
155 self.sessions.insert(session_nb, resource);
156 true
157 }
158 }
159 }
160
161 pub fn on_spdu(
164 &mut self,
165 spdu: &[u8],
166 provides: impl Fn(ResourceId) -> bool,
167 ) -> dvb_ci::Result<SessionOut> {
168 let mut out = SessionOut::default();
169 match spdu.first().copied() {
170 Some(tags::OPEN_SESSION_REQUEST) => {
172 let req = OpenSessionRequest::parse(spdu)?;
175 if provides(req.resource) {
176 let session_nb = self.alloc();
177 self.sessions.insert(session_nb, req.resource);
178 out.spdus.push(ser(&OpenSessionResponse {
179 status: SessionStatus::Ok,
180 resource: req.resource,
181 session_nb,
182 })?);
183 out.opened.push((session_nb, req.resource));
184 } else {
185 out.spdus.push(ser(&OpenSessionResponse {
186 status: SessionStatus::ResourceNonExistent,
187 resource: req.resource,
188 session_nb: 0,
189 })?);
190 }
191 }
192 Some(tags::OPEN_SESSION_RESPONSE) => {
197 let resp = OpenSessionResponse::parse(spdu)?;
198 if resp.status == SessionStatus::Ok {
199 out.opened.extend(
200 self.try_bind_module_chosen(resp.session_nb, resp.resource)
201 .then_some((resp.session_nb, resp.resource)),
202 );
203 }
204 }
205 Some(tags::CREATE_SESSION_RESPONSE) => {
207 let resp = CreateSessionResponse::parse(spdu)?;
208 if resp.status == SessionStatus::Ok {
209 out.opened.extend(
210 self.try_bind_module_chosen(resp.session_nb, resp.resource)
211 .then_some((resp.session_nb, resp.resource)),
212 );
213 }
214 }
215 Some(tags::CLOSE_SESSION_REQUEST) => {
217 let req = CloseSessionRequest::parse(spdu)?;
218 self.sessions.remove(&req.session_nb);
219 out.spdus.push(ser(&CloseSessionResponse {
220 status: SessionStatus::Ok,
221 session_nb: req.session_nb,
222 })?);
223 out.closed.push(req.session_nb);
224 }
225 Some(tags::CLOSE_SESSION_RESPONSE) => {
227 let resp = CloseSessionResponse::parse(spdu)?;
228 self.sessions.remove(&resp.session_nb);
229 out.closed.push(resp.session_nb);
230 }
231 Some(tags::SESSION_NUMBER) => {
233 let sn = SessionNumber::parse(spdu)?;
234 if spdu.len() > SessionNumber::HEADER_LEN {
235 out.apdus
236 .push((sn.session_nb, spdu[SessionNumber::HEADER_LEN..].to_vec()));
237 }
238 }
239 _ => {}
240 }
241 Ok(out)
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use dvb_ci::resource::{APPLICATION_INFORMATION, RESOURCE_MANAGER};
249
250 fn provides_rm(r: ResourceId) -> bool {
251 r == RESOURCE_MANAGER
252 }
253
254 #[test]
255 fn session_for_returns_the_lowest_open_session_of_a_resource() {
256 let mut s = SessionLayer::new();
257 assert_eq!(s.session_for(RESOURCE_MANAGER), None);
258 let rm_req = ser(&OpenSessionRequest {
259 resource: RESOURCE_MANAGER,
260 })
261 .unwrap();
262 let ai_req = ser(&OpenSessionRequest {
263 resource: APPLICATION_INFORMATION,
264 })
265 .unwrap();
266 let all = |_: ResourceId| true;
267 let rm = s.on_spdu(&rm_req, all).unwrap().opened[0].0;
268 let ai = s.on_spdu(&ai_req, all).unwrap().opened[0].0;
269 assert_ne!(rm, ai);
270 assert_eq!(s.session_for(RESOURCE_MANAGER), Some(rm));
271 assert_eq!(s.session_for(APPLICATION_INFORMATION), Some(ai));
272 s.close(rm).unwrap();
274 assert_eq!(s.session_for(RESOURCE_MANAGER), None);
275 }
276
277 #[test]
278 fn open_request_for_provided_resource_grants_and_tracks() {
279 let mut s = SessionLayer::new();
280 let req = ser(&OpenSessionRequest {
281 resource: RESOURCE_MANAGER,
282 })
283 .unwrap();
284 let out = s.on_spdu(&req, provides_rm).unwrap();
285 assert_eq!(out.opened.len(), 1);
286 let (nb, res) = out.opened[0];
287 assert_eq!(res, RESOURCE_MANAGER);
288 assert_eq!(s.resource_of(nb), Some(RESOURCE_MANAGER));
289 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
291 assert_eq!(resp.status, SessionStatus::Ok);
292 assert_eq!(resp.session_nb, nb);
293 }
294
295 #[test]
296 fn open_request_for_absent_resource_denied() {
297 let mut s = SessionLayer::new();
298 let req = ser(&OpenSessionRequest {
299 resource: APPLICATION_INFORMATION,
300 })
301 .unwrap();
302 let out = s.on_spdu(&req, provides_rm).unwrap();
303 assert!(out.opened.is_empty());
304 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
305 assert_eq!(resp.status, SessionStatus::ResourceNonExistent);
306 assert!(s.is_empty());
307 }
308
309 #[test]
310 fn create_session_tracked_on_ok_response() {
311 let mut s = SessionLayer::new();
312 let _spdu = s.create_session(APPLICATION_INFORMATION).unwrap();
313 let resp = ser(&CreateSessionResponse {
315 status: SessionStatus::Ok,
316 resource: APPLICATION_INFORMATION,
317 session_nb: 1,
318 })
319 .unwrap();
320 let out = s.on_spdu(&resp, |_| false).unwrap();
321 assert_eq!(out.opened, vec![(1, APPLICATION_INFORMATION)]);
322 assert_eq!(s.resource_of(1), Some(APPLICATION_INFORMATION));
323 }
324
325 #[test]
330 fn module_chosen_session_nb_colliding_with_a_different_resource_is_rejected() {
331 let mut s = SessionLayer::new();
332 let open = ser(&OpenSessionRequest {
334 resource: RESOURCE_MANAGER,
335 })
336 .unwrap();
337 let opened = s.on_spdu(&open, provides_rm).unwrap().opened;
338 let nb = opened[0].0;
339 assert_eq!(s.resource_of(nb), Some(RESOURCE_MANAGER));
340
341 let colliding = ser(&CreateSessionResponse {
344 status: SessionStatus::Ok,
345 resource: APPLICATION_INFORMATION,
346 session_nb: nb,
347 })
348 .unwrap();
349 let out = s.on_spdu(&colliding, |_| false).unwrap();
350 assert!(
351 out.opened.is_empty(),
352 "a colliding module-chosen session_nb must not be reported as opened"
353 );
354 assert_eq!(
355 s.resource_of(nb),
356 Some(RESOURCE_MANAGER),
357 "the original binding must survive the collision attempt"
358 );
359 }
360
361 #[test]
364 fn module_chosen_session_nb_zero_is_rejected() {
365 let mut s = SessionLayer::new();
366 let resp = ser(&OpenSessionResponse {
367 status: SessionStatus::Ok,
368 resource: APPLICATION_INFORMATION,
369 session_nb: 0,
370 })
371 .unwrap();
372 let out = s.on_spdu(&resp, |_| false).unwrap();
373 assert!(out.opened.is_empty());
374 assert_eq!(s.resource_of(0), None);
375 }
376
377 #[test]
382 fn alloc_probes_past_a_still_open_session_across_wraparound() {
383 let mut s = SessionLayer::new();
384 s.next = 65535;
386 let first = s.alloc();
387 assert_eq!(first, 65535);
388 s.sessions.insert(first, APPLICATION_INFORMATION);
389 s.sessions.insert(1, RESOURCE_MANAGER);
392 let second = s.alloc();
393 assert_ne!(
394 second, 1,
395 "alloc must not hand out a session_nb that is still open"
396 );
397 assert_eq!(s.resource_of(1), Some(RESOURCE_MANAGER), "untouched");
398 }
399
400 #[test]
401 fn session_number_routes_apdu_up() {
402 let mut s = SessionLayer::new();
403 let apdu = [0x9F, 0x80, 0x21, 0x00];
404 let mut spdu = ser(&SessionNumber { session_nb: 7 }).unwrap();
405 spdu.extend_from_slice(&apdu);
406 let out = s.on_spdu(&spdu, |_| false).unwrap();
407 assert_eq!(out.apdus, vec![(7, apdu.to_vec())]);
408 }
409
410 #[test]
411 fn close_request_acks_and_removes() {
412 let mut s = SessionLayer::new();
413 let req = ser(&OpenSessionRequest {
415 resource: RESOURCE_MANAGER,
416 })
417 .unwrap();
418 let nb = s.on_spdu(&req, provides_rm).unwrap().opened[0].0;
419 let close = ser(&CloseSessionRequest { session_nb: nb }).unwrap();
421 let out = s.on_spdu(&close, |_| false).unwrap();
422 assert_eq!(out.closed, vec![nb]);
423 assert!(s.is_empty());
424 assert_eq!(out.spdus[0][0], tags::CLOSE_SESSION_RESPONSE);
426 }
427
428 #[test]
429 fn send_apdu_prefixes_session_number() {
430 let s = SessionLayer::new();
431 let wire = s.send_apdu(3, &[0xAA, 0xBB]).unwrap();
432 let sn = SessionNumber::parse(&wire).unwrap();
433 assert_eq!(sn.session_nb, 3);
434 assert_eq!(&wire[SessionNumber::HEADER_LEN..], &[0xAA, 0xBB]);
435 }
436}