Complete first release candidate audit (#107)
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
This commit is contained in:
600
crates/libremetaverse/src/protocol_manager.rs
Normal file
600
crates/libremetaverse/src/protocol_manager.rs
Normal file
@@ -0,0 +1,600 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
|
||||
|
||||
use libremetaverse_types::compat::Object;
|
||||
|
||||
use crate::{Error, FieldType, GridClient, PacketFrequency};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MapField {
|
||||
pub count: i32,
|
||||
pub keyword_position: i32,
|
||||
pub name: String,
|
||||
pub type_: FieldType,
|
||||
}
|
||||
|
||||
impl Default for MapField {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
count: 0,
|
||||
keyword_position: 0,
|
||||
name: String::new(),
|
||||
type_: FieldType::U8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MapField {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
Ok(Self::default())
|
||||
}
|
||||
|
||||
pub fn compare_to(&self, obj: Option<Object>) -> Result<i32, Error> {
|
||||
let Some(obj) = obj else {
|
||||
return Ok(1);
|
||||
};
|
||||
let other = obj.downcast_ref::<Self>().ok_or(Error::Argument)?;
|
||||
Ok(self.keyword_position.cmp(&other.keyword_position) as i32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MapBlock {
|
||||
pub count: i32,
|
||||
pub fields: Vec<MapField>,
|
||||
pub keyword_position: i32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl MapBlock {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
Ok(Self::default())
|
||||
}
|
||||
|
||||
pub fn compare_to(&self, obj: Option<Object>) -> Result<i32, Error> {
|
||||
let Some(obj) = obj else {
|
||||
return Ok(1);
|
||||
};
|
||||
let other = obj.downcast_ref::<Self>().ok_or(Error::Argument)?;
|
||||
Ok(self.keyword_position.cmp(&other.keyword_position) as i32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MapPacket {
|
||||
pub blocks: Vec<MapBlock>,
|
||||
pub encoded: bool,
|
||||
pub frequency: PacketFrequency,
|
||||
pub id: u16,
|
||||
pub name: String,
|
||||
pub trusted: bool,
|
||||
}
|
||||
|
||||
impl Default for MapPacket {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
blocks: Vec::new(),
|
||||
encoded: false,
|
||||
frequency: PacketFrequency::Low,
|
||||
id: 0,
|
||||
name: String::new(),
|
||||
trusted: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MapPacket {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProtocolManager {
|
||||
pub high_maps: Vec<MapPacket>,
|
||||
pub keyword_positions: HashMap<String, i32>,
|
||||
pub low_maps: Vec<MapPacket>,
|
||||
pub medium_maps: Vec<MapPacket>,
|
||||
pub type_sizes: HashMap<FieldType, i32>,
|
||||
}
|
||||
|
||||
impl ProtocolManager {
|
||||
pub fn new(map_file: String, client: GridClient) -> Result<Self, Error> {
|
||||
// The C# instance retains this reference only to enrich parse-error logging.
|
||||
// Rust reports parsing failures to the caller, so no client ownership is needed.
|
||||
drop(client);
|
||||
let mut manager = Self::empty();
|
||||
manager.load_map_file(&map_file)?;
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
fn empty() -> Self {
|
||||
let type_sizes = HashMap::from([
|
||||
(FieldType::U8, 1),
|
||||
(FieldType::U16, 2),
|
||||
(FieldType::U32, 4),
|
||||
(FieldType::U64, 8),
|
||||
(FieldType::S8, 1),
|
||||
(FieldType::S16, 2),
|
||||
(FieldType::S32, 4),
|
||||
(FieldType::F32, 4),
|
||||
(FieldType::F64, 8),
|
||||
(FieldType::UUID, 16),
|
||||
(FieldType::BOOL, 1),
|
||||
(FieldType::Vector3, 12),
|
||||
(FieldType::Vector3d, 24),
|
||||
(FieldType::Vector4, 16),
|
||||
(FieldType::Quaternion, 16),
|
||||
(FieldType::IPADDR, 4),
|
||||
(FieldType::IPPORT, 2),
|
||||
(FieldType::Variable, -1),
|
||||
(FieldType::Fixed, -2),
|
||||
]);
|
||||
Self {
|
||||
high_maps: vec![MapPacket::default(); 256],
|
||||
keyword_positions: HashMap::new(),
|
||||
low_maps: vec![MapPacket::default(); 65_536],
|
||||
medium_maps: vec![MapPacket::default(); 256],
|
||||
type_sizes,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command_with_bytes(&self, data: Vec<u8>) -> Result<Option<MapPacket>, Error> {
|
||||
let Some(&frequency_byte) = data.get(4) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let result = if frequency_byte != 0xff {
|
||||
self.command_with_u_int16_packet_frequency(
|
||||
u16::from(frequency_byte),
|
||||
PacketFrequency::High,
|
||||
)
|
||||
} else if data.get(5) != Some(&0xff) {
|
||||
let Some(&command) = data.get(5) else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.command_with_u_int16_packet_frequency(u16::from(command), PacketFrequency::Medium)
|
||||
} else {
|
||||
let (Some(&high), Some(&low)) = (data.get(6), data.get(7)) else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.command_with_u_int16_packet_frequency(
|
||||
u16::from_be_bytes([high, low]),
|
||||
PacketFrequency::Low,
|
||||
)
|
||||
};
|
||||
match result {
|
||||
Ok(packet) => Ok(Some(packet)),
|
||||
Err(Error::IndexOutOfRange | Error::InvalidOperation) => Ok(None),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command_with_string(&self, command: String) -> Result<MapPacket, Error> {
|
||||
self.high_maps
|
||||
.iter()
|
||||
.chain(&self.medium_maps)
|
||||
.chain(&self.low_maps)
|
||||
.find(|packet| !packet.name.is_empty() && packet.name == command)
|
||||
.cloned()
|
||||
.ok_or(Error::InvalidOperation)
|
||||
}
|
||||
|
||||
pub fn command_with_u_int16_packet_frequency(
|
||||
&self,
|
||||
command: u16,
|
||||
frequency: PacketFrequency,
|
||||
) -> Result<MapPacket, Error> {
|
||||
let maps = match frequency {
|
||||
PacketFrequency::High => &self.high_maps,
|
||||
PacketFrequency::Medium => &self.medium_maps,
|
||||
PacketFrequency::Low => &self.low_maps,
|
||||
};
|
||||
let packet = maps
|
||||
.get(usize::from(command))
|
||||
.ok_or(Error::IndexOutOfRange)?;
|
||||
if packet.name.is_empty() {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
Ok(packet.clone())
|
||||
}
|
||||
|
||||
pub fn decode_map_file(map_file: String, output_file: String) -> Result<(), Error> {
|
||||
let input = File::open(map_file).map_err(|_| Error::InvalidOperation)?;
|
||||
let output = OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(output_file)
|
||||
.map_err(|_| Error::InvalidOperation)?;
|
||||
Self::decode(BufReader::new(input), BufWriter::new(output))
|
||||
}
|
||||
|
||||
fn decode(mut input: impl Read, mut output: impl Write) -> Result<(), Error> {
|
||||
let mut key = 0_u8;
|
||||
let mut buffer = [0_u8; 2048];
|
||||
loop {
|
||||
let count = input
|
||||
.read(&mut buffer)
|
||||
.map_err(|_| Error::InvalidOperation)?;
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
for byte in &mut buffer[..count] {
|
||||
*byte ^= key;
|
||||
key = key.wrapping_add(43);
|
||||
}
|
||||
output
|
||||
.write_all(&buffer[..count])
|
||||
.map_err(|_| Error::InvalidOperation)?;
|
||||
}
|
||||
output.flush().map_err(|_| Error::InvalidOperation)
|
||||
}
|
||||
|
||||
pub fn print_map(&self) -> Result<(), Error> {
|
||||
self.print_one_map(&self.low_maps, "Low ");
|
||||
self.print_one_map(&self.medium_maps, "Medium");
|
||||
self.print_one_map(&self.high_maps, "High ");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_one_map(&self, maps: &[MapPacket], frequency: &str) {
|
||||
for (index, packet) in maps
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, packet)| !packet.name.is_empty())
|
||||
{
|
||||
println!(
|
||||
"{frequency} {index:5} - {} - {} - {}",
|
||||
packet.name,
|
||||
if packet.trusted {
|
||||
"Trusted"
|
||||
} else {
|
||||
"Untrusted"
|
||||
},
|
||||
if packet.encoded {
|
||||
"Unencoded"
|
||||
} else {
|
||||
"Zerocoded"
|
||||
}
|
||||
);
|
||||
for block in &packet.blocks {
|
||||
if block.count == -1 {
|
||||
println!("\t{:4} {} (Variable)", block.keyword_position, block.name);
|
||||
} else {
|
||||
println!(
|
||||
"\t{:4} {} ({})",
|
||||
block.keyword_position, block.name, block.count
|
||||
);
|
||||
}
|
||||
for field in &block.fields {
|
||||
println!(
|
||||
"\t\t{:4} {} ({:?} / {})",
|
||||
field.keyword_position, field.name, field.type_, field.count
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_map_file(&mut self, map_file: &str) -> Result<(), Error> {
|
||||
let input = File::open(map_file).map_err(|_| Error::InvalidOperation)?;
|
||||
self.parse(BufReader::new(input))
|
||||
}
|
||||
|
||||
fn parse(&mut self, input: impl BufRead) -> Result<(), Error> {
|
||||
let mut low = 1_u16;
|
||||
let mut medium = 1_u16;
|
||||
let mut high = 1_u16;
|
||||
let mut in_packet = false;
|
||||
let mut in_block = false;
|
||||
let mut current_packet: Option<(PacketFrequency, usize)> = None;
|
||||
let mut current_block: Option<MapBlock> = None;
|
||||
|
||||
for (line_number, line) in input.lines().enumerate() {
|
||||
let line = line.map_err(|_| Error::Parse {
|
||||
position: line_number,
|
||||
context: "could not read protocol map line",
|
||||
})?;
|
||||
let trimmed = line.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
if !in_packet {
|
||||
if trimmed == "{" {
|
||||
in_packet = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if !in_block {
|
||||
if trimmed == "{" {
|
||||
if current_packet.is_none() {
|
||||
return Err(Self::parse_error(line_number, "block has no packet header"));
|
||||
}
|
||||
in_block = true;
|
||||
} else if trimmed == "}" {
|
||||
if let Some((frequency, index)) = current_packet.take() {
|
||||
self.packet_mut(frequency, index)?
|
||||
.blocks
|
||||
.sort_by_key(|block| block.keyword_position);
|
||||
}
|
||||
in_packet = false;
|
||||
} else if !trimmed.is_empty() && !trimmed.starts_with("//") {
|
||||
current_packet = Some(self.parse_packet_header(
|
||||
&trimmed,
|
||||
line_number,
|
||||
&mut low,
|
||||
&mut medium,
|
||||
&mut high,
|
||||
)?);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if trimmed.starts_with('{') {
|
||||
let tokens: Vec<_> = trimmed.split_whitespace().collect();
|
||||
if tokens.len() < 4 {
|
||||
return Err(Self::parse_error(
|
||||
line_number,
|
||||
"malformed field declaration",
|
||||
));
|
||||
}
|
||||
let type_ = Self::parse_field_type(tokens[2])
|
||||
.ok_or_else(|| Self::parse_error(line_number, "unknown field type"))?;
|
||||
let count = if tokens[3] == "}" {
|
||||
1
|
||||
} else {
|
||||
tokens[3]
|
||||
.parse()
|
||||
.map_err(|_| Self::parse_error(line_number, "invalid field count"))?
|
||||
};
|
||||
let keyword_position = self.keyword_position(tokens[1])?;
|
||||
let block = current_block
|
||||
.as_mut()
|
||||
.ok_or_else(|| Self::parse_error(line_number, "field has no block header"))?;
|
||||
block.fields.push(MapField {
|
||||
count,
|
||||
keyword_position,
|
||||
name: tokens[1].to_owned(),
|
||||
type_,
|
||||
});
|
||||
} else if trimmed == "}" {
|
||||
let mut block = current_block.take().ok_or_else(|| {
|
||||
Self::parse_error(line_number, "block terminator has no header")
|
||||
})?;
|
||||
block.fields.sort_by_key(|field| field.keyword_position);
|
||||
let (frequency, index) = current_packet
|
||||
.ok_or_else(|| Self::parse_error(line_number, "block has no packet"))?;
|
||||
self.packet_mut(frequency, index)?.blocks.push(block);
|
||||
in_block = false;
|
||||
} else if !trimmed.is_empty() && !trimmed.starts_with("//") {
|
||||
if current_block.is_some() {
|
||||
return Err(Self::parse_error(line_number, "block header is incomplete"));
|
||||
}
|
||||
let tokens: Vec<_> = trimmed.split_whitespace().collect();
|
||||
if tokens.len() < 2 {
|
||||
return Err(Self::parse_error(line_number, "malformed block header"));
|
||||
}
|
||||
let count = match tokens[1] {
|
||||
"Single" => 1,
|
||||
"Variable" => -1,
|
||||
"Multiple" => tokens
|
||||
.get(2)
|
||||
.ok_or_else(|| Self::parse_error(line_number, "missing block count"))?
|
||||
.parse()
|
||||
.map_err(|_| Self::parse_error(line_number, "invalid block count"))?,
|
||||
_ => return Err(Self::parse_error(line_number, "unknown block frequency")),
|
||||
};
|
||||
current_block = Some(MapBlock {
|
||||
count,
|
||||
fields: Vec::new(),
|
||||
keyword_position: self.keyword_position(tokens[0])?,
|
||||
name: tokens[0].to_owned(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if in_packet || in_block || current_block.is_some() {
|
||||
return Err(Self::parse_error(
|
||||
0,
|
||||
"unterminated protocol map declaration",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_packet_header(
|
||||
&mut self,
|
||||
line: &str,
|
||||
position: usize,
|
||||
low: &mut u16,
|
||||
medium: &mut u16,
|
||||
high: &mut u16,
|
||||
) -> Result<(PacketFrequency, usize), Error> {
|
||||
let tokens: Vec<_> = line.split_whitespace().collect();
|
||||
if tokens.len() < 4 {
|
||||
return Err(Self::parse_error(position, "malformed packet header"));
|
||||
}
|
||||
self.keyword_position(tokens[0])?;
|
||||
let (frequency, id, trusted_index, encoded_index) = match tokens[1] {
|
||||
"Fixed" => {
|
||||
if tokens.len() < 5 {
|
||||
return Err(Self::parse_error(position, "malformed fixed packet header"));
|
||||
}
|
||||
let raw = tokens[2].strip_prefix("0x").unwrap_or(tokens[2]);
|
||||
let raw = u32::from_str_radix(raw, 16)
|
||||
.map_err(|_| Self::parse_error(position, "invalid fixed packet id"))?;
|
||||
(PacketFrequency::Low, (raw ^ 0xffff_0000) as u16, 3, 4)
|
||||
}
|
||||
"Low" => {
|
||||
let id = *low;
|
||||
*low = low.checked_add(1).ok_or(Error::IndexOutOfRange)?;
|
||||
(PacketFrequency::Low, id, 2, 3)
|
||||
}
|
||||
"Medium" => {
|
||||
let id = *medium;
|
||||
*medium = medium.checked_add(1).ok_or(Error::IndexOutOfRange)?;
|
||||
(PacketFrequency::Medium, id, 2, 3)
|
||||
}
|
||||
"High" => {
|
||||
let id = *high;
|
||||
*high = high.checked_add(1).ok_or(Error::IndexOutOfRange)?;
|
||||
(PacketFrequency::High, id, 2, 3)
|
||||
}
|
||||
_ => return Err(Self::parse_error(position, "unknown packet frequency")),
|
||||
};
|
||||
let packet = MapPacket {
|
||||
blocks: Vec::new(),
|
||||
encoded: tokens.get(encoded_index) == Some(&"Zerocoded"),
|
||||
frequency,
|
||||
id,
|
||||
name: tokens[0].to_owned(),
|
||||
trusted: tokens.get(trusted_index) == Some(&"Trusted"),
|
||||
};
|
||||
let index = usize::from(id);
|
||||
*self.packet_mut(frequency, index)? = packet;
|
||||
Ok((frequency, index))
|
||||
}
|
||||
|
||||
fn packet_mut(
|
||||
&mut self,
|
||||
frequency: PacketFrequency,
|
||||
index: usize,
|
||||
) -> Result<&mut MapPacket, Error> {
|
||||
match frequency {
|
||||
PacketFrequency::High => self.high_maps.get_mut(index),
|
||||
PacketFrequency::Medium => self.medium_maps.get_mut(index),
|
||||
PacketFrequency::Low => self.low_maps.get_mut(index),
|
||||
}
|
||||
.ok_or(Error::IndexOutOfRange)
|
||||
}
|
||||
|
||||
fn keyword_position(&mut self, keyword: &str) -> Result<i32, Error> {
|
||||
if let Some(position) = self.keyword_positions.get(keyword) {
|
||||
return Ok(*position);
|
||||
}
|
||||
let mut hash = keyword
|
||||
.chars()
|
||||
.skip(1)
|
||||
.fold(0_i32, |hash, character| {
|
||||
hash.wrapping_add(character as i32).wrapping_mul(2)
|
||||
})
|
||||
.wrapping_mul(2)
|
||||
& 0x1fff;
|
||||
let start = hash;
|
||||
while self
|
||||
.keyword_positions
|
||||
.values()
|
||||
.any(|position| *position == hash)
|
||||
{
|
||||
hash = (hash + 1) & 0x1fff;
|
||||
if hash == start {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
}
|
||||
self.keyword_positions.insert(keyword.to_owned(), hash);
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
fn parse_field_type(value: &str) -> Option<FieldType> {
|
||||
Some(match value.to_ascii_lowercase().as_str() {
|
||||
"u8" => FieldType::U8,
|
||||
"u16" => FieldType::U16,
|
||||
"u32" => FieldType::U32,
|
||||
"u64" => FieldType::U64,
|
||||
"s8" => FieldType::S8,
|
||||
"s16" => FieldType::S16,
|
||||
"s32" => FieldType::S32,
|
||||
"f32" => FieldType::F32,
|
||||
"f64" => FieldType::F64,
|
||||
"uuid" => FieldType::UUID,
|
||||
"bool" => FieldType::BOOL,
|
||||
"vector3" => FieldType::Vector3,
|
||||
"vector3d" => FieldType::Vector3d,
|
||||
"vector4" => FieldType::Vector4,
|
||||
"quaternion" => FieldType::Quaternion,
|
||||
"ipaddr" => FieldType::IPADDR,
|
||||
"ipport" => FieldType::IPPORT,
|
||||
"variable" => FieldType::Variable,
|
||||
"fixed" => FieldType::Fixed,
|
||||
"single" => FieldType::Single,
|
||||
"multiple" => FieldType::Multiple,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
const fn parse_error(position: usize, context: &'static str) -> Error {
|
||||
Error::Parse { position, context }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Cursor;
|
||||
|
||||
use super::*;
|
||||
|
||||
const MAP: &str = r#"
|
||||
// version header
|
||||
{
|
||||
TestHigh High Trusted Zerocoded
|
||||
{
|
||||
AgentData Single
|
||||
{ AgentID UUID }
|
||||
{ SessionID UUID }
|
||||
}
|
||||
}
|
||||
{
|
||||
TestLow Fixed 0xFFFF00F2 Untrusted Unencoded
|
||||
{
|
||||
Data Variable
|
||||
{ Value U32 }
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn parses_and_resolves_protocol_commands() {
|
||||
let mut manager = ProtocolManager::empty();
|
||||
manager.parse(Cursor::new(MAP)).unwrap();
|
||||
|
||||
let high = manager.command_with_string("TestHigh".to_owned()).unwrap();
|
||||
assert_eq!(high.frequency, PacketFrequency::High);
|
||||
assert_eq!(high.id, 1);
|
||||
assert!(high.trusted);
|
||||
assert!(high.encoded);
|
||||
assert_eq!(high.blocks[0].fields.len(), 2);
|
||||
|
||||
let low = manager
|
||||
.command_with_bytes(vec![0, 0, 0, 0, 0xff, 0xff, 0, 0xf2])
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(low.name, "TestLow");
|
||||
assert_eq!(low.blocks[0].count, -1);
|
||||
assert_eq!(manager.type_sizes[&FieldType::Vector3d], 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xor_decoder_tracks_key_across_chunks() {
|
||||
let input: Vec<_> = (0_u16..4097).map(|value| value as u8).collect();
|
||||
let mut encoded = Vec::new();
|
||||
ProtocolManager::decode(Cursor::new(&input), &mut encoded).unwrap();
|
||||
let mut decoded = Vec::new();
|
||||
ProtocolManager::decode(Cursor::new(encoded), &mut decoded).unwrap();
|
||||
assert_eq!(decoded, input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_short_packet_headers_do_not_panic() {
|
||||
let manager = ProtocolManager::empty();
|
||||
assert!(manager.command_with_bytes(vec![0; 5]).unwrap().is_none());
|
||||
assert!(
|
||||
manager
|
||||
.command_with_bytes(vec![0, 0, 0, 0, 0xff])
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
manager
|
||||
.command_with_bytes(vec![0, 0, 0, 0, 0xff, 0xff, 0])
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user