pub mod incoming;
pub mod messages;
use crate::bitbuf::{BitWriter, RawBfWrite};
use crate::ffi::{NotThreadSafe, copy_cstr, vcall};
use std::error::Error;
use std::ffi::{CString, c_int};
use std::fmt::{self, Display, Formatter};
use std::marker::PhantomData;
use std::ptr::NonNull;
pub const MESSAGE_TYPE_BITS: u32 = 6;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MessageId(u8);
impl MessageId {
pub const NOP: Self = Self(0);
pub const DISCONNECT: Self = Self(1);
pub const FILE: Self = Self(2);
pub const TICK: Self = Self(3);
pub const STRING_CMD: Self = Self(4);
pub const SET_CONVAR: Self = Self(5);
pub const SIGNON_STATE: Self = Self(6);
pub const PRINT: Self = Self(7);
pub const SERVER_INFO: Self = Self(8);
pub const SEND_TABLE: Self = Self(9);
pub const CLASS_INFO: Self = Self(10);
pub const SET_PAUSE: Self = Self(11);
pub const CREATE_STRING_TABLE: Self = Self(12);
pub const UPDATE_STRING_TABLE: Self = Self(13);
pub const VOICE_INIT: Self = Self(14);
pub const VOICE_DATA: Self = Self(15);
pub const SOUNDS: Self = Self(17);
pub const SET_VIEW: Self = Self(18);
pub const FIX_ANGLE: Self = Self(19);
pub const CROSSHAIR_ANGLE: Self = Self(20);
pub const BSP_DECAL: Self = Self(21);
pub const USER_MESSAGE: Self = Self(23);
pub const ENTITY_MESSAGE: Self = Self(24);
pub const GAME_EVENT: Self = Self(25);
pub const PACKET_ENTITIES: Self = Self(26);
pub const TEMP_ENTITIES: Self = Self(27);
pub const PREFETCH: Self = Self(28);
pub const MENU: Self = Self(29);
pub const GAME_EVENT_LIST: Self = Self(30);
pub const GET_CVAR_VALUE: Self = Self(31);
pub const CMD_KEY_VALUES: Self = Self(32);
pub const fn new(id: u8) -> Option<Self> {
match id < 1 << MESSAGE_TYPE_BITS {
true => Some(Self(id)),
false => None,
}
}
pub const fn get(self) -> u8 {
self.0
}
pub const fn name(self) -> Option<&'static str> {
Some(match self.0 {
0 => "net_NOP",
1 => "net_Disconnect",
2 => "net_File",
3 => "net_Tick",
4 => "net_StringCmd",
5 => "net_SetConVar",
6 => "net_SignonState",
7 => "svc_Print",
8 => "svc_ServerInfo",
9 => "svc_SendTable",
10 => "svc_ClassInfo",
11 => "svc_SetPause",
12 => "svc_CreateStringTable",
13 => "svc_UpdateStringTable",
14 => "svc_VoiceInit",
15 => "svc_VoiceData",
17 => "svc_Sounds",
18 => "svc_SetView",
19 => "svc_FixAngle",
20 => "svc_CrosshairAngle",
21 => "svc_BSPDecal",
23 => "svc_UserMessage",
24 => "svc_EntityMessage",
25 => "svc_GameEvent",
26 => "svc_PacketEntities",
27 => "svc_TempEntities",
28 => "svc_Prefetch",
29 => "svc_Menu",
30 => "svc_GameEventList",
31 => "svc_GetCvarValue",
32 => "svc_CmdKeyValues",
_ => return None,
})
}
}
impl Display for MessageId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.name() {
Some(name) => f.write_str(name),
None => write!(f, "message {}", self.0),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Reliability {
#[default]
Reliable,
Unreliable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Flow {
Outgoing,
Incoming,
}
impl Flow {
const fn raw(self) -> c_int {
match self {
Self::Outgoing => 0,
Self::Incoming => 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SequenceData {
pub outgoing: c_int,
pub incoming: c_int,
pub outgoing_acknowledged: c_int,
}
pub trait NetMessage {
fn id(&self) -> MessageId;
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError>;
fn reliability(&self) -> Reliability {
Reliability::Reliable
}
fn encode(&self) -> Result<BitWriter, EncodeError> {
let mut out = BitWriter::new();
out.write_ubits(self.id().get().into(), MESSAGE_TYPE_BITS);
self.write_body(&mut out)?;
Ok(out)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum EncodeError {
#[error("{field} is {len} bytes long, but at most {max} fit")]
TooLong {
field: &'static str,
len: usize,
max: usize,
},
#[error("{field} is {value}, which exceeds its maximum of {max}")]
OutOfRange {
field: &'static str,
value: u64,
max: u64,
},
}
impl EncodeError {
pub(crate) fn check_len(field: &'static str, len: usize, max: usize) -> Result<(), Self> {
match len <= max {
true => Ok(()),
false => Err(Self::TooLong { field, len, max }),
}
}
pub(crate) fn check_bits(field: &'static str, value: u64, bits: u32) -> Result<(), Self> {
let max = (1u64 << bits) - 1;
match value <= max {
true => Ok(()),
false => Err(Self::OutOfRange { field, value, max }),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SendError {
Encode(EncodeError),
TooLarge { bits: usize },
}
impl Display for SendError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Encode(error) => Display::fmt(error, f),
Self::TooLarge { bits } => {
write!(f, "the channel has no room for a message of {bits} bits")
}
}
}
}
impl Error for SendError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Encode(error) => Some(error),
Self::TooLarge { .. } => None,
}
}
}
impl From<EncodeError> for SendError {
fn from(error: EncodeError) -> Self {
Self::Encode(error)
}
}
#[doc(alias = "INetChannel")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NetChannel<'s> {
raw: NonNull<sys::INetChannel>,
_scope: PhantomData<&'s ()>,
_not_thread_safe: NotThreadSafe,
}
impl<'s> NetChannel<'s> {
pub(crate) const unsafe fn from_raw(raw: NonNull<sys::INetChannel>) -> Self {
Self {
raw,
_scope: PhantomData,
_not_thread_safe: PhantomData,
}
}
pub const fn as_ptr(self) -> *mut sys::INetChannel {
self.raw.as_ptr()
}
const fn as_const(self) -> *const sys::INetChannel {
self.raw.as_ptr().cast_const()
}
#[doc(alias = "GetName")]
pub fn name(self) -> Option<CString> {
unsafe { copy_cstr(vcall!(self.as_const() => INetChannel_GetName())) }
}
#[doc(alias = "GetAddress")]
pub fn address(self) -> Option<CString> {
unsafe { copy_cstr(vcall!(self.as_const() => INetChannel_GetAddress())) }
}
#[doc(alias = "GetTimeConnected")]
pub fn time_connected(self) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetTimeConnected()) }
}
#[doc(alias = "GetDataRate")]
pub fn data_rate(self) -> c_int {
unsafe { vcall!(self.as_const() => INetChannel_GetDataRate()) }
}
#[doc(alias = "IsLoopback")]
pub fn is_loopback(self) -> bool {
unsafe { vcall!(self.as_const() => INetChannel_IsLoopback()) }
}
#[doc(alias = "IsTimingOut")]
pub fn is_timing_out(self) -> bool {
unsafe { vcall!(self.as_const() => INetChannel_IsTimingOut()) }
}
#[doc(alias = "GetLatency")]
pub fn latency(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetLatency(flow.raw())) }
}
#[doc(alias = "GetAvgLatency")]
pub fn average_latency(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetAvgLatency(flow.raw())) }
}
#[doc(alias = "GetAvgLoss")]
pub fn average_loss(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetAvgLoss(flow.raw())) }
}
#[doc(alias = "GetAvgChoke")]
pub fn average_choke(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetAvgChoke(flow.raw())) }
}
#[doc(alias = "GetAvgData")]
pub fn average_data(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetAvgData(flow.raw())) }
}
#[doc(alias = "GetAvgPackets")]
pub fn average_packets(self, flow: Flow) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetAvgPackets(flow.raw())) }
}
#[doc(alias = "GetTotalData")]
pub fn total_data(self, flow: Flow) -> c_int {
unsafe { vcall!(self.as_const() => INetChannel_GetTotalData(flow.raw())) }
}
#[doc(alias = "GetTimeSinceLastReceived")]
pub fn time_since_last_received(self) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetTimeSinceLastReceived()) }
}
#[doc(alias = "GetTimeoutSeconds")]
pub fn timeout_seconds(self) -> f32 {
unsafe { vcall!(self.as_const() => INetChannel_GetTimeoutSeconds()) }
}
#[doc(alias = "GetSequenceData")]
pub fn sequence_data(self) -> SequenceData {
let mut data = SequenceData {
outgoing: 0,
incoming: 0,
outgoing_acknowledged: 0,
};
unsafe {
vcall!(self.as_ptr() => INetChannel_GetSequenceData(
&raw mut data.outgoing,
&raw mut data.incoming,
&raw mut data.outgoing_acknowledged,
))
};
data
}
#[doc(alias = "HasPendingReliableData")]
pub fn has_pending_reliable_data(self) -> bool {
unsafe { vcall!(self.as_ptr() => INetChannel_HasPendingReliableData()) }
}
#[doc(alias = "IsOverflowed")]
pub fn is_overflowed(self) -> bool {
unsafe { vcall!(self.as_const() => INetChannel_IsOverflowed()) }
}
#[doc(alias = "GetNumBitsWritten")]
pub fn bits_waiting(self, reliability: Reliability) -> c_int {
let reliable = reliability == Reliability::Reliable;
unsafe { vcall!(self.as_ptr() => INetChannel_GetNumBitsWritten(reliable)) }
}
#[doc(alias = "GetProtocolVersion")]
pub fn protocol_version(self) -> c_int {
unsafe { vcall!(self.as_ptr() => INetChannel_GetProtocolVersion()) }
}
pub fn send(self, message: &impl NetMessage) -> Result<(), SendError> {
self.send_with(message, message.reliability())
}
pub fn send_with(
self,
message: &impl NetMessage,
reliability: Reliability,
) -> Result<(), SendError> {
self.send_encoded(&message.encode()?, reliability)
}
#[doc(alias = "SendData")]
pub fn send_encoded(self, bits: &BitWriter, reliability: Reliability) -> Result<(), SendError> {
let mut buffer = RawBfWrite::written(bits);
let reliable = reliability == Reliability::Reliable;
let sent = unsafe {
vcall!(self.as_ptr() => INetChannel_SendData(
(&raw mut buffer).cast::<sys::bf_write>(),
reliable,
))
};
match sent {
true => Ok(()),
false => Err(SendError::TooLarge { bits: bits.len() }),
}
}
}
#[cfg(test)]
pub(crate) mod test_support {
use super::*;
use crate::ffi::test_support::{mock_vtable, unexpected_call};
use std::cell::RefCell;
thread_local! {
static SENT: RefCell<Vec<(BitWriter, bool)>> = const { RefCell::new(Vec::new()) };
static ACCEPTS: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
}
unsafe extern "C" fn record_send_data(
_: *mut sys::INetChannel,
buffer: *mut sys::bf_write,
reliable: bool,
) -> bool {
let bits = unsafe { RawBfWrite::read_back(NonNull::new(buffer.cast()).unwrap()) }
.expect("a readable buffer");
SENT.with_borrow_mut(|sent| sent.push((bits, reliable)));
ACCEPTS.get()
}
pub(crate) struct MockChannel {
channel: *mut sys::INetChannel,
}
impl MockChannel {
pub(crate) fn new() -> Self {
let vtable = unsafe {
mock_vtable::<sys::INetChannel__bindgen_vtable>(
unexpected_call as *const (),
|vtable| {
(&raw mut (*vtable).INetChannel_SendData).write(record_send_data);
},
)
};
let channel = Box::into_raw(Box::new(sys::INetChannel {
vtable_: Box::into_raw(vtable),
}));
SENT.set(Vec::new());
ACCEPTS.set(true);
Self { channel }
}
pub(crate) fn channel(&self) -> NetChannel<'_> {
unsafe { NetChannel::from_raw(NonNull::new(self.channel).unwrap()) }
}
pub(crate) fn refuse(&self) {
ACCEPTS.set(false);
}
pub(crate) fn take_sent(&self) -> Vec<(BitWriter, bool)> {
SENT.take()
}
}
}
#[cfg(test)]
mod tests {
use super::test_support::MockChannel;
use super::*;
use crate::net::messages::{Print, Raw};
#[test]
fn messages_are_sent_as_their_type_then_fields() {
let mock = MockChannel::new();
let channel = mock.channel();
channel.send(&Print { text: c"hello" }).unwrap();
channel
.send_with(&Print { text: c"" }, Reliability::Unreliable)
.unwrap();
let sent = mock.take_sent();
assert_eq!(sent.len(), 2);
let (bits, reliable) = &sent[0];
let mut reader = bits.reader();
assert!(*reliable);
assert_eq!(reader.read_ubits(MESSAGE_TYPE_BITS), Ok(7));
assert_eq!(reader.read_cstring().as_deref(), Ok(c"hello"));
assert_eq!(reader.remaining(), 0);
assert!(!sent[1].1);
}
#[test]
fn a_full_stream_is_an_error() {
let mock = MockChannel::new();
let mut body = BitWriter::new();
body.write_bit(true);
mock.refuse();
let raw = Raw {
id: MessageId::NOP,
body: &body,
};
assert_eq!(
mock.channel().send(&raw),
Err(SendError::TooLarge { bits: 7 })
);
}
#[test]
fn types_fit_in_six_bits() {
assert_eq!(MessageId::new(63).map(MessageId::get), Some(63));
assert_eq!(MessageId::new(64), None);
assert_eq!(MessageId::SET_CONVAR.to_string(), "net_SetConVar");
assert_eq!(MessageId::new(40).unwrap().to_string(), "message 40");
}
}