Implement generated packet wire codecs (#47)
Some checks failed
Native code generation / deterministic (push) Successful in 4m29s
Imaging and meshing gate / native (push) Failing after 16s
JPEG 2000 feature / linux (push) Failing after 59s
Skia feature / linux (push) Failing after 1m37s

This commit is contained in:
2026-08-09 08:16:32 +00:00
parent 5bc22bdd83
commit 74801c3488
16 changed files with 87505 additions and 25470 deletions

View 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

View File

@@ -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)]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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());
}
}

View 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);
}