Implement bounded Binary LLSD codec

This commit is contained in:
2026-08-09 00:29:07 +00:00
parent d152e80ef9
commit 7a4f1f2fb1
17 changed files with 965 additions and 208 deletions

View File

@@ -0,0 +1,681 @@
//! Bounded Binary LLSD codec compatible with `LibreMetaverse`'s marker grammar.
#![allow(clippy::missing_errors_doc)] // Public Result shapes are fixed by the mapped API.
#![allow(clippy::needless_pass_by_value)] // Owned arguments mirror mapped C# value parameters.
#![allow(clippy::unnecessary_wraps)] // Result return shapes are fixed by the mapped API.
#![allow(clippy::unused_self)] // Parser error helpers retain local cursor context call sites.
use crate::{Error, OSD};
use libremetaverse_types::UUID;
use libremetaverse_types::compat::{ReadWrite, Uri};
use std::collections::HashMap;
use std::io::{Cursor, Read as _, Seek as _, SeekFrom};
use std::time::{Duration, UNIX_EPOCH};
const HEADER: &[u8] = b"<?llsd/binary?>";
const ALT_HEADER: &[u8] = b"<? llsd/binary ?>";
const MAX_INPUT_BYTES: usize = OSD::DEFAULT_MAX_BINARY_BYTES;
const fn is_binary_whitespace(byte: u8) -> bool {
matches!(byte, b' ' | b'\t' | b'\n' | b'\r')
}
pub(crate) fn deserialize_bytes(data: Vec<u8>) -> Result<OSD, Error> {
if data.len() > MAX_INPUT_BYTES {
return Err(parse_error(0, "binary LLSD input exceeds allocation limit"));
}
let mut parser = Parser::new(&data);
parser.skip_whitespace();
if parser.consume_prefix_ignore_ascii_case(ALT_HEADER)
|| parser.consume_prefix_ignore_ascii_case(HEADER)
{
parser.skip_whitespace();
}
let value = parser.parse_value(0)?;
value.validate_limits(
OSD::DEFAULT_MAX_DEPTH,
OSD::DEFAULT_MAX_NODES,
OSD::DEFAULT_MAX_BINARY_BYTES,
)?;
Ok(value)
}
pub(crate) fn deserialize_stream(mut stream: Box<dyn ReadWrite + Send>) -> Result<OSD, Error> {
let mut data = Vec::new();
(&mut *stream)
.take((MAX_INPUT_BYTES + 1) as u64)
.read_to_end(&mut data)
.map_err(|_| Error::InvalidOperation)?;
deserialize_bytes(data)
}
pub(crate) fn serialize(value: OSD) -> Result<Vec<u8>, Error> {
serialize_with_header(value, true)
}
pub(crate) fn serialize_with_header(value: OSD, prepend_header: bool) -> Result<Vec<u8>, Error> {
value.validate_limits(
OSD::DEFAULT_MAX_DEPTH,
OSD::DEFAULT_MAX_NODES,
OSD::DEFAULT_MAX_BINARY_BYTES,
)?;
let mut encoder = Encoder::new();
if prepend_header {
encoder.extend(HEADER)?;
encoder.push(b'\n')?;
}
encoder.write_value(&value, 0)?;
Ok(encoder.finish())
}
pub(crate) fn serialize_stream(value: OSD) -> Result<Cursor<Vec<u8>>, Error> {
serialize_stream_with_header(value, true)
}
pub(crate) fn serialize_stream_with_header(
value: OSD,
prepend_header: bool,
) -> Result<Cursor<Vec<u8>>, Error> {
let bytes = serialize_with_header(value, prepend_header)?;
let position = bytes.len() as u64;
let mut cursor = Cursor::new(bytes);
cursor.set_position(position);
Ok(cursor)
}
pub(crate) fn consume_bytes(
mut stream: Box<dyn ReadWrite + Send>,
consume_bytes: i32,
) -> Result<Vec<u8>, Error> {
let length = usize::try_from(consume_bytes).map_err(|_| Error::Argument)?;
if length > MAX_INPUT_BYTES {
return Err(Error::Argument);
}
let mut bytes = vec![0_u8; length];
stream
.read_exact(&mut bytes)
.map_err(|_| Error::IndexOutOfRange)?;
Ok(bytes)
}
pub(crate) fn find_byte(mut stream: Box<dyn ReadWrite + Send>, to_find: u8) -> Result<bool, Error> {
let start = stream
.stream_position()
.map_err(|_| Error::InvalidOperation)?;
let mut byte = [0_u8; 1];
if stream
.read(&mut byte)
.map_err(|_| Error::InvalidOperation)?
== 0
{
return Ok(false);
}
if byte[0] == to_find {
Ok(true)
} else {
stream
.seek(SeekFrom::Start(start))
.map_err(|_| Error::InvalidOperation)?;
Ok(false)
}
}
pub(crate) fn find_string(
mut stream: Box<dyn ReadWrite + Send>,
to_find: String,
) -> Result<bool, Error> {
let start = stream
.stream_position()
.map_err(|_| Error::InvalidOperation)?;
let expected = to_find.as_bytes();
if expected.len() > MAX_INPUT_BYTES {
return Err(Error::Argument);
}
let mut actual = vec![0_u8; expected.len()];
let matched = stream.read_exact(&mut actual).is_ok()
&& actual
.iter()
.zip(expected)
.all(|(left, right)| left.eq_ignore_ascii_case(right));
if !matched {
stream
.seek(SeekFrom::Start(start))
.map_err(|_| Error::InvalidOperation)?;
}
Ok(matched)
}
pub(crate) fn skip_whitespace(mut stream: Box<dyn ReadWrite + Send>) -> Result<(), Error> {
loop {
let start = stream
.stream_position()
.map_err(|_| Error::InvalidOperation)?;
let mut byte = [0_u8; 1];
if stream
.read(&mut byte)
.map_err(|_| Error::InvalidOperation)?
== 0
{
return Ok(());
}
if !is_binary_whitespace(byte[0]) {
stream
.seek(SeekFrom::Start(start))
.map_err(|_| Error::InvalidOperation)?;
return Ok(());
}
}
}
pub(crate) fn host_to_network_int_bytes(value: i32) -> Result<Vec<u8>, Error> {
Ok(value.to_be_bytes().to_vec())
}
pub(crate) fn network_to_host_int(bytes: Vec<u8>) -> Result<i32, Error> {
let bytes: [u8; 4] = bytes
.get(..4)
.ok_or(Error::IndexOutOfRange)?
.try_into()
.map_err(|_| Error::IndexOutOfRange)?;
Ok(i32::from_be_bytes(bytes))
}
pub(crate) fn network_to_host_double(bytes: Vec<u8>) -> Result<f64, Error> {
let bytes: [u8; 8] = bytes
.get(..8)
.ok_or(Error::IndexOutOfRange)?
.try_into()
.map_err(|_| Error::IndexOutOfRange)?;
Ok(f64::from_be_bytes(bytes))
}
struct Parser<'a> {
bytes: &'a [u8],
position: usize,
nodes: usize,
allocated: usize,
}
impl<'a> Parser<'a> {
const fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
position: 0,
nodes: 0,
allocated: 0,
}
}
fn parse_value(&mut self, depth: usize) -> Result<OSD, Error> {
if depth > OSD::DEFAULT_MAX_DEPTH {
return Err(self.error("binary LLSD nesting depth exceeded"));
}
self.nodes = self
.nodes
.checked_add(1)
.ok_or_else(|| self.error("binary LLSD node count overflow"))?;
if self.nodes > OSD::DEFAULT_MAX_NODES {
return Err(self.error("binary LLSD node limit exceeded"));
}
self.skip_whitespace();
let marker_position = self.position;
let marker = self.read_byte("missing binary LLSD value marker")?;
match marker {
b'!' => Ok(OSD::Undefined),
b'1' => Ok(OSD::Boolean(true)),
b'0' => Ok(OSD::Boolean(false)),
b'i' => Ok(OSD::Integer(i32::from_be_bytes(
self.read_array("truncated binary LLSD integer")?,
))),
b'r' => Ok(OSD::Real(f64::from_be_bytes(
self.read_array("truncated binary LLSD real")?,
))),
b'u' => {
let bytes = self.read_exact(16, "truncated binary LLSD UUID")?;
Ok(OSD::UUID(UUID::new_with_bytes_int32(bytes.to_vec(), 0)?))
}
b'b' => {
let length = self.read_length("invalid binary LLSD binary length")?;
self.add_allocation(length, "binary LLSD binary allocation limit exceeded")?;
Ok(OSD::Binary(
self.read_exact(length, "truncated binary LLSD binary value")?
.to_vec(),
))
}
b's' => {
let bytes = self.read_sized_text("string")?;
Ok(OSD::String(String::from_utf8_lossy(bytes).into_owned()))
}
b'l' => {
let bytes = self.read_sized_text("URI")?;
let text = String::from_utf8_lossy(bytes).into_owned();
let uri = OSD::String(text.clone())
.as_uri()?
.ok_or_else(|| self.error_at(marker_position, "invalid binary LLSD URI"))?;
Ok(OSD::Uri(uri))
}
b'd' => {
let timestamp = f64::from_le_bytes(self.read_array("truncated binary LLSD date")?);
Ok(OSD::Date(system_time_from_seconds(timestamp).ok_or_else(
|| self.error_at(marker_position, "invalid binary LLSD date"),
)?))
}
b'[' => self.parse_array(depth),
b'{' => self.parse_map(depth),
_ => Err(self.error_at(marker_position, "unknown binary LLSD type marker")),
}
}
fn parse_array(&mut self, depth: usize) -> Result<OSD, Error> {
let count = self.read_length("invalid binary LLSD array count")?;
if count > OSD::DEFAULT_MAX_NODES || count > self.remaining() {
return Err(self.error("binary LLSD array count exceeds available input"));
}
let allocation = count
.checked_mul(std::mem::size_of::<OSD>())
.ok_or_else(|| self.error("binary LLSD array allocation overflow"))?;
self.add_allocation(allocation, "binary LLSD array allocation limit exceeded")?;
let mut values = Vec::with_capacity(count);
for _ in 0..count {
values.push(self.parse_value(depth + 1)?);
}
self.expect_byte(b']', "missing binary LLSD array end marker")?;
Ok(OSD::Array(values))
}
fn parse_map(&mut self, depth: usize) -> Result<OSD, Error> {
let count = self.read_length("invalid binary LLSD map count")?;
if count > OSD::DEFAULT_MAX_NODES || count > self.remaining() {
return Err(self.error("binary LLSD map count exceeds available input"));
}
let allocation = count
.checked_mul(std::mem::size_of::<(String, OSD)>())
.ok_or_else(|| self.error("binary LLSD map allocation overflow"))?;
self.add_allocation(allocation, "binary LLSD map allocation limit exceeded")?;
let mut values = HashMap::with_capacity(count);
for _ in 0..count {
self.expect_byte(b'k', "missing binary LLSD map key marker")?;
let key_bytes = self.read_sized_text("map key")?;
let key = String::from_utf8_lossy(key_bytes).into_owned();
let value = self.parse_value(depth + 1)?;
values.insert(key, value);
}
self.expect_byte(b'}', "missing binary LLSD map end marker")?;
Ok(OSD::Map(values))
}
fn read_sized_text(&mut self, context: &'static str) -> Result<&'a [u8], Error> {
let length = self.read_length(match context {
"string" => "invalid binary LLSD string length",
"URI" => "invalid binary LLSD URI length",
_ => "invalid binary LLSD map key length",
})?;
self.add_allocation(length, "binary LLSD text allocation limit exceeded")?;
self.read_exact(
length,
match context {
"string" => "truncated binary LLSD string",
"URI" => "truncated binary LLSD URI",
_ => "truncated binary LLSD map key",
},
)
}
fn read_length(&mut self, context: &'static str) -> Result<usize, Error> {
let position = self.position;
let raw = i32::from_be_bytes(self.read_array(context)?);
usize::try_from(raw).map_err(|_| self.error_at(position, context))
}
fn read_array<const N: usize>(&mut self, context: &'static str) -> Result<[u8; N], Error> {
self.read_exact(N, context)?
.try_into()
.map_err(|_| self.error(context))
}
fn read_exact(&mut self, length: usize, context: &'static str) -> Result<&'a [u8], Error> {
let end = self
.position
.checked_add(length)
.ok_or_else(|| self.error(context))?;
let value = self
.bytes
.get(self.position..end)
.ok_or_else(|| self.error(context))?;
self.position = end;
Ok(value)
}
fn read_byte(&mut self, context: &'static str) -> Result<u8, Error> {
let byte = *self
.bytes
.get(self.position)
.ok_or_else(|| self.error(context))?;
self.position += 1;
Ok(byte)
}
fn expect_byte(&mut self, expected: u8, context: &'static str) -> Result<(), Error> {
let position = self.position;
if self.read_byte(context)? == expected {
Ok(())
} else {
Err(self.error_at(position, context))
}
}
fn add_allocation(&mut self, amount: usize, context: &'static str) -> Result<(), Error> {
self.allocated = self
.allocated
.checked_add(amount)
.ok_or_else(|| self.error(context))?;
if self.allocated > OSD::DEFAULT_MAX_BINARY_BYTES {
Err(self.error(context))
} else {
Ok(())
}
}
fn skip_whitespace(&mut self) {
while self
.bytes
.get(self.position)
.is_some_and(|byte| is_binary_whitespace(*byte))
{
self.position += 1;
}
}
fn consume_prefix_ignore_ascii_case(&mut self, prefix: &[u8]) -> bool {
let Some(candidate) = self.bytes.get(self.position..self.position + prefix.len()) else {
return false;
};
if candidate.eq_ignore_ascii_case(prefix) {
self.position += prefix.len();
true
} else {
false
}
}
const fn remaining(&self) -> usize {
self.bytes.len() - self.position
}
const fn error(&self, context: &'static str) -> Error {
self.error_at(self.position, context)
}
const fn error_at(&self, position: usize, context: &'static str) -> Error {
parse_error(position, context)
}
}
struct Encoder {
bytes: Vec<u8>,
}
impl Encoder {
fn new() -> Self {
Self {
bytes: Vec::with_capacity(128),
}
}
fn finish(self) -> Vec<u8> {
self.bytes
}
fn write_value(&mut self, value: &OSD, depth: usize) -> Result<(), Error> {
if depth > OSD::DEFAULT_MAX_DEPTH {
return Err(parse_error(
self.bytes.len(),
"binary LLSD nesting depth exceeded",
));
}
match value {
OSD::Undefined => self.push(b'!'),
OSD::Boolean(value) => self.push(if *value { b'1' } else { b'0' }),
OSD::Integer(value) => {
self.push(b'i')?;
self.extend(&value.to_be_bytes())
}
OSD::Real(value) => {
self.push(b'r')?;
self.extend(&value.to_be_bytes())
}
OSD::UUID(value) => {
self.push(b'u')?;
self.extend(&value.get_bytes()?)
}
OSD::String(value) => {
self.push(b's')?;
self.write_sized(value.as_bytes())
}
OSD::Binary(value) => {
self.push(b'b')?;
self.write_sized(value)
}
OSD::Date(value) => {
self.push(b'd')?;
self.extend(&crate::model::unix_seconds_for_codec(*value).to_le_bytes())
}
OSD::Uri(Uri(value)) => {
self.push(b'l')?;
self.write_sized(value.as_bytes())
}
OSD::Array(values) => {
self.push(b'[')?;
self.write_length(values.len())?;
for value in values {
self.write_value(value, depth + 1)?;
}
self.push(b']')
}
OSD::Map(values) => {
self.push(b'{')?;
self.write_length(values.len())?;
let mut entries: Vec<_> = values.iter().collect();
entries.sort_unstable_by_key(|(key, _)| *key);
for (key, value) in entries {
self.push(b'k')?;
self.write_sized(key.as_bytes())?;
self.write_value(value, depth + 1)?;
}
self.push(b'}')
}
OSD::LlsdXml(_) => Err(parse_error(
self.bytes.len(),
"LLSD XML fragments have no Binary LLSD marker",
)),
}
}
fn write_sized(&mut self, value: &[u8]) -> Result<(), Error> {
self.write_length(value.len())?;
self.extend(value)
}
fn write_length(&mut self, length: usize) -> Result<(), Error> {
let length = i32::try_from(length).map_err(|_| Error::Argument)?;
self.extend(&length.to_be_bytes())
}
fn push(&mut self, value: u8) -> Result<(), Error> {
if self.bytes.len() >= MAX_INPUT_BYTES {
return Err(Error::Argument);
}
self.bytes.push(value);
Ok(())
}
fn extend(&mut self, value: &[u8]) -> Result<(), Error> {
let length = self
.bytes
.len()
.checked_add(value.len())
.ok_or(Error::Argument)?;
if length > MAX_INPUT_BYTES {
return Err(Error::Argument);
}
self.bytes.extend_from_slice(value);
Ok(())
}
}
fn system_time_from_seconds(seconds: f64) -> Option<std::time::SystemTime> {
if !seconds.is_finite() {
return None;
}
if seconds >= 0.0 {
UNIX_EPOCH.checked_add(Duration::try_from_secs_f64(seconds).ok()?)
} else {
UNIX_EPOCH.checked_sub(Duration::try_from_secs_f64(-seconds).ok()?)
}
}
const fn parse_error(position: usize, context: &'static str) -> Error {
Error::Parse { position, context }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_variant_round_trips_with_header_and_nested_values() {
let uuid = UUID::new_with_string("97f4aeca-88a1-42a1-b385-b97b18abb255".into()).unwrap();
let value = OSD::Array(vec![
OSD::Undefined,
OSD::Boolean(true),
OSD::Integer(-42),
OSD::Real(12.5),
OSD::String("text 𐄷".into()),
OSD::UUID(uuid),
OSD::Date(UNIX_EPOCH + Duration::from_millis(1_199_218_231_125)),
OSD::Uri(Uri("https://example.test/a".into())),
OSD::Binary(vec![0, 1, 255]),
OSD::Map(HashMap::from([("nested".into(), OSD::Array(vec![]))])),
]);
let encoded = serialize(value.clone()).unwrap();
assert!(encoded.starts_with(b"<?llsd/binary?>\n"));
assert_eq!(deserialize_bytes(encoded).unwrap(), value);
assert_eq!(
deserialize_bytes(b" \t\r\n<? LlSd/BiNaRy ?>\r\ni\0\0\0\x07".to_vec()).unwrap(),
OSD::Integer(7)
);
assert!(matches!(
deserialize_bytes(b"\x0b!".to_vec()),
Err(Error::Parse {
position: 0,
context: "unknown binary LLSD type marker"
})
));
let mut encoded_stream = serialize_stream_with_header(value.clone(), false).unwrap();
assert_eq!(
encoded_stream.position(),
encoded_stream.get_ref().len() as u64
);
encoded_stream.set_position(0);
assert_eq!(deserialize_stream(Box::new(encoded_stream)).unwrap(), value);
assert_eq!(
host_to_network_int_bytes(-42).unwrap(),
(-42_i32).to_be_bytes()
);
assert_eq!(
network_to_host_int((-42_i32).to_be_bytes().to_vec()).unwrap(),
-42
);
assert_eq!(
network_to_host_double(12.5_f64.to_be_bytes().to_vec())
.unwrap()
.to_bits(),
12.5_f64.to_bits()
);
}
#[test]
fn malformed_seed_corpus_is_rejected_with_positions() {
let expected: &[(&str, &str)] = &[
("empty", "missing binary LLSD value marker"),
("unknown_marker", "unknown binary LLSD type marker"),
("truncated_integer", "truncated binary LLSD integer"),
("truncated_real", "truncated binary LLSD real"),
("truncated_uuid", "truncated binary LLSD UUID"),
(
"negative_binary_length",
"invalid binary LLSD binary length",
),
(
"truncated_binary_length",
"invalid binary LLSD binary length",
),
(
"negative_string_length",
"invalid binary LLSD string length",
),
("negative_array_count", "invalid binary LLSD array count"),
("negative_map_count", "invalid binary LLSD map count"),
(
"oversized_string_length",
"binary LLSD text allocation limit exceeded",
),
("missing_array_end", "missing binary LLSD array end marker"),
(
"missing_map_key_marker",
"missing binary LLSD map key marker",
),
("missing_map_end", "missing binary LLSD map end marker"),
("non_finite_date", "invalid binary LLSD date"),
];
let corpus = include_str!("../../../fuzz/corpus/binary_llsd/malformed.hex");
let seeds: HashMap<_, _> = corpus
.lines()
.map(|line| {
let (name, hex) = line.split_once(':').expect("named fuzz seed");
(name, decode_hex(hex))
})
.collect();
assert_eq!(seeds.len(), expected.len());
for (name, expected_context) in expected {
let seed = seeds.get(name).expect("fuzz seed named by test");
let Error::Parse { position, context } = deserialize_bytes(seed.clone()).unwrap_err()
else {
panic!("malformed seed did not return a positional parse error");
};
assert!(position <= seed.len());
assert_eq!(context, *expected_context);
}
}
fn decode_hex(value: &str) -> Vec<u8> {
assert_eq!(value.len() % 2, 0, "fuzz seed hex must contain byte pairs");
value
.as_bytes()
.chunks_exact(2)
.map(|pair| {
let text = std::str::from_utf8(pair).expect("ASCII fuzz seed hex");
u8::from_str_radix(text, 16).expect("valid fuzz seed hex")
})
.collect()
}
#[test]
fn excessive_depth_is_rejected_before_stack_growth() {
let mut bytes = Vec::new();
for _ in 0..=OSD::DEFAULT_MAX_DEPTH {
bytes.extend_from_slice(b"[\0\0\0\x01");
}
bytes.push(b'!');
bytes.extend(std::iter::repeat_n(b']', OSD::DEFAULT_MAX_DEPTH + 1));
assert!(matches!(
deserialize_bytes(bytes),
Err(Error::Parse {
context: "binary LLSD nesting depth exceeded",
..
})
));
}
}

View File

@@ -156,11 +156,11 @@ mod tests {
deserialize_bytes(b"Unknown conference".to_vec()),
Err(Error::Argument)
);
assert_eq!(
deserialize_bytes(b"<? llsd/binary ?>i\0\0\0\0".to_vec()),
Ok(OSD::Integer(0))
);
let cases: &[(&[u8], &str)] = &[
(
b"<? llsd/binary ?>i\0\0\0\0",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDBinary(System.Byte[])",
),
(
b"<? llsd/protobuf ?>",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDProtobuf(System.Byte[])",

View File

@@ -251,9 +251,7 @@ impl OSDParser {
stream: Box<dyn libremetaverse_types::compat::ReadWrite + Send>,
consume_bytes: i32,
) -> Result<Vec<u8>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.ConsumeBytes(System.IO.Stream,System.Int32)",
)
crate::binary::consume_bytes(stream, consume_bytes)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.Byte[])`.
pub fn deserialize_with_bytes(
@@ -293,17 +291,13 @@ impl OSDParser {
pub fn deserialize_llsd_binary_with_bytes(
binary_data: Vec<u8>,
) -> Result<libremetaverse_structured_data::OSD, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDBinary(System.Byte[])",
)
crate::binary::deserialize_bytes(binary_data)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDBinary(System.IO.Stream)`.
pub fn deserialize_llsd_binary_with_stream(
stream: Box<dyn libremetaverse_types::compat::ReadWrite + Send>,
) -> Result<libremetaverse_structured_data::OSD, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDBinary(System.IO.Stream)",
)
crate::binary::deserialize_stream(stream)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDNotation(System.IO.StringReader)`.
pub fn deserialize_llsd_notation_with_string_reader(
@@ -383,18 +377,14 @@ impl OSDParser {
stream: Box<dyn libremetaverse_types::compat::ReadWrite + Send>,
to_find: u8,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.FindByte(System.IO.Stream,System.Byte)",
)
crate::binary::find_byte(stream, to_find)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.FindString(System.IO.Stream,System.String)`.
pub fn find_string(
stream: Box<dyn libremetaverse_types::compat::ReadWrite + Send>,
to_find: String,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.FindString(System.IO.Stream,System.String)",
)
crate::binary::find_string(stream, to_find)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.GetLengthInBrackets(System.IO.StringReader)`.
pub fn get_length_in_brackets(
@@ -415,21 +405,15 @@ impl OSDParser {
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.HostToNetworkIntBytes(System.Int32)`.
pub fn host_to_network_int_bytes(int_host_end: i32) -> Result<Vec<u8>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.HostToNetworkIntBytes(System.Int32)",
)
crate::binary::host_to_network_int_bytes(int_host_end)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.NetworkToHostDouble(System.Byte[])`.
pub fn network_to_host_double(binary_net_end: Vec<u8>) -> Result<f64, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.NetworkToHostDouble(System.Byte[])",
)
crate::binary::network_to_host_double(binary_net_end)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.NetworkToHostInt(System.Byte[])`.
pub fn network_to_host_int(binary_net_end: Vec<u8>) -> Result<i32, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.NetworkToHostInt(System.Byte[])",
)
crate::binary::network_to_host_int(binary_net_end)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.PeekAndSkipWhitespace(System.IO.StringReader)`.
pub fn peek_and_skip_whitespace(
@@ -460,35 +444,27 @@ impl OSDParser {
pub fn serialize_llsd_binary_with_osd(
osd: libremetaverse_structured_data::OSD,
) -> Result<Vec<u8>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDBinary(LibreMetaverse.StructuredData.OSD)",
)
crate::binary::serialize(osd)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDBinary(LibreMetaverse.StructuredData.OSD,System.Boolean)`.
pub fn serialize_llsd_binary_with_osd_boolean(
osd: libremetaverse_structured_data::OSD,
prepend_header: bool,
) -> Result<Vec<u8>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDBinary(LibreMetaverse.StructuredData.OSD,System.Boolean)",
)
crate::binary::serialize_with_header(osd, prepend_header)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDBinaryStream(LibreMetaverse.StructuredData.OSD)`.
pub fn serialize_llsd_binary_stream_with_osd(
data: libremetaverse_structured_data::OSD,
) -> Result<std::io::Cursor<Vec<u8>>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDBinaryStream(LibreMetaverse.StructuredData.OSD)",
)
crate::binary::serialize_stream(data)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDBinaryStream(LibreMetaverse.StructuredData.OSD,System.Boolean)`.
pub fn serialize_llsd_binary_stream_with_osd_boolean(
data: libremetaverse_structured_data::OSD,
prepend_header: bool,
) -> Result<std::io::Cursor<Vec<u8>>, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDBinaryStream(LibreMetaverse.StructuredData.OSD,System.Boolean)",
)
crate::binary::serialize_stream_with_header(data, prepend_header)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDInnerXmlString(LibreMetaverse.StructuredData.OSD)`.
pub fn serialize_llsd_inner_xml_string(
@@ -568,9 +544,7 @@ impl OSDParser {
pub fn skip_white_space(
stream: Box<dyn libremetaverse_types::compat::ReadWrite + Send>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.StructuredData.OSDParser.SkipWhiteSpace(System.IO.Stream)",
)
crate::binary::skip_whitespace(stream)
}
/// C# member: `M:LibreMetaverse.StructuredData.OSDParser.UnescapeCharacter(System.String,System.Char)`.
pub fn unescape_character(

View File

@@ -2,6 +2,7 @@
extern crate self as libremetaverse_structured_data;
mod binary;
mod dispatch;
mod generated;
mod model;

View File

@@ -188,7 +188,7 @@ impl OSD {
Self::Real(value) => value.to_be_bytes().to_vec(),
Self::String(value) | Self::LlsdXml(value) => value.as_bytes().to_vec(),
Self::UUID(value) => value.get_bytes()?,
Self::Date(value) => unix_seconds_f64(*value).to_le_bytes().to_vec(),
Self::Date(value) => unix_seconds_for_codec(*value).to_le_bytes().to_vec(),
Self::Uri(value) => value.0.as_bytes().to_vec(),
Self::Binary(value) => value.clone(),
Self::Array(values) => values
@@ -842,7 +842,7 @@ impl OSDUUID {
scalar_wrapper!(OSDDate, SystemTime, Date, Date);
impl OSDDate {
pub fn as_binary(&self) -> Result<Vec<u8>, Error> {
Ok(unix_seconds_f64(self.value).to_le_bytes().to_vec())
Ok(unix_seconds_for_codec(self.value).to_le_bytes().to_vec())
}
pub const fn as_date(&self) -> Result<SystemTime, Error> {
Ok(self.value)
@@ -1425,7 +1425,7 @@ fn format_real(value: f64) -> String {
value.to_string()
}
}
fn unix_seconds_f64(value: SystemTime) -> f64 {
pub(crate) fn unix_seconds_for_codec(value: SystemTime) -> f64 {
match value.duration_since(UNIX_EPOCH) {
Ok(duration) => duration.as_secs_f64(),
Err(error) => -error.duration().as_secs_f64(),