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 self.sessions.remove(&req.session_nb);
230 out.spdus.push(ser(&CloseSessionResponse {
231 length_form: dvb_ci::length::LengthForm::Minimal,
232 status: SessionStatus::Ok,
233 session_nb: req.session_nb,
234 })?);
235 out.closed.push(req.session_nb);
236 }
237 Some(tags::CLOSE_SESSION_RESPONSE) => {
239 let resp = CloseSessionResponse::parse(spdu)?;
240 self.sessions.remove(&resp.session_nb);
241 out.closed.push(resp.session_nb);
242 }
243 Some(tags::SESSION_NUMBER) => {
245 let sn = SessionNumber::parse(spdu)?;
246 let header_len = sn.header_len();
250 if spdu.len() > header_len {
251 out.apdus.push((sn.session_nb, spdu[header_len..].to_vec()));
252 }
253 }
254 _ => {}
255 }
256 Ok(out)
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use dvb_ci::resource::{APPLICATION_INFORMATION, RESOURCE_MANAGER};
264
265 fn provides_rm(r: ResourceId) -> bool {
266 r == RESOURCE_MANAGER
267 }
268
269 #[test]
270 fn session_for_returns_the_lowest_open_session_of_a_resource() {
271 let mut s = SessionLayer::new();
272 assert_eq!(s.session_for(RESOURCE_MANAGER), None);
273 let rm_req = ser(&OpenSessionRequest {
274 length_form: dvb_ci::length::LengthForm::Minimal,
275 resource: RESOURCE_MANAGER,
276 })
277 .unwrap();
278 let ai_req = ser(&OpenSessionRequest {
279 length_form: dvb_ci::length::LengthForm::Minimal,
280 resource: APPLICATION_INFORMATION,
281 })
282 .unwrap();
283 let all = |_: ResourceId| true;
284 let rm = s.on_spdu(&rm_req, all).unwrap().opened[0].0;
285 let ai = s.on_spdu(&ai_req, all).unwrap().opened[0].0;
286 assert_ne!(rm, ai);
287 assert_eq!(s.session_for(RESOURCE_MANAGER), Some(rm));
288 assert_eq!(s.session_for(APPLICATION_INFORMATION), Some(ai));
289 s.close(rm).unwrap();
291 assert_eq!(s.session_for(RESOURCE_MANAGER), None);
292 }
293
294 #[test]
295 fn open_request_for_provided_resource_grants_and_tracks() {
296 let mut s = SessionLayer::new();
297 let req = ser(&OpenSessionRequest {
298 length_form: dvb_ci::length::LengthForm::Minimal,
299 resource: RESOURCE_MANAGER,
300 })
301 .unwrap();
302 let out = s.on_spdu(&req, provides_rm).unwrap();
303 assert_eq!(out.opened.len(), 1);
304 let (nb, res) = out.opened[0];
305 assert_eq!(res, RESOURCE_MANAGER);
306 assert_eq!(s.resource_of(nb), Some(RESOURCE_MANAGER));
307 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
309 assert_eq!(resp.status, SessionStatus::Ok);
310 assert_eq!(resp.session_nb, nb);
311 }
312
313 #[test]
314 fn open_request_for_absent_resource_denied() {
315 let mut s = SessionLayer::new();
316 let req = ser(&OpenSessionRequest {
317 length_form: dvb_ci::length::LengthForm::Minimal,
318 resource: APPLICATION_INFORMATION,
319 })
320 .unwrap();
321 let out = s.on_spdu(&req, provides_rm).unwrap();
322 assert!(out.opened.is_empty());
323 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
324 assert_eq!(resp.status, SessionStatus::ResourceNonExistent);
325 assert!(s.is_empty());
326 }
327
328 #[test]
329 fn create_session_tracked_on_ok_response() {
330 let mut s = SessionLayer::new();
331 let _spdu = s.create_session(APPLICATION_INFORMATION).unwrap();
332 let resp = ser(&CreateSessionResponse {
334 length_form: dvb_ci::length::LengthForm::Minimal,
335 status: SessionStatus::Ok,
336 resource: APPLICATION_INFORMATION,
337 session_nb: 1,
338 })
339 .unwrap();
340 let out = s.on_spdu(&resp, |_| false).unwrap();
341 assert_eq!(out.opened, vec![(1, APPLICATION_INFORMATION)]);
342 assert_eq!(s.resource_of(1), Some(APPLICATION_INFORMATION));
343 }
344
345 #[test]
350 fn module_chosen_session_nb_colliding_with_a_different_resource_is_rejected() {
351 let mut s = SessionLayer::new();
352 let open = ser(&OpenSessionRequest {
354 length_form: dvb_ci::length::LengthForm::Minimal,
355 resource: RESOURCE_MANAGER,
356 })
357 .unwrap();
358 let opened = s.on_spdu(&open, provides_rm).unwrap().opened;
359 let nb = opened[0].0;
360 assert_eq!(s.resource_of(nb), Some(RESOURCE_MANAGER));
361
362 let colliding = ser(&CreateSessionResponse {
365 length_form: dvb_ci::length::LengthForm::Minimal,
366 status: SessionStatus::Ok,
367 resource: APPLICATION_INFORMATION,
368 session_nb: nb,
369 })
370 .unwrap();
371 let out = s.on_spdu(&colliding, |_| false).unwrap();
372 assert!(
373 out.opened.is_empty(),
374 "a colliding module-chosen session_nb must not be reported as opened"
375 );
376 assert_eq!(
377 s.resource_of(nb),
378 Some(RESOURCE_MANAGER),
379 "the original binding must survive the collision attempt"
380 );
381 }
382
383 #[test]
386 fn module_chosen_session_nb_zero_is_rejected() {
387 let mut s = SessionLayer::new();
388 let resp = ser(&OpenSessionResponse {
389 length_form: dvb_ci::length::LengthForm::Minimal,
390 status: SessionStatus::Ok,
391 resource: APPLICATION_INFORMATION,
392 session_nb: 0,
393 })
394 .unwrap();
395 let out = s.on_spdu(&resp, |_| false).unwrap();
396 assert!(out.opened.is_empty());
397 assert_eq!(s.resource_of(0), None);
398 }
399
400 #[test]
405 fn alloc_probes_past_a_still_open_session_across_wraparound() {
406 let mut s = SessionLayer::new();
407 s.next = 65535;
409 let first = s.alloc();
410 assert_eq!(first, 65535);
411 s.sessions.insert(first, APPLICATION_INFORMATION);
412 s.sessions.insert(1, RESOURCE_MANAGER);
415 let second = s.alloc();
416 assert_ne!(
417 second, 1,
418 "alloc must not hand out a session_nb that is still open"
419 );
420 assert_eq!(s.resource_of(1), Some(RESOURCE_MANAGER), "untouched");
421 }
422
423 #[test]
424 fn session_number_routes_apdu_up() {
425 let mut s = SessionLayer::new();
426 let apdu = [0x9F, 0x80, 0x21, 0x00];
427 let mut spdu = ser(&SessionNumber {
428 length_form: dvb_ci::length::LengthForm::Minimal,
429 session_nb: 7,
430 })
431 .unwrap();
432 spdu.extend_from_slice(&apdu);
433 let out = s.on_spdu(&spdu, |_| false).unwrap();
434 assert_eq!(out.apdus, vec![(7, apdu.to_vec())]);
435 }
436
437 #[test]
438 fn close_request_acks_and_removes() {
439 let mut s = SessionLayer::new();
440 let req = ser(&OpenSessionRequest {
442 length_form: dvb_ci::length::LengthForm::Minimal,
443 resource: RESOURCE_MANAGER,
444 })
445 .unwrap();
446 let nb = s.on_spdu(&req, provides_rm).unwrap().opened[0].0;
447 let close = ser(&CloseSessionRequest {
449 length_form: dvb_ci::length::LengthForm::Minimal,
450 session_nb: nb,
451 })
452 .unwrap();
453 let out = s.on_spdu(&close, |_| false).unwrap();
454 assert_eq!(out.closed, vec![nb]);
455 assert!(s.is_empty());
456 assert_eq!(out.spdus[0][0], tags::CLOSE_SESSION_RESPONSE);
458 }
459
460 #[test]
461 fn send_apdu_prefixes_session_number() {
462 let s = SessionLayer::new();
463 let wire = s.send_apdu(3, &[0xAA, 0xBB]).unwrap();
464 let sn = SessionNumber::parse(&wire).unwrap();
465 assert_eq!(sn.session_nb, 3);
466 assert_eq!(&wire[SessionNumber::HEADER_LEN..], &[0xAA, 0xBB]);
467 }
468
469 #[test]
475 fn session_number_in_a_long_length_form_still_yields_the_apdu() {
476 let mut s = SessionLayer::new();
477 let wire = [0x90, 0x82, 0x00, 0x02, 0x00, 0x03, 0xAA, 0xBB];
478 let out = s.on_spdu(&wire, |_| false).unwrap();
479 assert_eq!(out.apdus, vec![(3, vec![0xAA, 0xBB])]);
480 }
481}