use super::{EncodeError, MessageId, NetMessage};
use crate::bitbuf::BitWriter;
use crate::math::{QAngle, Vector};
use std::ffi::CStr;
pub const MAX_COMMAND_LEN: usize = 1023;
pub const MAX_CONVAR_LEN: usize = 259;
pub const MAX_PRINT_LEN: usize = 2047;
pub const MAX_MESSAGE_DATA_BYTES: usize = 255;
pub const MAX_QUERY_NAME_LEN: usize = 255;
const EDICT_BITS: u32 = 11;
const DECAL_INDEX_BITS: u32 = 9;
const MODEL_INDEX_BITS: u32 = 13;
const SERVER_CLASS_BITS: u32 = 9;
const SOUND_INDEX_BITS: u32 = 14;
const PAYLOAD_LENGTH_BITS: u32 = 11;
#[doc(alias = "NET_StringCmd")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StringCmd<'a> {
pub command: &'a CStr,
}
impl NetMessage for StringCmd<'_> {
fn id(&self) -> MessageId {
MessageId::STRING_CMD
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
EncodeError::check_len("command", self.command.count_bytes(), MAX_COMMAND_LEN)?;
out.write_cstr(self.command);
Ok(())
}
}
#[doc(alias = "NET_SetConVar")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetConVar<'a> {
pub convars: &'a [(&'a CStr, &'a CStr)],
}
impl NetMessage for SetConVar<'_> {
fn id(&self) -> MessageId {
MessageId::SET_CONVAR
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
let count = u8::try_from(self.convars.len()).map_err(|_| EncodeError::OutOfRange {
field: "convar count",
value: self.convars.len() as u64,
max: u8::MAX.into(),
})?;
out.write_u8(count);
for &(name, value) in self.convars {
EncodeError::check_len("convar name", name.count_bytes(), MAX_CONVAR_LEN)?;
EncodeError::check_len("convar value", value.count_bytes(), MAX_CONVAR_LEN)?;
out.write_cstr(name);
out.write_cstr(value);
}
Ok(())
}
}
#[doc(alias = "SVC_Print")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Print<'a> {
pub text: &'a CStr,
}
impl NetMessage for Print<'_> {
fn id(&self) -> MessageId {
MessageId::PRINT
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
EncodeError::check_len("text", self.text.count_bytes(), MAX_PRINT_LEN)?;
out.write_cstr(self.text);
Ok(())
}
}
#[doc(alias = "SVC_SetPause")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetPause {
pub paused: bool,
}
impl NetMessage for SetPause {
fn id(&self) -> MessageId {
MessageId::SET_PAUSE
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
out.write_bit(self.paused);
Ok(())
}
}
#[doc(alias = "SVC_SetView")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetView {
pub entity: u16,
}
impl NetMessage for SetView {
fn id(&self) -> MessageId {
MessageId::SET_VIEW
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
EncodeError::check_bits("entity", self.entity.into(), EDICT_BITS)?;
out.write_ubits(self.entity.into(), EDICT_BITS);
Ok(())
}
}
#[doc(alias = "SVC_FixAngle")]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FixAngle {
pub relative: bool,
pub angles: QAngle,
}
impl NetMessage for FixAngle {
fn id(&self) -> MessageId {
MessageId::FIX_ANGLE
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
out.write_bit(self.relative);
out.write_bit_angle(self.angles.pitch, 16);
out.write_bit_angle(self.angles.yaw, 16);
out.write_bit_angle(self.angles.roll, 16);
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecalTarget {
pub entity: u16,
pub model: u16,
}
#[doc(alias = "SVC_BSPDecal")]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BspDecal {
pub position: Vector,
pub texture: u16,
pub target: Option<DecalTarget>,
pub low_priority: bool,
}
impl NetMessage for BspDecal {
fn id(&self) -> MessageId {
MessageId::BSP_DECAL
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
EncodeError::check_bits("texture", self.texture.into(), DECAL_INDEX_BITS)?;
if let Some(target) = self.target {
EncodeError::check_bits("entity", target.entity.into(), EDICT_BITS)?;
EncodeError::check_bits("model", target.model.into(), MODEL_INDEX_BITS)?;
}
out.write_bit_vec3_coord(self.position);
out.write_ubits(self.texture.into(), DECAL_INDEX_BITS);
match self.target {
Some(target) => {
out.write_bit(true);
out.write_ubits(target.entity.into(), EDICT_BITS);
out.write_ubits(target.model.into(), MODEL_INDEX_BITS);
}
None => out.write_bit(false),
}
out.write_bit(self.low_priority);
Ok(())
}
}
fn write_payload(
out: &mut BitWriter,
field: &'static str,
data: &BitWriter,
max_bytes: usize,
) -> Result<(), EncodeError> {
EncodeError::check_len(field, data.byte_len(), max_bytes)?;
EncodeError::check_bits(field, data.len() as u64, PAYLOAD_LENGTH_BITS)?;
out.write_ubits(data.len() as u32, PAYLOAD_LENGTH_BITS);
out.write_bits(data);
Ok(())
}
#[doc(alias = "SVC_UserMessage")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UserMessage<'a> {
pub message_type: u8,
pub data: &'a BitWriter,
}
impl NetMessage for UserMessage<'_> {
fn id(&self) -> MessageId {
MessageId::USER_MESSAGE
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
let mut body = BitWriter::new();
body.write_u8(self.message_type);
write_payload(
&mut body,
"user message data",
self.data,
MAX_MESSAGE_DATA_BYTES,
)?;
out.write_bits(&body);
Ok(())
}
}
#[doc(alias = "SVC_EntityMessage")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EntityMessage<'a> {
pub entity: u16,
pub class_id: u16,
pub data: &'a BitWriter,
}
impl NetMessage for EntityMessage<'_> {
fn id(&self) -> MessageId {
MessageId::ENTITY_MESSAGE
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
EncodeError::check_bits("entity", self.entity.into(), EDICT_BITS)?;
EncodeError::check_bits("class ID", self.class_id.into(), SERVER_CLASS_BITS)?;
let mut body = BitWriter::new();
body.write_ubits(self.entity.into(), EDICT_BITS);
body.write_ubits(self.class_id.into(), SERVER_CLASS_BITS);
write_payload(
&mut body,
"entity message data",
self.data,
MAX_MESSAGE_DATA_BYTES,
)?;
out.write_bits(&body);
Ok(())
}
}
#[doc(alias = "SVC_GameEvent")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GameEvent<'a> {
pub data: &'a BitWriter,
}
impl NetMessage for GameEvent<'_> {
fn id(&self) -> MessageId {
MessageId::GAME_EVENT
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
write_payload(out, "game event data", self.data, usize::MAX)
}
}
#[doc(alias = "SVC_Prefetch")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Prefetch {
pub sound: u16,
}
impl NetMessage for Prefetch {
fn id(&self) -> MessageId {
MessageId::PREFETCH
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
EncodeError::check_bits("sound", self.sound.into(), SOUND_INDEX_BITS)?;
out.write_ubits(self.sound.into(), SOUND_INDEX_BITS);
Ok(())
}
}
#[doc(alias = "SVC_GetCvarValue")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GetCvarValue<'a> {
pub cookie: i32,
pub name: &'a CStr,
}
impl NetMessage for GetCvarValue<'_> {
fn id(&self) -> MessageId {
MessageId::GET_CVAR_VALUE
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
EncodeError::check_len("name", self.name.count_bytes(), MAX_QUERY_NAME_LEN)?;
out.write_i32(self.cookie);
out.write_cstr(self.name);
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Raw<'a> {
pub id: MessageId,
pub body: &'a BitWriter,
}
impl NetMessage for Raw<'_> {
fn id(&self) -> MessageId {
self.id
}
fn write_body(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
out.write_bits(self.body);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bitbuf::BitReader;
use crate::net::MESSAGE_TYPE_BITS;
fn fields(message: &impl NetMessage, id: MessageId) -> (BitWriter, usize) {
let bits = message.encode().unwrap();
let mut reader = bits.reader();
assert_eq!(reader.read_ubits(MESSAGE_TYPE_BITS), Ok(id.get().into()));
let position = reader.position();
(bits, position)
}
fn reader_at(bits: &BitWriter, position: usize) -> BitReader<'_> {
let mut reader = bits.reader();
reader.read_bits(position).unwrap();
reader
}
#[test]
fn string_commands_and_convars() {
let (bits, at) = fields(
&StringCmd {
command: c"cl_soundscape_flush",
},
MessageId::STRING_CMD,
);
let mut reader = reader_at(&bits, at);
assert_eq!(reader.read_cstring().as_deref(), Ok(c"cl_soundscape_flush"));
assert_eq!(reader.remaining(), 0);
let convars = [(c"sv_cheats", c"1"), (c"mp_friendlyfire", c"0")];
let (bits, at) = fields(&SetConVar { convars: &convars }, MessageId::SET_CONVAR);
let mut reader = reader_at(&bits, at);
assert_eq!(reader.read_u8(), Ok(2));
for (name, value) in convars {
assert_eq!(reader.read_cstring().as_deref(), Ok(name));
assert_eq!(reader.read_cstring().as_deref(), Ok(value));
}
assert_eq!(reader.remaining(), 0);
}
#[test]
fn limits_are_checked_before_writing() {
let long = std::ffi::CString::new(vec![b'a'; MAX_COMMAND_LEN + 1]).unwrap();
assert!(matches!(
StringCmd { command: &long }.encode(),
Err(EncodeError::TooLong {
max: MAX_COMMAND_LEN,
..
})
));
let many = vec![(c"a", c"b"); 256];
assert!(matches!(
SetConVar { convars: &many }.encode(),
Err(EncodeError::OutOfRange { max: 255, .. })
));
assert!(matches!(
SetView { entity: 2048 }.encode(),
Err(EncodeError::OutOfRange { max: 2047, .. })
));
let mut data = BitWriter::new();
data.write_bytes(&[0; MAX_MESSAGE_DATA_BYTES + 1]);
assert!(matches!(
UserMessage {
message_type: 1,
data: &data,
}
.encode(),
Err(EncodeError::TooLong { .. })
));
}
#[test]
fn views_and_angles() {
let (bits, at) = fields(&SetView { entity: 1234 }, MessageId::SET_VIEW);
let mut reader = reader_at(&bits, at);
assert_eq!(reader.read_ubits(11), Ok(1234));
assert_eq!(reader.remaining(), 0);
let angles = QAngle {
pitch: 45.0,
yaw: 180.0,
roll: 0.0,
};
let (bits, at) = fields(
&FixAngle {
relative: true,
angles,
},
MessageId::FIX_ANGLE,
);
let mut reader = reader_at(&bits, at);
assert_eq!(reader.read_bit(), Ok(true));
assert_eq!(reader.read_u16(), Ok(8192));
assert_eq!(reader.read_u16(), Ok(32768));
assert_eq!(reader.read_u16(), Ok(0));
assert_eq!(reader.remaining(), 0);
}
#[test]
fn decals_on_entities_carry_their_model() {
let decal = BspDecal {
position: Vector::new(1.0, -2.5, 0.0),
texture: 300,
target: Some(DecalTarget {
entity: 2000,
model: 8000,
}),
low_priority: true,
};
let (bits, at) = fields(&decal, MessageId::BSP_DECAL);
let mut reader = reader_at(&bits, at);
assert_eq!(reader.read_bit_vec3_coord(), Ok(decal.position));
assert_eq!(reader.read_ubits(9), Ok(300));
assert_eq!(reader.read_bit(), Ok(true));
assert_eq!(reader.read_ubits(11), Ok(2000));
assert_eq!(reader.read_ubits(13), Ok(8000));
assert_eq!(reader.read_bit(), Ok(true));
assert_eq!(reader.remaining(), 0);
let world = BspDecal {
target: None,
low_priority: false,
..decal
};
let (bits, at) = fields(&world, MessageId::BSP_DECAL);
let mut reader = reader_at(&bits, at);
reader.read_bit_vec3_coord().unwrap();
reader.read_ubits(9).unwrap();
assert_eq!(reader.read_bit(), Ok(false));
assert_eq!(reader.read_bit(), Ok(false));
assert_eq!(reader.remaining(), 0);
}
#[test]
fn payloads_are_prefixed_with_their_length_in_bits() {
let mut data = BitWriter::new();
data.write_ubits(0b10110, 5);
let (bits, at) = fields(
&UserMessage {
message_type: 11,
data: &data,
},
MessageId::USER_MESSAGE,
);
let mut reader = reader_at(&bits, at);
assert_eq!(reader.read_u8(), Ok(11));
assert_eq!(reader.read_ubits(11), Ok(5));
assert_eq!(reader.read_ubits(5), Ok(0b10110));
assert_eq!(reader.remaining(), 0);
let (bits, at) = fields(
&EntityMessage {
entity: 7,
class_id: 300,
data: &data,
},
MessageId::ENTITY_MESSAGE,
);
let mut reader = reader_at(&bits, at);
assert_eq!(reader.read_ubits(11), Ok(7));
assert_eq!(reader.read_ubits(9), Ok(300));
assert_eq!(reader.read_ubits(11), Ok(5));
assert_eq!(reader.read_ubits(5), Ok(0b10110));
let (bits, at) = fields(&GameEvent { data: &data }, MessageId::GAME_EVENT);
let mut reader = reader_at(&bits, at);
assert_eq!(reader.read_ubits(11), Ok(5));
assert_eq!(reader.read_ubits(5), Ok(0b10110));
let mut large = BitWriter::new();
large.write_bytes(&[0; 256]);
assert!(GameEvent { data: &large }.encode().is_err());
}
#[test]
fn small_messages() {
let (bits, at) = fields(&SetPause { paused: true }, MessageId::SET_PAUSE);
assert_eq!(bits.len(), at + 1);
let (bits, at) = fields(&Prefetch { sound: 16383 }, MessageId::PREFETCH);
assert_eq!(reader_at(&bits, at).read_ubits(14), Ok(16383));
assert!(Prefetch { sound: 16384 }.encode().is_err());
let (bits, at) = fields(
&GetCvarValue {
cookie: -3,
name: c"cl_downloadfilter",
},
MessageId::GET_CVAR_VALUE,
);
let mut reader = reader_at(&bits, at);
assert_eq!(reader.read_i32(), Ok(-3));
assert_eq!(reader.read_cstring().as_deref(), Ok(c"cl_downloadfilter"));
let (bits, at) = fields(&Print { text: c"boo" }, MessageId::PRINT);
assert_eq!(reader_at(&bits, at).read_cstring().as_deref(), Ok(c"boo"));
}
}