Skip to content
File

Blob: firmware/crates/radio-core/src/signaling.rs

rust114 lines
1//! Validated Worker responses and recovery policy; no HTTP implementation here.
2use serde::{Deserialize, Serialize};
3 
4#[derive(Deserialize)]
5#[serde(rename_all = "camelCase")]
6pub struct Started {
7 generation: String,
8 session_description: Description,
9}
10 
11#[derive(Deserialize)]
12struct Description {
13 r#type: String,
14 sdp: String,
15}
16 
17// SDP contains temporary transport credentials. Never derive Debug on it.
18impl std::fmt::Debug for Started {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 f.debug_struct("Started").finish_non_exhaustive()
21 }
22}
23 
24#[derive(Debug, Clone, Serialize)]
25pub struct Identity {
26 generation: String,
27}
28 
29impl Started {
30 pub fn validate(self) -> Result<(Identity, String), &'static str> {
31 if self.generation.is_empty()
32 || self.generation.len() > 64
33 || self.session_description.r#type != "answer"
34 || !self.session_description.sdp.starts_with("v=0")
35 || self.session_description.sdp.len() > 16_000
36 || self.session_description.sdp.contains('\0')
37 {
38 return Err("invalid publisher answer");
39 }
40 Ok((
41 Identity {
42 generation: self.generation,
43 },
44 self.session_description.sdp,
45 ))
46 }
47}
48 
49#[derive(Debug, Deserialize)]
50pub struct Channels {
51 channels: Vec<Channel>,
52}
53#[derive(Debug, Deserialize)]
54#[serde(rename_all = "camelCase")]
55struct Channel {
56 data_channel_name: String,
57 id: u16,
58}
59 
60/// Validated, distinct application stream IDs. SFU stream 0 carries server
61/// events; 65535 is reserved by SCTP and cannot identify a data channel.
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct ChannelIds {
64 robot: u16,
65 spectrum: u16,
66}
67 
68impl ChannelIds {
69 pub fn new(robot: u16, spectrum: u16) -> Result<Self, &'static str> {
70 if robot == spectrum
71 || [robot, spectrum]
72 .iter()
73 .any(|id| *id == 0 || *id == u16::MAX)
74 {
75 return Err("invalid application channel IDs");
76 }
77 Ok(Self { robot, spectrum })
78 }
79 pub fn robot(self) -> u16 {
80 self.robot
81 }
82 pub fn spectrum(self) -> u16 {
83 self.spectrum
84 }
85}
86 
87impl Channels {
88 /// Match names rather than response order; use the IDs allocated by the SFU.
89 pub fn validate(&self) -> Result<ChannelIds, &'static str> {
90 let robot = self
91 .channels
92 .iter()
93 .find(|c| c.data_channel_name == "robot");
94 let spectrum = self
95 .channels
96 .iter()
97 .find(|c| c.data_channel_name == "spectrum");
98 match (self.channels.len(), robot, spectrum) {
99 (2, Some(robot), Some(spectrum)) => ChannelIds::new(robot.id, spectrum.id),
100 _ => Err("unexpected data channel allocation"),
101 }
102 }
103}
104 
105pub fn retryable(status: i32) -> bool {
106 status < 0 || status == 429 || status >= 500
107}
108 
109/// Recover after a non-429 4xx response or more than 55 seconds without a
110/// successful heartbeat.
111pub fn needs_recovery(status: i32, since_success_us: u64) -> bool {
112 ((400..500).contains(&status) && status != 429) || since_success_us > 55_000_000
113}