Implement generated packet wire codecs (#47)
This commit is contained in:
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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user