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 {
121 length_form: dvb_ci::length::LengthForm::Minimal,
122 resource,
123 })
124 }
125
126 pub fn send_apdu(&self, session_nb: u16, apdu: &[u8]) -> dvb_ci::Result<Vec<u8>> {
128 let mut v = ser(&SessionNumber {
129 length_form: dvb_ci::length::LengthForm::Minimal,
130 session_nb,
131 })?;
132 v.extend_from_slice(apdu);
133 Ok(v)
134 }
135
136 pub fn close(&mut self, session_nb: u16) -> dvb_ci::Result<Vec<u8>> {
138 self.sessions.remove(&session_nb);
139 ser(&CloseSessionRequest {
140 length_form: dvb_ci::length::LengthForm::Minimal,
141 session_nb,
142 })
143 }
144
145 fn try_bind_module_chosen(&mut self, session_nb: u16, resource: ResourceId) -> bool {
158 if session_nb == 0 {
159 return false;
160 }
161 match self.sessions.get(&session_nb) {
162 Some(&existing) => existing == resource,
163 None => {
164 self.sessions.insert(session_nb, resource);
165 true
166 }
167 }
168 }
169
170 pub fn on_spdu(
173 &mut self,
174 spdu: &[u8],
175 provides: impl Fn(ResourceId) -> bool,
176 ) -> dvb_ci::Result<SessionOut> {
177 let mut out = SessionOut::default();
178 match spdu.first().copied() {
179 Some(tags::OPEN_SESSION_REQUEST) => {
181 let req = OpenSessionRequest::parse(spdu)?;
184 if provides(req.resource) {
185 let session_nb = self.alloc();
186 self.sessions.insert(session_nb, req.resource);
187 out.spdus.push(ser(&OpenSessionResponse {
188 length_form: dvb_ci::length::LengthForm::Minimal,
189 status: SessionStatus::Ok,
190 resource: req.resource,
191 session_nb,
192 })?);
193 out.opened.push((session_nb, req.resource));
194 } else {
195 out.spdus.push(ser(&OpenSessionResponse {
196 length_form: dvb_ci::length::LengthForm::Minimal,
197 status: SessionStatus::ResourceNonExistent,
198 resource: req.resource,
199 session_nb: 0,
200 })?);
201 }
202 }
203 Some(tags::OPEN_SESSION_RESPONSE) => {
208 let resp = OpenSessionResponse::parse(spdu)?;
209 if resp.status == SessionStatus::Ok {
210 out.opened.extend(
211 self.try_bind_module_chosen(resp.session_nb, resp.resource)
212 .then_some((resp.session_nb, resp.resource)),
213 );
214 }
215 }
216 Some(tags::CREATE_SESSION_RESPONSE) => {
218 let resp = CreateSessionResponse::parse(spdu)?;
219 if resp.status == SessionStatus::Ok {
220 out.opened.extend(
221 self.try_bind_module_chosen(resp.session_nb, resp.resource)
222 .then_some((resp.session_nb, resp.resource)),
223 );
224 }
225 }
226 Some(tags::CLOSE_SESSION_REQUEST) => {
228 let req = CloseSessionRequest::parse(spdu)?;
229 let known = self.sessions.remove(&req.session_nb).is_some();
230 out.spdus.push(ser(&CloseSessionResponse {
231 length_form: dvb_ci::length::LengthForm::Minimal,
232 status: if known {
233 SessionStatus::Ok
234 } else {
235 SessionStatus::ResourceNonExistent
236 },
237 session_nb: req.session_nb,
238 })?);
239 if known {
240 out.closed.push(req.session_nb);
241 }
242 }
243 Some(tags::CLOSE_SESSION_RESPONSE) => {
245 let resp = CloseSessionResponse::parse(spdu)?;
246 self.sessions.remove(&resp.session_nb);
247 out.closed.push(resp.session_nb);
248 }
249 Some(tags::SESSION_NUMBER) => {
251 let sn = SessionNumber::parse(spdu)?;
252 let header_len = sn.header_len();
256 if spdu.len() > header_len {
257 out.apdus.push((sn.session_nb, spdu[header_len..].to_vec()));
258 }
259 }
260 _ => {}
261 }
262 Ok(out)
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use dvb_ci::resource::{APPLICATION_INFORMATION, RESOURCE_MANAGER};
270
271 fn provides_rm(r: ResourceId) -> bool {
272 r == RESOURCE_MANAGER
273 }
274
275 #[test]
276 fn session_for_returns_the_lowest_open_session_of_a_resource() {
277 let mut s = SessionLayer::new();
278 assert_eq!(s.session_for(RESOURCE_MANAGER), None);
279 let rm_req = ser(&OpenSessionRequest {
280 length_form: dvb_ci::length::LengthForm::Minimal,
281 resource: RESOURCE_MANAGER,
282 })
283 .unwrap();
284 let ai_req = ser(&OpenSessionRequest {
285 length_form: dvb_ci::length::LengthForm::Minimal,
286 resource: APPLICATION_INFORMATION,
287 })
288 .unwrap();
289 let all = |_: ResourceId| true;
290 let rm = s.on_spdu(&rm_req, all).unwrap().opened[0].0;
291 let ai = s.on_spdu(&ai_req, all).unwrap().opened[0].0;
292 assert_ne!(rm, ai);
293 assert_eq!(s.session_for(RESOURCE_MANAGER), Some(rm));
294 assert_eq!(s.session_for(APPLICATION_INFORMATION), Some(ai));
295 s.close(rm).unwrap();
297 assert_eq!(s.session_for(RESOURCE_MANAGER), None);
298 }
299
300 #[test]
301 fn open_request_for_provided_resource_grants_and_tracks() {
302 let mut s = SessionLayer::new();
303 let req = ser(&OpenSessionRequest {
304 length_form: dvb_ci::length::LengthForm::Minimal,
305 resource: RESOURCE_MANAGER,
306 })
307 .unwrap();
308 let out = s.on_spdu(&req, provides_rm).unwrap();
309 assert_eq!(out.opened.len(), 1);
310 let (nb, res) = out.opened[0];
311 assert_eq!(res, RESOURCE_MANAGER);
312 assert_eq!(s.resource_of(nb), Some(RESOURCE_MANAGER));
313 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
315 assert_eq!(resp.status, SessionStatus::Ok);
316 assert_eq!(resp.session_nb, nb);
317 }
318
319 #[test]
320 fn open_request_for_absent_resource_denied() {
321 let mut s = SessionLayer::new();
322 let req = ser(&OpenSessionRequest {
323 length_form: dvb_ci::length::LengthForm::Minimal,
324 resource: APPLICATION_INFORMATION,
325 })
326 .unwrap();
327 let out = s.on_spdu(&req, provides_rm).unwrap();
328 assert!(out.opened.is_empty());
329 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
330 assert_eq!(resp.status, SessionStatus::ResourceNonExistent);
331 assert!(s.is_empty());
332 }
333
334 #[test]
335 fn create_session_tracked_on_ok_response() {
336 let mut s = SessionLayer::new();
337 let _spdu = s.create_session(APPLICATION_INFORMATION).unwrap();
338 let resp = ser(&CreateSessionResponse {
340 length_form: dvb_ci::length::LengthForm::Minimal,
341 status: SessionStatus::Ok,
342 resource: APPLICATION_INFORMATION,
343 session_nb: 1,
344 })
345 .unwrap();
346 let out = s.on_spdu(&resp, |_| false).unwrap();
347 assert_eq!(out.opened, vec![(1, APPLICATION_INFORMATION)]);
348 assert_eq!(s.resource_of(1), Some(APPLICATION_INFORMATION));
349 }
350
351 #[test]
356 fn module_chosen_session_nb_colliding_with_a_different_resource_is_rejected() {
357 let mut s = SessionLayer::new();
358 let open = ser(&OpenSessionRequest {
360 length_form: dvb_ci::length::LengthForm::Minimal,
361 resource: RESOURCE_MANAGER,
362 })
363 .unwrap();
364 let opened = s.on_spdu(&open, provides_rm).unwrap().opened;
365 let nb = opened[0].0;
366 assert_eq!(s.resource_of(nb), Some(RESOURCE_MANAGER));
367
368 let colliding = ser(&CreateSessionResponse {
371 length_form: dvb_ci::length::LengthForm::Minimal,
372 status: SessionStatus::Ok,
373 resource: APPLICATION_INFORMATION,
374 session_nb: nb,
375 })
376 .unwrap();
377 let out = s.on_spdu(&colliding, |_| false).unwrap();
378 assert!(
379 out.opened.is_empty(),
380 "a colliding module-chosen session_nb must not be reported as opened"
381 );
382 assert_eq!(
383 s.resource_of(nb),
384 Some(RESOURCE_MANAGER),
385 "the original binding must survive the collision attempt"
386 );
387 }
388
389 #[test]
392 fn module_chosen_session_nb_zero_is_rejected() {
393 let mut s = SessionLayer::new();
394 let resp = ser(&OpenSessionResponse {
395 length_form: dvb_ci::length::LengthForm::Minimal,
396 status: SessionStatus::Ok,
397 resource: APPLICATION_INFORMATION,
398 session_nb: 0,
399 })
400 .unwrap();
401 let out = s.on_spdu(&resp, |_| false).unwrap();
402 assert!(out.opened.is_empty());
403 assert_eq!(s.resource_of(0), None);
404 }
405
406 #[test]
411 fn alloc_probes_past_a_still_open_session_across_wraparound() {
412 let mut s = SessionLayer::new();
413 s.next = 65535;
415 let first = s.alloc();
416 assert_eq!(first, 65535);
417 s.sessions.insert(first, APPLICATION_INFORMATION);
418 s.sessions.insert(1, RESOURCE_MANAGER);
421 let second = s.alloc();
422 assert_ne!(
423 second, 1,
424 "alloc must not hand out a session_nb that is still open"
425 );
426 assert_eq!(s.resource_of(1), Some(RESOURCE_MANAGER), "untouched");
427 }
428
429 #[test]
430 fn session_number_routes_apdu_up() {
431 let mut s = SessionLayer::new();
432 let apdu = [0x9F, 0x80, 0x21, 0x00];
433 let mut spdu = ser(&SessionNumber {
434 length_form: dvb_ci::length::LengthForm::Minimal,
435 session_nb: 7,
436 })
437 .unwrap();
438 spdu.extend_from_slice(&apdu);
439 let out = s.on_spdu(&spdu, |_| false).unwrap();
440 assert_eq!(out.apdus, vec![(7, apdu.to_vec())]);
441 }
442
443 #[test]
444 fn close_request_acks_and_removes() {
445 let mut s = SessionLayer::new();
446 let req = ser(&OpenSessionRequest {
448 length_form: dvb_ci::length::LengthForm::Minimal,
449 resource: RESOURCE_MANAGER,
450 })
451 .unwrap();
452 let nb = s.on_spdu(&req, provides_rm).unwrap().opened[0].0;
453 let close = ser(&CloseSessionRequest {
455 length_form: dvb_ci::length::LengthForm::Minimal,
456 session_nb: nb,
457 })
458 .unwrap();
459 let out = s.on_spdu(&close, |_| false).unwrap();
460 assert_eq!(out.closed, vec![nb]);
461 assert!(s.is_empty());
462 assert_eq!(out.spdus[0][0], tags::CLOSE_SESSION_RESPONSE);
464 }
465
466 #[test]
467 fn send_apdu_prefixes_session_number() {
468 let s = SessionLayer::new();
469 let wire = s.send_apdu(3, &[0xAA, 0xBB]).unwrap();
470 let sn = SessionNumber::parse(&wire).unwrap();
471 assert_eq!(sn.session_nb, 3);
472 assert_eq!(&wire[SessionNumber::HEADER_LEN..], &[0xAA, 0xBB]);
473 }
474
475 #[test]
481 fn session_number_in_a_long_length_form_still_yields_the_apdu() {
482 let mut s = SessionLayer::new();
483 let wire = [0x90, 0x82, 0x00, 0x02, 0x00, 0x03, 0xAA, 0xBB];
484 let out = s.on_spdu(&wire, |_| false).unwrap();
485 assert_eq!(out.apdus, vec![(3, vec![0xAA, 0xBB])]);
486 }
487
488 #[test]
489 fn closing_an_unallocated_session_is_answered_not_allocated() {
490 let mut s = SessionLayer::new();
491 let req = ser(&CloseSessionRequest {
492 length_form: dvb_ci::length::LengthForm::Minimal,
493 session_nb: 77,
494 })
495 .unwrap();
496 let out = s.on_spdu(&req, |_| true).unwrap();
497 let resp = CloseSessionResponse::parse(&out.spdus[0]).unwrap();
498 assert_eq!(resp.status, SessionStatus::ResourceNonExistent);
499 assert!(out.closed.is_empty(), "no session was closed");
500 }
501}