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 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}