1
0
Fork 0
mirror of https://codeberg.org/demostf/parser.git synced 2026-06-03 10:14:06 +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",
]
[[package]]
name = "const-fnv1a-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca"
[[package]]
name = "derive_more"
version = "2.0.1"
@ -985,6 +991,7 @@ dependencies = [
"Inflector",
"better-panic",
"bitbuffer",
"const-fnv1a-hash",
"enumflags2",
"fnv",
"iai-callgrind",

View file

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

View file

@ -6,6 +6,7 @@ use bitbuffer::{BitRead, BitWrite, BitWriteStream, LittleEndian};
use parse_display::Display;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use const_fnv1a_hash::fnv1a_hash_str_64;
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -17,7 +18,8 @@ pub struct GameEventDefinition {
impl GameEventDefinition {
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)]
pub struct GameEventEntry {
pub name: String,
pub hash: u64,
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))]
#[derive(BitRead, BitWrite, Debug, Clone, Copy, PartialEq, Display, Serialize, Deserialize)]
#[discriminant_bits = 3]

View file

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

View file

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

View file

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

View file

@ -4,9 +4,7 @@ use crate::demo::sendprop::{
RawSendPropDefinition, SendPropDefinition, SendPropFlag, SendPropIdentifier, SendPropType,
};
use crate::{Parse, ParseError, ParserState, Result, Stream};
use bitbuffer::{
BitRead, BitReadStream, BitWrite, BitWriteSized, BitWriteStream, Endianness, LittleEndian,
};
use bitbuffer::{BitRead, BitReadStream, BitWrite, BitWriteSized, BitWriteStream, Endianness, LittleEndian};
use parse_display::{Display, FromStr};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
@ -133,7 +131,7 @@ impl SendTableName {
impl<E: Endianness> BitRead<'_, E> for SendTableName {
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) {
const MINS: SendPropIdentifier =
SendPropIdentifier::new("DT_WORLD", "m_WorldMins");
const MAXS: SendPropIdentifier =
SendPropIdentifier::new("DT_WORLD", "m_WorldMaxs");
if let (
Some(SendProp {
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_name("DT_WORLD", "m_WorldMaxs", parser_state),
entity.get_prop_by_identifier(&MINS, parser_state),
entity.get_prop_by_identifier(&MAXS, parser_state),
) {
self.state.world = Some(World {
boundary_min,

View file

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