Skip to content
File

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

rust92 lines
1//! Validated Worker responses and recovery policy; no HTTP implementation here.
2use crate::protocol::{ROBOT_ID, SPECTRUM_ID};
3use serde::{Deserialize, Serialize};
4 
5#[derive(Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub struct Started {
8 generation: String,
9 session_description: Description,
10}
11 
12#[derive(Deserialize)]
13struct Description {
14 r#type: String,
15 sdp: String,
16}
17 
18// SDP contains temporary transport credentials. Never derive Debug on it.
19impl std::fmt::Debug for Started {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 f.debug_struct("Started").finish_non_exhaustive()
22 }
23}
24 
25#[derive(Debug, Clone, Serialize)]
26pub struct Identity {
27 generation: String,
28}
29 
30impl Started {
31 pub fn validate(self) -> Result<(Identity, String), &'static str> {
32 if self.generation.is_empty()
33 || self.generation.len() > 64
34 || self.session_description.r#type != "answer"
35 || !self.session_description.sdp.starts_with("v=0")
36 || self.session_description.sdp.len() > 16_000
37 || self.session_description.sdp.contains('\0')
38 {
39 return Err("invalid publisher answer");
40 }
41 Ok((
42 Identity {
43 generation: self.generation,
44 },
45 self.session_description.sdp,
46 ))
47 }
48}
49 
50#[derive(Debug, Deserialize)]
51pub struct Channels {
52 channels: Vec<Channel>,
53}
54#[derive(Debug, Deserialize)]
55#[serde(rename_all = "camelCase")]
56struct Channel {
57 data_channel_name: String,
58 id: u16,
59}
60 
61impl Channels {
62 /// The native SDK allocates streams in creation order and cannot select IDs.
63 pub fn validate(&self) -> Result<(), &'static str> {
64 if self.channels.len() != 2
65 || self
66 .channels
67 .iter()
68 .filter(|c| c.data_channel_name == "robot" && c.id == ROBOT_ID)
69 .count()
70 != 1
71 || self
72 .channels
73 .iter()
74 .filter(|c| c.data_channel_name == "spectrum" && c.id == SPECTRUM_ID)
75 .count()
76 != 1
77 {
78 return Err("unexpected data channel allocation");
79 }
80 Ok(())
81 }
82}
83 
84pub fn retryable(status: i32) -> bool {
85 status < 0 || status == 429 || status >= 500
86}
87 
88/// A generation rejected by the Worker or 55 seconds without success needs a new boot.
89pub fn needs_recovery(status: i32, since_success_us: u64) -> bool {
90 ((400..500).contains(&status) && status != 429) || since_success_us > 55_000_000
91}