1use std::collections::BTreeMap;
12
13use dvb_ci::resource::ResourceId;
14use dvb_ci::spdu::{
15 tags, CloseSessionRequest, CloseSessionResponse, CreateSession, CreateSessionResponse,
16 OpenSessionRequest, 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> {
95 let session_nb = self.alloc();
96 ser(&CreateSession {
97 resource,
98 session_nb,
99 })
100 }
101
102 #[must_use]
104 pub fn send_apdu(&self, session_nb: u16, apdu: &[u8]) -> Vec<u8> {
105 let mut v = ser(&SessionNumber { session_nb });
106 v.extend_from_slice(apdu);
107 v
108 }
109
110 pub fn close(&mut self, session_nb: u16) -> Vec<u8> {
112 self.sessions.remove(&session_nb);
113 ser(&CloseSessionRequest { session_nb })
114 }
115
116 pub fn on_spdu(&mut self, spdu: &[u8], provides: impl Fn(ResourceId) -> bool) -> SessionOut {
119 let mut out = SessionOut::default();
120 match spdu.first().copied() {
121 Some(tags::OPEN_SESSION_REQUEST) => {
123 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 }
142 Some(tags::CREATE_SESSION_RESPONSE) => {
144 if let Ok(resp) = CreateSessionResponse::parse(spdu) {
145 if resp.status == SessionStatus::Ok {
146 self.sessions.insert(resp.session_nb, resp.resource);
147 out.opened.push((resp.session_nb, resp.resource));
148 }
149 }
150 }
151 Some(tags::CLOSE_SESSION_REQUEST) => {
153 if let Ok(req) = CloseSessionRequest::parse(spdu) {
154 self.sessions.remove(&req.session_nb);
155 out.spdus.push(ser(&CloseSessionResponse {
156 status: SessionStatus::Ok,
157 session_nb: req.session_nb,
158 }));
159 out.closed.push(req.session_nb);
160 }
161 }
162 Some(tags::CLOSE_SESSION_RESPONSE) => {
164 if let Ok(resp) = CloseSessionResponse::parse(spdu) {
165 self.sessions.remove(&resp.session_nb);
166 out.closed.push(resp.session_nb);
167 }
168 }
169 Some(tags::SESSION_NUMBER) => {
171 if let Ok(sn) = SessionNumber::parse(spdu) {
172 if spdu.len() > SessionNumber::HEADER_LEN {
173 out.apdus
174 .push((sn.session_nb, spdu[SessionNumber::HEADER_LEN..].to_vec()));
175 }
176 }
177 }
178 _ => {}
179 }
180 out
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use dvb_ci::resource::{APPLICATION_INFORMATION, RESOURCE_MANAGER};
188
189 fn provides_rm(r: ResourceId) -> bool {
190 r == RESOURCE_MANAGER
191 }
192
193 #[test]
194 fn open_request_for_provided_resource_grants_and_tracks() {
195 let mut s = SessionLayer::new();
196 let req = ser(&OpenSessionRequest {
197 resource: RESOURCE_MANAGER,
198 });
199 let out = s.on_spdu(&req, provides_rm);
200 assert_eq!(out.opened.len(), 1);
201 let (nb, res) = out.opened[0];
202 assert_eq!(res, RESOURCE_MANAGER);
203 assert_eq!(s.resource_of(nb), Some(RESOURCE_MANAGER));
204 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
206 assert_eq!(resp.status, SessionStatus::Ok);
207 assert_eq!(resp.session_nb, nb);
208 }
209
210 #[test]
211 fn open_request_for_absent_resource_denied() {
212 let mut s = SessionLayer::new();
213 let req = ser(&OpenSessionRequest {
214 resource: APPLICATION_INFORMATION,
215 });
216 let out = s.on_spdu(&req, provides_rm);
217 assert!(out.opened.is_empty());
218 let resp = OpenSessionResponse::parse(&out.spdus[0]).unwrap();
219 assert_eq!(resp.status, SessionStatus::ResourceNonExistent);
220 assert!(s.is_empty());
221 }
222
223 #[test]
224 fn create_session_tracked_on_ok_response() {
225 let mut s = SessionLayer::new();
226 let _spdu = s.create_session(APPLICATION_INFORMATION);
227 let resp = ser(&CreateSessionResponse {
229 status: SessionStatus::Ok,
230 resource: APPLICATION_INFORMATION,
231 session_nb: 1,
232 });
233 let out = s.on_spdu(&resp, |_| false);
234 assert_eq!(out.opened, vec![(1, APPLICATION_INFORMATION)]);
235 assert_eq!(s.resource_of(1), Some(APPLICATION_INFORMATION));
236 }
237
238 #[test]
239 fn session_number_routes_apdu_up() {
240 let mut s = SessionLayer::new();
241 let apdu = [0x9F, 0x80, 0x21, 0x00];
242 let mut spdu = ser(&SessionNumber { session_nb: 7 });
243 spdu.extend_from_slice(&apdu);
244 let out = s.on_spdu(&spdu, |_| false);
245 assert_eq!(out.apdus, vec![(7, apdu.to_vec())]);
246 }
247
248 #[test]
249 fn close_request_acks_and_removes() {
250 let mut s = SessionLayer::new();
251 let req = ser(&OpenSessionRequest {
253 resource: RESOURCE_MANAGER,
254 });
255 let nb = s.on_spdu(&req, provides_rm).opened[0].0;
256 let close = ser(&CloseSessionRequest { session_nb: nb });
258 let out = s.on_spdu(&close, |_| false);
259 assert_eq!(out.closed, vec![nb]);
260 assert!(s.is_empty());
261 assert_eq!(out.spdus[0][0], tags::CLOSE_SESSION_RESPONSE);
263 }
264
265 #[test]
266 fn send_apdu_prefixes_session_number() {
267 let s = SessionLayer::new();
268 let wire = s.send_apdu(3, &[0xAA, 0xBB]);
269 let sn = SessionNumber::parse(&wire).unwrap();
270 assert_eq!(sn.session_nb, 3);
271 assert_eq!(&wire[SessionNumber::HEADER_LEN..], &[0xAA, 0xBB]);
272 }
273}