Implement generated packet wire codecs (#47)
This commit is contained in:
@@ -16,12 +16,19 @@ jobs:
|
||||
run: cargo test -p libremetaverse-codegen
|
||||
- name: Verify vendored hashes and checked-in output
|
||||
run: cargo run -p libremetaverse-codegen -- check
|
||||
- name: Test generated wire codecs and translated parity cases
|
||||
run: |
|
||||
cargo test -p libremetaverse --lib generated_codec_tests
|
||||
cargo test -p libremetaverse --test packet_wire
|
||||
cargo test -p libremetaverse-compat-tests --test wire_semantics
|
||||
- name: Regenerate twice without reference checkout access
|
||||
run: |
|
||||
cargo run -p libremetaverse-codegen -- generate
|
||||
cargo run -p libremetaverse-codegen -- generate
|
||||
git diff --exit-code -- codegen/generated crates/libremetaverse/src/packet_catalog.rs
|
||||
- name: Lint native generator
|
||||
run: cargo clippy -p libremetaverse-codegen --all-targets -- -D warnings
|
||||
run: |
|
||||
cargo clippy -p libremetaverse-codegen --all-targets -- -D warnings
|
||||
cargo clippy -p libremetaverse --all-targets --no-deps -- -D warnings
|
||||
- name: Prove normal consumer build does not execute generation
|
||||
run: cargo build -p libremetaverse --locked
|
||||
|
||||
@@ -230,5 +230,9 @@ checked-in catalog covering all 483 packets, 905 blocks, exact field widths,
|
||||
repetition rules, flags, IDs, and frequencies. It supplies the public
|
||||
`PacketType`, deterministic lookup tables, and real default construction and
|
||||
sizing behavior for every mapped packet/block while validating the complete
|
||||
shape against the pinned compiled C# API catalog. Wire encoding and decoding
|
||||
remain the separately gated next codegen issue.
|
||||
shape against the pinned compiled C# API catalog. The generated native packet
|
||||
codec implements the reference header frequencies and IDs, flags, sequences,
|
||||
appended ACKs, little-endian fields, big-endian ports, fixed and length-prefixed
|
||||
data, block repetition, zerocoding, bounded decoding, and MTU packet splitting.
|
||||
Golden-byte tests and generated round trips cover every packet type without
|
||||
requiring the C# toolchain at build or test time.
|
||||
|
||||
5560
api/RUST-MAPPING.tsv
5560
api/RUST-MAPPING.tsv
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.
|
||||
|
||||
| Assembly | Types | Members | Status |
|
||||
|---|---:|---:|---|
|
||||
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 4 types / 3,752 members; remaining surface is callable failure-only shims |
|
||||
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 5 types / 10,469 members; remaining surface is callable failure-only shims |
|
||||
| `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain |
|
||||
| `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain |
|
||||
| `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim |
|
||||
|
||||
@@ -51,6 +51,17 @@ receive the same zero defaults as the golden generator. The generated `Length`
|
||||
metadata intentionally preserves the golden generator's variable-block count
|
||||
semantics.
|
||||
|
||||
The same output contains the native wire implementation for every generated
|
||||
packet and block. It preserves high-, medium-, and low-frequency headers,
|
||||
network-order packet IDs, sequence numbers and appended ACKs; the reference
|
||||
field endianness and widths; one- and two-byte variable prefixes; fixed-field
|
||||
padding and truncation; fixed and variable block counts; normalized quaternion
|
||||
encoding; zerocoding boundaries; and the reference `ToBytesMultiple` splitting
|
||||
rules. Readers use checked bounds and allocation arithmetic and reject unknown
|
||||
IDs or truncated payloads without panicking. Generated tests round-trip all 483
|
||||
packet types and exercise every truncation boundary, while focused fixtures
|
||||
assert exact reference bytes and malformed-input behavior.
|
||||
|
||||
Generation also compares all packet, block, field, field-type, and `PacketType`
|
||||
entries against the pinned compiled public API catalog. This rejects a template
|
||||
parser or naming change even if the emitted Rust would otherwise compile. The
|
||||
|
||||
305
crates/libremetaverse/src/bit_pack.rs
Normal file
305
crates/libremetaverse/src/bit_pack.rs
Normal file
@@ -0,0 +1,305 @@
|
||||
//! Bit-level packing compatible with `LibreMetaverse`'s `BitPack` wire helper.
|
||||
|
||||
#![allow(clippy::missing_errors_doc)] // Public signatures are fixed by the C# compatibility map.
|
||||
#![allow(clippy::cast_precision_loss)] // Fixed-point conversion intentionally mirrors C# casts.
|
||||
#![allow(clippy::cast_possible_truncation)] // Fixed-point packing truncates exactly like C#.
|
||||
#![allow(clippy::cast_sign_loss)] // Signed fixed-point values are biased before conversion.
|
||||
|
||||
use crate::Error;
|
||||
use libremetaverse_types::{Color4, UUID};
|
||||
|
||||
const BITS_PER_BYTE: i32 = 8;
|
||||
|
||||
/// A cursor over a caller-owned byte buffer that reads and writes from the
|
||||
/// most-significant bit to the least-significant bit of each byte.
|
||||
pub struct BitPack {
|
||||
pub data: Vec<u8>,
|
||||
byte_pos: usize,
|
||||
bit_pos: i32,
|
||||
}
|
||||
|
||||
impl BitPack {
|
||||
pub fn new(data: Vec<u8>, pos: i32) -> Result<Self, Error> {
|
||||
let byte_pos = usize::try_from(pos).map_err(|_| Error::IndexOutOfRange)?;
|
||||
if byte_pos > data.len() {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
Ok(Self {
|
||||
data,
|
||||
byte_pos,
|
||||
bit_pos: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pack_bit(&mut self, bit: bool) -> Result<(), Error> {
|
||||
self.pack_bit_array(&[u8::from(bit)], 1)
|
||||
}
|
||||
|
||||
pub fn pack_bits_with_int32_int32(&mut self, data: i32, total_count: i32) -> Result<(), Error> {
|
||||
self.pack_bit_array(&data.to_le_bytes(), total_count)
|
||||
}
|
||||
|
||||
pub fn pack_bits_with_u_int32_int32(
|
||||
&mut self,
|
||||
data: u32,
|
||||
total_count: i32,
|
||||
) -> Result<(), Error> {
|
||||
self.pack_bit_array(&data.to_le_bytes(), total_count)
|
||||
}
|
||||
|
||||
pub fn pack_color(&mut self, data: Color4) -> Result<(), Error> {
|
||||
self.pack_bit_array(&data.get_bytes_with_method()?, 32)
|
||||
}
|
||||
|
||||
pub fn pack_fixed(
|
||||
&mut self,
|
||||
data: f32,
|
||||
is_signed: bool,
|
||||
int_bits: i32,
|
||||
frac_bits: i32,
|
||||
) -> Result<(), Error> {
|
||||
let unsigned_bits = int_bits.checked_add(frac_bits).ok_or(Error::Argument)?;
|
||||
let total_bits = unsigned_bits
|
||||
.checked_add(i32::from(is_signed))
|
||||
.ok_or(Error::Argument)?;
|
||||
let max = checked_power_of_two(int_bits)?;
|
||||
let min = if is_signed { -max } else { 0 };
|
||||
let scale = checked_power_of_two(frac_bits)?;
|
||||
let mut fixed = data.clamp(min as f32, max as f32);
|
||||
if is_signed {
|
||||
fixed += max as f32;
|
||||
}
|
||||
fixed *= scale as f32;
|
||||
|
||||
let storage_bits = match total_bits {
|
||||
..=8 => 8,
|
||||
9..=16 => 16,
|
||||
17..=31 => 32,
|
||||
_ => return Err(Error::Argument),
|
||||
};
|
||||
self.pack_bits_with_u_int32_int32(fixed as u32, storage_bits)
|
||||
}
|
||||
|
||||
pub fn pack_float(&mut self, data: f32) -> Result<(), Error> {
|
||||
self.pack_bit_array(&data.to_le_bytes(), 32)
|
||||
}
|
||||
|
||||
pub fn pack_uuid(&mut self, data: UUID) -> Result<(), Error> {
|
||||
for byte in data.get_bytes()? {
|
||||
self.pack_bit_array(&[byte], 8)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unpack_bits(&mut self, total_count: i32) -> Result<i32, Error> {
|
||||
Ok(self.unpack_bits_to_u32(total_count)?.cast_signed())
|
||||
}
|
||||
|
||||
pub fn unpack_byte(&mut self) -> Result<u8, Error> {
|
||||
u8::try_from(self.unpack_bits_to_u32(8)?).map_err(|_| Error::Argument)
|
||||
}
|
||||
|
||||
pub fn unpack_fixed(
|
||||
&mut self,
|
||||
signed: bool,
|
||||
int_bits: i32,
|
||||
frac_bits: i32,
|
||||
) -> Result<f32, Error> {
|
||||
let unsigned_bits = int_bits.checked_add(frac_bits).ok_or(Error::Argument)?;
|
||||
let total_bits = unsigned_bits
|
||||
.checked_add(i32::from(signed))
|
||||
.ok_or(Error::Argument)?;
|
||||
let max = checked_power_of_two(int_bits)?;
|
||||
let scale = checked_power_of_two(frac_bits)?;
|
||||
let packed = match total_bits {
|
||||
..=8 => u32::from(self.unpack_byte()?),
|
||||
9..=16 => self.unpack_bits_to_u32(16)?,
|
||||
17..=31 => self.unpack_bits_to_u32(32)?,
|
||||
_ => return Ok(0.0),
|
||||
};
|
||||
let mut fixed = packed as f32 / scale as f32;
|
||||
if signed {
|
||||
fixed -= max as f32;
|
||||
}
|
||||
Ok(fixed)
|
||||
}
|
||||
|
||||
pub fn unpack_float(&mut self) -> Result<f32, Error> {
|
||||
Ok(f32::from_bits(self.unpack_bits_to_u32(32)?))
|
||||
}
|
||||
|
||||
pub fn unpack_int(&mut self) -> Result<i32, Error> {
|
||||
self.unpack_bits(32)
|
||||
}
|
||||
|
||||
pub fn unpack_short(&mut self) -> Result<i16, Error> {
|
||||
Ok(u16::try_from(self.unpack_bits_to_u32(16)?)
|
||||
.map_err(|_| Error::Argument)?
|
||||
.cast_signed())
|
||||
}
|
||||
|
||||
pub fn unpack_string(&mut self, size: i32) -> Result<String, Error> {
|
||||
if self.bit_pos != 0 {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
let size = usize::try_from(size).map_err(|_| Error::IndexOutOfRange)?;
|
||||
let end = self
|
||||
.byte_pos
|
||||
.checked_add(size)
|
||||
.ok_or(Error::IndexOutOfRange)?;
|
||||
let bytes = self
|
||||
.data
|
||||
.get(self.byte_pos..end)
|
||||
.ok_or(Error::IndexOutOfRange)?;
|
||||
let value = String::from_utf8_lossy(bytes).into_owned();
|
||||
self.byte_pos = end;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn unpack_u_bits(&mut self, total_count: i32) -> Result<u32, Error> {
|
||||
self.unpack_bits_to_u32(total_count)
|
||||
}
|
||||
|
||||
pub fn unpack_u_int(&mut self) -> Result<u32, Error> {
|
||||
self.unpack_bits_to_u32(32)
|
||||
}
|
||||
|
||||
pub fn unpack_u_short(&mut self) -> Result<u16, Error> {
|
||||
u16::try_from(self.unpack_bits_to_u32(16)?).map_err(|_| Error::Argument)
|
||||
}
|
||||
|
||||
pub fn unpack_uuid(&mut self) -> Result<UUID, Error> {
|
||||
if self.bit_pos != 0 {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
let end = self
|
||||
.byte_pos
|
||||
.checked_add(16)
|
||||
.ok_or(Error::IndexOutOfRange)?;
|
||||
let bytes = self
|
||||
.data
|
||||
.get(self.byte_pos..end)
|
||||
.ok_or(Error::IndexOutOfRange)?;
|
||||
let value = UUID::new_with_bytes_int32(bytes.to_vec(), 0)?;
|
||||
self.byte_pos = end;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn bit_pos(&self) -> i32 {
|
||||
self.bit_pos
|
||||
}
|
||||
|
||||
pub fn set_bit_pos(&mut self, value: i32) {
|
||||
self.bit_pos = value;
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn byte_pos(&self) -> i32 {
|
||||
let position = if self.byte_pos != 0 && self.bit_pos == 0 {
|
||||
self.byte_pos - 1
|
||||
} else {
|
||||
self.byte_pos
|
||||
};
|
||||
i32::try_from(position).unwrap_or(i32::MAX)
|
||||
}
|
||||
|
||||
fn pack_bit_array(&mut self, source: &[u8], mut total_count: i32) -> Result<(), Error> {
|
||||
if total_count <= 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let required = usize::try_from((total_count + 7) / 8).map_err(|_| Error::Argument)?;
|
||||
if required > source.len() {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let mut source_byte = 0_usize;
|
||||
while total_count > 0 {
|
||||
let mut count = total_count.min(BITS_PER_BYTE);
|
||||
total_count -= count;
|
||||
while count > 0 {
|
||||
let target = self
|
||||
.data
|
||||
.get_mut(self.byte_pos)
|
||||
.ok_or(Error::IndexOutOfRange)?;
|
||||
let target_bit = 0x80_u8 >> self.bit_pos;
|
||||
let source_bit = 1_u8 << (count - 1);
|
||||
if source[source_byte] & source_bit != 0 {
|
||||
*target |= target_bit;
|
||||
} else {
|
||||
*target &= !target_bit;
|
||||
}
|
||||
count -= 1;
|
||||
self.bit_pos += 1;
|
||||
if self.bit_pos >= BITS_PER_BYTE {
|
||||
self.bit_pos = 0;
|
||||
self.byte_pos += 1;
|
||||
}
|
||||
}
|
||||
source_byte += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unpack_bits_to_u32(&mut self, mut total_count: i32) -> Result<u32, Error> {
|
||||
if total_count <= 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
if total_count > 32 {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let mut output = [0_u8; 4];
|
||||
let mut output_byte = 0_usize;
|
||||
while total_count > 0 {
|
||||
let mut count = total_count.min(BITS_PER_BYTE);
|
||||
total_count -= count;
|
||||
while count > 0 {
|
||||
output[output_byte] <<= 1;
|
||||
let source = *self.data.get(self.byte_pos).ok_or(Error::IndexOutOfRange)?;
|
||||
if source & (0x80_u8 >> self.bit_pos) != 0 {
|
||||
output[output_byte] += 1;
|
||||
}
|
||||
self.bit_pos += 1;
|
||||
count -= 1;
|
||||
if self.bit_pos >= BITS_PER_BYTE {
|
||||
self.bit_pos = 0;
|
||||
self.byte_pos += 1;
|
||||
}
|
||||
}
|
||||
output_byte += 1;
|
||||
}
|
||||
Ok(u32::from_le_bytes(output))
|
||||
}
|
||||
}
|
||||
|
||||
fn checked_power_of_two(bits: i32) -> Result<i32, Error> {
|
||||
let bits = u32::try_from(bits).map_err(|_| Error::Argument)?;
|
||||
1_i32.checked_shl(bits).ok_or(Error::Argument)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::BitPack;
|
||||
use crate::Error;
|
||||
|
||||
#[test]
|
||||
fn reports_bounds_errors_without_panicking() {
|
||||
let mut writer = BitPack::new(vec![], 0).unwrap();
|
||||
assert_eq!(writer.pack_bit(true), Err(Error::IndexOutOfRange));
|
||||
|
||||
let mut reader = BitPack::new(vec![0], 0).unwrap();
|
||||
assert_eq!(reader.unpack_u_bits(33), Err(Error::Argument));
|
||||
assert_eq!(reader.unpack_u_bits(16), Err(Error::IndexOutOfRange));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_position_matches_completed_byte_semantics() {
|
||||
let mut bits = BitPack::new(vec![0; 2], 0).unwrap();
|
||||
bits.pack_bit(true).unwrap();
|
||||
assert_eq!(bits.byte_pos(), 0);
|
||||
for _ in 1..8 {
|
||||
bits.pack_bit(false).unwrap();
|
||||
}
|
||||
assert_eq!(bits.byte_pos(), 0);
|
||||
assert_eq!(bits.bit_pos(), 0);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,13 @@
|
||||
|
||||
extern crate self as libremetaverse;
|
||||
|
||||
mod bit_pack;
|
||||
mod generated;
|
||||
mod j2k;
|
||||
mod message_codec;
|
||||
#[rustfmt::skip]
|
||||
pub mod packet_catalog;
|
||||
mod packet_wire;
|
||||
mod targa;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
2196
crates/libremetaverse/src/message_codec.rs
Normal file
2196
crates/libremetaverse/src/message_codec.rs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
740
crates/libremetaverse/src/packet_wire.rs
Normal file
740
crates/libremetaverse/src/packet_wire.rs
Normal file
@@ -0,0 +1,740 @@
|
||||
//! Bounded primitives shared by the generated UDP packet codecs.
|
||||
|
||||
use crate::packets::{Header, Packet};
|
||||
use crate::{Error, Helpers, PacketFrequency};
|
||||
use libremetaverse_types::{Quaternion, UUID, Vector3, Vector3d, Vector4};
|
||||
|
||||
/// A generous protocol safety ceiling. UDP packets normally remain below the
|
||||
/// 1200-byte MTU, but callers may serialize an unsplit packet for diagnostics.
|
||||
pub(crate) const MAX_PACKET_BYTES: usize = 16 * 1024 * 1024;
|
||||
|
||||
const HEADER_PREFIX_BYTES: usize = 6;
|
||||
|
||||
const fn parse(position: usize, context: &'static str) -> Error {
|
||||
Error::Parse { position, context }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WireReader<'a> {
|
||||
bytes: &'a [u8],
|
||||
position: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl<'a> WireReader<'a> {
|
||||
pub(crate) fn new(bytes: &'a [u8], position: usize, end: usize) -> Result<Self, Error> {
|
||||
if bytes.len() > MAX_PACKET_BYTES || position > end || end > bytes.len() {
|
||||
return Err(parse(position.min(bytes.len()), "packet bounds"));
|
||||
}
|
||||
Ok(Self {
|
||||
bytes,
|
||||
position,
|
||||
end,
|
||||
})
|
||||
}
|
||||
|
||||
fn take(&mut self, length: usize, context: &'static str) -> Result<&'a [u8], Error> {
|
||||
let end = self
|
||||
.position
|
||||
.checked_add(length)
|
||||
.ok_or_else(|| parse(self.position, context))?;
|
||||
if end > self.end {
|
||||
return Err(parse(self.position, context));
|
||||
}
|
||||
let output = &self.bytes[self.position..end];
|
||||
self.position = end;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub(crate) fn finish(self, position: &mut i32) -> Result<(), Error> {
|
||||
if self.position != self.end {
|
||||
return Err(parse(self.position, "trailing packet payload"));
|
||||
}
|
||||
*position = i32::try_from(self.position).map_err(|_| Error::Argument)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn commit_position(&self, position: &mut i32) -> Result<(), Error> {
|
||||
*position = i32::try_from(self.position).map_err(|_| Error::Argument)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn read_u8(&mut self) -> Result<u8, Error> {
|
||||
Ok(self.take(1, "truncated u8")?[0])
|
||||
}
|
||||
|
||||
pub(crate) fn read_i8(&mut self) -> Result<i8, Error> {
|
||||
Ok(self.read_u8()?.cast_signed())
|
||||
}
|
||||
|
||||
pub(crate) fn read_u16_le(&mut self) -> Result<u16, Error> {
|
||||
Ok(u16::from_le_bytes(self.read_array("truncated u16")?))
|
||||
}
|
||||
|
||||
pub(crate) fn read_u16_be(&mut self) -> Result<u16, Error> {
|
||||
Ok(u16::from_be_bytes(self.read_array("truncated IP port")?))
|
||||
}
|
||||
|
||||
pub(crate) fn read_i16_le(&mut self) -> Result<i16, Error> {
|
||||
Ok(i16::from_le_bytes(self.read_array("truncated i16")?))
|
||||
}
|
||||
|
||||
pub(crate) fn read_u32_le(&mut self) -> Result<u32, Error> {
|
||||
Ok(u32::from_le_bytes(self.read_array("truncated u32")?))
|
||||
}
|
||||
|
||||
pub(crate) fn read_i32_le(&mut self) -> Result<i32, Error> {
|
||||
Ok(i32::from_le_bytes(self.read_array("truncated i32")?))
|
||||
}
|
||||
|
||||
pub(crate) fn read_u64_le(&mut self) -> Result<u64, Error> {
|
||||
Ok(u64::from_le_bytes(self.read_array("truncated u64")?))
|
||||
}
|
||||
|
||||
pub(crate) fn read_f32_le(&mut self) -> Result<f32, Error> {
|
||||
Ok(f32::from_le_bytes(self.read_array("truncated f32")?))
|
||||
}
|
||||
|
||||
pub(crate) fn read_f64_le(&mut self) -> Result<f64, Error> {
|
||||
Ok(f64::from_le_bytes(self.read_array("truncated f64")?))
|
||||
}
|
||||
|
||||
fn read_array<const N: usize>(&mut self, context: &'static str) -> Result<[u8; N], Error> {
|
||||
self.take(N, context)?
|
||||
.try_into()
|
||||
.map_err(|_| parse(self.position, context))
|
||||
}
|
||||
|
||||
pub(crate) fn read_bytes(
|
||||
&mut self,
|
||||
length: usize,
|
||||
context: &'static str,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let source = self.take(length, context)?;
|
||||
let mut output = Vec::new();
|
||||
output
|
||||
.try_reserve_exact(length)
|
||||
.map_err(|_| Error::InvalidOperation)?;
|
||||
output.extend_from_slice(source);
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub(crate) fn read_variable(&mut self, prefix_bytes: usize) -> Result<Vec<u8>, Error> {
|
||||
let length = match prefix_bytes {
|
||||
1 => usize::from(self.read_u8()?),
|
||||
2 => usize::from(self.read_u16_le()?),
|
||||
_ => return Err(Error::InvalidOperation),
|
||||
};
|
||||
self.read_bytes(length, "truncated variable packet field")
|
||||
}
|
||||
|
||||
pub(crate) fn read_uuid(&mut self) -> Result<UUID, Error> {
|
||||
UUID::from_bytes(self.read_bytes(16, "truncated UUID")?, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn read_vector3(&mut self) -> Result<Vector3, Error> {
|
||||
Vector3::from_bytes(self.read_bytes(12, "truncated Vector3")?, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn read_vector3d(&mut self) -> Result<Vector3d, Error> {
|
||||
let mut value = Vector3d::zero();
|
||||
value.from_bytes(self.read_bytes(24, "truncated Vector3d")?, 0)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub(crate) fn read_vector4(&mut self) -> Result<Vector4, Error> {
|
||||
let mut value = Vector4::zero();
|
||||
value.from_bytes(self.read_bytes(16, "truncated Vector4")?, 0)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub(crate) fn read_quaternion(&mut self) -> Result<Quaternion, Error> {
|
||||
Quaternion::from_bytes(self.read_bytes(12, "truncated Quaternion")?, 0, true)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct WireWriter {
|
||||
bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl WireWriter {
|
||||
pub(crate) fn with_capacity(capacity: usize) -> Result<Self, Error> {
|
||||
if capacity > MAX_PACKET_BYTES {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let mut bytes = Vec::new();
|
||||
bytes
|
||||
.try_reserve_exact(capacity)
|
||||
.map_err(|_| Error::InvalidOperation)?;
|
||||
Ok(Self { bytes })
|
||||
}
|
||||
|
||||
pub(crate) fn into_inner(self) -> Vec<u8> {
|
||||
self.bytes
|
||||
}
|
||||
|
||||
fn reserve(&mut self, additional: usize) -> Result<(), Error> {
|
||||
let length = self
|
||||
.bytes
|
||||
.len()
|
||||
.checked_add(additional)
|
||||
.ok_or(Error::Argument)?;
|
||||
if length > MAX_PACKET_BYTES {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
self.bytes
|
||||
.try_reserve(additional)
|
||||
.map_err(|_| Error::InvalidOperation)
|
||||
}
|
||||
|
||||
pub(crate) fn write_bytes(&mut self, value: &[u8]) -> Result<(), Error> {
|
||||
self.reserve(value.len())?;
|
||||
self.bytes.extend_from_slice(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn write_u8(&mut self, value: u8) -> Result<(), Error> {
|
||||
self.write_bytes(&[value])
|
||||
}
|
||||
|
||||
pub(crate) fn write_i8(&mut self, value: i8) -> Result<(), Error> {
|
||||
self.write_u8(value.cast_unsigned())
|
||||
}
|
||||
|
||||
pub(crate) fn write_u16_le(&mut self, value: u16) -> Result<(), Error> {
|
||||
self.write_bytes(&value.to_le_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn write_u16_be(&mut self, value: u16) -> Result<(), Error> {
|
||||
self.write_bytes(&value.to_be_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn write_i16_le(&mut self, value: i16) -> Result<(), Error> {
|
||||
self.write_bytes(&value.to_le_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn write_u32_le(&mut self, value: u32) -> Result<(), Error> {
|
||||
self.write_bytes(&value.to_le_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn write_i32_le(&mut self, value: i32) -> Result<(), Error> {
|
||||
self.write_bytes(&value.to_le_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn write_u64_le(&mut self, value: u64) -> Result<(), Error> {
|
||||
self.write_bytes(&value.to_le_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn write_f32_le(&mut self, value: f32) -> Result<(), Error> {
|
||||
self.write_bytes(&value.to_le_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn write_f64_le(&mut self, value: f64) -> Result<(), Error> {
|
||||
self.write_bytes(&value.to_le_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn write_fixed(&mut self, value: &[u8], length: usize) -> Result<(), Error> {
|
||||
self.reserve(length)?;
|
||||
let copied = value.len().min(length);
|
||||
self.bytes.extend_from_slice(&value[..copied]);
|
||||
self.bytes.resize(self.bytes.len() + length - copied, 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn write_variable(
|
||||
&mut self,
|
||||
value: &[u8],
|
||||
prefix_bytes: usize,
|
||||
) -> Result<(), Error> {
|
||||
match prefix_bytes {
|
||||
1 => self.write_u8(u8::try_from(value.len()).map_err(|_| Error::Argument)?)?,
|
||||
2 => self.write_u16_le(u16::try_from(value.len()).map_err(|_| Error::Argument)?)?,
|
||||
_ => return Err(Error::InvalidOperation),
|
||||
}
|
||||
self.write_bytes(value)
|
||||
}
|
||||
|
||||
pub(crate) fn write_uuid(&mut self, value: &UUID) -> Result<(), Error> {
|
||||
let mut bytes = [0_u8; 16];
|
||||
value.to_bytes(&mut bytes, 0)?;
|
||||
self.write_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub(crate) fn write_vector3(&mut self, value: &Vector3) -> Result<(), Error> {
|
||||
let mut bytes = [0_u8; 12];
|
||||
value.to_bytes(&mut bytes, 0)?;
|
||||
self.write_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub(crate) fn write_vector3d(&mut self, value: &Vector3d) -> Result<(), Error> {
|
||||
let mut bytes = [0_u8; 24];
|
||||
value.to_bytes(&mut bytes, 0)?;
|
||||
self.write_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub(crate) fn write_vector4(&mut self, value: &Vector4) -> Result<(), Error> {
|
||||
let mut bytes = [0_u8; 16];
|
||||
value.to_bytes(&mut bytes, 0)?;
|
||||
self.write_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub(crate) fn write_quaternion(&mut self, value: &Quaternion) -> Result<(), Error> {
|
||||
let mut bytes = [0_u8; 12];
|
||||
value.to_bytes(&mut bytes, 0)?;
|
||||
self.write_bytes(&bytes)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn header_length(frequency: PacketFrequency) -> usize {
|
||||
match frequency {
|
||||
PacketFrequency::High => 7,
|
||||
PacketFrequency::Medium => 8,
|
||||
PacketFrequency::Low => 10,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clone_header(header: &Header) -> Header {
|
||||
Header {
|
||||
ack_list: header.ack_list.clone(),
|
||||
appended_acks: header.appended_acks,
|
||||
frequency: header.frequency,
|
||||
id: header.id,
|
||||
reliable: header.reliable,
|
||||
resent: header.resent,
|
||||
sequence: header.sequence,
|
||||
zerocoded: header.zerocoded,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn encode_header(header: &Header, writer: &mut WireWriter) -> Result<(), Error> {
|
||||
let mut flags = 0_u8;
|
||||
if header.reliable {
|
||||
flags |= Helpers::MSG_RELIABLE;
|
||||
}
|
||||
if header.resent {
|
||||
flags |= Helpers::MSG_RESENT;
|
||||
}
|
||||
if header.zerocoded {
|
||||
flags |= Helpers::MSG_ZEROCODED;
|
||||
}
|
||||
if header.appended_acks {
|
||||
flags |= Helpers::MSG_APPENDED_ACKS;
|
||||
}
|
||||
writer.write_u8(flags)?;
|
||||
writer.write_bytes(&header.sequence.to_be_bytes())?;
|
||||
writer.write_u8(0)?;
|
||||
match header.frequency {
|
||||
PacketFrequency::High => {
|
||||
writer.write_u8(u8::try_from(header.id).map_err(|_| Error::Argument)?)?;
|
||||
}
|
||||
PacketFrequency::Medium => {
|
||||
writer.write_u8(0xff)?;
|
||||
writer.write_u8(u8::try_from(header.id).map_err(|_| Error::Argument)?)?;
|
||||
}
|
||||
PacketFrequency::Low => {
|
||||
writer.write_bytes(&[0xff, 0xff])?;
|
||||
writer.write_bytes(&header.id.to_be_bytes())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn encode_acks(header: &Header, writer: &mut WireWriter) -> Result<(), Error> {
|
||||
let Some(acks) = &header.ack_list else {
|
||||
return Ok(());
|
||||
};
|
||||
if acks.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let count = u8::try_from(acks.len()).map_err(|_| Error::Argument)?;
|
||||
for ack in acks {
|
||||
writer.write_bytes(&ack.to_be_bytes())?;
|
||||
}
|
||||
writer.write_u8(count)
|
||||
}
|
||||
|
||||
pub(crate) fn ack_length(header: &Header) -> Result<usize, Error> {
|
||||
match &header.ack_list {
|
||||
Some(acks) if !acks.is_empty() => {
|
||||
let _ = u8::try_from(acks.len()).map_err(|_| Error::Argument)?;
|
||||
acks.len()
|
||||
.checked_mul(4)
|
||||
.and_then(|length| length.checked_add(1))
|
||||
.ok_or(Error::Argument)
|
||||
}
|
||||
_ => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn decode_header(
|
||||
bytes: &[u8],
|
||||
position: &mut i32,
|
||||
packet_end: &mut i32,
|
||||
) -> Result<Header, Error> {
|
||||
if bytes.len() > MAX_PACKET_BYTES || *position < 0 || *packet_end < 0 {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let start = usize::try_from(*position).map_err(|_| Error::Argument)?;
|
||||
let mut end = usize::try_from(*packet_end).map_err(|_| Error::Argument)?;
|
||||
if end >= bytes.len() || start > end || end - start + 1 < 7 {
|
||||
return Err(parse(start.min(bytes.len()), "truncated packet header"));
|
||||
}
|
||||
let flags = bytes[start];
|
||||
let appended_acks = flags & Helpers::MSG_APPENDED_ACKS != 0;
|
||||
let reliable = flags & Helpers::MSG_RELIABLE != 0;
|
||||
let resent = flags & Helpers::MSG_RESENT != 0;
|
||||
let zerocoded = flags & Helpers::MSG_ZEROCODED != 0;
|
||||
let sequence = u32::from_be_bytes(
|
||||
bytes
|
||||
.get(start + 1..start + 5)
|
||||
.ok_or_else(|| parse(start, "truncated packet sequence"))?
|
||||
.try_into()
|
||||
.map_err(|_| parse(start, "truncated packet sequence"))?,
|
||||
);
|
||||
|
||||
let marker = *bytes
|
||||
.get(start + 6)
|
||||
.ok_or_else(|| parse(start, "truncated packet ID"))?;
|
||||
let (frequency, id, logical_header_length) = if marker == 0xff {
|
||||
let second = *bytes
|
||||
.get(start + 7)
|
||||
.ok_or_else(|| parse(start + 7, "truncated packet ID"))?;
|
||||
if second == 0xff {
|
||||
let high = *bytes
|
||||
.get(start + 8)
|
||||
.ok_or_else(|| parse(start + 8, "truncated packet ID"))?;
|
||||
let id = if zerocoded && high == 0 {
|
||||
let count = *bytes
|
||||
.get(start + 9)
|
||||
.ok_or_else(|| parse(start + 9, "truncated zerocoded packet ID"))?;
|
||||
if count == 0 {
|
||||
return Err(parse(start + 9, "zero-length zerocode run"));
|
||||
}
|
||||
u16::from(
|
||||
*bytes
|
||||
.get(start + 10)
|
||||
.ok_or_else(|| parse(start + 10, "truncated zerocoded packet ID"))?,
|
||||
)
|
||||
} else {
|
||||
let low = *bytes
|
||||
.get(start + 9)
|
||||
.ok_or_else(|| parse(start + 9, "truncated packet ID"))?;
|
||||
(u16::from(high) << 8) | u16::from(low)
|
||||
};
|
||||
(PacketFrequency::Low, id, 10)
|
||||
} else {
|
||||
(PacketFrequency::Medium, u16::from(second), 8)
|
||||
}
|
||||
} else {
|
||||
(PacketFrequency::High, u16::from(marker), 7)
|
||||
};
|
||||
|
||||
let mut ack_list = None;
|
||||
if appended_acks {
|
||||
let count = usize::from(bytes[end]);
|
||||
let ack_bytes = count.checked_mul(4).ok_or(Error::Argument)?;
|
||||
let total = ack_bytes.checked_add(1).ok_or(Error::Argument)?;
|
||||
if total > end - start + 1 || end + 1 - total < start + logical_header_length {
|
||||
return Err(parse(end, "truncated appended ACK list"));
|
||||
}
|
||||
let ack_start = end + 1 - total;
|
||||
let mut acks = Vec::new();
|
||||
acks.try_reserve_exact(count)
|
||||
.map_err(|_| Error::InvalidOperation)?;
|
||||
for chunk in bytes[ack_start..ack_start + ack_bytes].chunks_exact(4) {
|
||||
acks.push(u32::from_be_bytes(
|
||||
chunk
|
||||
.try_into()
|
||||
.map_err(|_| parse(ack_start, "truncated appended ACK"))?,
|
||||
));
|
||||
}
|
||||
ack_list = Some(acks);
|
||||
end = ack_start
|
||||
.checked_sub(1)
|
||||
.ok_or_else(|| parse(ack_start, "invalid appended ACK boundary"))?;
|
||||
}
|
||||
|
||||
*position = i32::try_from(start + logical_header_length).map_err(|_| Error::Argument)?;
|
||||
*packet_end = i32::try_from(end).map_err(|_| Error::Argument)?;
|
||||
Ok(Header {
|
||||
ack_list,
|
||||
appended_acks,
|
||||
frequency,
|
||||
id,
|
||||
reliable,
|
||||
resent,
|
||||
sequence,
|
||||
zerocoded,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn header_to_slice(
|
||||
header: &Header,
|
||||
bytes: &mut [u8],
|
||||
position: &mut i32,
|
||||
) -> Result<(), Error> {
|
||||
if *position < 0 {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let start = usize::try_from(*position).map_err(|_| Error::Argument)?;
|
||||
let mut writer = WireWriter::with_capacity(header_length(header.frequency))?;
|
||||
encode_header(header, &mut writer)?;
|
||||
let encoded = writer.into_inner();
|
||||
let end = start.checked_add(encoded.len()).ok_or(Error::Argument)?;
|
||||
if end > bytes.len() {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
bytes[start..end].copy_from_slice(&encoded);
|
||||
*position = i32::try_from(end).map_err(|_| Error::Argument)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn acks_to_slice(
|
||||
header: &Header,
|
||||
bytes: &mut [u8],
|
||||
position: &mut i32,
|
||||
) -> Result<(), Error> {
|
||||
if *position < 0 {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let start = usize::try_from(*position).map_err(|_| Error::Argument)?;
|
||||
let mut writer = WireWriter::with_capacity(ack_length(header)?)?;
|
||||
encode_acks(header, &mut writer)?;
|
||||
let encoded = writer.into_inner();
|
||||
let end = start.checked_add(encoded.len()).ok_or(Error::Argument)?;
|
||||
if end > bytes.len() {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
bytes[start..end].copy_from_slice(&encoded);
|
||||
*position = i32::try_from(end).map_err(|_| Error::Argument)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn zero_decode(
|
||||
src: Option<&[u8]>,
|
||||
source_length: i32,
|
||||
dest: Option<&mut [u8]>,
|
||||
) -> Result<i32, Error> {
|
||||
let src = src.ok_or(Error::ArgumentNull)?;
|
||||
let dest = dest.ok_or(Error::ArgumentNull)?;
|
||||
if source_length < i32::try_from(HEADER_PREFIX_BYTES).unwrap_or(i32::MAX) {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let source_length = usize::try_from(source_length).map_err(|_| Error::Argument)?;
|
||||
if source_length > src.len() {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
if dest.len() < HEADER_PREFIX_BYTES {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
dest[..HEADER_PREFIX_BYTES].copy_from_slice(&src[..HEADER_PREFIX_BYTES]);
|
||||
let mut source = HEADER_PREFIX_BYTES;
|
||||
let mut target = HEADER_PREFIX_BYTES;
|
||||
while source < source_length {
|
||||
if src[source] == 0 {
|
||||
let trailing = source + 1 >= source_length;
|
||||
let count = if trailing {
|
||||
1
|
||||
} else {
|
||||
usize::from(src[source + 1])
|
||||
};
|
||||
let end = target.checked_add(count).ok_or(Error::IndexOutOfRange)?;
|
||||
if end > dest.len() {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
dest[target..end].fill(0);
|
||||
target = end;
|
||||
source += if trailing { 1 } else { 2 };
|
||||
} else {
|
||||
if target >= dest.len() {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
dest[target] = src[source];
|
||||
target += 1;
|
||||
source += 1;
|
||||
}
|
||||
}
|
||||
i32::try_from(target).map_err(|_| Error::Argument)
|
||||
}
|
||||
|
||||
pub(crate) fn zero_encode(
|
||||
src: Option<&[u8]>,
|
||||
source_length: i32,
|
||||
dest: Option<&mut [u8]>,
|
||||
) -> Result<i32, Error> {
|
||||
let src = src.ok_or(Error::ArgumentNull)?;
|
||||
let dest = dest.ok_or(Error::ArgumentNull)?;
|
||||
if source_length < i32::try_from(HEADER_PREFIX_BYTES).unwrap_or(i32::MAX) {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let source_length = usize::try_from(source_length).map_err(|_| Error::Argument)?;
|
||||
if source_length > src.len() {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
if dest.len() < HEADER_PREFIX_BYTES {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
dest[..HEADER_PREFIX_BYTES].copy_from_slice(&src[..HEADER_PREFIX_BYTES]);
|
||||
|
||||
let body_end = if src[0] & Helpers::MSG_APPENDED_ACKS == 0 {
|
||||
source_length
|
||||
} else {
|
||||
let count = usize::from(src[source_length - 1]);
|
||||
let suffix = count
|
||||
.checked_mul(4)
|
||||
.and_then(|length| length.checked_add(1))
|
||||
.ok_or(Error::Argument)?;
|
||||
if suffix > source_length - HEADER_PREFIX_BYTES {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
source_length - suffix
|
||||
};
|
||||
|
||||
let mut source = HEADER_PREFIX_BYTES;
|
||||
let mut target = HEADER_PREFIX_BYTES;
|
||||
while source < body_end {
|
||||
if src[source] != 0 {
|
||||
if target >= dest.len() {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
dest[target] = src[source];
|
||||
target += 1;
|
||||
source += 1;
|
||||
continue;
|
||||
}
|
||||
let run_start = source;
|
||||
while source < body_end && src[source] == 0 {
|
||||
source += 1;
|
||||
}
|
||||
let mut remaining = source - run_start;
|
||||
while remaining != 0 {
|
||||
if target.checked_add(2).is_none_or(|end| end > dest.len()) {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
let count = remaining.min(usize::from(u8::MAX));
|
||||
dest[target] = 0;
|
||||
dest[target + 1] = u8::try_from(count).map_err(|_| Error::Argument)?;
|
||||
target += 2;
|
||||
remaining -= count;
|
||||
}
|
||||
}
|
||||
while source < source_length {
|
||||
if target >= dest.len() {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
dest[target] = src[source];
|
||||
target += 1;
|
||||
source += 1;
|
||||
}
|
||||
i32::try_from(target).map_err(|_| Error::Argument)
|
||||
}
|
||||
|
||||
pub(crate) fn build_packet_from_bytes(
|
||||
packet_buffer: &[u8],
|
||||
packet_end: &mut i32,
|
||||
zero_buffer: &mut [u8],
|
||||
) -> Result<Packet, Error> {
|
||||
let mut position = 0_i32;
|
||||
let header = decode_header(packet_buffer, &mut position, packet_end)?;
|
||||
let packet_type = crate::packet_catalog::packet_type(header.id, header.frequency);
|
||||
let descriptor = crate::packet_catalog::descriptor_by_type(packet_type)
|
||||
.ok_or_else(|| parse(usize::try_from(position).unwrap_or(0), "unknown packet ID"))?;
|
||||
if header.zerocoded {
|
||||
let decoded_length = zero_decode(
|
||||
Some(packet_buffer),
|
||||
packet_end.checked_add(1).ok_or(Error::Argument)?,
|
||||
Some(zero_buffer),
|
||||
)?;
|
||||
*packet_end = decoded_length.checked_sub(1).ok_or(Error::Argument)?;
|
||||
let decoded_length = usize::try_from(decoded_length).map_err(|_| Error::Argument)?;
|
||||
crate::packet_catalog::validate_packet_payload(
|
||||
packet_type,
|
||||
clone_header(&header),
|
||||
&zero_buffer[..decoded_length],
|
||||
&mut position,
|
||||
packet_end,
|
||||
)?;
|
||||
} else {
|
||||
crate::packet_catalog::validate_packet_payload(
|
||||
packet_type,
|
||||
clone_header(&header),
|
||||
packet_buffer,
|
||||
&mut position,
|
||||
packet_end,
|
||||
)?;
|
||||
}
|
||||
Ok(Packet {
|
||||
has_variable_blocks: descriptor.blocks.iter().any(|block| {
|
||||
matches!(
|
||||
block.repetition,
|
||||
crate::packet_catalog::BlockRepetition::Variable
|
||||
)
|
||||
}),
|
||||
header,
|
||||
type_: packet_type,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{WireReader, WireWriter};
|
||||
|
||||
#[test]
|
||||
fn scalar_endianness_matches_the_packet_template_protocol() {
|
||||
let mut writer = WireWriter::with_capacity(35).expect("writer");
|
||||
writer.write_u8(0x81).unwrap();
|
||||
writer.write_i8(-2).unwrap();
|
||||
writer.write_u16_le(0x1234).unwrap();
|
||||
writer.write_u16_be(0x5678).unwrap();
|
||||
writer.write_i16_le(-0x1234).unwrap();
|
||||
writer.write_u32_le(0x1122_3344).unwrap();
|
||||
writer.write_i32_le(-0x1122_3344).unwrap();
|
||||
writer.write_u64_le(0x0102_0304_0506_0708).unwrap();
|
||||
writer.write_f32_le(1.5).unwrap();
|
||||
writer.write_f64_le(-2.25).unwrap();
|
||||
let bytes = writer.into_inner();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
[
|
||||
0x81, 0xfe, 0x34, 0x12, 0x56, 0x78, 0xcc, 0xed, 0x44, 0x33, 0x22, 0x11, 0xbc, 0xcc,
|
||||
0xdd, 0xee, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0xc0, 0x3f, 0, 0, 0, 0, 0, 0, 2, 0xc0,
|
||||
]
|
||||
);
|
||||
|
||||
let mut reader = WireReader::new(&bytes, 0, bytes.len()).unwrap();
|
||||
assert_eq!(reader.read_u8().unwrap(), 0x81);
|
||||
assert_eq!(reader.read_i8().unwrap(), -2);
|
||||
assert_eq!(reader.read_u16_le().unwrap(), 0x1234);
|
||||
assert_eq!(reader.read_u16_be().unwrap(), 0x5678);
|
||||
assert_eq!(reader.read_i16_le().unwrap(), -0x1234);
|
||||
assert_eq!(reader.read_u32_le().unwrap(), 0x1122_3344);
|
||||
assert_eq!(reader.read_i32_le().unwrap(), -0x1122_3344);
|
||||
assert_eq!(reader.read_u64_le().unwrap(), 0x0102_0304_0506_0708);
|
||||
assert_eq!(reader.read_f32_le().unwrap().to_bits(), 1.5_f32.to_bits());
|
||||
assert_eq!(
|
||||
reader.read_f64_le().unwrap().to_bits(),
|
||||
(-2.25_f64).to_bits()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixed_and_variable_fields_enforce_reference_lengths() {
|
||||
let mut writer = WireWriter::with_capacity(32).expect("writer");
|
||||
writer.write_fixed(&[1, 2], 4).unwrap();
|
||||
writer.write_fixed(&[3, 4, 5, 6], 2).unwrap();
|
||||
writer.write_variable(&[7, 8, 9], 1).unwrap();
|
||||
writer.write_variable(&[10, 11], 2).unwrap();
|
||||
let bytes = writer.into_inner();
|
||||
assert_eq!(bytes, [1, 2, 0, 0, 3, 4, 3, 7, 8, 9, 2, 0, 10, 11]);
|
||||
|
||||
let mut reader = WireReader::new(&bytes, 0, bytes.len()).unwrap();
|
||||
assert_eq!(reader.read_bytes(4, "fixed").unwrap(), [1, 2, 0, 0]);
|
||||
assert_eq!(reader.read_bytes(2, "fixed").unwrap(), [3, 4]);
|
||||
assert_eq!(reader.read_variable(1).unwrap(), [7, 8, 9]);
|
||||
assert_eq!(reader.read_variable(2).unwrap(), [10, 11]);
|
||||
|
||||
let mut oversized = WireWriter::with_capacity(300).unwrap();
|
||||
assert!(oversized.write_variable(&[0; 256], 1).is_err());
|
||||
}
|
||||
}
|
||||
178
crates/libremetaverse/tests/packet_wire.rs
Normal file
178
crates/libremetaverse/tests/packet_wire.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
use libremetaverse::packets::{
|
||||
AgentThrottlePacket, Header, Packet, PacketType, UseCircuitCodePacket,
|
||||
};
|
||||
use libremetaverse::{Error, Helpers, PacketFrequency};
|
||||
use libremetaverse_types::UUID;
|
||||
|
||||
fn header(frequency: PacketFrequency, id: u16) -> Header {
|
||||
Header {
|
||||
ack_list: None,
|
||||
appended_acks: false,
|
||||
frequency,
|
||||
id,
|
||||
reliable: true,
|
||||
resent: false,
|
||||
sequence: 0x0102_0304,
|
||||
zerocoded: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn header_bytes(header: &Header) -> Vec<u8> {
|
||||
let length = match header.frequency {
|
||||
PacketFrequency::High => 7,
|
||||
PacketFrequency::Medium => 8,
|
||||
PacketFrequency::Low => 10,
|
||||
};
|
||||
let mut bytes = vec![0_u8; length];
|
||||
let mut position = 0;
|
||||
header
|
||||
.to_bytes(&mut bytes, &mut position)
|
||||
.expect("encode header");
|
||||
assert_eq!(usize::try_from(position).unwrap(), bytes.len());
|
||||
bytes
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_frequencies_match_the_reference_wire_bytes() {
|
||||
// Golden source: codegen/inputs/message_template.msg frequency rules and
|
||||
// LibreMetaverse.Tests/PacketTests.cs::HeaderFlags (translated in
|
||||
// tests/compat/tests/wire_semantics.rs::packet_header_flags).
|
||||
assert_eq!(
|
||||
header_bytes(&header(PacketFrequency::High, 0x2a)),
|
||||
[0x40, 1, 2, 3, 4, 0, 0x2a]
|
||||
);
|
||||
assert_eq!(
|
||||
header_bytes(&header(PacketFrequency::Medium, 0x2a)),
|
||||
[0x40, 1, 2, 3, 4, 0, 0xff, 0x2a]
|
||||
);
|
||||
assert_eq!(
|
||||
header_bytes(&header(PacketFrequency::Low, 0x1234)),
|
||||
[0x40, 1, 2, 3, 4, 0, 0xff, 0xff, 0x12, 0x34]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appended_acks_are_big_endian_and_removed_from_the_payload_boundary() {
|
||||
let mut value = header(PacketFrequency::High, 0x2a);
|
||||
value.appended_acks = true;
|
||||
value.ack_list = Some(vec![0x0102_0304, 0xa0b0_c0d0]);
|
||||
let mut bytes = header_bytes(&value);
|
||||
let mut ack_bytes = [0_u8; 9];
|
||||
let mut ack_position = 0;
|
||||
value
|
||||
.acks_to_bytes(&mut ack_bytes, &mut ack_position)
|
||||
.expect("encode ACKs");
|
||||
assert_eq!(ack_bytes, [1, 2, 3, 4, 0xa0, 0xb0, 0xc0, 0xd0, 2]);
|
||||
bytes.extend_from_slice(&ack_bytes);
|
||||
|
||||
let mut position = 0;
|
||||
let mut packet_end = i32::try_from(bytes.len()).unwrap() - 1;
|
||||
let decoded = Header::build_header(bytes, &mut position, &mut packet_end).expect("decode");
|
||||
assert!(decoded.appended_acks);
|
||||
assert_eq!(decoded.ack_list, value.ack_list);
|
||||
assert_eq!(position, 7);
|
||||
assert_eq!(packet_end, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_circuit_code_matches_the_reference_golden_packet() {
|
||||
// Golden schema: codegen/inputs/message_template.msg::UseCircuitCode
|
||||
// (Low 3, CircuitCode/U32 + SessionID/LLUUID + ID/LLUUID). Header behavior
|
||||
// is linked to the translated HeaderFlags test above.
|
||||
let session = (0_u8..16).collect::<Vec<_>>();
|
||||
let agent = (16_u8..32).collect::<Vec<_>>();
|
||||
let mut packet = UseCircuitCodePacket::new_with_constructor().expect("packet constructor");
|
||||
packet.circuit_code.code = 0x1122_3344;
|
||||
packet.circuit_code.session_id =
|
||||
UUID::new_with_bytes_int32(session.clone(), 0).expect("session UUID");
|
||||
packet.circuit_code.id = UUID::new_with_bytes_int32(agent.clone(), 0).expect("agent UUID");
|
||||
|
||||
let encoded = packet.to_bytes_with_method().expect("encode packet");
|
||||
let mut expected = vec![
|
||||
0x40, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 3, 0x44, 0x33, 0x22, 0x11,
|
||||
];
|
||||
expected.extend_from_slice(&session);
|
||||
expected.extend_from_slice(&agent);
|
||||
assert_eq!(encoded, expected);
|
||||
|
||||
let mut position = 0;
|
||||
let decoded =
|
||||
UseCircuitCodePacket::new_with_bytes_int32(encoded.clone(), &mut position).expect("decode");
|
||||
assert_eq!(usize::try_from(position).unwrap(), encoded.len());
|
||||
assert_eq!(decoded.circuit_code.code, 0x1122_3344);
|
||||
assert_eq!(
|
||||
decoded.circuit_code.session_id.get_bytes().unwrap(),
|
||||
session
|
||||
);
|
||||
assert_eq!(decoded.circuit_code.id.get_bytes().unwrap(), agent);
|
||||
assert_eq!(decoded.to_bytes_with_method().unwrap(), encoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_and_unknown_packets_return_errors_without_panicking() {
|
||||
let valid = [0x40, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 3];
|
||||
for length in 0..valid.len() {
|
||||
let bytes = valid[..length].to_vec();
|
||||
let outcome = std::panic::catch_unwind(|| {
|
||||
let mut end = i32::try_from(bytes.len()).unwrap() - 1;
|
||||
Packet::build_packet_with_bytes_int32_bytes(bytes.clone(), &mut end, vec![0; 64])
|
||||
});
|
||||
assert!(outcome.is_ok(), "truncation at {length} panicked");
|
||||
assert!(
|
||||
outcome.unwrap().is_err(),
|
||||
"truncation at {length} was accepted"
|
||||
);
|
||||
}
|
||||
|
||||
let missing_payload = valid.to_vec();
|
||||
let mut end = i32::try_from(missing_payload.len()).unwrap() - 1;
|
||||
assert!(
|
||||
Packet::build_packet_with_bytes_int32_bytes(missing_payload, &mut end, vec![0; 64])
|
||||
.is_err()
|
||||
);
|
||||
|
||||
let unknown = vec![0x40, 0, 0, 0, 0, 0, 0xfe];
|
||||
let mut end = i32::try_from(unknown.len()).unwrap() - 1;
|
||||
let Err(error) = Packet::build_packet_with_bytes_int32_bytes(unknown, &mut end, vec![0; 64])
|
||||
else {
|
||||
panic!("unknown packet ID was accepted");
|
||||
};
|
||||
assert!(matches!(error, Error::Parse { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packet_factory_decodes_zerocoded_payloads() {
|
||||
let mut packet = AgentThrottlePacket::new_with_constructor().expect("packet constructor");
|
||||
packet.agent_data.circuit_code = 0x1122_3344;
|
||||
packet.throttle.gen_counter = 0x5566_7788;
|
||||
packet.throttle.throttles = vec![1, 0, 0, 2, 3];
|
||||
let raw = packet.to_bytes_with_method().expect("raw packet");
|
||||
let mut encoded = vec![0_u8; raw.len() * 2 + 2];
|
||||
let encoded_length = Helpers::zero_encode(
|
||||
Some(&raw),
|
||||
i32::try_from(raw.len()).unwrap(),
|
||||
Some(&mut encoded),
|
||||
)
|
||||
.expect("zero encode");
|
||||
encoded.truncate(usize::try_from(encoded_length).unwrap());
|
||||
|
||||
let mut end = encoded_length - 1;
|
||||
let built =
|
||||
Packet::build_packet_with_bytes_int32_bytes(encoded, &mut end, vec![0_u8; raw.len()])
|
||||
.expect("packet factory");
|
||||
assert_eq!(built.type_, PacketType::AgentThrottle);
|
||||
assert_eq!(end, i32::try_from(raw.len()).unwrap() - 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packet_id_dispatch_covers_known_and_unknown_ids() {
|
||||
assert_eq!(
|
||||
Packet::get_type(3, PacketFrequency::Low).expect("packet type"),
|
||||
PacketType::UseCircuitCode
|
||||
);
|
||||
assert_eq!(
|
||||
Packet::get_type(u16::MAX, PacketFrequency::High).expect("unknown type"),
|
||||
PacketType::Default
|
||||
);
|
||||
assert_eq!(Helpers::MSG_RELIABLE, 0x40);
|
||||
}
|
||||
@@ -381,8 +381,8 @@ fn packet_to_bytes_multiple() {
|
||||
)
|
||||
.expect("Packet BuildPacket");
|
||||
assert_eq!(built.type_, PacketType::DirPlacesReply);
|
||||
end = packet_data.len() as i32 - 1;
|
||||
let packet = DirPlacesReplyPacket::new_with_bytes_int32(packet_data, &mut end)
|
||||
let mut start = 0;
|
||||
let packet = DirPlacesReplyPacket::new_with_bytes_int32(packet_data, &mut start)
|
||||
.expect("DirPlacesReplyPacket byte constructor");
|
||||
assert_eq!(
|
||||
packet
|
||||
|
||||
@@ -843,6 +843,84 @@ fn field_default(kind: FieldKind) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn field_sample(field: &PacketField) -> String {
|
||||
match field.kind {
|
||||
FieldKind::Bool => "true".to_owned(),
|
||||
FieldKind::U8 => "0xa5_u8".to_owned(),
|
||||
FieldKind::U16 | FieldKind::IpPort => "0x1234_u16".to_owned(),
|
||||
FieldKind::U32 | FieldKind::IpAddr => "0x1122_3344_u32".to_owned(),
|
||||
FieldKind::U64 => "0x0102_0304_0506_0708_u64".to_owned(),
|
||||
FieldKind::S8 => "-37_i8".to_owned(),
|
||||
FieldKind::S16 => "-0x1234_i16".to_owned(),
|
||||
FieldKind::S32 => "-0x1122_3344_i32".to_owned(),
|
||||
FieldKind::F32 => "1.5_f32".to_owned(),
|
||||
FieldKind::F64 => "-2.25_f64".to_owned(),
|
||||
FieldKind::LlUuid => "libremetaverse_types::UUID::new_with_bytes_int32((0_u8..16).collect(), 0).expect(\"sample UUID\")".to_owned(),
|
||||
FieldKind::LlVector3 => "libremetaverse_types::Vector3 { x: 1.25, y: -2.5, z: 3.75 }".to_owned(),
|
||||
FieldKind::LlVector3d => "libremetaverse_types::Vector3d { x: 1.25, y: -2.5, z: 3.75 }".to_owned(),
|
||||
FieldKind::LlVector4 => "libremetaverse_types::Vector4 { x: 1.25, y: -2.5, z: 3.75, w: -4.5 }".to_owned(),
|
||||
FieldKind::LlQuaternion => "libremetaverse_types::Quaternion::identity()".to_owned(),
|
||||
FieldKind::Variable => "vec![0_u8, 1, 2, 0, 0xff]".to_owned(),
|
||||
FieldKind::Fixed => format!("vec![0xa5_u8; {}]", field.count),
|
||||
}
|
||||
}
|
||||
|
||||
fn field_decode_statement(field: &PacketField) -> String {
|
||||
let name = rust_snake(&field.name);
|
||||
let expression = match field.kind {
|
||||
FieldKind::Bool => "reader.read_u8()? != 0".to_owned(),
|
||||
FieldKind::U8 => "reader.read_u8()?".to_owned(),
|
||||
FieldKind::U16 => "reader.read_u16_le()?".to_owned(),
|
||||
FieldKind::U32 | FieldKind::IpAddr => "reader.read_u32_le()?".to_owned(),
|
||||
FieldKind::U64 => "reader.read_u64_le()?".to_owned(),
|
||||
FieldKind::S8 => "reader.read_i8()?".to_owned(),
|
||||
FieldKind::S16 => "reader.read_i16_le()?".to_owned(),
|
||||
FieldKind::S32 => "reader.read_i32_le()?".to_owned(),
|
||||
FieldKind::F32 => "reader.read_f32_le()?".to_owned(),
|
||||
FieldKind::F64 => "reader.read_f64_le()?".to_owned(),
|
||||
FieldKind::LlUuid => "reader.read_uuid()?".to_owned(),
|
||||
FieldKind::LlVector3 => "reader.read_vector3()?".to_owned(),
|
||||
FieldKind::LlVector3d => "reader.read_vector3d()?".to_owned(),
|
||||
FieldKind::LlVector4 => "reader.read_vector4()?".to_owned(),
|
||||
FieldKind::LlQuaternion => "reader.read_quaternion()?".to_owned(),
|
||||
FieldKind::IpPort => "reader.read_u16_be()?".to_owned(),
|
||||
FieldKind::Variable => format!("reader.read_variable({})?", field.count),
|
||||
FieldKind::Fixed => format!(
|
||||
"reader.read_bytes({}, \"truncated fixed packet field\")?",
|
||||
field.count
|
||||
),
|
||||
};
|
||||
format!("self.{name} = {expression};")
|
||||
}
|
||||
|
||||
fn field_encode_statement(field: &PacketField) -> String {
|
||||
let name = rust_snake(&field.name);
|
||||
match field.kind {
|
||||
FieldKind::Bool => format!("writer.write_u8(u8::from(self.{name}))?;"),
|
||||
FieldKind::U8 => format!("writer.write_u8(self.{name})?;"),
|
||||
FieldKind::U16 => format!("writer.write_u16_le(self.{name})?;"),
|
||||
FieldKind::U32 | FieldKind::IpAddr => {
|
||||
format!("writer.write_u32_le(self.{name})?;")
|
||||
}
|
||||
FieldKind::U64 => format!("writer.write_u64_le(self.{name})?;"),
|
||||
FieldKind::S8 => format!("writer.write_i8(self.{name})?;"),
|
||||
FieldKind::S16 => format!("writer.write_i16_le(self.{name})?;"),
|
||||
FieldKind::S32 => format!("writer.write_i32_le(self.{name})?;"),
|
||||
FieldKind::F32 => format!("writer.write_f32_le(self.{name})?;"),
|
||||
FieldKind::F64 => format!("writer.write_f64_le(self.{name})?;"),
|
||||
FieldKind::LlUuid => format!("writer.write_uuid(&self.{name})?;"),
|
||||
FieldKind::LlVector3 => format!("writer.write_vector3(&self.{name})?;"),
|
||||
FieldKind::LlVector3d => format!("writer.write_vector3d(&self.{name})?;"),
|
||||
FieldKind::LlVector4 => format!("writer.write_vector4(&self.{name})?;"),
|
||||
FieldKind::LlQuaternion => format!("writer.write_quaternion(&self.{name})?;"),
|
||||
FieldKind::IpPort => format!("writer.write_u16_be(self.{name})?;"),
|
||||
FieldKind::Variable => {
|
||||
format!("writer.write_variable(&self.{name}, {})?;", field.count)
|
||||
}
|
||||
FieldKind::Fixed => format!("writer.write_fixed(&self.{name}, {})?;", field.count),
|
||||
}
|
||||
}
|
||||
|
||||
fn ordered_packets(protocol: &PacketProtocol) -> Vec<&PacketDefinition> {
|
||||
[
|
||||
PacketFrequency::Low,
|
||||
@@ -958,6 +1036,7 @@ fn append_descriptors(body: &mut String, packets: &[&PacketDefinition]) {
|
||||
body.push_str("];\n\n");
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn append_dispatch(body: &mut String, packets: &[&PacketDefinition]) {
|
||||
body.push_str("#[must_use]\n#[allow(clippy::too_many_lines)]\npub fn packet_type(id: u16, frequency: crate::PacketFrequency) -> PacketType {\n match (frequency, id) {\n");
|
||||
for packet in packets {
|
||||
@@ -1001,8 +1080,102 @@ fn append_dispatch(body: &mut String, packets: &[&PacketDefinition]) {
|
||||
crate::packets::Header { ack_list: None, appended_acks: false, frequency, id, reliable: true, resent: false, sequence: 0, zerocoded }\n\
|
||||
}\n\n\
|
||||
fn usize_to_i32(value: usize) -> i32 { i32::try_from(value).unwrap_or(i32::MAX) }\n\n\
|
||||
pub(crate) trait GeneratedBlock { fn new_generated() -> Self; fn generated_length(&self) -> i32; }\n\n\
|
||||
pub(crate) trait GeneratedPacket { fn new_generated() -> Self; fn generated_length(&self) -> i32; const USES_BUFFER_POOLING: bool; }\n\n",
|
||||
pub(crate) trait GeneratedBlock: Sized {\n\
|
||||
fn new_generated() -> Self;\n\
|
||||
fn generated_length(&self) -> i32;\n\
|
||||
fn decode_payload(&mut self, reader: &mut crate::packet_wire::WireReader<'_>) -> Result<(), crate::Error>;\n\
|
||||
fn encode_payload(&self, writer: &mut crate::packet_wire::WireWriter) -> Result<(), crate::Error>;\n\
|
||||
fn new_from_bytes(bytes: &[u8], position: &mut i32) -> Result<Self, crate::Error> {\n\
|
||||
let mut value = Self::new_generated();\n\
|
||||
Self::decode_from_bytes(&mut value, bytes, position)?;\n\
|
||||
Ok(value)\n\
|
||||
}\n\
|
||||
fn decode_from_bytes(&mut self, bytes: &[u8], position: &mut i32) -> Result<(), crate::Error> {\n\
|
||||
if *position < 0 { return Err(crate::Error::Argument); }\n\
|
||||
let start = usize::try_from(*position).map_err(|_| crate::Error::Argument)?;\n\
|
||||
let mut reader = crate::packet_wire::WireReader::new(bytes, start, bytes.len())?;\n\
|
||||
self.decode_payload(&mut reader)?;\n\
|
||||
reader.commit_position(position)\n\
|
||||
}\n\
|
||||
fn encode_to_slice(&self, bytes: &mut [u8], position: &mut i32) -> Result<(), crate::Error> {\n\
|
||||
if *position < 0 { return Err(crate::Error::Argument); }\n\
|
||||
let start = usize::try_from(*position).map_err(|_| crate::Error::Argument)?;\n\
|
||||
let capacity = usize::try_from(self.generated_length()).map_err(|_| crate::Error::Argument)?;\n\
|
||||
let mut writer = crate::packet_wire::WireWriter::with_capacity(capacity)?;\n\
|
||||
self.encode_payload(&mut writer)?;\n\
|
||||
let encoded = writer.into_inner();\n\
|
||||
let end = start.checked_add(encoded.len()).ok_or(crate::Error::Argument)?;\n\
|
||||
if end > bytes.len() { return Err(crate::Error::IndexOutOfRange); }\n\
|
||||
bytes[start..end].copy_from_slice(&encoded);\n\
|
||||
*position = i32::try_from(end).map_err(|_| crate::Error::Argument)?;\n\
|
||||
Ok(())\n\
|
||||
}\n\
|
||||
}\n\n\
|
||||
pub(crate) trait GeneratedPacket: Sized {\n\
|
||||
const USES_BUFFER_POOLING: bool;\n\
|
||||
fn new_generated() -> Self;\n\
|
||||
fn generated_length(&self) -> i32;\n\
|
||||
fn generated_header(&self) -> crate::packets::Header;\n\
|
||||
fn store_header(&mut self, header: crate::packets::Header);\n\
|
||||
fn decode_payload(&mut self, reader: &mut crate::packet_wire::WireReader<'_>) -> Result<(), crate::Error>;\n\
|
||||
fn encode_payload(&self, writer: &mut crate::packet_wire::WireWriter) -> Result<(), crate::Error>;\n\
|
||||
fn encode_multiple(&self) -> Result<Vec<Vec<u8>>, crate::Error>;\n\
|
||||
fn encode_packet(&self) -> Result<Vec<u8>, crate::Error> {\n\
|
||||
let header = self.generated_header();\n\
|
||||
let payload = usize::try_from(self.generated_length()).map_err(|_| crate::Error::Argument)?;\n\
|
||||
let capacity = payload.checked_add(crate::packet_wire::ack_length(&header)?).ok_or(crate::Error::Argument)?;\n\
|
||||
let mut writer = crate::packet_wire::WireWriter::with_capacity(capacity)?;\n\
|
||||
crate::packet_wire::encode_header(&header, &mut writer)?;\n\
|
||||
self.encode_payload(&mut writer)?;\n\
|
||||
crate::packet_wire::encode_acks(&header, &mut writer)?;\n\
|
||||
Ok(writer.into_inner())\n\
|
||||
}\n\
|
||||
fn new_from_bytes(bytes: &[u8], position: &mut i32) -> Result<Self, crate::Error> {\n\
|
||||
let mut value = Self::new_generated();\n\
|
||||
let mut packet_end = i32::try_from(bytes.len()).map_err(|_| crate::Error::Argument)?.checked_sub(1).ok_or(crate::Error::Argument)?;\n\
|
||||
Self::decode_from_bytes(&mut value, bytes, position, &mut packet_end, None)?;\n\
|
||||
Ok(value)\n\
|
||||
}\n\
|
||||
fn new_from_header(header: crate::packets::Header, bytes: &[u8], position: &mut i32) -> Result<Self, crate::Error> {\n\
|
||||
let mut value = Self::new_generated();\n\
|
||||
let mut packet_end = i32::try_from(bytes.len()).map_err(|_| crate::Error::Argument)?.checked_sub(1).ok_or(crate::Error::Argument)?;\n\
|
||||
Self::decode_from_header(&mut value, header, bytes, position, &mut packet_end)?;\n\
|
||||
Ok(value)\n\
|
||||
}\n\
|
||||
fn decode_from_header(&mut self, header: crate::packets::Header, bytes: &[u8], position: &mut i32, packet_end: &mut i32) -> Result<(), crate::Error> {\n\
|
||||
if *position < 0 || *packet_end < 0 { return Err(crate::Error::Argument); }\n\
|
||||
let start = usize::try_from(*position).map_err(|_| crate::Error::Argument)?;\n\
|
||||
let end = usize::try_from(packet_end.checked_add(1).ok_or(crate::Error::Argument)?).map_err(|_| crate::Error::Argument)?;\n\
|
||||
let mut reader = crate::packet_wire::WireReader::new(bytes, start, end)?;\n\
|
||||
self.decode_payload(&mut reader)?;\n\
|
||||
reader.finish(position)?;\n\
|
||||
self.store_header(header);\n\
|
||||
Ok(())\n\
|
||||
}\n\
|
||||
fn decode_from_bytes(&mut self, bytes: &[u8], position: &mut i32, packet_end: &mut i32, zero_buffer: Option<&mut [u8]>) -> Result<(), crate::Error> {\n\
|
||||
let header = crate::packet_wire::decode_header(bytes, position, packet_end)?;\n\
|
||||
if header.zerocoded && let Some(buffer) = zero_buffer {\n\
|
||||
let decoded_length = crate::packet_wire::zero_decode(Some(bytes), packet_end.checked_add(1).ok_or(crate::Error::Argument)?, Some(buffer))?;\n\
|
||||
*packet_end = decoded_length.checked_sub(1).ok_or(crate::Error::Argument)?;\n\
|
||||
let end = usize::try_from(decoded_length).map_err(|_| crate::Error::Argument)?;\n\
|
||||
return self.decode_from_header(header, &buffer[..end], position, packet_end);\n\
|
||||
}\n\
|
||||
self.decode_from_header(header, bytes, position, packet_end)\n\
|
||||
}\n\
|
||||
}\n\n",
|
||||
);
|
||||
body.push_str(
|
||||
"#[allow(clippy::too_many_lines)]\npub(crate) fn validate_packet_payload(\n packet_type: PacketType,\n header: crate::packets::Header,\n bytes: &[u8],\n position: &mut i32,\n packet_end: &mut i32,\n) -> Result<(), crate::Error> {\n match packet_type {\n",
|
||||
);
|
||||
for packet in packets {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" PacketType::{} => {{ let mut packet = <crate::packets::{}Packet as GeneratedPacket>::new_generated(); GeneratedPacket::decode_from_header(&mut packet, header, bytes, position, packet_end) }},",
|
||||
packet.name, packet.name
|
||||
);
|
||||
}
|
||||
body.push_str(
|
||||
" PacketType::Default => Err(crate::Error::InvalidOperation),\n }\n}\n\n",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1043,7 +1216,17 @@ fn append_block_impls(body: &mut String, packets: &[&PacketDefinition]) {
|
||||
}
|
||||
body.push_str(" length\n");
|
||||
}
|
||||
body.push_str(" }\n}\n\n");
|
||||
body.push_str(
|
||||
" }\n\n fn decode_payload(&mut self, reader: &mut crate::packet_wire::WireReader<'_>) -> Result<(), crate::Error> {\n",
|
||||
);
|
||||
for field in &block.fields {
|
||||
let _ = writeln!(body, " {}", field_decode_statement(field));
|
||||
}
|
||||
body.push_str(" Ok(())\n }\n\n fn encode_payload(&self, writer: &mut crate::packet_wire::WireWriter) -> Result<(), crate::Error> {\n");
|
||||
for field in &block.fields {
|
||||
let _ = writeln!(body, " {}", field_encode_statement(field));
|
||||
}
|
||||
body.push_str(" Ok(())\n }\n}\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1052,6 +1235,135 @@ fn packet_has_composed_base(name: &str) -> bool {
|
||||
matches!(name, "DirPlacesReply" | "TestMessage")
|
||||
}
|
||||
|
||||
fn packet_can_split(packet: &PacketDefinition) -> bool {
|
||||
let mut saw_variable = false;
|
||||
let mut has_variable = false;
|
||||
for block in &packet.blocks {
|
||||
if block.repetition == BlockRepetition::Variable {
|
||||
saw_variable = true;
|
||||
has_variable = true;
|
||||
} else if saw_variable {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
has_variable
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn append_packet_encode_multiple(body: &mut String, packet: &PacketDefinition) {
|
||||
if !packet_can_split(packet) {
|
||||
body.push_str(
|
||||
" fn encode_multiple(&self) -> Result<Vec<Vec<u8>>, crate::Error> {\n Ok(vec![self.encode_packet()?])\n }\n",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let variable_blocks = packet
|
||||
.blocks
|
||||
.iter()
|
||||
.filter(|block| block.repetition == BlockRepetition::Variable)
|
||||
.collect::<Vec<_>>();
|
||||
body.push_str(
|
||||
" #[allow(clippy::too_many_lines)]\n fn encode_multiple(&self) -> Result<Vec<Vec<u8>>, crate::Error> {\n let header = self.generated_header();\n let mut packets = Vec::new();\n",
|
||||
);
|
||||
for block in &variable_blocks {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" let mut {}_start = 0_usize;",
|
||||
rust_snake(&block.name)
|
||||
);
|
||||
}
|
||||
body.push_str(" loop {\n let first_packet = packets.is_empty();\n let mut packet_header = crate::packet_wire::clone_header(&header);\n if !first_packet { packet_header.appended_acks = false; packet_header.ack_list = None; }\n let acks_length = crate::packet_wire::ack_length(&packet_header)?;\n let mut fixed_length = crate::packet_wire::header_length(packet_header.frequency);
|
||||
");
|
||||
for block in &packet.blocks {
|
||||
let field_name = rust_snake(&block.name);
|
||||
match block.repetition {
|
||||
BlockRepetition::Single => {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" fixed_length = fixed_length.checked_add(usize::try_from(GeneratedBlock::generated_length(&self.{field_name})).map_err(|_| crate::Error::Argument)?).ok_or(crate::Error::Argument)?;"
|
||||
);
|
||||
}
|
||||
BlockRepetition::Multiple(_) => {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" for block in &self.{field_name} {{ fixed_length = fixed_length.checked_add(usize::try_from(GeneratedBlock::generated_length(block)).map_err(|_| crate::Error::Argument)?).ok_or(crate::Error::Argument)?; }}"
|
||||
);
|
||||
}
|
||||
BlockRepetition::Variable => {}
|
||||
}
|
||||
}
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" fixed_length = fixed_length.checked_add({}).ok_or(crate::Error::Argument)?;",
|
||||
variable_blocks.len()
|
||||
);
|
||||
body.push_str(" let mut variable_length = 0_usize;\n");
|
||||
for block in &variable_blocks {
|
||||
let field_name = rust_snake(&block.name);
|
||||
let _ = writeln!(body, " let mut {field_name}_count = 0_usize;");
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" while {field_name}_start + {field_name}_count < self.{field_name}.len() && {field_name}_count < usize::from(u8::MAX) {{"
|
||||
);
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" let block_length = usize::try_from(GeneratedBlock::generated_length(&self.{field_name}[{field_name}_start + {field_name}_count])).map_err(|_| crate::Error::Argument)?;"
|
||||
);
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" if fixed_length.saturating_add(variable_length).saturating_add(block_length).saturating_add(acks_length) <= usize::try_from(crate::packets::Packet::MTU).map_err(|_| crate::Error::Argument)? || {field_name}_count == 0 {{ variable_length = variable_length.checked_add(block_length).ok_or(crate::Error::Argument)?; {field_name}_count += 1; }} else {{ break; }}"
|
||||
);
|
||||
body.push_str(" }\n");
|
||||
}
|
||||
body.push_str(" let capacity = fixed_length.checked_add(variable_length).and_then(|length| length.checked_add(acks_length)).ok_or(crate::Error::Argument)?;\n let mut writer = crate::packet_wire::WireWriter::with_capacity(capacity)?;\n crate::packet_wire::encode_header(&packet_header, &mut writer)?;\n");
|
||||
for block in &packet.blocks {
|
||||
let field_name = rust_snake(&block.name);
|
||||
match block.repetition {
|
||||
BlockRepetition::Single => {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" GeneratedBlock::encode_payload(&self.{field_name}, &mut writer)?;"
|
||||
);
|
||||
}
|
||||
BlockRepetition::Multiple(count) => {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" if self.{field_name}.len() != {count} {{ return Err(crate::Error::Argument); }}"
|
||||
);
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" for block in &self.{field_name} {{ GeneratedBlock::encode_payload(block, &mut writer)?; }}"
|
||||
);
|
||||
}
|
||||
BlockRepetition::Variable => {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" writer.write_u8(u8::try_from({field_name}_count).map_err(|_| crate::Error::Argument)?)?;"
|
||||
);
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" for block in &self.{field_name}[{field_name}_start..{field_name}_start + {field_name}_count] {{ GeneratedBlock::encode_payload(block, &mut writer)?; }}"
|
||||
);
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" {field_name}_start += {field_name}_count;"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
body.push_str(" crate::packet_wire::encode_acks(&packet_header, &mut writer)?;\n packets.push(writer.into_inner());\n if ");
|
||||
for (index, block) in variable_blocks.iter().enumerate() {
|
||||
if index != 0 {
|
||||
body.push_str(" && ");
|
||||
}
|
||||
let field_name = rust_snake(&block.name);
|
||||
let _ = write!(body, "{field_name}_start >= self.{field_name}.len()");
|
||||
}
|
||||
body.push_str(" { break; }\n }\n Ok(packets)\n }\n");
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn append_packet_impls(body: &mut String, packets: &[&PacketDefinition]) {
|
||||
for packet in packets {
|
||||
let type_name = format!("{}Packet", packet.name);
|
||||
@@ -1105,14 +1417,8 @@ fn append_packet_impls(body: &mut String, packets: &[&PacketDefinition]) {
|
||||
body.push_str(" }\n }\n\n fn generated_length(&self) -> i32 {\n");
|
||||
if packet.blocks.is_empty() {
|
||||
let _ = writeln!(body, " {base_length}_i32");
|
||||
body.push_str(" }\n}\n\n");
|
||||
continue;
|
||||
}
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" let mut length = {}_i32;",
|
||||
base_length + variable_count
|
||||
);
|
||||
} else {
|
||||
let _ = writeln!(body, " let mut length = {base_length}_i32;");
|
||||
for block in &packet.blocks {
|
||||
let field_name = rust_snake(&block.name);
|
||||
match block.repetition {
|
||||
@@ -1137,8 +1443,170 @@ fn append_packet_impls(body: &mut String, packets: &[&PacketDefinition]) {
|
||||
}
|
||||
}
|
||||
}
|
||||
body.push_str(" length\n }\n}\n\n");
|
||||
body.push_str(" length\n");
|
||||
}
|
||||
body.push_str(" }\n\n fn generated_header(&self) -> crate::packets::Header {\n");
|
||||
if packet_has_composed_base(&packet.name) {
|
||||
body.push_str(" crate::packet_wire::clone_header(&self.base.header)\n");
|
||||
} else {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" new_header(crate::PacketFrequency::{}, {}, {})",
|
||||
frequency_name(packet.frequency),
|
||||
packet.id,
|
||||
packet.zerocoded
|
||||
);
|
||||
}
|
||||
body.push_str(
|
||||
" }\n\n fn store_header(&mut self, header: crate::packets::Header) {\n",
|
||||
);
|
||||
if packet_has_composed_base(&packet.name) {
|
||||
body.push_str(" self.base.header = header;\n");
|
||||
} else {
|
||||
body.push_str(" let _ = header;\n");
|
||||
}
|
||||
body.push_str(" }\n\n fn decode_payload(&mut self, reader: &mut crate::packet_wire::WireReader<'_>) -> Result<(), crate::Error> {\n");
|
||||
for block in &packet.blocks {
|
||||
let field_name = rust_snake(&block.name);
|
||||
let block_type = format!("crate::packets::{}Packet{}Block", packet.name, block.name);
|
||||
match block.repetition {
|
||||
BlockRepetition::Single => {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" GeneratedBlock::decode_payload(&mut self.{field_name}, reader)?;"
|
||||
);
|
||||
}
|
||||
BlockRepetition::Variable => {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" let {field_name}_count = usize::from(reader.read_u8()?);"
|
||||
);
|
||||
let _ = writeln!(body, " let mut {field_name} = Vec::new();");
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" {field_name}.try_reserve_exact({field_name}_count).map_err(|_| crate::Error::InvalidOperation)?;"
|
||||
);
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" for _ in 0..{field_name}_count {{ let mut block = <{block_type} as GeneratedBlock>::new_generated(); GeneratedBlock::decode_payload(&mut block, reader)?; {field_name}.push(block); }}"
|
||||
);
|
||||
let _ = writeln!(body, " self.{field_name} = {field_name};");
|
||||
}
|
||||
BlockRepetition::Multiple(count) => {
|
||||
let _ = writeln!(body, " let mut {field_name} = Vec::new();");
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" {field_name}.try_reserve_exact({count}).map_err(|_| crate::Error::InvalidOperation)?;"
|
||||
);
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" for _ in 0..{count} {{ let mut block = <{block_type} as GeneratedBlock>::new_generated(); GeneratedBlock::decode_payload(&mut block, reader)?; {field_name}.push(block); }}"
|
||||
);
|
||||
let _ = writeln!(body, " self.{field_name} = {field_name};");
|
||||
}
|
||||
}
|
||||
}
|
||||
if packet.blocks.is_empty() {
|
||||
body.push_str(" let _ = reader;\n");
|
||||
}
|
||||
body.push_str(" Ok(())\n }\n\n fn encode_payload(&self, writer: &mut crate::packet_wire::WireWriter) -> Result<(), crate::Error> {\n");
|
||||
for block in &packet.blocks {
|
||||
let field_name = rust_snake(&block.name);
|
||||
match block.repetition {
|
||||
BlockRepetition::Single => {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" GeneratedBlock::encode_payload(&self.{field_name}, writer)?;"
|
||||
);
|
||||
}
|
||||
BlockRepetition::Variable => {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" writer.write_u8(u8::try_from(self.{field_name}.len()).map_err(|_| crate::Error::Argument)?)?;"
|
||||
);
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" for block in &self.{field_name} {{ GeneratedBlock::encode_payload(block, writer)?; }}"
|
||||
);
|
||||
}
|
||||
BlockRepetition::Multiple(count) => {
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" if self.{field_name}.len() != {count} {{ return Err(crate::Error::Argument); }}"
|
||||
);
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" for block in &self.{field_name} {{ GeneratedBlock::encode_payload(block, writer)?; }}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if packet.blocks.is_empty() {
|
||||
body.push_str(" let _ = writer;\n");
|
||||
}
|
||||
body.push_str(" Ok(())\n }\n\n");
|
||||
append_packet_encode_multiple(body, packet);
|
||||
body.push_str("}\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn append_packet_codec_tests(body: &mut String, packets: &[&PacketDefinition]) {
|
||||
body.push_str(
|
||||
"#[cfg(test)]\nmod generated_codec_tests {\n use super::{GeneratedBlock, GeneratedPacket};\n\n fn assert_round_trip<T: GeneratedPacket>(packet: &T, name: &str) {\n let expected = packet.encode_packet().unwrap_or_else(|error| panic!(\"{name}: encode failed: {error}\"));\n let (wire, mut zero_buffer) = if expected[0] & crate::Helpers::MSG_ZEROCODED != 0 {\n let mut encoded = vec![0_u8; expected.len().saturating_mul(2).saturating_add(2)];\n let encoded_length = crate::packet_wire::zero_encode(\n Some(&expected),\n i32::try_from(expected.len()).expect(\"packet length fits i32\"),\n Some(&mut encoded),\n )\n .unwrap_or_else(|error| panic!(\"{name}: zero encode failed: {error}\"));\n encoded.truncate(usize::try_from(encoded_length).expect(\"encoded length fits usize\"));\n (encoded, vec![0_u8; expected.len()])\n } else {\n (expected.clone(), Vec::new())\n };\n let mut decoded = T::new_generated();\n let mut position = 0_i32;\n let mut packet_end = i32::try_from(wire.len()).expect(\"wire length fits i32\") - 1;\n let buffer = (!zero_buffer.is_empty()).then_some(zero_buffer.as_mut_slice());\n decoded\n .decode_from_bytes(&wire, &mut position, &mut packet_end, buffer)\n .unwrap_or_else(|error| panic!(\"{name}: decode failed: {error}\"));\n let actual = decoded.encode_packet().unwrap_or_else(|error| panic!(\"{name}: re-encode failed: {error}\"));\n assert_eq!(actual, expected, \"{name}: round-trip bytes differ\");\n assert_eq!(position, i32::try_from(expected.len()).expect(\"packet length fits i32\"), \"{name}: final position\");\n\n for truncated_length in 0..wire.len() {\n let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n let mut candidate = T::new_generated();\n let mut truncated_position = 0_i32;\n let mut truncated_end = i32::try_from(truncated_length).expect(\"truncated length fits i32\") - 1;\n let mut scratch = vec![0_u8; expected.len()];\n let truncated_buffer = (!zero_buffer.is_empty()).then_some(scratch.as_mut_slice());\n candidate.decode_from_bytes(\n &wire[..truncated_length],\n &mut truncated_position,\n &mut truncated_end,\n truncated_buffer,\n )\n }));\n assert!(outcome.is_ok(), \"{name}: truncation at {truncated_length} panicked\");\n }\n }\n\n #[test]\n #[allow(clippy::too_many_lines)]\n fn every_generated_packet_round_trips_all_fields() {\n",
|
||||
);
|
||||
for packet in packets {
|
||||
let mutates_packet = packet
|
||||
.blocks
|
||||
.iter()
|
||||
.any(|block| block.repetition == BlockRepetition::Variable || !block.fields.is_empty());
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" let {}packet = <crate::packets::{}Packet as GeneratedPacket>::new_generated();",
|
||||
if mutates_packet { "mut " } else { "" },
|
||||
packet.name,
|
||||
);
|
||||
for block in &packet.blocks {
|
||||
let block_name = rust_snake(&block.name);
|
||||
match block.repetition {
|
||||
BlockRepetition::Single => {
|
||||
for field in &block.fields {
|
||||
let field_name = rust_snake(&field.name);
|
||||
let sample = field_sample(field);
|
||||
let _ =
|
||||
writeln!(body, " packet.{block_name}.{field_name} = {sample};");
|
||||
}
|
||||
}
|
||||
BlockRepetition::Multiple(_) => {
|
||||
let _ = writeln!(body, " for block in &mut packet.{block_name} {{");
|
||||
for field in &block.fields {
|
||||
let field_name = rust_snake(&field.name);
|
||||
let sample = field_sample(field);
|
||||
let _ = writeln!(body, " block.{field_name} = {sample};");
|
||||
}
|
||||
body.push_str(" }\n");
|
||||
}
|
||||
BlockRepetition::Variable => {
|
||||
let block_type = format!("{}Packet{}Block", packet.name, block.name);
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" let mut block = <crate::packets::{block_type} as GeneratedBlock>::new_generated();"
|
||||
);
|
||||
for field in &block.fields {
|
||||
let field_name = rust_snake(&field.name);
|
||||
let sample = field_sample(field);
|
||||
let _ = writeln!(body, " block.{field_name} = {sample};");
|
||||
}
|
||||
let _ = writeln!(body, " packet.{block_name}.push(block);");
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = writeln!(
|
||||
body,
|
||||
" assert_round_trip(&packet, {:?});",
|
||||
packet.name
|
||||
);
|
||||
}
|
||||
body.push_str(" }\n}\n");
|
||||
}
|
||||
|
||||
fn csharp_field_type(kind: FieldKind) -> &'static str {
|
||||
@@ -1401,6 +1869,7 @@ pub fn packet_catalog_bytes(root: &Path) -> Result<Vec<u8>, String> {
|
||||
append_dispatch(&mut body, &packets);
|
||||
append_block_impls(&mut body, &packets);
|
||||
append_packet_impls(&mut body, &packets);
|
||||
append_packet_codec_tests(&mut body, &packets);
|
||||
let syntax = syn::parse_file(&body)
|
||||
.map_err(|error| format!("generated packet Rust is invalid: {error}"))?;
|
||||
let formatted = prettyplease::unparse(&syntax);
|
||||
|
||||
@@ -38,6 +38,7 @@ TARGETS = {
|
||||
# implementations. The generated module keeps catalog markers and re-exports
|
||||
# the hand-written type so coverage remains deterministic.
|
||||
NATIVE_TYPES = {
|
||||
"T:LibreMetaverse.BitPack": "crate::bit_pack::BitPack",
|
||||
"T:LibreMetaverse.Packets.PacketType": "crate::packet_catalog::PacketType",
|
||||
"T:LibreMetaverse.PrimMesher.Coord": "crate::prim_mesher::Coord",
|
||||
"T:LibreMetaverse.PrimMesher.Face": "crate::prim_mesher::Face",
|
||||
@@ -296,6 +297,52 @@ COMPOSED_BASE_TYPES = {
|
||||
"T:LibreMetaverse.Packets.TestMessagePacket",
|
||||
"T:LibreMetaverse.Rendering.Joint",
|
||||
}
|
||||
|
||||
NATIVE_MESSAGE_CODEC_TYPES = {
|
||||
"T:LibreMetaverse.Messages.Linden.AgentGroupDataUpdateMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.AgentGroupDataUpdateMessage.GroupData",
|
||||
"T:LibreMetaverse.Messages.Linden.AgentGroupDataUpdateMessage.NewGroupData",
|
||||
"T:LibreMetaverse.Messages.Linden.ChatSessionAcceptInvitation",
|
||||
"T:LibreMetaverse.Messages.Linden.ChatSessionRequestBlock",
|
||||
"T:LibreMetaverse.Messages.Linden.ChatSessionRequestMuteUpdate",
|
||||
"T:LibreMetaverse.Messages.Linden.ChatSessionRequestStartConference",
|
||||
"T:LibreMetaverse.Messages.Linden.ChatterBoxInvitationMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.ChatterBoxSessionAgentListUpdatesMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.ChatterBoxSessionAgentListUpdatesMessage.AgentUpdatesBlock",
|
||||
"T:LibreMetaverse.Messages.Linden.ChatterBoxSessionStartReplyMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.ChatterboxSessionEventReplyMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.CopyInventoryFromNotecardMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.DirLandReplyMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.DirLandReplyMessage.QueryReply",
|
||||
"T:LibreMetaverse.Messages.Linden.EnableSimulatorMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.EnableSimulatorMessage.SimulatorInfoBlock",
|
||||
"T:LibreMetaverse.Messages.Linden.EstablishAgentCommunicationMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.LandStatReplyMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.LandStatReplyMessage.ReportDataBlock",
|
||||
"T:LibreMetaverse.Messages.Linden.MapLayerMessageBase",
|
||||
"T:LibreMetaverse.Messages.Linden.MapLayerReplyVariant",
|
||||
"T:LibreMetaverse.Messages.Linden.MapLayerReplyVariant.LayerData",
|
||||
"T:LibreMetaverse.Messages.Linden.ParcelObjectOwnersReplyMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.ParcelObjectOwnersReplyMessage.PrimOwner",
|
||||
"T:LibreMetaverse.Messages.Linden.ParcelPropertiesMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.ParcelPropertiesUpdateMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.ParcelVoiceInfoRequestMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.PlacesReplyMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.PlacesReplyMessage.QueryData",
|
||||
"T:LibreMetaverse.Messages.Linden.ProvisionVoiceAccountRequestMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.RemoteParcelRequestReply",
|
||||
"T:LibreMetaverse.Messages.Linden.RequiredVoiceVersionMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.ScriptRunningReplyMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.SendPostcardMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.TeleportFailedMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.TeleportFinishMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.UpdateAgentInformationMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.UpdateAgentInventoryRequestMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.UpdateAgentLanguageMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.UpdateScriptAgentRequestMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.UpdateScriptTaskUpdateMessage",
|
||||
"T:LibreMetaverse.Messages.Linden.ViewerStatsMessage",
|
||||
}
|
||||
OPEN_ENUM_IDS = {"T:LibreMetaverse.BakeType", "T:LibreMetaverse.HoleType"}
|
||||
TRAIT_SUPERTRAITS = {
|
||||
"T:LibreMetaverse.IBakingTextureProvider": "std::any::Any",
|
||||
@@ -746,10 +793,68 @@ def native_member_body(member_id: str) -> str | None:
|
||||
body = NATIVE_MEMBER_BODIES.get(member_id)
|
||||
if body is not None:
|
||||
return body
|
||||
message_owner = "T:" + member_id[2:].split(".#ctor", 1)[0] if member_id.startswith("M:") and ".#ctor" in member_id else None
|
||||
if message_owner in NATIVE_MESSAGE_CODEC_TYPES:
|
||||
return "Ok(<Self as crate::message_codec::GeneratedMessage>::new_generated())"
|
||||
message_owner = "T:" + member_id[2:].rsplit(".Deserialize", 1)[0] if member_id.startswith("M:") and ".Deserialize(" in member_id else None
|
||||
if message_owner in NATIVE_MESSAGE_CODEC_TYPES:
|
||||
return "<Self as crate::message_codec::GeneratedMessage>::deserialize_generated(self, &map)"
|
||||
message_owner = "T:" + member_id[2:].rsplit(".Serialize", 1)[0] if member_id.startswith("M:") and member_id.endswith(".Serialize") else None
|
||||
if message_owner in NATIVE_MESSAGE_CODEC_TYPES:
|
||||
return "<Self as crate::message_codec::GeneratedMessage>::serialize_generated(self)"
|
||||
if re.fullmatch(r"M:LibreMetaverse\.Packets\.[^.]+Packet\.#ctor", member_id):
|
||||
return "Ok(<Self as crate::packet_catalog::GeneratedPacket>::new_generated())"
|
||||
if re.fullmatch(
|
||||
r"M:LibreMetaverse\.Packets\.[^.]+Packet\.#ctor\(LibreMetaverse\.Packets\.Header,System\.Byte\[\],System\.Int32@\)",
|
||||
member_id,
|
||||
):
|
||||
return "<Self as crate::packet_catalog::GeneratedPacket>::new_from_header(head, &bytes, i)"
|
||||
if re.fullmatch(
|
||||
r"M:LibreMetaverse\.Packets\.[^.]+Packet\.#ctor\(System\.Byte\[\],System\.Int32@\)",
|
||||
member_id,
|
||||
):
|
||||
return "<Self as crate::packet_catalog::GeneratedPacket>::new_from_bytes(&bytes, i)"
|
||||
if re.fullmatch(r"M:LibreMetaverse\.Packets\.[^.]+Packet\.[^.]+Block\.#ctor", member_id):
|
||||
return "Ok(<Self as crate::packet_catalog::GeneratedBlock>::new_generated())"
|
||||
if re.fullmatch(
|
||||
r"M:LibreMetaverse\.Packets\.[^.]+Packet\.[^.]+Block\.#ctor\(System\.Byte\[\],System\.Int32@\)",
|
||||
member_id,
|
||||
):
|
||||
return "<Self as crate::packet_catalog::GeneratedBlock>::new_from_bytes(&bytes, i)"
|
||||
if re.fullmatch(
|
||||
r"M:LibreMetaverse\.Packets\.[^.]+Packet\.[^.]+Block\.FromBytes\(System\.Byte\[\],System\.Int32@\)",
|
||||
member_id,
|
||||
):
|
||||
return "<Self as crate::packet_catalog::GeneratedBlock>::decode_from_bytes(self, &bytes, i)"
|
||||
if re.fullmatch(
|
||||
r"M:LibreMetaverse\.Packets\.[^.]+Packet\.[^.]+Block\.ToBytes\(System\.Byte\[\],System\.Int32@\)",
|
||||
member_id,
|
||||
):
|
||||
return "<Self as crate::packet_catalog::GeneratedBlock>::encode_to_slice(self, bytes, i)"
|
||||
if re.fullmatch(
|
||||
r"M:LibreMetaverse\.Packets\.[^.]+Packet\.FromBytes\(LibreMetaverse\.Packets\.Header,System\.Byte\[\],System\.Int32@,System\.Int32@\)",
|
||||
member_id,
|
||||
):
|
||||
return "<Self as crate::packet_catalog::GeneratedPacket>::decode_from_header(self, header, &bytes, i, packet_end)"
|
||||
if re.fullmatch(
|
||||
r"M:LibreMetaverse\.Packets\.[^.]+Packet\.FromBytes\(System\.Byte\[\],System\.Int32@,System\.Int32@,System\.Byte\[\]\)",
|
||||
member_id,
|
||||
):
|
||||
return "<Self as crate::packet_catalog::GeneratedPacket>::decode_from_bytes(self, &bytes, i, packet_end, zero_buffer)"
|
||||
if re.fullmatch(r"M:LibreMetaverse\.Packets\.[^.]+Packet\.ToBytes", member_id):
|
||||
return "<Self as crate::packet_catalog::GeneratedPacket>::encode_packet(self)"
|
||||
if re.fullmatch(
|
||||
r"M:LibreMetaverse\.Packets\.[^.]+Packet\.ToBytes\(LibreMetaverse\.Interfaces\.IByteBufferPool,System\.Int32@\)",
|
||||
member_id,
|
||||
):
|
||||
return "{ let _ = pool; let bytes = <Self as crate::packet_catalog::GeneratedPacket>::encode_packet(self)?; *size = i32::try_from(bytes.len()).map_err(|_| crate::Error::Argument)?; Ok(bytes) }"
|
||||
if re.fullmatch(r"M:LibreMetaverse\.Packets\.[^.]+Packet\.ToBytesMultiple", member_id):
|
||||
return "<Self as crate::packet_catalog::GeneratedPacket>::encode_multiple(self)"
|
||||
if re.fullmatch(
|
||||
r"M:LibreMetaverse\.Packets\.[^.]+Packet\.ToBytesMultiple\(LibreMetaverse\.Interfaces\.IByteBufferPool,System\.Int32\[\]@\)",
|
||||
member_id,
|
||||
):
|
||||
return "{ let _ = pool; let packets = <Self as crate::packet_catalog::GeneratedPacket>::encode_multiple(self)?; *sizes = packets.iter().map(|packet| i32::try_from(packet.len()).unwrap_or(i32::MAX)).collect(); Ok(packets) }"
|
||||
if re.fullmatch(r"P:LibreMetaverse\.Packets\.[^.]+Packet\.Length", member_id):
|
||||
return "<Self as crate::packet_catalog::GeneratedPacket>::generated_length(self)"
|
||||
if re.fullmatch(r"P:LibreMetaverse\.Packets\.[^.]+Packet\.UsesBufferPooling", member_id):
|
||||
@@ -760,6 +865,20 @@ def native_member_body(member_id: str) -> str | None:
|
||||
return "Ok(crate::packet_catalog::packet_type(id, frequency))"
|
||||
if member_id == "M:LibreMetaverse.Packets.Packet.BuildPacket(LibreMetaverse.Packets.PacketType)":
|
||||
return "crate::packet_catalog::build_packet(type_)"
|
||||
if member_id == "M:LibreMetaverse.Packets.Packet.BuildPacket(System.Byte[],System.Int32@,System.Byte[])":
|
||||
return "{ let mut zero_buffer = zero_buffer; crate::packet_wire::build_packet_from_bytes(&packet_buffer, packet_end, &mut zero_buffer) }"
|
||||
if member_id == "M:LibreMetaverse.Packets.Header.BuildHeader(System.Byte[],System.Int32@,System.Int32@)":
|
||||
return "crate::packet_wire::decode_header(&bytes, pos, packet_end)"
|
||||
if member_id == "M:LibreMetaverse.Packets.Header.FromBytes(System.Byte[],System.Int32@,System.Int32@)":
|
||||
return "{ *self = crate::packet_wire::decode_header(&bytes, pos, packet_end)?; Ok(()) }"
|
||||
if member_id == "M:LibreMetaverse.Packets.Header.ToBytes(System.Byte[],System.Int32@)":
|
||||
return "crate::packet_wire::header_to_slice(self, bytes, i)"
|
||||
if member_id == "M:LibreMetaverse.Packets.Header.AcksToBytes(System.Byte[],System.Int32@)":
|
||||
return "crate::packet_wire::acks_to_slice(self, bytes, i)"
|
||||
if member_id == "M:LibreMetaverse.Helpers.ZeroDecode(System.Byte[],System.Int32,System.Byte[])":
|
||||
return "crate::packet_wire::zero_decode(src, srclen, dest)"
|
||||
if member_id == "M:LibreMetaverse.Helpers.ZeroEncode(System.Byte[],System.Int32,System.Byte[])":
|
||||
return "crate::packet_wire::zero_encode(src, srclen, dest)"
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -985,6 +985,7 @@ def validate_generated_shims() -> None:
|
||||
"crate::json_codec::",
|
||||
"crate::notation::",
|
||||
"crate::packet_catalog::",
|
||||
"crate::packet_wire::",
|
||||
"crate::protobuf::",
|
||||
"crate::xml_codec::",
|
||||
)
|
||||
@@ -1156,9 +1157,8 @@ def method_receiver(owner: dict, item: dict) -> tuple[str, str]:
|
||||
return "&self", "shared_self"
|
||||
if owner["doc_id"] == "T:LibreMetaverse.BitPack" and item["name"].startswith(("Pack", "Unpack")):
|
||||
return "&mut self", "mutable_self"
|
||||
if (
|
||||
owner["doc_id"] == "T:LibreMetaverse.Packets.InventoryDescendentsPacket"
|
||||
and item["name"].startswith("FromBytes")
|
||||
if owner["doc_id"].startswith("T:LibreMetaverse.Packets.") and item["name"].startswith(
|
||||
"FromBytes"
|
||||
):
|
||||
return "&mut self", "mutable_self"
|
||||
observers = ("Get", "TryGet", "Find", "Contains", "Has", "Is", "Can", "To", "Equals", "Compare")
|
||||
@@ -1215,6 +1215,17 @@ def member_signature(mapper: Mapper, owner: dict, item: dict, rust_name: str) ->
|
||||
return signature, ownership, "sync", "Result<_, crate::Error>", "method"
|
||||
generics = generic_names(item, owner)
|
||||
mapped_parameters = [map_parameter(mapper, parameter, generics) for parameter in item.get("parameters", [])]
|
||||
packet_owner = owner["doc_id"].startswith("T:LibreMetaverse.Packets.")
|
||||
if (
|
||||
packet_owner
|
||||
and item["name"] == "FromBytes"
|
||||
and len(mapped_parameters) == 4
|
||||
and item["doc_id"].endswith(",System.Byte[])")
|
||||
):
|
||||
mapped_parameters[3] = ("optional_mutable_borrow", "zero_buffer: Option<&mut [u8]>")
|
||||
if packet_owner and item["name"] in {"AcksToBytes", "ToBytes"} and mapped_parameters:
|
||||
if owner["doc_id"] == "T:LibreMetaverse.Packets.Header" or owner["doc_id"].endswith("Block"):
|
||||
mapped_parameters[0] = ("mutable_borrow", "bytes: &mut [u8]")
|
||||
own = [value[0] for value in mapped_parameters]
|
||||
parameters = ", ".join(value[1] for value in mapped_parameters)
|
||||
kind = item["kind"]
|
||||
|
||||
Reference in New Issue
Block a user