1use std::collections::BTreeMap;
12
13use dvb_ci::resource::ResourceId;
14use dvb_ci::spdu::{
15 tags, CloseSessionRequest, CloseSessionResponse, CreateSessionResponse, OpenSessionRequest,
16 OpenSessionResponse, SessionNumber, SessionStatus,
17};
18use dvb_common::{Parse, Serialize};
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) => {
124 if let Ok(req) = OpenSessionRequest::parse(spdu) {
125 if provides(req.resource) {
126 let session_nb = self.alloc();
127 self.sessions.insert(session_nb, req.resource);
128 out.spdus.push(ser(&OpenSessionResponse {
129 status: SessionStatus::Ok,
130 resource: req.resource,
131 session_nb,
132 }));
133 out.opened.push((session_nb, req.resource));
134 } else {
135 out.spdus.push(ser(&OpenSessionResponse {
136 status: SessionStatus::ResourceNonExistent,
137 resource: req.resource,
138 session_nb: 0,
139 }));
140 }
141 }
142 }
143 Some(tags::OPEN_SESSION_RESPONSE) => {
146 if let Ok(resp) = OpenSessionResponse::parse(spdu) {
147 if resp.status == SessionStatus::Ok {
148 self.sessions.insert(resp.session_nb, resp.resource);
149 out.opened.push((resp.session_nb, resp.resource));
150 }
151 }
152 }
153 Some(tags::CREATE_SESSION_RESPONSE) => {
155 if let Ok(resp) = CreateSessionResponse::parse(spdu) {
156 if resp.status == SessionStatus::Ok {
157 self.sessions.insert(resp.session_nb, resp.resource);
158 out.opened.push((resp.session_nb, resp.resource));
159 }
160 }
161 }
162 Some(tags::CLOSE_SESSION_REQUEST) => {
164 if let Ok(req) = CloseSessionRequest::parse(spdu) {
165 self.sessions.remove(&req.session_nb);
166 out.spdus.push(ser(&CloseSessionResponse {
167 status: SessionStatus::Ok,
168 session_nb: req.session_nb,
169 }));
170 out.closed.push(req.session_nb);
171 }
172 }
173 Some(tags::CLOSE_SESSION_RESPONSE) => {
175 if let Ok(resp) = CloseSessionResponse::parse(spdu) {
176 self.sessions.remove(&resp.session_nb);
177 out.closed.push(resp.session_nb);
178 }
179 }
180 Some(tags::SESSION_NUMBER) => {
182 if let Ok(sn) = SessionNumber::parse(spdu) {
183 if spdu.len() > SessionNumber::HEADER_LEN {
184 out.apdus
185 .push((sn.session_nb, spdu[SessionNumber::HEADER_LEN..].to_vec()));
186 }
187 }
188 }
189 _ => {}
190 }
191 out
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use dvb_ci::resource::{APPLICATION_INFORMATION, RESOURCE_MANAGER};
199
200 fn provides_rm(r: ResourceId) -> bool {
201 r == RESOURCE_MANAGER
202 }
203
204 #[test]
205 fn open_request_for_provided_resource_grants_and_tracks() {
206 let mut s = SessionLayer::new();
207 let req = ser(&OpenSessionRequest {
208 resource: RESOURCE_MANAGER,
209 });
210 let out = s.on_spdu(&req, provides_rm);
211 assert_eq!(out.opened.len(), 1);
212 let (nb, res) = out.opened[0];
213 assert_eq!(res, RESOURCE_MANAGER);
214 assert_eq!(s.resource_of(nb), Some(RESOURCE_MANAGER));
215 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
217 assert_eq!(resp.status, SessionStatus::Ok);
218 assert_eq!(resp.session_nb, nb);
219 }
220
221 #[test]
222 fn open_request_for_absent_resource_denied() {
223 let mut s = SessionLayer::new();
224 let req = ser(&OpenSessionRequest {
225 resource: APPLICATION_INFORMATION,
226 });
227 let out = s.on_spdu(&req, provides_rm);
228 assert!(out.opened.is_empty());
229 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
230 assert_eq!(resp.status, SessionStatus::ResourceNonExistent);
231 assert!(s.is_empty());
232 }
233
234 #[test]
235 fn create_session_tracked_on_ok_response() {
236 let mut s = SessionLayer::new();
237 let _spdu = s.create_session(APPLICATION_INFORMATION);
238 let resp = ser(&CreateSessionResponse {
240 status: SessionStatus::Ok,
241 resource: APPLICATION_INFORMATION,
242 session_nb: 1,
243 });
244 let out = s.on_spdu(&resp, |_| false);
245 assert_eq!(out.opened, vec![(1, APPLICATION_INFORMATION)]);
246 assert_eq!(s.resource_of(1), Some(APPLICATION_INFORMATION));
247 }
248
249 #[test]
250 fn session_number_routes_apdu_up() {
251 let mut s = SessionLayer::new();
252 let apdu = [0x9F, 0x80, 0x21, 0x00];
253 let mut spdu = ser(&SessionNumber { session_nb: 7 });
254 spdu.extend_from_slice(&apdu);
255 let out = s.on_spdu(&spdu, |_| false);
256 assert_eq!(out.apdus, vec![(7, apdu.to_vec())]);
257 }
258
259 #[test]
260 fn close_request_acks_and_removes() {
261 let mut s = SessionLayer::new();
262 let req = ser(&OpenSessionRequest {
264 resource: RESOURCE_MANAGER,
265 });
266 let nb = s.on_spdu(&req, provides_rm).opened[0].0;
267 let close = ser(&CloseSessionRequest { session_nb: nb });
269 let out = s.on_spdu(&close, |_| false);
270 assert_eq!(out.closed, vec![nb]);
271 assert!(s.is_empty());
272 assert_eq!(out.spdus[0][0], tags::CLOSE_SESSION_RESPONSE);
274 }
275
276 #[test]
277 fn send_apdu_prefixes_session_number() {
278 let s = SessionLayer::new();
279 let wire = s.send_apdu(3, &[0xAA, 0xBB]);
280 let sn = SessionNumber::parse(&wire).unwrap();
281 assert_eq!(sn.session_nb, 3);
282 assert_eq!(&wire[SessionNumber::HEADER_LEN..], &[0xAA, 0xBB]);
283 }
284}