Skip to main content

fortress_rollback/network/
messages.rs

1use serde::{Deserialize, Serialize};
2
3use crate::Frame;
4
5/// Connection status for a peer in the network protocol.
6///
7/// # Note
8///
9/// This type is re-exported in [`__internal`](crate::__internal) for testing and fuzzing.
10/// It is not part of the stable public API.
11#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
12pub struct ConnectionStatus {
13    /// Whether this peer has disconnected.
14    pub disconnected: bool,
15    /// The last frame received from this peer.
16    pub last_frame: Frame,
17}
18
19impl Default for ConnectionStatus {
20    fn default() -> Self {
21        Self {
22            disconnected: false,
23            last_frame: Frame::NULL,
24        }
25    }
26}
27
28impl std::fmt::Display for ConnectionStatus {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        // Destructure to ensure all fields are included when new fields are added.
31        let Self {
32            disconnected,
33            last_frame,
34        } = self;
35
36        if *disconnected {
37            write!(f, "Disconnected(last_frame={})", last_frame.as_i32())
38        } else {
39            write!(f, "Connected(last_frame={})", last_frame.as_i32())
40        }
41    }
42}
43
44#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
45pub(crate) struct SyncRequest {
46    pub random_request: u32, // please reply back with this random data
47}
48
49#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
50pub(crate) struct SyncReply {
51    pub random_reply: u32, // here's your random data back
52}
53
54#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub(crate) struct Input {
56    pub peer_connect_status: Vec<ConnectionStatus>,
57    pub disconnect_requested: bool,
58    pub start_frame: Frame,
59    pub ack_frame: Frame,
60    pub bytes: Vec<u8>,
61}
62
63impl Default for Input {
64    fn default() -> Self {
65        Self {
66            peer_connect_status: Vec::new(),
67            disconnect_requested: false,
68            start_frame: Frame::NULL,
69            ack_frame: Frame::NULL,
70            bytes: Vec::new(),
71        }
72    }
73}
74
75impl std::fmt::Debug for Input {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        // Destructure to ensure all fields are included when new fields are added.
78        let Self {
79            peer_connect_status,
80            disconnect_requested,
81            start_frame,
82            ack_frame,
83            bytes,
84        } = self;
85
86        f.debug_struct("Input")
87            .field("peer_connect_status", peer_connect_status)
88            .field("disconnect_requested", disconnect_requested)
89            .field("start_frame", start_frame)
90            .field("ack_frame", ack_frame)
91            .field("bytes", &BytesDebug(bytes))
92            .finish()
93    }
94}
95struct BytesDebug<'a>(&'a [u8]);
96
97impl std::fmt::Debug for BytesDebug<'_> {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.write_str("0x")?;
100        for byte in self.0 {
101            write!(f, "{:02x}", byte)?;
102        }
103        Ok(())
104    }
105}
106
107#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
108pub(crate) struct InputAck {
109    pub ack_frame: Frame,
110}
111
112impl Default for InputAck {
113    fn default() -> Self {
114        Self {
115            ack_frame: Frame::NULL,
116        }
117    }
118}
119
120#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
121pub(crate) struct QualityReport {
122    /// Frame advantage of other player.
123    ///
124    /// While on the one hand 2 bytes is overkill for a value that is typically in the range of say
125    /// -8 to 8 (for the default prediction window size of 8), on the other hand if we don't get a
126    /// chance to read quality reports for a time (due to being paused in a background tab, or
127    /// someone stepping through code in a debugger) then it is easy to exceed the range of a signed
128    /// 1 byte integer at common FPS values.
129    ///
130    /// So by using an i16 instead of an i8, we can avoid clamping the value for +/- ~32k frames, or
131    /// about +/- 524 seconds of frame advantage - and after 500+ seconds it's a pretty reasonable
132    /// assumption that the other player will have been disconnected, or at least that they're so
133    /// far ahead/behind that clamping the value to an i16 won't matter for any practical purpose.
134    pub frame_advantage: i16,
135    pub ping: u128,
136}
137
138#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
139pub(crate) struct QualityReply {
140    pub pong: u128,
141}
142
143#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
144pub(crate) struct ChecksumReport {
145    pub checksum: u128,
146    pub frame: Frame,
147}
148
149#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
150pub(crate) struct MessageHeader {
151    pub magic: u16,
152}
153
154#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
155pub(crate) enum MessageBody {
156    SyncRequest(SyncRequest),
157    SyncReply(SyncReply),
158    Input(Input),
159    InputAck(InputAck),
160    QualityReport(QualityReport),
161    QualityReply(QualityReply),
162    ChecksumReport(ChecksumReport),
163    KeepAlive,
164}
165
166/// A messages that [`NonBlockingSocket`] sends and receives. When implementing [`NonBlockingSocket`],
167/// you should deserialize received messages into this `Message` type and pass them.
168///
169/// [`NonBlockingSocket`]: crate::NonBlockingSocket
170#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
171pub struct Message {
172    pub(crate) header: MessageHeader,
173    pub(crate) body: MessageBody,
174}
175
176#[cfg(test)]
177#[allow(
178    clippy::panic,
179    clippy::unwrap_used,
180    clippy::expect_used,
181    clippy::indexing_slicing
182)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_connection_status_default() {
188        let status = ConnectionStatus::default();
189        assert!(!status.disconnected);
190        assert_eq!(status.last_frame, Frame::NULL);
191    }
192
193    #[test]
194    fn test_connection_status_debug_clone() {
195        let status = ConnectionStatus {
196            disconnected: true,
197            last_frame: Frame::new(100),
198        };
199        let cloned = status;
200        assert!(cloned.disconnected);
201        assert_eq!(cloned.last_frame, Frame::new(100));
202        let debug = format!("{:?}", status);
203        assert!(debug.contains("ConnectionStatus"));
204    }
205
206    #[test]
207    fn test_connection_status_display_connected() {
208        let status = ConnectionStatus {
209            disconnected: false,
210            last_frame: Frame::new(42),
211        };
212        let display = format!("{}", status);
213        assert_eq!(display, "Connected(last_frame=42)");
214    }
215
216    #[test]
217    fn test_connection_status_display_disconnected() {
218        let status = ConnectionStatus {
219            disconnected: true,
220            last_frame: Frame::new(100),
221        };
222        let display = format!("{}", status);
223        assert_eq!(display, "Disconnected(last_frame=100)");
224    }
225
226    #[test]
227    fn test_connection_status_display_null_frame() {
228        let status = ConnectionStatus::default();
229        let display = format!("{}", status);
230        assert_eq!(display, "Connected(last_frame=-1)");
231    }
232
233    #[test]
234    fn test_sync_request_default() {
235        let req = SyncRequest::default();
236        assert_eq!(req.random_request, 0);
237    }
238
239    #[test]
240    fn test_sync_reply_default() {
241        let reply = SyncReply::default();
242        assert_eq!(reply.random_reply, 0);
243    }
244
245    #[test]
246    fn test_input_default() {
247        let input = Input::default();
248        assert!(input.peer_connect_status.is_empty());
249        assert!(!input.disconnect_requested);
250        assert_eq!(input.start_frame, Frame::NULL);
251        assert_eq!(input.ack_frame, Frame::NULL);
252        assert!(input.bytes.is_empty());
253    }
254
255    #[test]
256    fn test_input_debug() {
257        let input = Input {
258            peer_connect_status: vec![ConnectionStatus::default()],
259            disconnect_requested: true,
260            start_frame: Frame::new(10),
261            ack_frame: Frame::new(5),
262            bytes: vec![0xDE, 0xAD, 0xBE, 0xEF],
263        };
264        let debug = format!("{:?}", input);
265        assert!(debug.contains("Input"));
266        assert!(debug.contains("disconnect_requested"));
267        assert!(debug.contains("0xdeadbeef"));
268    }
269
270    #[test]
271    fn test_input_ack_default() {
272        let ack = InputAck::default();
273        assert_eq!(ack.ack_frame, Frame::NULL);
274    }
275
276    #[test]
277    fn test_quality_report_default() {
278        let report = QualityReport::default();
279        assert_eq!(report.frame_advantage, 0);
280        assert_eq!(report.ping, 0);
281    }
282
283    #[test]
284    fn test_quality_reply_default() {
285        let reply = QualityReply::default();
286        assert_eq!(reply.pong, 0);
287    }
288
289    #[test]
290    fn test_checksum_report_default() {
291        let report = ChecksumReport::default();
292        assert_eq!(report.checksum, 0);
293        assert_eq!(report.frame, Frame::default());
294    }
295
296    #[test]
297    fn test_message_header_default() {
298        let header = MessageHeader::default();
299        assert_eq!(header.magic, 0);
300    }
301
302    #[test]
303    fn test_message_body_variants() {
304        // Test each variant can be created and compared
305        let sync_req = MessageBody::SyncRequest(SyncRequest { random_request: 42 });
306        let sync_req2 = MessageBody::SyncRequest(SyncRequest { random_request: 42 });
307        assert_eq!(sync_req, sync_req2);
308
309        let sync_reply = MessageBody::SyncReply(SyncReply { random_reply: 123 });
310        let debug = format!("{:?}", sync_reply);
311        assert!(debug.contains("SyncReply"));
312
313        let input = MessageBody::Input(Input::default());
314        assert!(matches!(input, MessageBody::Input(_)));
315
316        let input_ack = MessageBody::InputAck(InputAck::default());
317        assert!(matches!(input_ack, MessageBody::InputAck(_)));
318
319        let quality_report = MessageBody::QualityReport(QualityReport::default());
320        assert!(matches!(quality_report, MessageBody::QualityReport(_)));
321
322        let quality_reply = MessageBody::QualityReply(QualityReply::default());
323        assert!(matches!(quality_reply, MessageBody::QualityReply(_)));
324
325        let checksum_report = MessageBody::ChecksumReport(ChecksumReport::default());
326        assert!(matches!(checksum_report, MessageBody::ChecksumReport(_)));
327
328        let keep_alive = MessageBody::KeepAlive;
329        assert!(matches!(keep_alive, MessageBody::KeepAlive));
330    }
331
332    #[test]
333    #[allow(clippy::redundant_clone)] // Testing Clone trait implementation
334    fn test_message_clone_eq() {
335        let msg = Message {
336            header: MessageHeader { magic: 0x1234 },
337            body: MessageBody::KeepAlive,
338        };
339        let cloned = msg.clone();
340        assert_eq!(msg, cloned);
341    }
342
343    #[test]
344    fn test_message_serialization() {
345        use crate::network::codec;
346
347        let msg = Message {
348            header: MessageHeader { magic: 0xABCD },
349            body: MessageBody::SyncRequest(SyncRequest {
350                random_request: 999,
351            }),
352        };
353
354        // Test that serialization/deserialization roundtrips correctly
355        let serialized = codec::encode(&msg).expect("serialization should succeed");
356        let (deserialized, _): (Message, _) =
357            codec::decode(&serialized).expect("deserialization should succeed");
358        assert_eq!(msg, deserialized);
359    }
360
361    #[test]
362    fn test_input_serialization() {
363        use crate::network::codec;
364
365        let input = Input {
366            peer_connect_status: vec![
367                ConnectionStatus {
368                    disconnected: false,
369                    last_frame: Frame::new(10),
370                },
371                ConnectionStatus {
372                    disconnected: true,
373                    last_frame: Frame::new(20),
374                },
375            ],
376            disconnect_requested: false,
377            start_frame: Frame::new(100),
378            ack_frame: Frame::new(50),
379            bytes: vec![1, 2, 3, 4, 5],
380        };
381
382        let serialized = codec::encode(&input).expect("serialization should succeed");
383        let (deserialized, _): (Input, _) =
384            codec::decode(&serialized).expect("deserialization should succeed");
385        assert_eq!(input, deserialized);
386    }
387
388    #[test]
389    fn test_bytes_debug_empty() {
390        let input = Input {
391            peer_connect_status: vec![],
392            disconnect_requested: false,
393            start_frame: Frame::NULL,
394            ack_frame: Frame::NULL,
395            bytes: vec![],
396        };
397        let debug = format!("{:?}", input);
398        assert!(debug.contains("0x")); // Empty bytes should still show "0x" prefix
399    }
400}