1use crate::error::{Error, Result};
15use crate::length;
16use crate::resource::ResourceId;
17use broadcast_common::{Parse, Serialize};
18
19pub mod tags {
21 pub const SESSION_NUMBER: u8 = 0x90;
23 pub const OPEN_SESSION_REQUEST: u8 = 0x91;
25 pub const OPEN_SESSION_RESPONSE: u8 = 0x92;
27 pub const CREATE_SESSION: u8 = 0x93;
29 pub const CREATE_SESSION_RESPONSE: u8 = 0x94;
31 pub const CLOSE_SESSION_REQUEST: u8 = 0x95;
33 pub const CLOSE_SESSION_RESPONSE: u8 = 0x96;
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize))]
40#[non_exhaustive]
41pub enum SessionStatus {
42 Ok,
44 ResourceNonExistent,
47 ResourceUnavailable,
49 ResourceVersionTooLow,
51 ResourceBusy,
53 Reserved(u8),
55}
56
57impl SessionStatus {
58 #[must_use]
60 pub fn from_u8(v: u8) -> Self {
61 match v {
62 0x00 => Self::Ok,
63 0xF0 => Self::ResourceNonExistent,
64 0xF1 => Self::ResourceUnavailable,
65 0xF2 => Self::ResourceVersionTooLow,
66 0xF3 => Self::ResourceBusy,
67 other => Self::Reserved(other),
68 }
69 }
70 #[must_use]
72 pub const fn to_u8(self) -> u8 {
73 match self {
74 Self::Ok => 0x00,
75 Self::ResourceNonExistent => 0xF0,
76 Self::ResourceUnavailable => 0xF1,
77 Self::ResourceVersionTooLow => 0xF2,
78 Self::ResourceBusy => 0xF3,
79 Self::Reserved(v) => v,
80 }
81 }
82 #[must_use]
84 pub fn name(&self) -> &'static str {
85 match self {
86 Self::Ok => "ok",
87 Self::ResourceNonExistent => "resource_non_existent",
88 Self::ResourceUnavailable => "resource_unavailable",
89 Self::ResourceVersionTooLow => "resource_version_too_low",
90 Self::ResourceBusy => "resource_busy",
91 Self::Reserved(_) => "reserved",
92 }
93 }
94}
95broadcast_common::impl_spec_display!(SessionStatus, Reserved);
96
97fn parse_spdu_header<'a>(bytes: &'a [u8], expected: u8, what: &'static str) -> Result<&'a [u8]> {
100 let first = *bytes.first().ok_or(Error::BufferTooShort {
101 need: 1,
102 have: 0,
103 what,
104 })?;
105 if first != expected {
106 return Err(Error::UnexpectedSpduTag {
107 got: first,
108 expected,
109 what,
110 });
111 }
112 let (len, hdr) = length::decode(&bytes[1..])?;
113 let start = 1 + hdr;
114 let end = start + len;
115 if bytes.len() < end {
116 return Err(Error::LengthMismatch {
117 what,
118 declared: len,
119 actual: bytes.len().saturating_sub(start),
120 });
121 }
122 Ok(&bytes[start..end])
123}
124
125fn spdu_len(value_len: usize) -> usize {
126 1 + length::encoded_len(value_len) + value_len
127}
128
129fn write_spdu_header(tag: u8, value_len: usize, buf: &mut [u8]) -> Result<usize> {
130 let total = spdu_len(value_len);
131 if buf.len() < total {
132 return Err(Error::OutputBufferTooSmall {
133 need: total,
134 have: buf.len(),
135 });
136 }
137 buf[0] = tag;
138 let n = length::encode_into(value_len, &mut buf[1..])?;
139 Ok(1 + n)
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144#[cfg_attr(feature = "serde", derive(serde::Serialize))]
145pub struct OpenSessionRequest {
146 pub resource: ResourceId,
148}
149
150impl<'a> Parse<'a> for OpenSessionRequest {
151 type Error = Error;
152 fn parse(bytes: &'a [u8]) -> Result<Self> {
153 let v = parse_spdu_header(bytes, tags::OPEN_SESSION_REQUEST, "open_session_request")?;
154 Ok(Self {
155 resource: ResourceId::parse(v)?,
156 })
157 }
158}
159impl Serialize for OpenSessionRequest {
160 type Error = Error;
161 fn serialized_len(&self) -> usize {
162 spdu_len(ResourceId::LEN)
163 }
164 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
165 let pos = write_spdu_header(tags::OPEN_SESSION_REQUEST, ResourceId::LEN, buf)?;
166 let n = self.resource.serialize_into(&mut buf[pos..])?;
167 Ok(pos + n)
168 }
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize))]
174pub struct OpenSessionResponse {
175 pub status: SessionStatus,
177 pub resource: ResourceId,
179 pub session_nb: u16,
181}
182
183impl<'a> Parse<'a> for OpenSessionResponse {
184 type Error = Error;
185 fn parse(bytes: &'a [u8]) -> Result<Self> {
186 let v = parse_spdu_header(bytes, tags::OPEN_SESSION_RESPONSE, "open_session_response")?;
187 if v.len() != 7 {
188 return Err(Error::InvalidObject {
189 what: "open_session_response",
190 reason: "value must be 7 bytes",
191 });
192 }
193 Ok(Self {
194 status: SessionStatus::from_u8(v[0]),
195 resource: ResourceId::parse(&v[1..5])?,
196 session_nb: u16::from_be_bytes([v[5], v[6]]),
197 })
198 }
199}
200impl Serialize for OpenSessionResponse {
201 type Error = Error;
202 fn serialized_len(&self) -> usize {
203 spdu_len(7)
204 }
205 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
206 let mut pos = write_spdu_header(tags::OPEN_SESSION_RESPONSE, 7, buf)?;
207 buf[pos] = self.status.to_u8();
208 pos += 1;
209 pos += self.resource.serialize_into(&mut buf[pos..])?;
210 buf[pos..pos + 2].copy_from_slice(&self.session_nb.to_be_bytes());
211 Ok(pos + 2)
212 }
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217#[cfg_attr(feature = "serde", derive(serde::Serialize))]
218pub struct CreateSession {
219 pub resource: ResourceId,
221 pub session_nb: u16,
223}
224
225impl<'a> Parse<'a> for CreateSession {
226 type Error = Error;
227 fn parse(bytes: &'a [u8]) -> Result<Self> {
228 let v = parse_spdu_header(bytes, tags::CREATE_SESSION, "create_session")?;
229 if v.len() != 6 {
230 return Err(Error::InvalidObject {
231 what: "create_session",
232 reason: "value must be 6 bytes",
233 });
234 }
235 Ok(Self {
236 resource: ResourceId::parse(&v[0..4])?,
237 session_nb: u16::from_be_bytes([v[4], v[5]]),
238 })
239 }
240}
241impl Serialize for CreateSession {
242 type Error = Error;
243 fn serialized_len(&self) -> usize {
244 spdu_len(6)
245 }
246 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
247 let mut pos = write_spdu_header(tags::CREATE_SESSION, 6, buf)?;
248 pos += self.resource.serialize_into(&mut buf[pos..])?;
249 buf[pos..pos + 2].copy_from_slice(&self.session_nb.to_be_bytes());
250 Ok(pos + 2)
251 }
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256#[cfg_attr(feature = "serde", derive(serde::Serialize))]
257pub struct CreateSessionResponse {
258 pub status: SessionStatus,
260 pub resource: ResourceId,
262 pub session_nb: u16,
264}
265
266impl<'a> Parse<'a> for CreateSessionResponse {
267 type Error = Error;
268 fn parse(bytes: &'a [u8]) -> Result<Self> {
269 let v = parse_spdu_header(
270 bytes,
271 tags::CREATE_SESSION_RESPONSE,
272 "create_session_response",
273 )?;
274 if v.len() != 7 {
275 return Err(Error::InvalidObject {
276 what: "create_session_response",
277 reason: "value must be 7 bytes",
278 });
279 }
280 Ok(Self {
281 status: SessionStatus::from_u8(v[0]),
282 resource: ResourceId::parse(&v[1..5])?,
283 session_nb: u16::from_be_bytes([v[5], v[6]]),
284 })
285 }
286}
287impl Serialize for CreateSessionResponse {
288 type Error = Error;
289 fn serialized_len(&self) -> usize {
290 spdu_len(7)
291 }
292 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
293 let mut pos = write_spdu_header(tags::CREATE_SESSION_RESPONSE, 7, buf)?;
294 buf[pos] = self.status.to_u8();
295 pos += 1;
296 pos += self.resource.serialize_into(&mut buf[pos..])?;
297 buf[pos..pos + 2].copy_from_slice(&self.session_nb.to_be_bytes());
298 Ok(pos + 2)
299 }
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304#[cfg_attr(feature = "serde", derive(serde::Serialize))]
305pub struct CloseSessionRequest {
306 pub session_nb: u16,
308}
309
310impl<'a> Parse<'a> for CloseSessionRequest {
311 type Error = Error;
312 fn parse(bytes: &'a [u8]) -> Result<Self> {
313 let v = parse_spdu_header(bytes, tags::CLOSE_SESSION_REQUEST, "close_session_request")?;
314 if v.len() != 2 {
315 return Err(Error::InvalidObject {
316 what: "close_session_request",
317 reason: "value must be 2 bytes",
318 });
319 }
320 Ok(Self {
321 session_nb: u16::from_be_bytes([v[0], v[1]]),
322 })
323 }
324}
325impl Serialize for CloseSessionRequest {
326 type Error = Error;
327 fn serialized_len(&self) -> usize {
328 spdu_len(2)
329 }
330 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
331 let pos = write_spdu_header(tags::CLOSE_SESSION_REQUEST, 2, buf)?;
332 buf[pos..pos + 2].copy_from_slice(&self.session_nb.to_be_bytes());
333 Ok(pos + 2)
334 }
335}
336
337#[derive(Debug, Clone, Copy, PartialEq, Eq)]
339#[cfg_attr(feature = "serde", derive(serde::Serialize))]
340pub struct CloseSessionResponse {
341 pub status: SessionStatus,
343 pub session_nb: u16,
345}
346
347impl<'a> Parse<'a> for CloseSessionResponse {
348 type Error = Error;
349 fn parse(bytes: &'a [u8]) -> Result<Self> {
350 let v = parse_spdu_header(
351 bytes,
352 tags::CLOSE_SESSION_RESPONSE,
353 "close_session_response",
354 )?;
355 if v.len() != 3 {
356 return Err(Error::InvalidObject {
357 what: "close_session_response",
358 reason: "value must be 3 bytes",
359 });
360 }
361 Ok(Self {
362 status: SessionStatus::from_u8(v[0]),
363 session_nb: u16::from_be_bytes([v[1], v[2]]),
364 })
365 }
366}
367impl Serialize for CloseSessionResponse {
368 type Error = Error;
369 fn serialized_len(&self) -> usize {
370 spdu_len(3)
371 }
372 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
373 let mut pos = write_spdu_header(tags::CLOSE_SESSION_RESPONSE, 3, buf)?;
374 buf[pos] = self.status.to_u8();
375 pos += 1;
376 buf[pos..pos + 2].copy_from_slice(&self.session_nb.to_be_bytes());
377 Ok(pos + 2)
378 }
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq)]
385#[cfg_attr(feature = "serde", derive(serde::Serialize))]
386pub struct SessionNumber {
387 pub session_nb: u16,
389}
390
391impl SessionNumber {
392 pub const HEADER_LEN: usize = 4;
395}
396
397impl<'a> Parse<'a> for SessionNumber {
398 type Error = Error;
399 fn parse(bytes: &'a [u8]) -> Result<Self> {
400 let v = parse_spdu_header(bytes, tags::SESSION_NUMBER, "session_number")?;
404 if v.len() != 2 {
405 return Err(Error::InvalidObject {
406 what: "session_number",
407 reason: "value must be 2 bytes",
408 });
409 }
410 Ok(Self {
411 session_nb: u16::from_be_bytes([v[0], v[1]]),
412 })
413 }
414}
415impl Serialize for SessionNumber {
416 type Error = Error;
417 fn serialized_len(&self) -> usize {
418 spdu_len(2)
419 }
420 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
421 let pos = write_spdu_header(tags::SESSION_NUMBER, 2, buf)?;
422 buf[pos..pos + 2].copy_from_slice(&self.session_nb.to_be_bytes());
423 Ok(pos + 2)
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use crate::resource::CONDITIONAL_ACCESS_SUPPORT;
431
432 #[test]
433 fn open_session_request_round_trip() {
434 let o = OpenSessionRequest {
435 resource: CONDITIONAL_ACCESS_SUPPORT,
436 };
437 let bytes = o.to_bytes();
438 assert_eq!(bytes, [0x91, 0x04, 0x00, 0x03, 0x00, 0x41]);
439 assert_eq!(OpenSessionRequest::parse(&bytes).unwrap(), o);
440 }
441
442 #[test]
443 fn open_session_response_round_trip() {
444 let o = OpenSessionResponse {
445 status: SessionStatus::Ok,
446 resource: CONDITIONAL_ACCESS_SUPPORT,
447 session_nb: 0x0102,
448 };
449 let bytes = o.to_bytes();
450 assert_eq!(bytes[0], tags::OPEN_SESSION_RESPONSE);
451 assert_eq!(bytes[1], 0x07);
452 let parsed = OpenSessionResponse::parse(&bytes).unwrap();
453 assert_eq!(parsed, o);
454 assert_eq!(parsed.status.name(), "ok");
455 }
456
457 #[test]
458 fn create_session_round_trip() {
459 let c = CreateSession {
460 resource: CONDITIONAL_ACCESS_SUPPORT,
461 session_nb: 5,
462 };
463 let bytes = c.to_bytes();
464 assert_eq!(bytes[1], 0x06);
465 assert_eq!(CreateSession::parse(&bytes).unwrap(), c);
466 }
467
468 #[test]
469 fn create_session_response_round_trip() {
470 let c = CreateSessionResponse {
471 status: SessionStatus::ResourceBusy,
472 resource: CONDITIONAL_ACCESS_SUPPORT,
473 session_nb: 5,
474 };
475 let bytes = c.to_bytes();
476 let parsed = CreateSessionResponse::parse(&bytes).unwrap();
477 assert_eq!(parsed, c);
478 assert_eq!(parsed.status.name(), "resource_busy");
479 }
480
481 #[test]
482 fn close_session_round_trips() {
483 let req = CloseSessionRequest { session_nb: 0x00FF };
484 assert_eq!(req.to_bytes(), [0x95, 0x02, 0x00, 0xFF]);
485 assert_eq!(CloseSessionRequest::parse(&req.to_bytes()).unwrap(), req);
486
487 let resp = CloseSessionResponse {
488 status: SessionStatus::Ok,
489 session_nb: 0x00FF,
490 };
491 assert_eq!(resp.to_bytes(), [0x96, 0x03, 0x00, 0x00, 0xFF]);
492 assert_eq!(CloseSessionResponse::parse(&resp.to_bytes()).unwrap(), resp);
493 }
494
495 #[test]
496 fn session_number_round_trip_and_header_len() {
497 let sn = SessionNumber { session_nb: 0x1234 };
498 let bytes = sn.to_bytes();
499 assert_eq!(bytes, [0x90, 0x02, 0x12, 0x34]);
500 assert_eq!(bytes.len(), SessionNumber::HEADER_LEN);
501 let mut with_body = bytes.to_vec();
503 with_body.extend_from_slice(&[0x9F, 0x80, 0x30, 0x00]);
504 assert_eq!(SessionNumber::parse(&with_body).unwrap(), sn);
505 }
506
507 #[test]
508 fn mutating_session_nb_changes_bytes() {
509 let req = CloseSessionRequest { session_nb: 1 };
510 let a = req.to_bytes();
511 let b = CloseSessionRequest { session_nb: 2 }.to_bytes();
512 assert_ne!(a, b);
513 }
514
515 #[test]
516 fn rejects_wrong_tag() {
517 assert!(matches!(
518 OpenSessionRequest::parse(&[0x92, 0x04, 0, 0, 0, 0]),
519 Err(Error::UnexpectedSpduTag { .. })
520 ));
521 }
522}