raknet-rust 0.2.0

Asynchronous, high-performance RakNet transport library for Rust.
Documentation
use std::net::SocketAddr;

use crate::client::RaknetClientEvent;
use crate::proxy::RaknetRelayProxyEvent;
use crate::server::{PeerId, RaknetServerEvent};
use crate::transport::TransportMetricsSnapshot;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RaknetEventSource {
    Server,
    Client,
    Proxy,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RaknetEventKind {
    Connected,
    Disconnected,
    Packet,
    OfflinePacket,
    Forwarded,
    Dropped,
    ReceiptAcked,
    DecodeError,
    RateLimited,
    SessionLimitReached,
    ProxyDropped,
    WorkerError,
    WorkerStopped,
    Metrics,
}

#[derive(Debug)]
pub enum RaknetEvent {
    Server(RaknetServerEvent),
    Client(RaknetClientEvent),
    Proxy(RaknetRelayProxyEvent),
}

impl RaknetEvent {
    pub fn source(&self) -> RaknetEventSource {
        match self {
            Self::Server(_) => RaknetEventSource::Server,
            Self::Client(_) => RaknetEventSource::Client,
            Self::Proxy(_) => RaknetEventSource::Proxy,
        }
    }

    pub fn kind(&self) -> RaknetEventKind {
        match self {
            Self::Server(event) => match event {
                RaknetServerEvent::PeerConnected { .. } => RaknetEventKind::Connected,
                RaknetServerEvent::PeerDisconnected { .. } => RaknetEventKind::Disconnected,
                RaknetServerEvent::Packet { .. } => RaknetEventKind::Packet,
                RaknetServerEvent::OfflinePacket { .. } => RaknetEventKind::OfflinePacket,
                RaknetServerEvent::ReceiptAcked { .. } => RaknetEventKind::ReceiptAcked,
                RaknetServerEvent::PeerRateLimited { .. } => RaknetEventKind::RateLimited,
                RaknetServerEvent::SessionLimitReached { .. } => {
                    RaknetEventKind::SessionLimitReached
                }
                RaknetServerEvent::ProxyDropped { .. } => RaknetEventKind::ProxyDropped,
                RaknetServerEvent::DecodeError { .. } => RaknetEventKind::DecodeError,
                RaknetServerEvent::WorkerError { .. } => RaknetEventKind::WorkerError,
                RaknetServerEvent::WorkerStopped { .. } => RaknetEventKind::WorkerStopped,
                RaknetServerEvent::Metrics { .. } => RaknetEventKind::Metrics,
            },
            Self::Client(event) => match event {
                RaknetClientEvent::Connected { .. } => RaknetEventKind::Connected,
                RaknetClientEvent::Packet { .. } => RaknetEventKind::Packet,
                RaknetClientEvent::ReceiptAcked { .. } => RaknetEventKind::ReceiptAcked,
                RaknetClientEvent::DecodeError { .. } => RaknetEventKind::DecodeError,
                RaknetClientEvent::Disconnected { .. } => RaknetEventKind::Disconnected,
            },
            Self::Proxy(event) => match event {
                RaknetRelayProxyEvent::SessionStarted { .. } => RaknetEventKind::Connected,
                RaknetRelayProxyEvent::Forwarded { .. } => RaknetEventKind::Forwarded,
                RaknetRelayProxyEvent::Dropped { .. } => RaknetEventKind::Dropped,
                RaknetRelayProxyEvent::DecodeError { .. } => RaknetEventKind::DecodeError,
                RaknetRelayProxyEvent::SessionClosed { .. } => RaknetEventKind::Disconnected,
                RaknetRelayProxyEvent::DownstreamRateLimited { .. } => RaknetEventKind::RateLimited,
                RaknetRelayProxyEvent::DownstreamSessionLimitReached { .. } => {
                    RaknetEventKind::SessionLimitReached
                }
                RaknetRelayProxyEvent::DownstreamProxyDropped { .. } => {
                    RaknetEventKind::ProxyDropped
                }
                RaknetRelayProxyEvent::DownstreamDecodeError { .. } => RaknetEventKind::DecodeError,
                RaknetRelayProxyEvent::DownstreamWorkerError { .. } => RaknetEventKind::WorkerError,
                RaknetRelayProxyEvent::DownstreamWorkerStopped { .. } => {
                    RaknetEventKind::WorkerStopped
                }
            },
        }
    }

    pub fn peer_id(&self) -> Option<PeerId> {
        match self {
            Self::Server(event) => match event {
                RaknetServerEvent::PeerConnected { peer_id, .. }
                | RaknetServerEvent::PeerDisconnected { peer_id, .. }
                | RaknetServerEvent::Packet { peer_id, .. }
                | RaknetServerEvent::ReceiptAcked { peer_id, .. } => Some(*peer_id),
                RaknetServerEvent::OfflinePacket { .. }
                | RaknetServerEvent::PeerRateLimited { .. }
                | RaknetServerEvent::SessionLimitReached { .. }
                | RaknetServerEvent::ProxyDropped { .. }
                | RaknetServerEvent::DecodeError { .. }
                | RaknetServerEvent::WorkerError { .. }
                | RaknetServerEvent::WorkerStopped { .. }
                | RaknetServerEvent::Metrics { .. } => None,
            },
            Self::Client(_) => None,
            Self::Proxy(event) => match event {
                RaknetRelayProxyEvent::SessionStarted { peer_id, .. }
                | RaknetRelayProxyEvent::Forwarded { peer_id, .. }
                | RaknetRelayProxyEvent::Dropped { peer_id, .. }
                | RaknetRelayProxyEvent::DecodeError { peer_id, .. }
                | RaknetRelayProxyEvent::SessionClosed { peer_id, .. } => Some(*peer_id),
                RaknetRelayProxyEvent::DownstreamRateLimited { .. }
                | RaknetRelayProxyEvent::DownstreamSessionLimitReached { .. }
                | RaknetRelayProxyEvent::DownstreamProxyDropped { .. }
                | RaknetRelayProxyEvent::DownstreamDecodeError { .. }
                | RaknetRelayProxyEvent::DownstreamWorkerError { .. }
                | RaknetRelayProxyEvent::DownstreamWorkerStopped { .. } => None,
            },
        }
    }

    pub fn primary_addr(&self) -> Option<SocketAddr> {
        match self {
            Self::Server(event) => match event {
                RaknetServerEvent::PeerConnected { addr, .. }
                | RaknetServerEvent::PeerDisconnected { addr, .. }
                | RaknetServerEvent::Packet { addr, .. }
                | RaknetServerEvent::OfflinePacket { addr, .. }
                | RaknetServerEvent::ReceiptAcked { addr, .. }
                | RaknetServerEvent::PeerRateLimited { addr }
                | RaknetServerEvent::SessionLimitReached { addr }
                | RaknetServerEvent::ProxyDropped { addr }
                | RaknetServerEvent::DecodeError { addr, .. } => Some(*addr),
                RaknetServerEvent::WorkerError { .. }
                | RaknetServerEvent::WorkerStopped { .. }
                | RaknetServerEvent::Metrics { .. } => None,
            },
            Self::Client(event) => match event {
                RaknetClientEvent::Connected { server_addr, .. } => Some(*server_addr),
                RaknetClientEvent::Packet { .. }
                | RaknetClientEvent::ReceiptAcked { .. }
                | RaknetClientEvent::DecodeError { .. }
                | RaknetClientEvent::Disconnected { .. } => None,
            },
            Self::Proxy(event) => match event {
                RaknetRelayProxyEvent::SessionStarted {
                    downstream_addr, ..
                }
                | RaknetRelayProxyEvent::DownstreamRateLimited {
                    addr: downstream_addr,
                }
                | RaknetRelayProxyEvent::DownstreamSessionLimitReached {
                    addr: downstream_addr,
                }
                | RaknetRelayProxyEvent::DownstreamProxyDropped {
                    addr: downstream_addr,
                }
                | RaknetRelayProxyEvent::DownstreamDecodeError {
                    addr: downstream_addr,
                    ..
                } => Some(*downstream_addr),
                RaknetRelayProxyEvent::Forwarded { .. }
                | RaknetRelayProxyEvent::Dropped { .. }
                | RaknetRelayProxyEvent::DecodeError { .. }
                | RaknetRelayProxyEvent::SessionClosed { .. }
                | RaknetRelayProxyEvent::DownstreamWorkerError { .. }
                | RaknetRelayProxyEvent::DownstreamWorkerStopped { .. } => None,
            },
        }
    }

    pub fn payload_len(&self) -> Option<usize> {
        match self {
            Self::Server(event) => match event {
                RaknetServerEvent::Packet { payload, .. } => Some(payload.len()),
                _ => None,
            },
            Self::Client(event) => match event {
                RaknetClientEvent::Packet { payload, .. } => Some(payload.len()),
                _ => None,
            },
            Self::Proxy(event) => match event {
                RaknetRelayProxyEvent::Forwarded { payload_len, .. } => Some(*payload_len),
                _ => None,
            },
        }
    }

    pub fn decode_error(&self) -> Option<&str> {
        match self {
            Self::Server(event) => match event {
                RaknetServerEvent::DecodeError { error, .. } => Some(error.as_str()),
                _ => None,
            },
            Self::Client(event) => match event {
                RaknetClientEvent::DecodeError { error } => Some(error.as_str()),
                _ => None,
            },
            Self::Proxy(event) => match event {
                RaknetRelayProxyEvent::DecodeError { error, .. }
                | RaknetRelayProxyEvent::DownstreamDecodeError { error, .. } => {
                    Some(error.as_str())
                }
                _ => None,
            },
        }
    }

    pub fn metrics_snapshot(&self) -> Option<(usize, &TransportMetricsSnapshot, u64)> {
        match self {
            Self::Server(RaknetServerEvent::Metrics {
                shard_id,
                snapshot,
                dropped_non_critical_events,
            }) => Some((*shard_id, snapshot.as_ref(), *dropped_non_critical_events)),
            _ => None,
        }
    }

    pub fn as_server(&self) -> Option<&RaknetServerEvent> {
        match self {
            Self::Server(event) => Some(event),
            _ => None,
        }
    }

    pub fn as_client(&self) -> Option<&RaknetClientEvent> {
        match self {
            Self::Client(event) => Some(event),
            _ => None,
        }
    }

    pub fn as_proxy(&self) -> Option<&RaknetRelayProxyEvent> {
        match self {
            Self::Proxy(event) => Some(event),
            _ => None,
        }
    }
}

impl From<RaknetServerEvent> for RaknetEvent {
    fn from(value: RaknetServerEvent) -> Self {
        Self::Server(value)
    }
}

impl From<RaknetClientEvent> for RaknetEvent {
    fn from(value: RaknetClientEvent) -> Self {
        Self::Client(value)
    }
}

impl From<RaknetRelayProxyEvent> for RaknetEvent {
    fn from(value: RaknetRelayProxyEvent) -> Self {
        Self::Proxy(value)
    }
}

#[cfg(test)]
mod tests {
    use bytes::Bytes;

    use super::{RaknetEvent, RaknetEventKind, RaknetEventSource};
    use crate::client::RaknetClientEvent;
    use crate::handshake::{OfflinePacket, UnconnectedPing};
    use crate::protocol::constants::DEFAULT_UNCONNECTED_MAGIC;
    use crate::protocol::reliability::Reliability;
    use crate::proxy::{
        RaknetRelayProxyEvent, RelayDirection, RelayDropReason, RelaySessionCloseReason,
    };
    use crate::server::{PeerDisconnectReason, PeerId, RaknetServerEvent};
    use crate::transport::TransportMetricsSnapshot;

    #[test]
    fn server_packet_maps_to_common_fields() {
        let peer_id = PeerId::from_u64(7);
        let addr = "127.0.0.1:19132".parse().expect("valid socket addr");
        let event = RaknetEvent::from(RaknetServerEvent::Packet {
            peer_id,
            addr,
            payload: Bytes::from_static(b"abc"),
            reliability: Reliability::ReliableOrdered,
            reliable_index: None,
            sequence_index: None,
            ordering_index: None,
            ordering_channel: Some(0),
        });

        assert_eq!(event.source(), RaknetEventSource::Server);
        assert_eq!(event.kind(), RaknetEventKind::Packet);
        assert_eq!(event.peer_id(), Some(peer_id));
        assert_eq!(event.primary_addr(), Some(addr));
        assert_eq!(event.payload_len(), Some(3));
    }

    #[test]
    fn client_decode_error_is_exposed() {
        let event = RaknetEvent::from(RaknetClientEvent::DecodeError {
            error: "bad packet".to_string(),
        });

        assert_eq!(event.source(), RaknetEventSource::Client);
        assert_eq!(event.kind(), RaknetEventKind::DecodeError);
        assert_eq!(event.decode_error(), Some("bad packet"));
        assert!(event.peer_id().is_none());
        assert!(event.primary_addr().is_none());
    }

    #[test]
    fn proxy_drop_maps_to_common_kind_and_peer() {
        let peer_id = PeerId::from_u64(99);
        let event = RaknetEvent::from(RaknetRelayProxyEvent::Dropped {
            peer_id,
            direction: RelayDirection::DownstreamToUpstream,
            reason: RelayDropReason::NoSession,
        });

        assert_eq!(event.source(), RaknetEventSource::Proxy);
        assert_eq!(event.kind(), RaknetEventKind::Dropped);
        assert_eq!(event.peer_id(), Some(peer_id));
        assert!(event.primary_addr().is_none());
    }

    #[test]
    fn server_metrics_can_be_extracted() {
        let snapshot = TransportMetricsSnapshot {
            session_count: 3,
            ..TransportMetricsSnapshot::default()
        };

        let event = RaknetEvent::from(RaknetServerEvent::Metrics {
            shard_id: 2,
            snapshot: Box::new(snapshot),
            dropped_non_critical_events: 17,
        });

        let (shard_id, extracted_snapshot, dropped) =
            event.metrics_snapshot().expect("metrics must be present");
        assert_eq!(shard_id, 2);
        assert_eq!(dropped, 17);
        assert_eq!(extracted_snapshot.session_count, 3);
    }

    #[test]
    fn disconnected_kind_is_normalized_across_sources() {
        let server_peer_id = PeerId::from_u64(1);
        let server_addr = "127.0.0.1:19132".parse().expect("valid socket addr");
        let server_event = RaknetEvent::from(RaknetServerEvent::PeerDisconnected {
            peer_id: server_peer_id,
            addr: server_addr,
            reason: PeerDisconnectReason::Requested,
        });
        assert_eq!(server_event.kind(), RaknetEventKind::Disconnected);

        let client_event = RaknetEvent::from(RaknetClientEvent::Disconnected {
            reason: crate::client::ClientDisconnectReason::Requested,
        });
        assert_eq!(client_event.kind(), RaknetEventKind::Disconnected);

        let proxy_event = RaknetEvent::from(RaknetRelayProxyEvent::SessionClosed {
            peer_id: server_peer_id,
            reason: RelaySessionCloseReason::ProxyShutdown,
        });
        assert_eq!(proxy_event.kind(), RaknetEventKind::Disconnected);
    }

    #[test]
    fn server_offline_packet_maps_to_common_fields() {
        let addr = "127.0.0.1:19132".parse().expect("valid socket addr");
        let event = RaknetEvent::from(RaknetServerEvent::OfflinePacket {
            addr,
            packet: OfflinePacket::UnconnectedPing(UnconnectedPing {
                ping_time: 123,
                client_guid: 456,
                magic: DEFAULT_UNCONNECTED_MAGIC,
            }),
        });

        assert_eq!(event.source(), RaknetEventSource::Server);
        assert_eq!(event.kind(), RaknetEventKind::OfflinePacket);
        assert_eq!(event.primary_addr(), Some(addr));
        assert!(event.peer_id().is_none());
        assert!(event.payload_len().is_none());
    }
}