Skip to main content

camloc_common/
hosts.rs

1use crate::Position;
2
3#[allow(clippy::unusual_byte_groupings)]
4pub mod constants {
5
6    pub const MAIN_PORT: u16 = 0xdddd;
7    pub const ORGANIZER_STARTER_PORT: u16 = 0xdddb;
8
9    pub mod status_reply {
10
11        pub mod host_type {
12            pub const CONFIGLESS: u8 = 0b10_0_0_0000;
13            pub const CLIENT: u8 = 0b01_0_0_0000;
14            pub const SERVER: u8 = 0b11_0_0_0000;
15        }
16
17        pub mod state {
18            pub const RUNNING: u8 = 0b00_1_0_0000;
19            pub const IDLE: u8 = 0b00_0_0_0000;
20        }
21
22        pub mod masks {
23            pub const HOST_TYPE: u8 = 0b11_0_0_0000;
24            pub const STATE: u8 = 0b00_1_0_0000;
25            pub const CALIBRATED: u8 = 0b00_0_1_0000;
26
27            pub const ONES: u8 = 0xff;
28        }
29    }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum HostType {
34    Client { calibrated: bool },
35    ConfiglessClient,
36    Server,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub struct HostInfo {
41    pub host_state: HostState,
42    pub host_type: HostType,
43}
44
45#[derive(Debug, thiserror::Error)]
46#[error("Tried to convert unreachable host to u8, which shouldn't happen")]
47pub struct ConvertUnreachableHost;
48impl TryInto<u8> for HostInfo {
49    type Error = ConvertUnreachableHost;
50
51    fn try_into(self) -> Result<u8, Self::Error> {
52        use constants::status_reply::{host_type::*, masks, state::*};
53        use HostState::*;
54        use HostType::*;
55
56        Ok(match self.host_type {
57            Client { calibrated } => {
58                CLIENT
59                    | (if calibrated {
60                        masks::CALIBRATED & masks::ONES
61                    } else {
62                        0
63                    })
64            }
65
66            ConfiglessClient => CONFIGLESS,
67            Server => SERVER,
68        } | (match self.host_state {
69            Unreachable => return Err(ConvertUnreachableHost),
70            Running => RUNNING,
71            Idle => IDLE,
72        }))
73    }
74}
75
76impl TryFrom<u8> for HostInfo {
77    type Error = ();
78
79    fn try_from(v: u8) -> Result<Self, Self::Error> {
80        use constants::status_reply::{host_type::*, masks, state::*};
81        use HostState::*;
82        use HostType::*;
83
84        let host_type = match v & masks::HOST_TYPE {
85            CONFIGLESS => ConfiglessClient,
86
87            CLIENT => {
88                let calibrated = (v & masks::CALIBRATED) != 0;
89                Client { calibrated }
90            }
91
92            SERVER => Server,
93
94            _ => return Err(()),
95        };
96
97        let host_state = match v & masks::STATE {
98            RUNNING => Running,
99            IDLE => Idle,
100            _ => return Err(()),
101        };
102
103        Ok(HostInfo {
104            host_type,
105            host_state,
106        })
107    }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
111pub enum HostState {
112    Unreachable,
113    Running,
114    Idle,
115}
116
117#[must_use]
118#[derive(Debug, Clone, Copy, PartialEq)]
119pub enum Command<'a> {
120    Ping,
121
122    Connect {
123        position: Position,
124        fov: f64,
125    },
126    ClientDisconnect,
127
128    Start,
129    StartServer {
130        cube: [u8; 4],
131    },
132    StartConfigless {
133        ip: &'a str,
134    },
135    Stop,
136
137    RequestImage,
138    ImagesDone,
139
140    ValueUpdate(ClientData),
141    InfoUpdate {
142        client_ip: &'a str,
143        position: Position,
144        fov: Option<f64>,
145    },
146}
147
148impl Command<'_> {
149    pub const PING: u8 = 0x0b;
150    pub const CONNECT: u8 = 0xcc;
151    pub const CLIENT_DISCONNECT: u8 = 0xdc;
152    pub const START: u8 = 0x60;
153    pub const START_SERVER: u8 = 0x55;
154    pub const START_CONFIGLESS: u8 = 0x6c;
155    pub const STOP: u8 = 0xcd;
156    pub const REQUEST_IMAGE: u8 = 0x17;
157    pub const IMAGES_DONE: u8 = 0x1d;
158    pub const VALUE_UPDATE: u8 = 0x21;
159    pub const INFO_UPDATE: u8 = 0x1f;
160}
161
162impl From<Command<'_>> for Vec<u8> {
163    fn from(value: Command) -> Self {
164        match value {
165            Command::Ping => vec![Command::PING],
166
167            Command::Connect { position, fov } => [
168                Command::CONNECT.to_be_bytes().as_slice(),
169                position.to_be_bytes().as_slice(),
170                fov.to_be_bytes().as_slice(),
171            ]
172            .concat(),
173
174            Command::ClientDisconnect => vec![Command::CLIENT_DISCONNECT],
175
176            Command::Start => vec![Command::START],
177            Command::StartServer { cube } => [
178                Command::START_SERVER.to_be_bytes().as_slice(),
179                cube.map(u8::to_be).as_slice(),
180            ]
181            .concat(),
182
183            Command::StartConfigless { ip } => [
184                Command::START_CONFIGLESS.to_be_bytes().as_slice(),
185                (ip.len() as u16).to_be_bytes().as_slice(),
186                ip.as_bytes(),
187            ]
188            .concat(),
189
190            Command::Stop => vec![Command::STOP],
191
192            Command::RequestImage => vec![Command::REQUEST_IMAGE],
193
194            Command::ImagesDone => vec![Command::IMAGES_DONE],
195
196            Command::ValueUpdate(ClientData {
197                marker_id,
198                x_position: value,
199            }) => [
200                Command::VALUE_UPDATE.to_be_bytes().as_slice(),
201                marker_id.to_be_bytes().as_slice(),
202                value.to_be_bytes().as_slice(),
203            ]
204            .concat(),
205
206            Command::InfoUpdate {
207                client_ip,
208                position,
209                fov,
210            } => {
211                let ip = client_ip.as_bytes();
212                let ip_len = (ip.len() as u16).to_be_bytes();
213                let c = Command::INFO_UPDATE.to_be_bytes();
214                let p = position.to_be_bytes();
215                let f = if fov.is_some() { 1u8 } else { 0u8 }.to_be_bytes();
216
217                let mut v = vec![
218                    ip_len.as_slice(),
219                    ip,
220                    c.as_slice(),
221                    p.as_slice(),
222                    f.as_slice(),
223                ];
224
225                let fov = fov.map(f64::to_be_bytes);
226                if let Some(fov) = &fov {
227                    v.push(fov);
228                }
229
230                v.concat()
231            }
232        }
233    }
234}
235
236impl<'a> TryFrom<&'a [u8]> for Command<'a> {
237    type Error = ();
238
239    fn try_from(buf: &'a [u8]) -> Result<Self, Self::Error> {
240        let len = buf.len();
241        if len < 1 {
242            return Err(());
243        }
244
245        // without the command byte
246        let cmd = buf[0];
247        let buf = &buf[1..];
248
249        (|| {
250            Some(match cmd {
251                Command::PING => Command::Ping,
252                Command::START => Command::Start,
253                Command::STOP => Command::Stop,
254                Command::REQUEST_IMAGE => Command::RequestImage,
255                Command::IMAGES_DONE => Command::ImagesDone,
256                Command::CLIENT_DISCONNECT => Command::ClientDisconnect,
257
258                Command::VALUE_UPDATE => Command::ValueUpdate(ClientData {
259                    marker_id: u8::from_be(*buf.first()?),
260                    x_position: f64::from_be_bytes(buf.get(1..9)?.try_into().ok()?),
261                }),
262
263                Command::CONNECT => Command::Connect {
264                    position: Position::from_be_bytes(&buf.get(..24)?.try_into().ok()?),
265                    fov: f64::from_be_bytes(buf.get(24..32)?.try_into().ok()?),
266                },
267
268                Command::INFO_UPDATE => {
269                    let ip_len = u16::from_be_bytes(buf.get(..2)?.try_into().ok()?) as usize;
270                    let client_ip = std::str::from_utf8(buf.get(2..2 + ip_len)?).unwrap();
271                    let position = Position::from_be_bytes(&buf.get(..26)?.try_into().ok()?);
272                    let fov = if *buf.get(26)? == 1 {
273                        Some(f64::from_be_bytes(buf.get(27..35)?.try_into().ok()?))
274                    } else {
275                        None
276                    };
277                    Command::InfoUpdate {
278                        client_ip,
279                        position,
280                        fov,
281                    }
282                }
283
284                Command::START_CONFIGLESS => {
285                    let ip_len = u16::from_be_bytes(buf.get(..2)?.try_into().ok()?) as usize;
286
287                    Command::StartConfigless {
288                        ip: std::str::from_utf8(buf.get(2..2 + ip_len)?).ok()?,
289                    }
290                }
291
292                Command::START_SERVER => Command::StartServer {
293                    cube: buf.get(..4)?.try_into().ok()?,
294                },
295
296                _ => return None,
297            })
298        })()
299        .ok_or(())
300    }
301}
302
303impl<'a> TryFrom<&'a mut [u8]> for Command<'a> {
304    type Error = ();
305
306    fn try_from(buf: &'a mut [u8]) -> Result<Self, Self::Error> {
307        (&*buf).try_into()
308    }
309}
310
311#[derive(Debug, Clone, Copy, PartialEq)]
312pub struct ClientData {
313    pub x_position: f64,
314    pub marker_id: u8,
315}
316
317impl ClientData {
318    pub fn new(marker_id: u8, x_position: f64) -> ClientData {
319        Self {
320            x_position,
321            marker_id,
322        }
323    }
324}