1mod crypto;
61mod packet;
62mod wire;
63
64use std::collections::BTreeMap;
65use std::fmt;
66use std::net::{IpAddr, TcpListener, UdpSocket};
67use std::sync::atomic::{AtomicBool, Ordering};
68use std::sync::mpsc::{Receiver, Sender};
69use std::sync::{Arc, mpsc};
70
71pub use packet::{Arg, Packet};
72
73use crate::error::{Error, Result};
74use wire::{Event, Frame, Link};
75
76pub const DEFAULT_PORT: u16 = 7777;
78
79const MAX_NAME: usize = 32;
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
84pub struct PeerId(u32);
85
86impl PeerId {
87 pub const SERVER: PeerId = PeerId(0);
89
90 pub fn number(self) -> u32 {
93 self.0
94 }
95
96 pub fn from_number(number: u32) -> Self {
98 Self(number)
99 }
100
101 pub fn is_server(self) -> bool {
103 self == Self::SERVER
104 }
105}
106
107impl fmt::Display for PeerId {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 write!(f, "{}", self.0)
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum Reason {
116 Left,
118 Closed,
120 Kicked(String),
122 ServerFull,
124 WrongGame(String),
126 WrongVersion(String),
128 TimedOut,
130 Failed(String),
132 Lost(String),
134}
135
136impl fmt::Display for Reason {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 f.write_str(&crate::lang::Lang::new().describe_reason(self))
139 }
140}
141
142#[derive(Debug, Clone, PartialEq)]
144pub enum NetEvent {
145 Connected(PeerId),
148 Message(PeerId, Packet),
150 Disconnected(PeerId, Reason),
152}
153
154enum State {
155 Offline,
156 Hosting { port: u16, stop: Arc<AtomicBool> },
157 Joining,
158 Joined,
159}
160
161struct Peer {
162 name: String,
163 link: Link,
164 ping: f32,
165}
166
167pub struct Net {
169 state: State,
170 events_rx: Option<Receiver<Event>>,
171 peers: BTreeMap<PeerId, Peer>,
172 pub(crate) events: Vec<NetEvent>,
173 pending: Vec<NetEvent>,
175 game: String,
176 version: String,
177 name: String,
178 max_players: usize,
179 id: PeerId,
180 warned_offline: bool,
181}
182
183impl fmt::Debug for Net {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 let state = match self.state {
186 State::Offline => "offline",
187 State::Hosting { .. } => "hosting",
188 State::Joining => "joining",
189 State::Joined => "joined",
190 };
191 f.debug_struct("Net").field("state", &state).field("players", &self.peers.len()).finish_non_exhaustive()
192 }
193}
194
195impl Net {
196 pub fn new(game: impl Into<String>) -> Self {
199 Self {
200 state: State::Offline,
201 events_rx: None,
202 peers: BTreeMap::new(),
203 events: Vec::new(),
204 pending: Vec::new(),
205 game: game.into(),
206 version: String::new(),
207 name: "Player".into(),
208 max_players: 32,
209 id: PeerId::SERVER,
210 warned_offline: false,
211 }
212 }
213
214 pub fn set_name(&mut self, name: impl Into<String>) {
217 self.name = clean_name(&name.into());
218 }
219
220 pub fn set_version(&mut self, version: impl Into<String>) {
223 self.version = version.into();
224 }
225
226 pub fn set_max_players(&mut self, max: usize) {
228 self.max_players = max;
229 }
230
231 pub fn host(&mut self, port: u16) -> Result<()> {
238 self.close();
239 let listener = TcpListener::bind(("0.0.0.0", port))
240 .map_err(|e| Error::Net(format!("could not host on port {port}: {e}")))?;
241 let port = listener.local_addr().map(|a| a.port()).unwrap_or(port);
242 listener.set_nonblocking(true)?;
243 let stop = Arc::new(AtomicBool::new(false));
244 let tx = self.new_channel();
245 let flag = stop.clone();
246 std::thread::Builder::new()
247 .name("audio-code net server".into())
248 .spawn(move || wire::accept_loop(listener, flag, tx))?;
249 self.state = State::Hosting { port, stop };
250 self.id = PeerId::SERVER;
251 Ok(())
252 }
253
254 pub fn join(&mut self, address: &str) {
263 self.close();
264 let tx = self.new_channel();
265 let hello = Frame::Hello {
266 protocol: wire::PROTOCOL,
267 game: self.game.clone(),
268 version: self.version.clone(),
269 name: self.name.clone(),
270 };
271 let address = address.to_string();
272 let spawned = std::thread::Builder::new()
273 .name("audio-code net client".into())
274 .spawn(move || wire::connect(address, hello, tx));
275 if let Err(e) = spawned {
276 self.pending.push(NetEvent::Disconnected(PeerId::SERVER, Reason::Failed(e.to_string())));
277 return;
278 }
279 self.state = State::Joining;
280 }
281
282 pub fn close(&mut self) {
286 let bye = if self.is_host() { Reason::Closed } else { Reason::Left };
287 for peer in std::mem::take(&mut self.peers).into_values() {
288 peer.link.close(Some(bye.clone()));
289 }
290 if let State::Hosting { stop, .. } = &self.state {
291 stop.store(true, Ordering::Relaxed);
292 }
293 self.state = State::Offline;
294 self.events_rx = None;
295 self.id = PeerId::SERVER;
296 self.warned_offline = false;
297 }
298
299 pub fn kick(&mut self, peer: PeerId, reason: &str) {
302 if !self.is_host() {
303 return;
304 }
305 if let Some(p) = self.peers.remove(&peer) {
306 p.link.close(Some(Reason::Kicked(reason.to_string())));
307 self.pending.push(NetEvent::Disconnected(peer, Reason::Kicked(reason.to_string())));
308 }
309 }
310
311 pub fn send(&mut self, peer: PeerId, packet: impl Into<Packet>) {
314 let bytes = encode(packet.into());
315 match self.peers.get(&peer) {
316 Some(p) => p.link.send(bytes),
317 None => self.warn_offline(),
318 }
319 }
320
321 pub fn send_all(&mut self, packet: impl Into<Packet>) {
323 self.send_filtered(packet.into(), |_| true);
324 }
325
326 pub fn send_all_except(&mut self, except: PeerId, packet: impl Into<Packet>) {
329 self.send_filtered(packet.into(), |p| p != except);
330 }
331
332 fn send_filtered(&mut self, packet: Packet, keep: impl Fn(PeerId) -> bool) {
333 if self.peers.is_empty() {
334 if !self.is_host() {
335 self.warn_offline();
336 }
337 return;
338 }
339 let bytes = encode(packet);
340 for (_, p) in self.peers.iter().filter(|(id, _)| keep(**id)) {
341 p.link.send(bytes.clone());
342 }
343 }
344
345 fn warn_offline(&mut self) {
346 if !self.warned_offline && !matches!(self.state, State::Joined | State::Hosting { .. }) {
347 self.warned_offline = true;
348 eprintln!("audio-code: a packet was sent while not connected; it was dropped");
349 }
350 }
351
352 pub fn events(&self) -> &[NetEvent] {
354 &self.events
355 }
356
357 pub fn joined(&self) -> Vec<PeerId> {
360 self.events
361 .iter()
362 .filter_map(|e| match e {
363 NetEvent::Connected(p) => Some(*p),
364 _ => None,
365 })
366 .collect()
367 }
368
369 pub fn left(&self) -> Vec<(PeerId, Reason)> {
372 self.events
373 .iter()
374 .filter_map(|e| match e {
375 NetEvent::Disconnected(p, r) => Some((*p, r.clone())),
376 _ => None,
377 })
378 .collect()
379 }
380
381 pub fn messages(&self) -> Vec<(PeerId, Packet)> {
383 self.events
384 .iter()
385 .filter_map(|e| match e {
386 NetEvent::Message(p, packet) => Some((*p, packet.clone())),
387 _ => None,
388 })
389 .collect()
390 }
391
392 pub fn received(&self, kind: &str) -> Vec<(PeerId, Packet)> {
394 self.events
395 .iter()
396 .filter_map(|e| match e {
397 NetEvent::Message(p, packet) if packet.is(kind) => Some((*p, packet.clone())),
398 _ => None,
399 })
400 .collect()
401 }
402
403 pub fn players(&self) -> Vec<PeerId> {
406 self.peers.keys().copied().collect()
407 }
408
409 pub fn player_count(&self) -> usize {
411 self.peers.len()
412 }
413
414 pub fn is_connected_to(&self, peer: PeerId) -> bool {
416 self.peers.contains_key(&peer)
417 }
418
419 pub fn name(&self, peer: PeerId) -> &str {
422 if peer == self.id && !matches!(self.state, State::Offline | State::Joining) {
423 return &self.name;
424 }
425 self.peers.get(&peer).map_or("", |p| p.name.as_str())
426 }
427
428 pub fn ping(&self, peer: PeerId) -> f32 {
431 self.peers.get(&peer).map_or(0.0, |p| p.ping)
432 }
433
434 pub fn id(&self) -> PeerId {
437 self.id
438 }
439
440 pub fn is_host(&self) -> bool {
442 matches!(self.state, State::Hosting { .. })
443 }
444
445 pub fn is_client(&self) -> bool {
447 matches!(self.state, State::Joined)
448 }
449
450 pub fn is_joining(&self) -> bool {
452 matches!(self.state, State::Joining)
453 }
454
455 pub fn is_online(&self) -> bool {
457 self.is_host() || self.is_client()
458 }
459
460 pub fn port(&self) -> Option<u16> {
462 match self.state {
463 State::Hosting { port, .. } => Some(port),
464 _ => None,
465 }
466 }
467
468 pub fn local_ip() -> Option<IpAddr> {
471 let socket = UdpSocket::bind("0.0.0.0:0").ok()?;
472 socket.connect("8.8.8.8:80").ok()?;
474 socket.local_addr().ok().map(|a| a.ip()).filter(|ip| !ip.is_unspecified())
475 }
476
477 fn new_channel(&mut self) -> Sender<Event> {
478 let (tx, rx) = mpsc::channel();
479 self.events_rx = Some(rx);
480 tx
481 }
482
483 pub(crate) fn update(&mut self) {
485 self.events = std::mem::take(&mut self.pending);
486 let Some(rx) = &self.events_rx else {
487 return;
488 };
489 let reports: Vec<Event> = rx.try_iter().collect();
490 for report in reports {
491 self.handle(report);
492 }
493 }
494
495 fn handle(&mut self, report: Event) {
496 match report {
497 Event::Hello { link, protocol, game, version, name } => {
498 if !self.is_host() {
499 link.close(None);
500 return;
501 }
502 let refusal = if game != self.game {
503 Some(Reason::WrongGame(self.game.clone()))
504 } else if version != self.version || protocol != wire::PROTOCOL {
505 Some(Reason::WrongVersion(self.version.clone()))
506 } else if self.peers.len() >= self.max_players {
507 Some(Reason::ServerFull)
508 } else {
509 None
510 };
511 if let Some(reason) = refusal {
512 link.close(Some(reason));
513 return;
514 }
515 let peer = PeerId(link.id);
516 link.send_frame(&Frame::Welcome { id: link.id, name: self.name.clone() });
517 self.peers.insert(peer, Peer { name: clean_name(&name), link, ping: 0.0 });
518 self.events.push(NetEvent::Connected(peer));
519 }
520 Event::Welcome { link, id, name } => {
521 self.state = State::Joined;
522 self.id = PeerId(id);
523 self.peers.insert(PeerId::SERVER, Peer { name: clean_name(&name), link, ping: 0.0 });
524 self.events.push(NetEvent::Connected(PeerId::SERVER));
525 }
526 Event::Data { peer, packet } => {
527 let peer = PeerId(peer);
528 if self.peers.contains_key(&peer) {
529 self.events.push(NetEvent::Message(peer, packet));
530 }
531 }
532 Event::Ping { peer, seconds } => {
533 if let Some(p) = self.peers.get_mut(&PeerId(peer)) {
534 p.ping = seconds;
535 }
536 }
537 Event::Closed { peer, reason } => {
538 let peer = PeerId(peer);
539 let known = self.peers.remove(&peer).is_some();
540 let joining = self.is_joining() && peer == PeerId::SERVER;
541 if known || joining {
542 self.events.push(NetEvent::Disconnected(peer, reason));
543 }
544 if !self.is_host() && peer == PeerId::SERVER {
545 self.state = State::Offline;
546 self.events_rx = None;
547 self.id = PeerId::SERVER;
548 }
549 }
550 }
551 }
552}
553
554impl Drop for Net {
555 fn drop(&mut self) {
556 self.close();
557 }
558}
559
560fn encode(packet: Packet) -> Arc<[u8]> {
561 let bytes = Frame::Data(packet).encode();
562 if bytes.len() > wire::MAX_FRAME {
563 eprintln!("audio-code: a packet of {} bytes is too large to send (at most 1 MB); it was dropped", bytes.len());
564 return Arc::from(&[][..]);
565 }
566 bytes.into()
567}
568
569fn clean_name(name: &str) -> String {
570 name.chars().filter(|c| !c.is_control()).take(MAX_NAME).collect::<String>().trim().to_string()
571}
572
573#[cfg(test)]
574mod tests {
575 use super::*;
576 use std::time::{Duration, Instant};
577
578 fn pump(nets: &mut [&mut Net], mut done: impl FnMut(&[&mut Net]) -> bool) -> Vec<Vec<NetEvent>> {
581 let mut all = vec![Vec::new(); nets.len()];
582 let start = Instant::now();
583 while start.elapsed() < Duration::from_secs(8) {
584 for (net, events) in nets.iter_mut().zip(&mut all) {
585 net.update();
586 events.extend(net.events.iter().cloned());
587 }
588 if done(nets) {
589 break;
590 }
591 std::thread::sleep(Duration::from_millis(5));
592 }
593 all
594 }
595
596 fn host() -> (Net, String) {
597 let mut server = Net::new("game");
598 server.set_name("Host");
599 server.host(0).unwrap();
600 let address = format!("127.0.0.1:{}", server.port().unwrap());
601 (server, address)
602 }
603
604 #[test]
605 fn join_chat_and_leave() {
606 let (mut server, address) = host();
607 let mut client = Net::new("game");
608 client.set_name("Anna");
609 client.join(&address);
610 pump(&mut [&mut server, &mut client], |n| n[0].player_count() == 1 && n[1].is_client());
611 let anna = server.players()[0];
612 assert_eq!(server.name(anna), "Anna");
613 assert_eq!(client.name(PeerId::SERVER), "Host");
614 assert_eq!(client.id(), anna);
615 assert_eq!(client.name(client.id()), "Anna");
616
617 client.send_all(Packet::new("chat").with("hi"));
618 let events = pump(&mut [&mut server, &mut client], |n| !n[0].received("chat").is_empty());
619 assert_eq!(events[0], [NetEvent::Message(anna, Packet::new("chat").with("hi"))]);
620
621 server.send(anna, "pong");
622 let events = pump(&mut [&mut server, &mut client], |n| !n[1].messages().is_empty());
623 assert_eq!(events[1], [NetEvent::Message(PeerId::SERVER, Packet::new("pong"))]);
624
625 client.close();
626 let events = pump(&mut [&mut server], |n| n[0].player_count() == 0);
627 assert_eq!(events[0], [NetEvent::Disconnected(anna, Reason::Left)]);
628 }
629
630 #[test]
631 fn refuses_wrong_game_version_and_full() {
632 let (mut server, address) = host();
633 server.set_version("2");
634 server.set_max_players(1);
635 let mut other = Net::new("other game");
636 other.join(&address);
637 let mut old = Net::new("game");
638 old.set_version("1");
639 old.join(&address);
640 let events = pump(&mut [&mut server, &mut other, &mut old], |n| !n[1].is_joining() && !n[2].is_joining());
641 assert_eq!(events[1], [NetEvent::Disconnected(PeerId::SERVER, Reason::WrongGame("game".into()))]);
642 assert_eq!(events[2], [NetEvent::Disconnected(PeerId::SERVER, Reason::WrongVersion("2".into()))]);
643
644 let mut first = Net::new("game");
645 first.set_version("2");
646 first.join(&address);
647 pump(&mut [&mut server, &mut first], |n| n[1].is_client());
648 let mut second = Net::new("game");
649 second.set_version("2");
650 second.join(&address);
651 let events = pump(&mut [&mut server, &mut second], |n| !n[1].is_joining());
652 assert_eq!(events[1], [NetEvent::Disconnected(PeerId::SERVER, Reason::ServerFull)]);
653 }
654
655 #[test]
656 fn kick_and_server_close() {
657 let (mut server, address) = host();
658 let mut a = Net::new("game");
659 let mut b = Net::new("game");
660 a.join(&address);
661 b.join(&address);
662 pump(&mut [&mut server, &mut a, &mut b], |n| n[0].player_count() == 2 && n[1].is_client() && n[2].is_client());
663
664 server.kick(a.id(), "spam");
665 let events = pump(&mut [&mut server, &mut a], |n| !n[1].is_online());
666 assert_eq!(events[1], [NetEvent::Disconnected(PeerId::SERVER, Reason::Kicked("spam".into()))]);
667
668 server.close();
669 let events = pump(&mut [&mut b], |n| !n[0].is_online());
670 assert_eq!(events[0], [NetEvent::Disconnected(PeerId::SERVER, Reason::Closed)]);
671 }
672
673 #[test]
674 fn join_failure_is_an_event() {
675 let port = TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port();
677 let mut client = Net::new("game");
678 client.join(&format!("127.0.0.1:{port}"));
679 let events = pump(&mut [&mut client], |n| !n[0].is_joining());
680 assert!(matches!(events[0].as_slice(), [NetEvent::Disconnected(PeerId::SERVER, Reason::Failed(_))]));
681 client.send_all("hello");
683 }
684}