1pub mod delta;
7pub mod history;
8pub mod reliable;
9
10use std::collections::VecDeque;
11use std::fmt;
12
13pub const MAX_PACKET_BYTES: usize = 1200;
14pub const MAX_PLAYERS: usize = 16;
15pub const MAX_INPUTS_PER_PACKET: usize = 6;
16pub const MAX_QUEUED_INPUTS: usize = 64;
17pub const BUTTON_JUMP: u8 = 1;
18pub const BUTTON_FIRE: u8 = 2;
19pub const BUTTON_RELOAD: u8 = 4;
20pub const MAX_PITCH: f32 = 1.55;
21const MAGIC: &[u8; 4] = b"FVRX";
22pub const PROTOCOL_VERSION: u8 = 3;
24
25#[derive(Clone, Copy, Debug, Default, PartialEq)]
26pub struct InputFrame {
27 pub sequence: u32,
28 pub move_x: f32,
30 pub move_z: f32,
32 pub yaw: f32,
33 pub pitch: f32,
35 pub buttons: u8,
36}
37impl InputFrame {
38 pub fn sanitized(mut self) -> Self {
39 let finite = |v: f32| if v.is_finite() { v } else { 0.0 };
40 self.move_x = finite(self.move_x).clamp(-1.0, 1.0);
41 self.move_z = finite(self.move_z).clamp(-1.0, 1.0);
42 self.yaw = (finite(self.yaw) + std::f32::consts::PI).rem_euclid(std::f32::consts::TAU)
43 - std::f32::consts::PI;
44 self.pitch = finite(self.pitch).clamp(-MAX_PITCH, MAX_PITCH);
45 self.buttons &= BUTTON_JUMP | BUTTON_FIRE | BUTTON_RELOAD;
46 self
47 }
48 pub fn is_valid(self) -> bool {
49 self.move_x.is_finite()
50 && self.move_x.abs() <= 1.0
51 && self.move_z.is_finite()
52 && self.move_z.abs() <= 1.0
53 && self.yaw.is_finite()
54 && self.yaw.abs() <= std::f32::consts::PI
55 && self.pitch.is_finite()
56 && self.pitch.abs() <= MAX_PITCH
57 && self.buttons & !(BUTTON_JUMP | BUTTON_FIRE | BUTTON_RELOAD) == 0
58 }
59}
60
61#[derive(Clone, Debug, PartialEq)]
62pub enum ClientPacket {
63 EventAck {
64 session: u64,
65 sequence: u64,
66 },
67 Hello {
68 nonce: u64,
69 },
70 Input {
71 session: u64,
72 inputs: Vec<InputFrame>,
73 },
74}
75#[derive(Clone, Debug, PartialEq)]
76pub enum ServerPacket {
77 ReliableEvent {
78 session: u64,
79 event: reliable::EventPacket,
80 },
81 Welcome {
82 nonce: u64,
83 session: u64,
84 player_id: u8,
85 tick: u32,
86 },
87 Snapshot(Snapshot),
88}
89#[derive(Clone, Debug, Default, PartialEq)]
90pub struct Snapshot {
91 pub session: u64,
93 pub tick: u32,
94 pub ack_sequence: u32,
95 pub ack_valid: bool,
96 pub players: Vec<PlayerSnapshot>,
97}
98#[derive(Clone, Copy, Debug, Default, PartialEq)]
99pub struct PlayerSnapshot {
100 pub id: u8,
101 pub position: [f32; 3],
102 pub velocity: [f32; 3],
103 pub yaw: f32,
104 pub pitch: f32,
105 pub health: u8,
106 pub ammo: u8,
107 pub grounded: bool,
108 pub jump_coyote: f32,
109 pub jump_buffer: f32,
110 pub jump_held: bool,
111 pub fire_cooldown: u16,
112 pub reload_ticks: u16,
113 pub respawn_ticks: u16,
114}
115impl PlayerSnapshot {
116 fn is_valid(self) -> bool {
117 (self.id as usize) < MAX_PLAYERS
118 && self
119 .position
120 .into_iter()
121 .all(|v| v.is_finite() && v.abs() <= 4096.0)
122 && self
123 .velocity
124 .into_iter()
125 .all(|v| v.is_finite() && v.abs() <= 256.0)
126 && self.yaw.is_finite()
127 && self.yaw.abs() <= std::f32::consts::PI
128 && self.pitch.is_finite()
129 && self.pitch.abs() <= MAX_PITCH
130 && self.jump_coyote.is_finite()
131 && (0. ..=0.5).contains(&self.jump_coyote)
132 && self.jump_buffer.is_finite()
133 && (0. ..=0.5).contains(&self.jump_buffer)
134 && self.health <= 100
135 && self.ammo <= 30
136 && self.fire_cooldown <= 4096
137 && self.reload_ticks <= 4096
138 && self.respawn_ticks <= 4096
139 }
140}
141
142#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub enum ProtocolError {
144 TooLarge,
145 Truncated,
146 Header,
147 Version,
148 Kind,
149 Invalid,
150 Trailing,
151}
152impl fmt::Display for ProtocolError {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 write!(f, "invalid Ferravex packet: {self:?}")
155 }
156}
157impl std::error::Error for ProtocolError {}
158
159pub fn sequence_newer(candidate: u32, previous: u32) -> bool {
161 let delta = candidate.wrapping_sub(previous);
162 delta != 0 && delta < (1 << 31)
163}
164
165#[derive(Debug, Default)]
167pub struct InputQueue {
168 queue: VecDeque<InputFrame>,
169 newest: Option<u32>,
170}
171impl InputQueue {
172 pub fn push(&mut self, input: InputFrame) -> bool {
173 if !input.is_valid()
174 || self.queue.len() >= MAX_QUEUED_INPUTS
175 || self
176 .newest
177 .is_some_and(|old| !sequence_newer(input.sequence, old))
178 {
179 return false;
180 }
181 self.newest = Some(input.sequence);
182 self.queue.push_back(input);
183 true
184 }
185 pub fn pop(&mut self) -> Option<InputFrame> {
186 self.queue.pop_front()
187 }
188 pub fn pop_latest(&mut self) -> Option<InputFrame> {
193 let mut latest = self.queue.pop_back()?;
194 for older in self.queue.drain(..) {
195 latest.buttons |= older.buttons;
196 }
197 Some(latest)
198 }
199 pub fn len(&self) -> usize {
200 self.queue.len()
201 }
202 pub fn is_empty(&self) -> bool {
203 self.queue.is_empty()
204 }
205}
206
207fn header(kind: u8) -> Vec<u8> {
208 let mut out = Vec::with_capacity(MAX_PACKET_BYTES);
209 out.extend_from_slice(MAGIC);
210 out.extend_from_slice(&[PROTOCOL_VERSION, kind]);
211 out
212}
213fn put_u32(out: &mut Vec<u8>, n: u32) {
214 out.extend_from_slice(&n.to_le_bytes());
215}
216fn put_u64(out: &mut Vec<u8>, n: u64) {
217 out.extend_from_slice(&n.to_le_bytes());
218}
219fn put_f32(out: &mut Vec<u8>, n: f32) {
220 put_u32(out, n.to_bits());
221}
222fn put_u16(out: &mut Vec<u8>, n: u16) {
223 out.extend_from_slice(&n.to_le_bytes());
224}
225fn put_input(out: &mut Vec<u8>, input: &InputFrame) {
226 put_u32(out, input.sequence);
227 for n in [input.move_x, input.move_z, input.yaw, input.pitch] {
228 put_f32(out, n);
229 }
230 out.push(input.buttons);
231}
232
233pub fn encode_client(packet: &ClientPacket) -> Result<Vec<u8>, ProtocolError> {
234 let mut out = match packet {
235 ClientPacket::EventAck { .. } => header(6),
236 ClientPacket::Hello { .. } => header(1),
237 ClientPacket::Input { .. } => header(2),
238 };
239 match packet {
240 ClientPacket::EventAck { session, sequence } => {
241 if *session == 0 {
242 return Err(ProtocolError::Invalid);
243 }
244 put_u64(&mut out, *session);
245 put_u64(&mut out, *sequence);
246 }
247 ClientPacket::Hello { nonce } => put_u64(&mut out, *nonce),
248 ClientPacket::Input { session, inputs } => {
249 if *session == 0
250 || inputs.is_empty()
251 || inputs.len() > MAX_INPUTS_PER_PACKET
252 || inputs.iter().any(|input| !input.is_valid())
253 {
254 return Err(ProtocolError::Invalid);
255 }
256 put_u64(&mut out, *session);
257 out.push(inputs.len() as u8);
258 for input in inputs {
259 put_input(&mut out, input);
260 }
261 }
262 }
263 Ok(out)
264}
265pub fn encode_server(packet: &ServerPacket) -> Result<Vec<u8>, ProtocolError> {
266 let mut out = match packet {
267 ServerPacket::ReliableEvent { .. } => header(5),
268 ServerPacket::Welcome { .. } => header(3),
269 ServerPacket::Snapshot(_) => header(4),
270 };
271 match packet {
272 ServerPacket::ReliableEvent { session, event } => {
273 if *session == 0 || event.payload.len() > reliable::EVENT_BYTES {
274 return Err(ProtocolError::Invalid);
275 }
276 put_u64(&mut out, *session);
277 put_u64(&mut out, event.sequence);
278 put_u16(&mut out, event.payload.len() as u16);
279 out.extend_from_slice(&event.payload);
280 }
281 ServerPacket::Welcome {
282 nonce,
283 session,
284 player_id,
285 tick,
286 } => {
287 if *session == 0 || *player_id as usize >= MAX_PLAYERS {
288 return Err(ProtocolError::Invalid);
289 }
290 put_u64(&mut out, *nonce);
291 put_u64(&mut out, *session);
292 out.push(*player_id);
293 put_u32(&mut out, *tick);
294 }
295 ServerPacket::Snapshot(snapshot) => {
296 if snapshot.session == 0 || snapshot.players.len() > MAX_PLAYERS {
297 return Err(ProtocolError::Invalid);
298 }
299 let mut ids = 0u16;
300 for p in &snapshot.players {
301 if !p.is_valid() || ids & (1u16 << p.id) != 0 {
302 return Err(ProtocolError::Invalid);
303 }
304 ids |= 1u16 << p.id;
305 }
306 put_u64(&mut out, snapshot.session);
307 put_u32(&mut out, snapshot.tick);
308 put_u32(&mut out, snapshot.ack_sequence);
309 out.push(snapshot.ack_valid as u8);
310 out.push(snapshot.players.len() as u8);
311 for p in &snapshot.players {
312 out.push(p.id);
313 for n in p
314 .position
315 .into_iter()
316 .chain(p.velocity)
317 .chain([p.yaw, p.pitch])
318 {
319 put_f32(&mut out, n);
320 }
321 out.extend_from_slice(&[p.health, p.ammo, p.grounded as u8]);
322 put_f32(&mut out, p.jump_coyote);
323 put_f32(&mut out, p.jump_buffer);
324 out.push(p.jump_held as u8);
325 put_u16(&mut out, p.fire_cooldown);
326 put_u16(&mut out, p.reload_ticks);
327 put_u16(&mut out, p.respawn_ticks);
328 }
329 }
330 }
331 if out.len() > MAX_PACKET_BYTES {
332 return Err(ProtocolError::TooLarge);
333 }
334 Ok(out)
335}
336
337struct Reader<'a> {
338 data: &'a [u8],
339 pos: usize,
340}
341impl<'a> Reader<'a> {
342 fn new(data: &'a [u8]) -> Result<(Self, u8), ProtocolError> {
343 if data.len() > MAX_PACKET_BYTES {
344 return Err(ProtocolError::TooLarge);
345 }
346 if data.len() < 6 {
347 return Err(ProtocolError::Truncated);
348 }
349 if &data[..4] != MAGIC {
350 return Err(ProtocolError::Header);
351 }
352 if data[4] != PROTOCOL_VERSION {
353 return Err(ProtocolError::Version);
354 }
355 Ok((Self { data, pos: 6 }, data[5]))
356 }
357 fn bytes<const N: usize>(&mut self) -> Result<[u8; N], ProtocolError> {
358 let bytes = self
359 .data
360 .get(self.pos..self.pos + N)
361 .ok_or(ProtocolError::Truncated)?;
362 self.pos += N;
363 Ok(bytes.try_into().expect("slice length checked"))
364 }
365 fn u8(&mut self) -> Result<u8, ProtocolError> {
366 Ok(self.bytes::<1>()?[0])
367 }
368 fn u16(&mut self) -> Result<u16, ProtocolError> {
369 Ok(u16::from_le_bytes(self.bytes()?))
370 }
371 fn u32(&mut self) -> Result<u32, ProtocolError> {
372 Ok(u32::from_le_bytes(self.bytes()?))
373 }
374 fn u64(&mut self) -> Result<u64, ProtocolError> {
375 Ok(u64::from_le_bytes(self.bytes()?))
376 }
377 fn f32(&mut self) -> Result<f32, ProtocolError> {
378 Ok(f32::from_bits(self.u32()?))
379 }
380 fn boolean(&mut self) -> Result<bool, ProtocolError> {
381 match self.u8()? {
382 0 => Ok(false),
383 1 => Ok(true),
384 _ => Err(ProtocolError::Invalid),
385 }
386 }
387 fn end(self) -> Result<(), ProtocolError> {
388 if self.pos == self.data.len() {
389 Ok(())
390 } else {
391 Err(ProtocolError::Trailing)
392 }
393 }
394 fn input(&mut self) -> Result<InputFrame, ProtocolError> {
395 let frame = InputFrame {
396 sequence: self.u32()?,
397 move_x: self.f32()?,
398 move_z: self.f32()?,
399 yaw: self.f32()?,
400 pitch: self.f32()?,
401 buttons: self.u8()?,
402 };
403 if !frame.is_valid() {
404 return Err(ProtocolError::Invalid);
405 }
406 Ok(frame)
407 }
408}
409pub fn decode_client(data: &[u8]) -> Result<ClientPacket, ProtocolError> {
410 let (mut reader, kind) = Reader::new(data)?;
411 let packet = match kind {
412 6 => {
413 let session = reader.u64()?;
414 let sequence = reader.u64()?;
415 if session == 0 {
416 return Err(ProtocolError::Invalid);
417 }
418 ClientPacket::EventAck { session, sequence }
419 }
420 1 => ClientPacket::Hello {
421 nonce: reader.u64()?,
422 },
423 2 => {
424 let session = reader.u64()?;
425 let count = reader.u8()? as usize;
426 if session == 0 || count == 0 || count > MAX_INPUTS_PER_PACKET {
427 return Err(ProtocolError::Invalid);
428 }
429 let mut inputs = Vec::with_capacity(count);
430 for _ in 0..count {
431 inputs.push(reader.input()?);
432 }
433 ClientPacket::Input { session, inputs }
434 }
435 _ => return Err(ProtocolError::Kind),
436 };
437 reader.end()?;
438 Ok(packet)
439}
440pub fn decode_server(data: &[u8]) -> Result<ServerPacket, ProtocolError> {
441 let (mut r, kind) = Reader::new(data)?;
442 let packet = match kind {
443 5 => {
444 let session = r.u64()?;
445 let sequence = r.u64()?;
446 let len = r.u16()? as usize;
447 if session == 0 || len > reliable::EVENT_BYTES {
448 return Err(ProtocolError::Invalid);
449 }
450 let payload = r
451 .data
452 .get(r.pos..r.pos + len)
453 .ok_or(ProtocolError::Truncated)?
454 .to_vec();
455 r.pos += len;
456 ServerPacket::ReliableEvent {
457 session,
458 event: reliable::EventPacket { sequence, payload },
459 }
460 }
461 3 => {
462 let nonce = r.u64()?;
463 let session = r.u64()?;
464 let player_id = r.u8()?;
465 let tick = r.u32()?;
466 if session == 0 || player_id as usize >= MAX_PLAYERS {
467 return Err(ProtocolError::Invalid);
468 }
469 ServerPacket::Welcome {
470 nonce,
471 session,
472 player_id,
473 tick,
474 }
475 }
476 4 => {
477 let session = r.u64()?;
478 if session == 0 {
479 return Err(ProtocolError::Invalid);
480 }
481 let tick = r.u32()?;
482 let ack_sequence = r.u32()?;
483 let ack_valid = r.boolean()?;
484 let count = r.u8()? as usize;
485 if count > MAX_PLAYERS {
486 return Err(ProtocolError::Invalid);
487 }
488 let mut players = Vec::with_capacity(count);
489 let mut ids = 0u16;
490 for _ in 0..count {
491 let p = PlayerSnapshot {
492 id: r.u8()?,
493 position: [r.f32()?, r.f32()?, r.f32()?],
494 velocity: [r.f32()?, r.f32()?, r.f32()?],
495 yaw: r.f32()?,
496 pitch: r.f32()?,
497 health: r.u8()?,
498 ammo: r.u8()?,
499 grounded: r.boolean()?,
500 jump_coyote: r.f32()?,
501 jump_buffer: r.f32()?,
502 jump_held: r.boolean()?,
503 fire_cooldown: r.u16()?,
504 reload_ticks: r.u16()?,
505 respawn_ticks: r.u16()?,
506 };
507 if !p.is_valid() || ids & (1u16 << p.id) != 0 {
508 return Err(ProtocolError::Invalid);
509 }
510 ids |= 1u16 << p.id;
511 players.push(p);
512 }
513 ServerPacket::Snapshot(Snapshot {
514 session,
515 tick,
516 ack_sequence,
517 ack_valid,
518 players,
519 })
520 }
521 _ => return Err(ProtocolError::Kind),
522 };
523 r.end()?;
524 Ok(packet)
525}
526
527#[cfg(not(target_arch = "wasm32"))]
529pub mod udp {
530 use super::MAX_PACKET_BYTES;
531 use std::{
532 io,
533 net::{SocketAddr, ToSocketAddrs, UdpSocket},
534 };
535 pub struct Socket {
536 inner: UdpSocket,
537 }
538 impl Socket {
539 pub fn bind(addr: impl ToSocketAddrs) -> io::Result<Self> {
540 let inner = UdpSocket::bind(addr)?;
541 inner.set_nonblocking(true)?;
542 Ok(Self { inner })
543 }
544 pub fn connect(addr: impl ToSocketAddrs) -> io::Result<Self> {
545 let remote = addr.to_socket_addrs()?.next().ok_or_else(|| {
546 io::Error::new(io::ErrorKind::InvalidInput, "empty remote address")
547 })?;
548 let this = Self::bind(if remote.is_ipv4() {
549 "0.0.0.0:0"
550 } else {
551 "[::]:0"
552 })?;
553 this.inner.connect(remote)?;
554 Ok(this)
555 }
556 pub fn local_addr(&self) -> io::Result<SocketAddr> {
557 self.inner.local_addr()
558 }
559 pub fn send(&self, bytes: &[u8]) -> io::Result<usize> {
560 check_size(bytes)?;
561 self.inner.send(bytes)
562 }
563 pub fn send_to(&self, bytes: &[u8], addr: SocketAddr) -> io::Result<usize> {
564 check_size(bytes)?;
565 self.inner.send_to(bytes, addr)
566 }
567 pub fn recv_from(
573 &self,
574 buffer: &mut [u8; MAX_PACKET_BYTES + 1],
575 ) -> io::Result<Option<(usize, SocketAddr)>> {
576 match self.inner.recv_from(buffer) {
577 Ok(pair) => Ok(Some(pair)),
578 Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
579 #[cfg(windows)]
580 Err(e) if e.raw_os_error() == Some(10040) => Err(io::Error::new(
581 io::ErrorKind::InvalidData,
582 "oversized UDP datagram discarded by Winsock",
583 )),
584 Err(e) => Err(e),
585 }
586 }
587 }
588 fn check_size(bytes: &[u8]) -> io::Result<()> {
589 if bytes.len() > MAX_PACKET_BYTES {
590 Err(io::Error::new(
591 io::ErrorKind::InvalidInput,
592 "packet exceeds MTU budget",
593 ))
594 } else {
595 Ok(())
596 }
597 }
598}
599
600#[cfg(test)]
601mod tests {
602 use super::*;
603 #[test]
604 fn codec_round_trip_and_budget() {
605 let frame = InputFrame {
606 sequence: u32::MAX,
607 move_x: 0.5,
608 move_z: -1.0,
609 yaw: 2.0,
610 pitch: 0.1,
611 buttons: 7,
612 };
613 for packet in [
614 ClientPacket::Hello { nonce: 123 },
615 ClientPacket::Input {
616 session: 99,
617 inputs: vec![frame; MAX_INPUTS_PER_PACKET],
618 },
619 ] {
620 assert_eq!(
621 decode_client(&encode_client(&packet).unwrap()).unwrap(),
622 packet
623 );
624 }
625 let players = (0..MAX_PLAYERS)
626 .map(|id| PlayerSnapshot {
627 id: id as u8,
628 health: 100,
629 ammo: 30,
630 jump_coyote: 0.1,
631 jump_buffer: 0.12,
632 jump_held: true,
633 ..Default::default()
634 })
635 .collect();
636 let packet = ServerPacket::Snapshot(Snapshot {
637 session: 99,
638 tick: 44,
639 ack_valid: true,
640 ack_sequence: 3,
641 players,
642 });
643 let bytes = encode_server(&packet).unwrap();
644 assert_eq!(bytes.len(), 840);
645 assert!(bytes.len() < MAX_PACKET_BYTES);
646 assert_eq!(decode_server(&bytes).unwrap(), packet);
647 for length in 0..bytes.len() {
648 assert!(decode_server(&bytes[..length]).is_err());
649 }
650 }
651 #[test]
652 fn jump_state_numeric_and_version_limits_fail_closed() {
653 let player = PlayerSnapshot {
654 health: 100,
655 ammo: 30,
656 ..Default::default()
657 };
658 let make = |p| {
659 ServerPacket::Snapshot(Snapshot {
660 session: 1,
661 players: vec![p],
662 ..Default::default()
663 })
664 };
665 for bad in [f32::NAN, f32::INFINITY, -0.01, 0.51] {
666 assert!(
667 encode_server(&make(PlayerSnapshot {
668 jump_coyote: bad,
669 ..player
670 }))
671 .is_err()
672 );
673 assert!(
674 encode_server(&make(PlayerSnapshot {
675 jump_buffer: bad,
676 ..player
677 }))
678 .is_err()
679 );
680 }
681 let mut bytes = encode_server(&make(player)).unwrap();
682 bytes[4] = 2;
683 assert_eq!(decode_server(&bytes), Err(ProtocolError::Version));
684 }
685 #[test]
686 fn reliable_event_wire_is_bounded_session_bound_and_versioned() {
687 let ack = ClientPacket::EventAck {
688 session: 77,
689 sequence: 123,
690 };
691 assert_eq!(decode_client(&encode_client(&ack).unwrap()).unwrap(), ack);
692 let packet = ServerPacket::ReliableEvent {
693 session: 77,
694 event: reliable::EventPacket {
695 sequence: 123,
696 payload: vec![4; reliable::EVENT_BYTES],
697 },
698 };
699 let bytes = encode_server(&packet).unwrap();
700 assert!(bytes.len() <= MAX_PACKET_BYTES);
701 assert_eq!(decode_server(&bytes).unwrap(), packet);
702 for n in 0..bytes.len() {
703 assert!(decode_server(&bytes[..n]).is_err());
704 }
705 let mut old = bytes.clone();
706 old[4] = 1;
707 assert_eq!(decode_server(&old), Err(ProtocolError::Version));
708 assert!(
709 encode_client(&ClientPacket::EventAck {
710 session: 0,
711 sequence: 1
712 })
713 .is_err()
714 );
715 assert!(
716 encode_server(&ServerPacket::ReliableEvent {
717 session: 77,
718 event: reliable::EventPacket {
719 sequence: 0,
720 payload: vec![0; reliable::EVENT_BYTES + 1]
721 }
722 })
723 .is_err()
724 );
725 }
726 #[test]
727 fn reject_bad_numeric_and_trailing_data() {
728 let p = ClientPacket::Input {
729 session: 3,
730 inputs: vec![InputFrame {
731 move_x: f32::NAN,
732 ..Default::default()
733 }],
734 };
735 assert!(encode_client(&p).is_err());
736 let mut bytes = encode_client(&ClientPacket::Hello { nonce: 5 }).unwrap();
737 bytes.push(0);
738 assert_eq!(decode_client(&bytes), Err(ProtocolError::Trailing));
739 let frame = InputFrame {
740 pitch: f32::INFINITY,
741 ..Default::default()
742 };
743 assert!(frame.sanitized().is_valid());
744 }
745 #[test]
746 fn queue_is_bounded_deduplicated_and_wrap_safe() {
747 assert!(sequence_newer(0, u32::MAX));
748 assert!(!sequence_newer(u32::MAX, 0));
749 assert!(!sequence_newer(1 << 31, 0));
750 let mut q = InputQueue::default();
751 assert!(q.push(InputFrame {
752 sequence: u32::MAX,
753 ..Default::default()
754 }));
755 assert!(q.push(InputFrame::default()));
756 assert!(!q.push(InputFrame::default()));
757 for seq in 1..MAX_QUEUED_INPUTS as u32 {
758 q.push(InputFrame {
759 sequence: seq,
760 ..Default::default()
761 });
762 }
763 assert_eq!(q.len(), MAX_QUEUED_INPUTS);
764 assert!(!q.push(InputFrame {
765 sequence: 200,
766 ..Default::default()
767 }));
768 }
769 #[test]
770 fn coalescing_keeps_latest_movement_and_recovers_action_press() {
771 let mut queue = InputQueue::default();
772 queue.push(InputFrame {
773 sequence: 1,
774 move_z: 1.,
775 buttons: BUTTON_FIRE,
776 ..Default::default()
777 });
778 queue.push(InputFrame {
779 sequence: 2,
780 move_x: 1.,
781 ..Default::default()
782 });
783 let input = queue.pop_latest().unwrap();
784 assert_eq!(input.sequence, 2);
785 assert_eq!(input.move_z, 0.);
786 assert_eq!(input.move_x, 1.);
787 assert_eq!(input.buttons, BUTTON_FIRE);
788 assert!(queue.is_empty());
789 assert!(!queue.push(InputFrame {
790 sequence: 1,
791 buttons: BUTTON_FIRE,
792 ..Default::default()
793 }));
794 }
795 #[test]
796 fn arbitrary_bytes_never_panic() {
797 let mut state = 0x12345678u32;
798 for len in 0..=MAX_PACKET_BYTES + 5 {
799 let bytes: Vec<_> = (0..len)
800 .map(|_| {
801 state ^= state << 13;
802 state ^= state >> 17;
803 state ^= state << 5;
804 state as u8
805 })
806 .collect();
807 let _ = decode_client(&bytes);
808 let _ = decode_server(&bytes);
809 }
810 }
811 #[cfg(not(target_arch = "wasm32"))]
812 #[test]
813 fn native_udp_actual_socket_round_trip() {
814 use std::{thread, time::Duration};
815 let server = udp::Socket::bind("127.0.0.1:0").unwrap();
816 let client = udp::Socket::connect(server.local_addr().unwrap()).unwrap();
817 let bytes = encode_client(&ClientPacket::Hello { nonce: 88 }).unwrap();
818 client.send(&bytes).unwrap();
819 let mut buffer = [0; MAX_PACKET_BYTES + 1];
820 for _ in 0..100 {
821 if let Some((n, peer)) = server.recv_from(&mut buffer).unwrap() {
822 assert_eq!(
823 decode_client(&buffer[..n]).unwrap(),
824 ClientPacket::Hello { nonce: 88 }
825 );
826 server.send_to(&buffer[..n], peer).unwrap();
827 for _ in 0..100 {
828 if let Some((n, _)) = client.recv_from(&mut buffer).unwrap() {
829 assert_eq!(
830 decode_client(&buffer[..n]).unwrap(),
831 ClientPacket::Hello { nonce: 88 }
832 );
833 return;
834 }
835 thread::sleep(Duration::from_millis(1));
836 }
837 panic!("loopback UDP response not received");
838 }
839 thread::sleep(Duration::from_millis(1));
840 }
841 panic!("loopback UDP packet not received");
842 }
843}