nimble_protocol/
client_to_host.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/nimble-rust/nimble
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5use crate::host_to_client::TickIdUtil;
6use crate::serialize::CombinedSteps;
7use crate::{ClientRequestId, SessionConnectionSecret, Version};
8use flood_rs::{Deserialize, ReadOctetStream, Serialize, WriteOctetStream};
9use nimble_blob_stream::prelude::ReceiverToSenderFrontCommands;
10use nimble_participant::ParticipantId;
11use std::fmt::{Debug, Display};
12use std::{fmt, io};
13use tick_id::TickId;
14
15#[repr(u8)]
16enum ClientToHostCommand {
17    JoinGame = 0x01,
18    Steps = 0x02,
19    DownloadGameState = 0x03,
20    BlobStreamChannel = 0x04,
21    Connect = 0x05,
22    Ping = 0x06,
23}
24
25impl TryFrom<u8> for ClientToHostCommand {
26    type Error = io::Error;
27
28    fn try_from(value: u8) -> io::Result<Self> {
29        Ok(match value {
30            0x01 => Self::JoinGame,
31            0x02 => Self::Steps,
32            0x03 => Self::DownloadGameState,
33            0x04 => Self::BlobStreamChannel,
34            0x05 => Self::Connect,
35            0x06 => Self::Ping,
36            _ => Err(io::Error::new(
37                io::ErrorKind::InvalidData,
38                format!("Unknown ClientToHostCommand {value}"),
39            ))?,
40        })
41    }
42}
43
44#[derive(Debug, Copy, Clone, PartialEq, Eq)]
45pub struct ConnectRequest {
46    pub nimble_version: Version,
47    pub use_debug_stream: bool,
48    pub application_version: Version,
49    pub client_request_id: ClientRequestId,
50}
51impl ConnectRequest {
52    /// # Errors
53    ///
54    /// `io::Error` // TODO:
55    pub fn to_stream(&self, stream: &mut impl WriteOctetStream) -> io::Result<()> {
56        self.nimble_version.to_stream(stream)?;
57        stream.write_u8(u8::from(self.use_debug_stream))?;
58        self.application_version.to_stream(stream)?;
59        self.client_request_id.serialize(stream)?;
60        Ok(())
61    }
62
63    /// # Errors
64    ///
65    /// `io::Error` // TODO:
66    pub fn from_stream(stream: &mut impl ReadOctetStream) -> io::Result<Self> {
67        Ok(Self {
68            nimble_version: Version::from_stream(stream)?,
69            use_debug_stream: stream.read_u8()? != 0,
70            application_version: Version::from_stream(stream)?,
71            client_request_id: ClientRequestId::deserialize(stream)?,
72        })
73    }
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
77pub struct DownloadGameStateRequest {
78    pub request_id: u8,
79}
80
81impl DownloadGameStateRequest {
82    /// # Errors
83    ///
84    /// `io::Error` // TODO:
85    pub fn to_stream(&self, stream: &mut impl WriteOctetStream) -> io::Result<()> {
86        stream.write_u8(self.request_id)
87    }
88
89    /// # Errors
90    ///
91    /// `io::Error` // TODO:
92    pub fn from_stream(stream: &mut impl ReadOctetStream) -> io::Result<Self> {
93        Ok(Self {
94            request_id: stream.read_u8()?,
95        })
96    }
97}
98
99#[derive(Debug, Clone)]
100#[allow(clippy::module_name_repetitions)] // TODO:
101pub enum ClientToHostCommands<StepT: Clone + Debug + Serialize + Deserialize + Display> {
102    JoinGameType(JoinGameRequest),
103    Steps(StepsRequest<StepT>),
104    DownloadGameState(DownloadGameStateRequest),
105    BlobStreamChannel(ReceiverToSenderFrontCommands),
106    ConnectType(ConnectRequest),
107    Ping(u16),
108}
109
110impl<StepT: Clone + Debug + Serialize + Deserialize + Display> Serialize
111    for ClientToHostCommands<StepT>
112{
113    fn serialize(&self, stream: &mut impl WriteOctetStream) -> io::Result<()> {
114        stream.write_u8(self.into())?;
115        match self {
116            Self::Steps(predicted_steps_and_ack) => predicted_steps_and_ack.to_stream(stream),
117            Self::JoinGameType(join_game_request) => join_game_request.to_stream(stream),
118            Self::DownloadGameState(download_game_state) => download_game_state.to_stream(stream),
119            Self::BlobStreamChannel(blob_stream_command) => blob_stream_command.to_stream(stream),
120            Self::ConnectType(connect_request) => connect_request.to_stream(stream),
121            Self::Ping(ping_time) => stream.write_u16(*ping_time),
122        }
123    }
124}
125
126impl<StepT: Clone + Debug + Serialize + Deserialize + Display> Deserialize
127    for ClientToHostCommands<StepT>
128{
129    fn deserialize(stream: &mut impl ReadOctetStream) -> io::Result<Self> {
130        let command_value = stream.read_u8()?;
131        let command = ClientToHostCommand::try_from(command_value)?;
132        let x = match command {
133            ClientToHostCommand::JoinGame => {
134                Self::JoinGameType(JoinGameRequest::from_stream(stream)?)
135            }
136            ClientToHostCommand::Steps => Self::Steps(StepsRequest::from_stream(stream)?),
137            ClientToHostCommand::DownloadGameState => {
138                Self::DownloadGameState(DownloadGameStateRequest::from_stream(stream)?)
139            }
140            ClientToHostCommand::BlobStreamChannel => {
141                Self::BlobStreamChannel(ReceiverToSenderFrontCommands::from_stream(stream)?)
142            }
143            ClientToHostCommand::Connect => Self::ConnectType(ConnectRequest::from_stream(stream)?),
144            ClientToHostCommand::Ping => Self::Ping(stream.read_u16()?),
145        };
146        Ok(x)
147    }
148}
149
150impl<StepT: Deserialize + Serialize + Debug + Display + Clone> From<&ClientToHostCommands<StepT>>
151    for u8
152{
153    fn from(command: &ClientToHostCommands<StepT>) -> Self {
154        match command {
155            ClientToHostCommands::Steps(_) => ClientToHostCommand::Steps as Self,
156            ClientToHostCommands::JoinGameType(_) => ClientToHostCommand::JoinGame as Self,
157            ClientToHostCommands::DownloadGameState(_) => {
158                ClientToHostCommand::DownloadGameState as Self
159            }
160            ClientToHostCommands::BlobStreamChannel(_) => {
161                ClientToHostCommand::BlobStreamChannel as Self
162            }
163            ClientToHostCommands::ConnectType(_) => ClientToHostCommand::Connect as Self,
164            ClientToHostCommands::Ping(_) => ClientToHostCommand::Ping as Self,
165        }
166    }
167}
168
169impl<StepT: Clone + Debug + Eq + PartialEq + Serialize + Deserialize + Display> Display
170    for ClientToHostCommands<StepT>
171{
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        match self {
174            Self::JoinGameType(join) => write!(f, "join {join:?}"),
175            Self::Steps(predicted_steps_and_ack) => {
176                write!(f, "steps {predicted_steps_and_ack}")
177            }
178            Self::DownloadGameState(download_game_state) => {
179                write!(f, "download game state {download_game_state:?}")
180            }
181            Self::BlobStreamChannel(blob_command) => {
182                write!(f, "blob stream channel {blob_command:?}")
183            }
184            &Self::ConnectType(connect_request) => write!(f, "connect {connect_request:?}"),
185            Self::Ping(_) => write!(f, "ping"),
186        }
187    }
188}
189
190// --- Individual commands ---
191
192#[repr(u8)]
193pub enum JoinGameTypeValue {
194    NoSecret,
195    SessionSecret,
196    HostMigrationParticipantId,
197}
198
199impl JoinGameTypeValue {
200    #[must_use]
201    pub const fn to_octet(&self) -> u8 {
202        match self {
203            Self::NoSecret => Self::NoSecret as u8,
204            Self::SessionSecret => Self::SessionSecret as u8,
205            Self::HostMigrationParticipantId => Self::HostMigrationParticipantId as u8,
206        }
207    }
208    /// # Errors
209    ///
210    /// `io::Error` // TODO:
211    pub fn to_stream(self, stream: &mut impl WriteOctetStream) -> io::Result<()> {
212        stream.write_u8(self.to_octet())?;
213        Ok(())
214    }
215
216    /// # Errors
217    ///
218    /// `io::Error` // TODO:
219    pub fn from_stream(stream: &mut impl ReadOctetStream) -> io::Result<Self> {
220        let join_game_type_value_raw = stream.read_u8()?;
221        Self::try_from(join_game_type_value_raw)
222    }
223}
224
225#[derive(Debug, PartialEq, Eq, Clone, Copy)]
226pub enum JoinGameType {
227    NoSecret,
228    UseSessionSecret(SessionConnectionSecret),
229    HostMigrationParticipantId(ParticipantId),
230}
231
232impl TryFrom<u8> for JoinGameTypeValue {
233    type Error = io::Error;
234
235    fn try_from(value: u8) -> io::Result<Self> {
236        Ok(match value {
237            0x00 => Self::NoSecret,
238            0x01 => Self::SessionSecret,
239            0x02 => Self::HostMigrationParticipantId,
240            _ => Err(io::Error::new(
241                io::ErrorKind::InvalidData,
242                format!("Unknown join game type {value}"),
243            ))?,
244        })
245    }
246}
247
248impl JoinGameType {
249    #[must_use]
250    pub const fn to_octet(&self) -> u8 {
251        match self {
252            Self::NoSecret => JoinGameTypeValue::NoSecret as u8,
253            Self::UseSessionSecret(_) => JoinGameTypeValue::SessionSecret as u8,
254            Self::HostMigrationParticipantId(_) => {
255                JoinGameTypeValue::HostMigrationParticipantId as u8
256            }
257        }
258    }
259
260    /// # Errors
261    ///
262    /// `io::Error` // TODO:
263    pub fn to_stream(&self, stream: &mut impl WriteOctetStream) -> io::Result<()> {
264        stream.write_u8(self.to_octet())?;
265        match self {
266            Self::NoSecret => {}
267            Self::UseSessionSecret(session_secret) => session_secret.to_stream(stream)?,
268            Self::HostMigrationParticipantId(participant_id) => participant_id.serialize(stream)?,
269        }
270        Ok(())
271    }
272
273    /// # Errors
274    ///
275    /// `io::Error` // TODO:
276    pub fn from_stream(stream: &mut impl ReadOctetStream) -> io::Result<Self> {
277        let join_game_type_value_raw = stream.read_u8()?;
278        let value = JoinGameTypeValue::try_from(join_game_type_value_raw)?;
279        let join_game_type = match value {
280            JoinGameTypeValue::NoSecret => Self::NoSecret,
281            JoinGameTypeValue::SessionSecret => {
282                Self::UseSessionSecret(SessionConnectionSecret::from_stream(stream)?)
283            }
284            JoinGameTypeValue::HostMigrationParticipantId => {
285                Self::HostMigrationParticipantId(ParticipantId::deserialize(stream)?)
286            }
287        };
288        Ok(join_game_type)
289    }
290}
291
292#[derive(Debug, Copy, Clone, PartialEq, Eq)]
293pub struct JoinPlayerRequest {
294    pub local_index: u8,
295}
296
297impl JoinPlayerRequest {
298    /// # Errors
299    ///
300    /// `io::Error` // TODO:
301    pub fn to_stream(&self, stream: &mut impl WriteOctetStream) -> io::Result<()> {
302        stream.write_u8(self.local_index)
303    }
304
305    /// # Errors
306    ///
307    /// `io::Error` // TODO:
308    pub fn from_stream(stream: &mut impl ReadOctetStream) -> io::Result<Self> {
309        Ok(Self {
310            local_index: stream.read_u8()?,
311        })
312    }
313}
314
315#[derive(Debug, PartialEq, Eq, Clone)]
316pub struct JoinPlayerRequests {
317    pub players: Vec<JoinPlayerRequest>,
318}
319
320impl JoinPlayerRequests {
321    /// # Errors
322    ///
323    /// `io::Error` // TODO:
324    pub fn to_stream(&self, stream: &mut impl WriteOctetStream) -> io::Result<()> {
325        stream.write_u8(u8::try_from(self.players.len()).map_err(|err| {
326            io::Error::new(
327                io::ErrorKind::InvalidData,
328                format!("too many players {err}"),
329            )
330        })?)?;
331        for player in &self.players {
332            player.to_stream(stream)?;
333        }
334        Ok(())
335    }
336
337    /// # Errors
338    ///
339    /// `io::Error` // TODO:
340    pub fn from_stream(stream: &mut impl ReadOctetStream) -> io::Result<Self> {
341        let count = stream.read_u8()?;
342        let mut vec = Vec::<JoinPlayerRequest>::with_capacity(count as usize);
343        for _ in 0..count {
344            vec.push(JoinPlayerRequest::from_stream(stream)?);
345        }
346
347        Ok(Self { players: vec })
348    }
349}
350
351#[derive(Debug, PartialEq, Eq, Clone)]
352pub struct JoinGameRequest {
353    pub client_request_id: ClientRequestId,
354    pub join_game_type: JoinGameType,
355    pub player_requests: JoinPlayerRequests,
356}
357
358impl JoinGameRequest {
359    /// # Errors
360    ///
361    /// `io::Error` // TODO:
362    pub fn to_stream(&self, stream: &mut impl WriteOctetStream) -> io::Result<()> {
363        self.client_request_id.serialize(stream)?;
364        self.join_game_type.to_stream(stream)?;
365        // TODO: Add more for other join game types.
366        self.player_requests.to_stream(stream)?;
367        Ok(())
368    }
369
370    /// # Errors
371    ///
372    /// `io::Error` // TODO:
373    pub fn from_stream(stream: &mut impl ReadOctetStream) -> io::Result<Self> {
374        Ok(Self {
375            client_request_id: ClientRequestId::deserialize(stream)?,
376            join_game_type: JoinGameType::from_stream(stream)?,
377            player_requests: JoinPlayerRequests::from_stream(stream)?,
378        })
379    }
380}
381
382#[derive(Debug, PartialEq, Eq, Clone)]
383pub struct StepsAck {
384    pub waiting_for_tick_id: TickId,
385}
386
387impl Display for StepsAck {
388    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389        write!(f, "waiting:{}", self.waiting_for_tick_id)
390    }
391}
392
393impl StepsAck {
394    /// # Errors
395    ///
396    /// `io::Error` // TODO:
397    pub fn to_stream(&self, stream: &mut impl WriteOctetStream) -> io::Result<()> {
398        TickIdUtil::to_stream(self.waiting_for_tick_id, stream)?;
399        Ok(())
400    }
401
402    /// # Errors
403    ///
404    /// `io::Error` // TODO:
405    pub fn from_stream(stream: &mut impl ReadOctetStream) -> io::Result<Self> {
406        Ok(Self {
407            waiting_for_tick_id: TickIdUtil::from_stream(stream)?,
408        })
409    }
410}
411
412#[derive(Debug, Clone)]
413pub struct StepsRequest<StepT: Clone + Serialize + Deserialize + Debug + Display> {
414    pub ack: StepsAck,
415    pub combined_predicted_steps: CombinedSteps<StepT>,
416}
417
418impl<StepT: Clone + Serialize + Deserialize + Debug + Display> Display for StepsRequest<StepT> {
419    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420        write!(
421            f,
422            "steps-request ack:{}, steps:{}",
423            self.ack, self.combined_predicted_steps
424        )
425    }
426}
427
428impl<StepT: Clone + Serialize + Deserialize + Debug + Display> StepsRequest<StepT> {
429    /// # Errors
430    ///
431    /// `io::Error` // TODO:
432    pub fn to_stream(&self, stream: &mut impl WriteOctetStream) -> io::Result<()> {
433        self.ack.to_stream(stream)?;
434
435        self.combined_predicted_steps.serialize(stream)?;
436        Ok(())
437    }
438
439    /// # Errors
440    ///
441    /// `io::Error` // TODO:
442    pub fn from_stream(stream: &mut impl ReadOctetStream) -> io::Result<Self> {
443        Ok(Self {
444            ack: StepsAck::from_stream(stream)?,
445            combined_predicted_steps: CombinedSteps::deserialize(stream)?,
446        })
447    }
448}