1
0
Fork 0
mirror of https://codeberg.org/demostf/parser.git synced 2026-06-03 18:24:05 +02:00

remove some bounds checks

This commit is contained in:
Robin Appelman 2025-07-14 14:52:38 +02:00
commit 6ef2b1cea8
9 changed files with 102 additions and 93 deletions

7
Cargo.lock generated
View file

@ -132,6 +132,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "const-fnv1a-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca"
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "2.0.1" version = "2.0.1"
@ -985,6 +991,7 @@ dependencies = [
"Inflector", "Inflector",
"better-panic", "better-panic",
"bitbuffer", "bitbuffer",
"const-fnv1a-hash",
"enumflags2", "enumflags2",
"fnv", "fnv",
"iai-callgrind", "iai-callgrind",

View file

@ -64,6 +64,7 @@ steamid-ng = "1.0.0"
tracing = { version = "0.1.40", optional = true } tracing = { version = "0.1.40", optional = true }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"], optional = true } tracing-subscriber = { version = "0.3.18", features = ["env-filter"], optional = true }
itertools = "0.13.0" itertools = "0.13.0"
const-fnv1a-hash = "1.1.0"
# schema # schema
schemars = { version = "0.8.21", optional = true } schemars = { version = "0.8.21", optional = true }

View file

@ -6,6 +6,7 @@ use bitbuffer::{BitRead, BitWrite, BitWriteStream, LittleEndian};
use parse_display::Display; use parse_display::Display;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::Ordering; use std::cmp::Ordering;
use const_fnv1a_hash::fnv1a_hash_str_64;
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -17,7 +18,8 @@ pub struct GameEventDefinition {
impl GameEventDefinition { impl GameEventDefinition {
pub fn get_entry(&self, name: &str) -> Option<&GameEventEntry> { pub fn get_entry(&self, name: &str) -> Option<&GameEventEntry> {
self.entries.iter().find(|entry| entry.name == name) let hash = fnv1a_hash_str_64(name);
self.entries.iter().find(|entry| entry.hash == hash)
} }
} }
@ -45,9 +47,21 @@ impl Ord for GameEventDefinition {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GameEventEntry { pub struct GameEventEntry {
pub name: String, pub name: String,
pub hash: u64,
pub kind: GameEventValueType, pub kind: GameEventValueType,
} }
impl GameEventEntry {
pub fn new<S: Into<String>>(name: S, kind: GameEventValueType) -> Self {
let name = name.into();
GameEventEntry {
hash: fnv1a_hash_str_64(&name),
name: name.into(),
kind,
}
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(BitRead, BitWrite, Debug, Clone, Copy, PartialEq, Display, Serialize, Deserialize)] #[derive(BitRead, BitWrite, Debug, Clone, Copy, PartialEq, Display, Serialize, Deserialize)]
#[discriminant_bits = 3] #[discriminant_bits = 3]

View file

@ -1,6 +1,7 @@
use bitbuffer::{BitRead, BitWrite, BitWriteSized, BitWriteStream, LittleEndian}; use bitbuffer::{BitRead, BitWrite, BitWriteSized, BitWriteStream, LittleEndian};
use parse_display::Display; use parse_display::Display;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use crate::demo::gameevent_gen::GameEventType; use crate::demo::gameevent_gen::GameEventType;
use crate::demo::gamevent::{ use crate::demo::gamevent::{
@ -75,18 +76,15 @@ fn test_game_event_roundtrip() {
GameEventDefinition { GameEventDefinition {
id: GameEventTypeId(0), id: GameEventTypeId(0),
event_type: GameEventType::ServerShutdown, event_type: GameEventType::ServerShutdown,
entries: vec![GameEventEntry { entries: vec![GameEventEntry::new("reason", GameEventValueType::String)],
name: "reason".to_string(),
kind: GameEventValueType::String,
}],
}, },
GameEventDefinition { GameEventDefinition {
id: GameEventTypeId(1), id: GameEventTypeId(1),
event_type: GameEventType::ServerChangeLevelFailed, event_type: GameEventType::ServerChangeLevelFailed,
entries: vec![GameEventEntry { entries: vec![GameEventEntry::new(
name: "level_name".to_string(), "level_name",
kind: GameEventValueType::String, GameEventValueType::String,
}], )],
}, },
GameEventDefinition { GameEventDefinition {
id: GameEventTypeId(2), id: GameEventTypeId(2),
@ -168,11 +166,8 @@ impl BitRead<'_, LittleEndian> for GameEventDefinition {
let mut entry_type = stream.read()?; let mut entry_type = stream.read()?;
while entry_type != GameEventValueType::None { while entry_type != GameEventValueType::None {
let entry_name = stream.read()?; let entry_name: Cow<str> = stream.read()?;
entries.push(GameEventEntry { entries.push(GameEventEntry::new(entry_name, entry_type));
name: entry_name,
kind: entry_type,
});
entry_type = stream.read()?; entry_type = stream.read()?;
} }
@ -207,10 +202,10 @@ fn test_event_definition_roundtrip() {
crate::test_roundtrip_write(GameEventDefinition { crate::test_roundtrip_write(GameEventDefinition {
id: GameEventTypeId(0), id: GameEventTypeId(0),
event_type: GameEventType::ServerChangeLevelFailed, event_type: GameEventType::ServerChangeLevelFailed,
entries: vec![GameEventEntry { entries: vec![GameEventEntry::new(
name: "level_name".to_string(), "level_name",
kind: GameEventValueType::String, GameEventValueType::String,
}], )],
}); });
} }
@ -244,10 +239,7 @@ fn test_event_list_roundtrip() {
event_list: vec![GameEventDefinition { event_list: vec![GameEventDefinition {
id: GameEventTypeId(0), id: GameEventTypeId(0),
event_type: GameEventType::ServerChangeLevelFailed, event_type: GameEventType::ServerChangeLevelFailed,
entries: vec![GameEventEntry { entries: vec![GameEventEntry::new("level_name", GameEventValueType::String)],
name: "level_name".to_string(),
kind: GameEventValueType::String,
}],
}], }],
}); });
crate::test_roundtrip_write(GameEventListMessage { crate::test_roundtrip_write(GameEventListMessage {
@ -256,55 +248,25 @@ fn test_event_list_roundtrip() {
id: GameEventTypeId(0), id: GameEventTypeId(0),
event_type: GameEventType::ServerSpawn, event_type: GameEventType::ServerSpawn,
entries: vec![ entries: vec![
GameEventEntry { GameEventEntry::new("hostname", GameEventValueType::String),
name: "hostname".to_string(), GameEventEntry::new("address", GameEventValueType::String),
kind: GameEventValueType::String, GameEventEntry::new("ip", GameEventValueType::Long),
}, GameEventEntry::new("port", GameEventValueType::Short),
GameEventEntry { GameEventEntry::new("game", GameEventValueType::String),
name: "address".to_string(), GameEventEntry::new("map_name", GameEventValueType::String),
kind: GameEventValueType::String, GameEventEntry::new("max_players", GameEventValueType::Long),
}, GameEventEntry::new("os", GameEventValueType::String),
GameEventEntry { GameEventEntry::new("dedicated", GameEventValueType::Boolean),
name: "ip".to_string(), GameEventEntry::new("password", GameEventValueType::Boolean),
kind: GameEventValueType::Long,
},
GameEventEntry {
name: "port".to_string(),
kind: GameEventValueType::Short,
},
GameEventEntry {
name: "game".to_string(),
kind: GameEventValueType::String,
},
GameEventEntry {
name: "map_name".to_string(),
kind: GameEventValueType::String,
},
GameEventEntry {
name: "max_players".to_string(),
kind: GameEventValueType::Long,
},
GameEventEntry {
name: "os".to_string(),
kind: GameEventValueType::String,
},
GameEventEntry {
name: "dedicated".to_string(),
kind: GameEventValueType::Boolean,
},
GameEventEntry {
name: "password".to_string(),
kind: GameEventValueType::Boolean,
},
], ],
}, },
GameEventDefinition { GameEventDefinition {
id: GameEventTypeId(1), id: GameEventTypeId(1),
event_type: GameEventType::ServerChangeLevelFailed, event_type: GameEventType::ServerChangeLevelFailed,
entries: vec![GameEventEntry { entries: vec![GameEventEntry::new(
name: "level_name".to_string(), "level_name",
kind: GameEventValueType::String, GameEventValueType::String,
}], )],
}, },
GameEventDefinition { GameEventDefinition {
id: GameEventTypeId(2), id: GameEventTypeId(2),

View file

@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use std::borrow::Cow; use std::borrow::Cow;
use crate::demo::message::stringtable::log_base2;
use crate::demo::packet::datatable::{ClassId, SendTable}; use crate::demo::packet::datatable::{ClassId, SendTable};
use crate::demo::parser::{Encode, ParseBitSkip}; use crate::demo::parser::{Encode, ParseBitSkip};
use crate::demo::sendprop::{SendProp, SendPropIdentifier, SendPropValue}; use crate::demo::sendprop::{SendProp, SendPropIdentifier, SendPropValue};
@ -352,10 +351,21 @@ impl Parse<'_> for PacketEntitiesMessage {
fn parse(stream: &mut Stream, state: &ParserState) -> Result<Self> { fn parse(stream: &mut Stream, state: &ParserState) -> Result<Self> {
let max_entries = stream.read_sized(11)?; let max_entries = stream.read_sized(11)?;
let delta: Option<ServerTick> = stream.read()?; let delta: Option<ServerTick> = stream.read()?;
let base_line = stream.read()?; #[derive(BitRead)]
let updated_entries: u16 = stream.read_sized(11)?; struct Header {
let length: u32 = stream.read_sized(20)?; base_line: BaselineIndex,
let updated_base_line = stream.read()?; #[bitbuffer(size = 11)]
updated_entries: u16,
#[bitbuffer(size = 20)]
length: u32,
updated_base_line: bool,
}
let Header {
base_line,
updated_entries,
length,
updated_base_line,
} = stream.read()?;
let mut data = stream.read_bits(length as usize)?; let mut data = stream.read_bits(length as usize)?;
@ -486,10 +496,16 @@ impl PacketEntitiesMessage {
baseline_index: BaselineIndex, baseline_index: BaselineIndex,
delta: Option<ServerTick>, delta: Option<ServerTick>,
) -> Result<PacketEntity> { ) -> Result<PacketEntity> {
let bits = log_base2(state.server_classes.len()) + 1; let bits = state.server_class_bits;
let class_index: ClassId = stream.read_sized::<u16>(bits as usize)?.into(); #[derive(BitReadSized)]
struct Data {
let serial = stream.read_sized(10)?; #[bitbuffer(size = "input_size")]
raw_index: u16,
#[bitbuffer(size = 10)]
serial: u32,
}
let Data { raw_index, serial } = stream.read_sized(bits)?;
let class_index: ClassId = raw_index.into();
Ok(PacketEntity { Ok(PacketEntity {
server_class: class_index, server_class: class_index,
@ -509,8 +525,7 @@ impl PacketEntitiesMessage {
stream: &mut BitWriteStream<LittleEndian>, stream: &mut BitWriteStream<LittleEndian>,
state: &ParserState, state: &ParserState,
) -> Result<()> { ) -> Result<()> {
let bits = log_base2(state.server_classes.len()) + 1; u16::from(entity.server_class).write_sized(stream, state.server_class_bits)?;
u16::from(entity.server_class).write_sized(stream, bits as usize)?;
entity.serial_number.write_sized(stream, 10)?; entity.serial_number.write_sized(stream, 10)?;
Ok(()) Ok(())
@ -613,12 +628,12 @@ impl ParseBitSkip<'_> for PacketEntitiesMessage {
} }
#[test] #[test]
fn test_packet_entitier_message_roundtrip() { fn test_packet_entity_message_roundtrip() {
use crate::demo::packet::datatable::{SendTable, SendTableName, ServerClass, ServerClassName}; use crate::demo::packet::datatable::{SendTable, SendTableName, ServerClass, ServerClassName};
use crate::demo::sendprop::{FloatDefinition, SendPropDefinition, SendPropParseDefinition}; use crate::demo::sendprop::{FloatDefinition, SendPropDefinition, SendPropParseDefinition};
let mut state = ParserState::new(24, |_| false, false); let mut state = ParserState::new(24, |_| false, false);
state.server_classes = vec![ state.set_server_classes(vec![
ServerClass { ServerClass {
id: ClassId::from(0), id: ClassId::from(0),
name: ServerClassName::from("class1"), name: ServerClassName::from("class1"),
@ -629,7 +644,7 @@ fn test_packet_entitier_message_roundtrip() {
name: ServerClassName::from("class2"), name: ServerClassName::from("class2"),
data_table: SendTableName::from("table2"), data_table: SendTableName::from("table2"),
}, },
]; ]);
state.send_tables = vec![ state.send_tables = vec![
SendTable { SendTable {
name: SendTableName::from("table1"), name: SendTableName::from("table1"),

View file

@ -1,6 +1,6 @@
use super::stringtable::read_var_int; use super::stringtable::read_var_int;
use crate::demo::message::packetentities::PacketEntitiesMessage; use crate::demo::message::packetentities::PacketEntitiesMessage;
use crate::demo::message::stringtable::{encode_var_int_fixed, log_base2}; use crate::demo::message::stringtable::{encode_var_int_fixed};
use crate::demo::packet::datatable::ClassId; use crate::demo::packet::datatable::ClassId;
use crate::demo::parser::{Encode, ParseBitSkip}; use crate::demo::parser::{Encode, ParseBitSkip};
use crate::demo::sendprop::SendProp; use crate::demo::sendprop::SendProp;
@ -53,8 +53,8 @@ impl Parse<'_> for TempEntitiesMessage {
}; };
let class_id = if stream.read()? { let class_id = if stream.read()? {
let bits = log_base2(state.server_classes.len()) + 1; let bits = state.server_class_bits;
(stream.read_sized::<u16>(bits as usize)?.saturating_sub(1)).into() (stream.read_sized::<u16>(bits)?.saturating_sub(1)).into()
} else { } else {
let last = events.last().ok_or(ParseError::InvalidDemo( let last = events.last().ok_or(ParseError::InvalidDemo(
"temp entity update without previous", "temp entity update without previous",
@ -118,9 +118,9 @@ impl Encode for TempEntitiesMessage {
if event.class_id != last_class_id { if event.class_id != last_class_id {
true.write(stream)?; true.write(stream)?;
let bits = log_base2(state.server_classes.len()) + 1; let bits = state.server_class_bits;
let id: u16 = event.class_id.into(); let id: u16 = event.class_id.into();
(id + 1).write_sized(stream, bits as usize)?; (id + 1).write_sized(stream, bits)?;
} else { } else {
false.write(stream)?; false.write(stream)?;
} }

View file

@ -4,9 +4,7 @@ use crate::demo::sendprop::{
RawSendPropDefinition, SendPropDefinition, SendPropFlag, SendPropIdentifier, SendPropType, RawSendPropDefinition, SendPropDefinition, SendPropFlag, SendPropIdentifier, SendPropType,
}; };
use crate::{Parse, ParseError, ParserState, Result, Stream}; use crate::{Parse, ParseError, ParserState, Result, Stream};
use bitbuffer::{ use bitbuffer::{BitRead, BitReadStream, BitWrite, BitWriteSized, BitWriteStream, Endianness, LittleEndian};
BitRead, BitReadStream, BitWrite, BitWriteSized, BitWriteStream, Endianness, LittleEndian,
};
use parse_display::{Display, FromStr}; use parse_display::{Display, FromStr};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::borrow::Cow; use std::borrow::Cow;
@ -133,7 +131,7 @@ impl SendTableName {
impl<E: Endianness> BitRead<'_, E> for SendTableName { impl<E: Endianness> BitRead<'_, E> for SendTableName {
fn read(stream: &mut BitReadStream<'_, E>) -> bitbuffer::Result<Self> { fn read(stream: &mut BitReadStream<'_, E>) -> bitbuffer::Result<Self> {
String::read(stream).map(SendTableName::from) <String as BitRead<'_, E>>::read(stream).map(SendTableName::from)
} }
} }

View file

@ -185,6 +185,11 @@ impl GameStateAnalyser {
} }
pub fn handle_world_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) { pub fn handle_world_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
const MINS: SendPropIdentifier =
SendPropIdentifier::new("DT_WORLD", "m_WorldMins");
const MAXS: SendPropIdentifier =
SendPropIdentifier::new("DT_WORLD", "m_WorldMaxs");
if let ( if let (
Some(SendProp { Some(SendProp {
value: SendPropValue::Vector(boundary_min), value: SendPropValue::Vector(boundary_min),
@ -195,8 +200,8 @@ impl GameStateAnalyser {
.. ..
}), }),
) = ( ) = (
entity.get_prop_by_name("DT_WORLD", "m_WorldMins", parser_state), entity.get_prop_by_identifier(&MINS, parser_state),
entity.get_prop_by_name("DT_WORLD", "m_WorldMaxs", parser_state), entity.get_prop_by_identifier(&MAXS, parser_state),
) { ) {
self.state.world = Some(World { self.state.world = Some(World {
boundary_min, boundary_min,

View file

@ -8,7 +8,7 @@ use crate::demo::message::packetentities::{
BaselineIndex, EntityId, PacketEntitiesMessage, PacketEntity, UpdateType, BaselineIndex, EntityId, PacketEntitiesMessage, PacketEntity, UpdateType,
}; };
use crate::demo::message::stringtable::StringTableMeta; use crate::demo::message::stringtable::StringTableMeta;
use crate::demo::message::{Message, MessageType}; use crate::demo::message::{log_base2, Message, MessageType};
use crate::demo::packet::datatable::{ use crate::demo::packet::datatable::{
ClassId, ParseSendTable, SendTable, SendTableName, ServerClass, ClassId, ParseSendTable, SendTable, SendTableName, ServerClass,
}; };
@ -40,6 +40,7 @@ pub struct ParserState {
// indexed by ClassId // indexed by ClassId
pub send_tables: Vec<SendTable>, pub send_tables: Vec<SendTable>,
pub server_classes: Vec<ServerClass>, pub server_classes: Vec<ServerClass>,
pub server_class_bits: usize,
pub instance_baselines: [Baseline; 2], pub instance_baselines: [Baseline; 2],
pub demo_meta: DemoMeta, pub demo_meta: DemoMeta,
analyser_handles: fn(message_type: MessageType) -> bool, analyser_handles: fn(message_type: MessageType) -> bool,
@ -85,6 +86,7 @@ impl ParserState {
entity_classes: HashMap::with_hasher(NullHasherBuilder), entity_classes: HashMap::with_hasher(NullHasherBuilder),
send_tables: Vec::new(), send_tables: Vec::new(),
server_classes: Vec::new(), server_classes: Vec::new(),
server_class_bits: 0,
instance_baselines: [Baseline::default(), Baseline::default()], instance_baselines: [Baseline::default(), Baseline::default()],
demo_meta: DemoMeta::default(), demo_meta: DemoMeta::default(),
analyser_handles, analyser_handles,
@ -94,6 +96,11 @@ impl ParserState {
} }
} }
pub fn set_server_classes(&mut self, server_classes: Vec<ServerClass>) {
self.server_class_bits = log_base2(server_classes.len()) as usize + 1;
self.server_classes = server_classes;
}
pub fn get_static_baseline( pub fn get_static_baseline(
&self, &self,
class_id: ClassId, class_id: ClassId,
@ -193,7 +200,7 @@ impl ParserState {
}) })
.collect::<Result<_>>()?; .collect::<Result<_>>()?;
self.server_classes = server_classes; self.set_server_classes(server_classes);
self.send_tables.reserve(self.server_classes.len()); self.send_tables.reserve(self.server_classes.len());