Implement StructuredData OSD model and containers

This commit is contained in:
2026-08-09 00:10:12 +00:00
parent ae0b7966e2
commit d152e80ef9
15 changed files with 2408 additions and 1456 deletions

1
Cargo.lock generated
View File

@@ -272,6 +272,7 @@ dependencies = [
name = "libremetaverse-structured-data" name = "libremetaverse-structured-data"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"base64",
"libremetaverse-types", "libremetaverse-types",
] ]

View File

@@ -73,5 +73,12 @@ hierarchical token-bucket throttling are native Rust as well. Cache and bucket
tests use injected monotonic clocks, and concurrency invariants run without tests use injected monotonic clocks, and concurrency invariants run without
sleep-based timing. All 45 Types and all 942 mapped members now resolve to sleep-based timing. All 45 Types and all 942 mapped members now resolve to
native implementations; no generated Types shims remain. native implementations; no generated Types shims remain.
Milestone 05 now has a native, format-neutral OSD value model with structural
equality and hashing, permissive `LibreMetaverse` conversion defaults, explicit
integer/date byte order, synchronized snapshot-based arrays and maps, and
bounded parser dispatch. OSD map order is intentionally unspecified at the
public model boundary; encoders must choose and document any stable ordering
they require. Untrusted dispatch limits input bytes, nesting depth, decoded
nodes, and aggregate binary allocation before returning a value.
The controlled audit aggregates every expected failure by standardized C# The controlled audit aggregates every expected failure by standardized C#
member ID and rejects unrelated fixture, assertion, compile, or symbol errors. member ID and rejects unrelated fixture, assertion, compile, or symbol errors.

View File

@@ -24778,7 +24778,7 @@ M:LibreMetaverse.StructuredData.OSDArray.Clear method System.Void LibreMetaverse
M:LibreMetaverse.StructuredData.OSDArray.Contains(LibreMetaverse.StructuredData.OSD) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Contains(LibreMetaverse.StructuredData.OSD llsd) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::contains_with_osd pub fn contains_with_osd(&self, llsd: libremetaverse_structured_data::OSD) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped M:LibreMetaverse.StructuredData.OSDArray.Contains(LibreMetaverse.StructuredData.OSD) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Contains(LibreMetaverse.StructuredData.OSD llsd) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::contains_with_osd pub fn contains_with_osd(&self, llsd: libremetaverse_structured_data::OSD) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
M:LibreMetaverse.StructuredData.OSDArray.Contains(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Contains(System.String element) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::contains_with_string pub fn contains_with_string(&self, element: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped M:LibreMetaverse.StructuredData.OSDArray.Contains(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Contains(System.String element) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::contains_with_string pub fn contains_with_string(&self, element: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
M:LibreMetaverse.StructuredData.OSDArray.Copy method LibreMetaverse.StructuredData.OSD LibreMetaverse.StructuredData.OSDArray.Copy() libremetaverse-structured-data libremetaverse_structured_data::OSDArray::copy pub fn copy(&self) -> Result<libremetaverse_structured_data::OSD, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.StructuredData.OSDArray.Copy method LibreMetaverse.StructuredData.OSD LibreMetaverse.StructuredData.OSDArray.Copy() libremetaverse-structured-data libremetaverse_structured_data::OSDArray::copy pub fn copy(&self) -> Result<libremetaverse_structured_data::OSD, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped
M:LibreMetaverse.StructuredData.OSDArray.CopyTo(LibreMetaverse.StructuredData.OSD[],System.Int32) method System.Void LibreMetaverse.StructuredData.OSDArray.CopyTo(LibreMetaverse.StructuredData.OSD[] array, System.Int32 index) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::copy_to pub fn copy_to(&self, array: Vec<libremetaverse_structured_data::OSD>, index: i32) -> Result<(), crate::Error> shared_self,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.StructuredData.OSDArray.CopyTo(LibreMetaverse.StructuredData.OSD[],System.Int32) method System.Void LibreMetaverse.StructuredData.OSDArray.CopyTo(LibreMetaverse.StructuredData.OSD[] array, System.Int32 index) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::copy_to pub fn copy_to(&self, array: &mut [libremetaverse_structured_data::OSD], index: i32) -> Result<(), crate::Error> shared_self,mutable_borrow,owned sync Result<_, crate::Error> direct_snake_case method mapped
M:LibreMetaverse.StructuredData.OSDArray.IndexOf(LibreMetaverse.StructuredData.OSD) method System.Int32 LibreMetaverse.StructuredData.OSDArray.IndexOf(LibreMetaverse.StructuredData.OSD item) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::index_of pub fn index_of(&self, item: libremetaverse_structured_data::OSD) -> Result<i32, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.StructuredData.OSDArray.IndexOf(LibreMetaverse.StructuredData.OSD) method System.Int32 LibreMetaverse.StructuredData.OSDArray.IndexOf(LibreMetaverse.StructuredData.OSD item) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::index_of pub fn index_of(&self, item: libremetaverse_structured_data::OSD) -> Result<i32, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
M:LibreMetaverse.StructuredData.OSDArray.Insert(System.Int32,LibreMetaverse.StructuredData.OSD) method System.Void LibreMetaverse.StructuredData.OSDArray.Insert(System.Int32 index, LibreMetaverse.StructuredData.OSD item) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::insert pub fn insert(&self, index: i32, item: libremetaverse_structured_data::OSD) -> Result<(), crate::Error> shared_self,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.StructuredData.OSDArray.Insert(System.Int32,LibreMetaverse.StructuredData.OSD) method System.Void LibreMetaverse.StructuredData.OSDArray.Insert(System.Int32 index, LibreMetaverse.StructuredData.OSD item) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::insert pub fn insert(&self, index: i32, item: libremetaverse_structured_data::OSD) -> Result<(), crate::Error> shared_self,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped
M:LibreMetaverse.StructuredData.OSDArray.Remove(LibreMetaverse.StructuredData.OSD) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Remove(LibreMetaverse.StructuredData.OSD llsd) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::remove pub fn remove(&self, llsd: libremetaverse_structured_data::OSD) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.StructuredData.OSDArray.Remove(LibreMetaverse.StructuredData.OSD) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Remove(LibreMetaverse.StructuredData.OSD llsd) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::remove pub fn remove(&self, llsd: libremetaverse_structured_data::OSD) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
@@ -24843,7 +24843,7 @@ M:LibreMetaverse.StructuredData.OSDMap.Clear method System.Void LibreMetaverse.S
M:LibreMetaverse.StructuredData.OSDMap.Contains(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Contains(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD> kvp) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::contains pub fn contains(&self, kvp: (String, libremetaverse_structured_data::OSD)) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.StructuredData.OSDMap.Contains(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Contains(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD> kvp) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::contains pub fn contains(&self, kvp: (String, libremetaverse_structured_data::OSD)) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
M:LibreMetaverse.StructuredData.OSDMap.ContainsKey(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDMap.ContainsKey(System.String key) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::contains_key pub fn contains_key(&self, key: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.StructuredData.OSDMap.ContainsKey(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDMap.ContainsKey(System.String key) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::contains_key pub fn contains_key(&self, key: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
M:LibreMetaverse.StructuredData.OSDMap.Copy method LibreMetaverse.StructuredData.OSD LibreMetaverse.StructuredData.OSDMap.Copy() libremetaverse-structured-data libremetaverse_structured_data::OSDMap::copy pub fn copy(&self) -> Result<libremetaverse_structured_data::OSD, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.StructuredData.OSDMap.Copy method LibreMetaverse.StructuredData.OSD LibreMetaverse.StructuredData.OSDMap.Copy() libremetaverse-structured-data libremetaverse_structured_data::OSDMap::copy pub fn copy(&self) -> Result<libremetaverse_structured_data::OSD, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped
M:LibreMetaverse.StructuredData.OSDMap.CopyTo(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}[],System.Int32) method System.Void LibreMetaverse.StructuredData.OSDMap.CopyTo(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD>[] array, System.Int32 index) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::copy_to pub fn copy_to(&self, array: Vec<(String, libremetaverse_structured_data::OSD)>, index: i32) -> Result<(), crate::Error> shared_self,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.StructuredData.OSDMap.CopyTo(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}[],System.Int32) method System.Void LibreMetaverse.StructuredData.OSDMap.CopyTo(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD>[] array, System.Int32 index) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::copy_to pub fn copy_to(&self, array: &mut [(String, libremetaverse_structured_data::OSD)], index: i32) -> Result<(), crate::Error> shared_self,mutable_borrow,owned sync Result<_, crate::Error> direct_snake_case method mapped
M:LibreMetaverse.StructuredData.OSDMap.GetEnumerator method System.Collections.IDictionaryEnumerator LibreMetaverse.StructuredData.OSDMap.GetEnumerator() libremetaverse-structured-data libremetaverse_structured_data::OSDMap::get_enumerator pub fn get_enumerator(&self) -> Result<libremetaverse_types::compat::IDictionaryEnumerator, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.StructuredData.OSDMap.GetEnumerator method System.Collections.IDictionaryEnumerator LibreMetaverse.StructuredData.OSDMap.GetEnumerator() libremetaverse-structured-data libremetaverse_structured_data::OSDMap::get_enumerator pub fn get_enumerator(&self) -> Result<libremetaverse_types::compat::IDictionaryEnumerator, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped
M:LibreMetaverse.StructuredData.OSDMap.Remove(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Remove(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD> kvp) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::remove_with_key_value_pair pub fn remove_with_key_value_pair(&self, kvp: (String, libremetaverse_structured_data::OSD)) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped M:LibreMetaverse.StructuredData.OSDMap.Remove(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Remove(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD> kvp) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::remove_with_key_value_pair pub fn remove_with_key_value_pair(&self, kvp: (String, libremetaverse_structured_data::OSD)) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
M:LibreMetaverse.StructuredData.OSDMap.Remove(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Remove(System.String key) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::remove_with_string pub fn remove_with_string(&self, key: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped M:LibreMetaverse.StructuredData.OSDMap.Remove(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Remove(System.String key) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::remove_with_string pub fn remove_with_string(&self, key: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
Can't render this file because it is too large.

View File

@@ -12,7 +12,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.
| `LibreMetaverse.RLV` | 28 | 499 | callable failure-only shim | | `LibreMetaverse.RLV` | 28 | 499 | callable failure-only shim |
| `LibreMetaverse.Rendering.MeshFoundry` | 1 | 14 | callable failure-only shim | | `LibreMetaverse.Rendering.MeshFoundry` | 1 | 14 | callable failure-only shim |
| `LibreMetaverse.Rendering.Simple` | 1 | 6 | callable failure-only shim | | `LibreMetaverse.Rendering.Simple` | 1 | 6 | callable failure-only shim |
| `LibreMetaverse.StructuredData` | 16 | 295 | callable failure-only shim | | `LibreMetaverse.StructuredData` | 16 | 295 | native implementation: 15 types / 255 members; remaining surface is callable failure-only shims |
| `LibreMetaverse.Types` | 45 | 942 | native implementation: 45 types / 942 members; no generated shims remain | | `LibreMetaverse.Types` | 45 | 942 | native implementation: 45 types / 942 members; no generated shims remain |
| `LibreMetaverse.Utilities` | 3 | 13 | callable failure-only shim | | `LibreMetaverse.Utilities` | 3 | 13 | callable failure-only shim |
| `LibreMetaverse.Voice.Vivox` | 64 | 531 | callable failure-only shim | | `LibreMetaverse.Voice.Vivox` | 64 | 531 | callable failure-only shim |

View File

@@ -8,6 +8,7 @@ repository.workspace = true
description = "OSD and LLSD shims for the MetaCrate LibreMetaverse rewrite" description = "OSD and LLSD shims for the MetaCrate LibreMetaverse rewrite"
[dependencies] [dependencies]
base64 = "0.22"
libremetaverse-types = { path = "../libremetaverse-types" } libremetaverse-types = { path = "../libremetaverse-types" }
[lints] [lints]

View File

@@ -0,0 +1,194 @@
//! Bounded wire-format dispatch for the public `OSDParser.Deserialize` overloads.
use crate::{Error, OSD, OSDParser};
use libremetaverse_types::compat::ReadWrite;
use std::io::Read as _;
const PROBE_LENGTH: usize = 20;
const MAX_INPUT_BYTES: usize = OSD::DEFAULT_MAX_BINARY_BYTES;
const BINARY_HEADER: &str = "<? llsd/binary ?>";
const PROTOBUF_HEADER: &str = "<? llsd/protobuf ?>";
const XML_HEADERS: [&str; 3] = ["<llsd>", "<?xml", "<? llsd/xml ?>"];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum WireFormat {
Binary,
Json,
Protobuf,
Xml,
}
pub(crate) fn deserialize_bytes(data: Vec<u8>) -> Result<OSD, Error> {
if data.len() > MAX_INPUT_BYTES {
return Err(Error::Argument);
}
let format = classify_bytes(&data).ok_or(Error::Argument)?;
let value = match format {
WireFormat::Binary => OSDParser::deserialize_llsd_binary_with_bytes(data)?,
WireFormat::Protobuf => OSDParser::deserialize_llsd_protobuf_with_bytes(data)?,
WireFormat::Xml => OSDParser::deserialize_llsd_xml_with_bytes(data)?,
WireFormat::Json => OSDParser::deserialize_json_with_string(
String::from_utf8(data).map_err(|_| Error::Argument)?,
)?,
};
validate_decoded(value)
}
pub(crate) fn deserialize_string(data: String) -> Result<OSD, Error> {
if data.len() > MAX_INPUT_BYTES {
return Err(Error::Argument);
}
let value = match classify_string(&data) {
WireFormat::Binary => OSDParser::deserialize_llsd_binary_with_bytes(data.into_bytes())?,
WireFormat::Protobuf => OSDParser::deserialize_llsd_protobuf_with_bytes(data.into_bytes())?,
WireFormat::Xml => OSDParser::deserialize_llsd_xml_with_string(data)?,
WireFormat::Json => OSDParser::deserialize_json_with_string(data)?,
};
validate_decoded(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::Argument)?;
deserialize_bytes(data)
}
fn validate_decoded(value: OSD) -> Result<OSD, Error> {
value.validate_limits(
OSD::DEFAULT_MAX_DEPTH,
OSD::DEFAULT_MAX_NODES,
OSD::DEFAULT_MAX_BINARY_BYTES,
)?;
Ok(value)
}
fn classify_bytes(data: &[u8]) -> Option<WireFormat> {
let probe = &data[..data.len().min(PROBE_LENGTH)];
let ascii = String::from_utf8_lossy(probe);
let utf8 = ascii.trim_start_matches('\u{feff}').trim_start();
if starts_with_any_ignore_ascii_case(utf8, &XML_HEADERS) {
return Some(WireFormat::Xml);
}
if starts_with_ignore_ascii_case(&ascii, PROTOBUF_HEADER) {
return Some(WireFormat::Protobuf);
}
if starts_with_ignore_ascii_case(&ascii, BINARY_HEADER) {
return Some(WireFormat::Binary);
}
if starts_with_any_ignore_ascii_case(&ascii, &XML_HEADERS) {
return Some(WireFormat::Xml);
}
let first = utf8.as_bytes().first().copied()?;
if matches!(first, b'{' | b'[' | b'"' | b'-' | b'0'..=b'9')
|| utf8.starts_with("true")
|| utf8.starts_with("false")
|| utf8.starts_with("null")
{
Some(WireFormat::Json)
} else {
None
}
}
fn classify_string(data: &str) -> WireFormat {
if starts_with_ignore_ascii_case(data, PROTOBUF_HEADER) {
WireFormat::Protobuf
} else if starts_with_ignore_ascii_case(data, BINARY_HEADER) {
WireFormat::Binary
} else if starts_with_any_ignore_ascii_case(data, &XML_HEADERS) {
WireFormat::Xml
} else {
WireFormat::Json
}
}
fn starts_with_any_ignore_ascii_case(value: &str, prefixes: &[&str]) -> bool {
prefixes
.iter()
.any(|prefix| starts_with_ignore_ascii_case(value, prefix))
}
fn starts_with_ignore_ascii_case(value: &str, prefix: &str) -> bool {
value
.get(..prefix.len())
.is_some_and(|candidate| candidate.eq_ignore_ascii_case(prefix))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn byte_dispatch_recognizes_headers_bom_whitespace_and_json_tokens() {
assert_eq!(
classify_bytes(b"<? llsd/binary ?>i"),
Some(WireFormat::Binary)
);
assert_eq!(
classify_bytes(b"<? LLSD/PROTOBUF ?>"),
Some(WireFormat::Protobuf)
);
assert_eq!(
classify_bytes(b" \n<?xml version='1.0'"),
Some(WireFormat::Xml)
);
assert_eq!(
classify_bytes(b"\xef\xbb\xbf <llsd>"),
Some(WireFormat::Xml)
);
assert_eq!(classify_bytes(b" [1,2]"), Some(WireFormat::Json));
assert_eq!(classify_bytes(b"Unknown conference"), None);
}
#[test]
fn string_dispatch_matches_reference_fallback_to_json() {
assert_eq!(classify_string("<? llsd/binary ?>i"), WireFormat::Binary);
assert_eq!(classify_string("<LLSD>"), WireFormat::Xml);
assert_eq!(classify_string("plain text"), WireFormat::Json);
}
#[test]
fn public_byte_dispatch_rejects_unrecognized_plain_text() {
assert_eq!(
deserialize_bytes(b"Unknown conference".to_vec()),
Err(Error::Argument)
);
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[])",
),
(
b"<llsd><undef /></llsd>",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDXml(System.Byte[])",
),
(
b"{}",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeJson(System.String)",
),
];
for (input, expected_member) in cases {
assert_eq!(
deserialize_bytes(input.to_vec())
.expect_err("format decoder is intentionally owned by a later issue")
.csharp_member(),
Some(*expected_member)
);
}
let stream = Box::new(std::io::Cursor::new(b"{}".to_vec()));
assert_eq!(
deserialize_stream(stream)
.expect_err("JSON decoder is intentionally owned by issue #36")
.csharp_member(),
Some("M:LibreMetaverse.StructuredData.OSDParser.DeserializeJson(System.String)")
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,9 @@
extern crate self as libremetaverse_structured_data; extern crate self as libremetaverse_structured_data;
mod dispatch;
mod generated; mod generated;
mod model;
pub mod xml { pub mod xml {
/// Project-owned XML element boundary; external XML APIs are not copied. /// Project-owned XML element boundary; external XML APIs are not copied.

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,19 @@ impl<T: std::io::Read + std::io::Write> ReadWrite for T {}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Object { pub enum Object {
Undefined,
Boolean(bool),
Integer(i32),
UInteger(u32),
Long(i64),
ULong(u64),
Real(f64),
Color4(crate::Color4), Color4(crate::Color4),
Date(std::time::SystemTime),
Bytes(Vec<u8>),
Uri(Uri),
Array(Vec<Object>),
Map(std::collections::HashMap<String, Object>),
Matrix4(crate::Matrix4), Matrix4(crate::Matrix4),
Quaternion(crate::Quaternion), Quaternion(crate::Quaternion),
String(String), String(String),
@@ -36,6 +48,18 @@ pub enum Object {
impl PartialEq for Object { impl PartialEq for Object {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
match (self, other) { match (self, other) {
(Self::Undefined, Self::Undefined) => true,
(Self::Boolean(lhs), Self::Boolean(rhs)) => lhs == rhs,
(Self::Integer(lhs), Self::Integer(rhs)) => lhs == rhs,
(Self::UInteger(lhs), Self::UInteger(rhs)) => lhs == rhs,
(Self::Long(lhs), Self::Long(rhs)) => lhs == rhs,
(Self::ULong(lhs), Self::ULong(rhs)) => lhs == rhs,
(Self::Real(lhs), Self::Real(rhs)) => lhs.to_bits() == rhs.to_bits(),
(Self::Date(lhs), Self::Date(rhs)) => lhs == rhs,
(Self::Bytes(lhs), Self::Bytes(rhs)) => lhs == rhs,
(Self::Uri(lhs), Self::Uri(rhs)) => lhs == rhs,
(Self::Array(lhs), Self::Array(rhs)) => lhs == rhs,
(Self::Map(lhs), Self::Map(rhs)) => lhs == rhs,
(Self::Color4(lhs), Self::Color4(rhs)) => crate::Color4::eq(*lhs, *rhs), (Self::Color4(lhs), Self::Color4(rhs)) => crate::Color4::eq(*lhs, *rhs),
(Self::Matrix4(lhs), Self::Matrix4(rhs)) => crate::Matrix4::eq(*lhs, *rhs), (Self::Matrix4(lhs), Self::Matrix4(rhs)) => crate::Matrix4::eq(*lhs, *rhs),
(Self::Quaternion(lhs), Self::Quaternion(rhs)) => crate::Quaternion::eq(*lhs, *rhs), (Self::Quaternion(lhs), Self::Quaternion(rhs)) => crate::Quaternion::eq(*lhs, *rhs),
@@ -56,6 +80,22 @@ impl std::hash::Hash for Object {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state); std::mem::discriminant(self).hash(state);
match self { match self {
Self::Undefined => {}
Self::Boolean(value) => value.hash(state),
Self::Integer(value) => value.hash(state),
Self::UInteger(value) => value.hash(state),
Self::Long(value) => value.hash(state),
Self::ULong(value) => value.hash(state),
Self::Real(value) => value.to_bits().hash(state),
Self::Date(value) => value.hash(state),
Self::Bytes(value) => value.hash(state),
Self::Uri(value) => value.hash(state),
Self::Array(value) => value.hash(state),
Self::Map(value) => {
let mut entries: Vec<_> = value.iter().collect();
entries.sort_unstable_by_key(|(key, _)| *key);
entries.hash(state);
}
Self::Color4(value) => value.get_hash_code().hash(state), Self::Color4(value) => value.get_hash_code().hash(state),
Self::Matrix4(value) => value.get_hash_code().hash(state), Self::Matrix4(value) => value.get_hash_code().hash(state),
Self::Quaternion(value) => value.get_hash_code().hash(state), Self::Quaternion(value) => value.get_hash_code().hash(state),

View File

@@ -243,6 +243,7 @@ dependencies = [
name = "libremetaverse-structured-data" name = "libremetaverse-structured-data"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"base64",
"libremetaverse-types", "libremetaverse-types",
] ]

View File

@@ -22,12 +22,9 @@ fn catalog_values_and_inert_layouts_are_real() {
} }
#[test] #[test]
fn serializer_entry_point_returns_its_exact_member_id() { fn parser_dispatch_rejects_empty_input_without_calling_a_decoder() {
let Err(error) = OSDParser::deserialize_with_bytes(Vec::new()) else { let Err(error) = OSDParser::deserialize_with_bytes(Vec::new()) else {
panic!("failure-only serializer unexpectedly returned a value"); panic!("empty structured data unexpectedly returned a value");
}; };
assert_eq!( assert_eq!(error, libremetaverse_types::Error::Argument);
error.csharp_member(),
Some("M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.Byte[])")
);
} }

View File

@@ -20,5 +20,5 @@
"LibreMetaverse.Tests/TypeTests.cs::TypeTests.VectorCasting::test", "LibreMetaverse.Tests/TypeTests.cs::TypeTests.VectorCasting::test",
"LibreMetaverse.Tests/UtilsConversionsTests.cs::UtilsConversionsTests.StringToBytes::test" "LibreMetaverse.Tests/UtilsConversionsTests.cs::UtilsConversionsTests.StringToBytes::test"
], ],
"support_passes": 62 "support_passes": 73
} }

View File

@@ -38,6 +38,21 @@ TARGETS = {
# implementations. The generated module keeps catalog markers and re-exports # implementations. The generated module keeps catalog markers and re-exports
# the hand-written type so coverage remains deterministic. # the hand-written type so coverage remains deterministic.
NATIVE_TYPES = { NATIVE_TYPES = {
"T:LibreMetaverse.StructuredData.OSD": "crate::model::OSD",
"T:LibreMetaverse.StructuredData.OSDArray": "crate::model::OSDArray",
"T:LibreMetaverse.StructuredData.OSDBinary": "crate::model::OSDBinary",
"T:LibreMetaverse.StructuredData.OSDBoolean": "crate::model::OSDBoolean",
"T:LibreMetaverse.StructuredData.OSDDate": "crate::model::OSDDate",
"T:LibreMetaverse.StructuredData.OSDException": "crate::model::OSDException",
"T:LibreMetaverse.StructuredData.OSDFormat": "crate::model::OSDFormat",
"T:LibreMetaverse.StructuredData.OSDInteger": "crate::model::OSDInteger",
"T:LibreMetaverse.StructuredData.OSDLlsdXml": "crate::model::OSDLlsdXml",
"T:LibreMetaverse.StructuredData.OSDMap": "crate::model::OSDMap",
"T:LibreMetaverse.StructuredData.OSDReal": "crate::model::OSDReal",
"T:LibreMetaverse.StructuredData.OSDString": "crate::model::OSDString",
"T:LibreMetaverse.StructuredData.OSDType": "crate::model::OSDType",
"T:LibreMetaverse.StructuredData.OSDUUID": "crate::model::OSDUUID",
"T:LibreMetaverse.StructuredData.OSDUri": "crate::model::OSDUri",
"T:LibreMetaverse.CacheDictionary`2": "crate::collections::CacheDictionary", "T:LibreMetaverse.CacheDictionary`2": "crate::collections::CacheDictionary",
"T:LibreMetaverse.CacheDictionary`2.CacheDictionaryEnumerator": "crate::collections::CacheDictionaryCacheDictionaryEnumerator", "T:LibreMetaverse.CacheDictionary`2.CacheDictionaryEnumerator": "crate::collections::CacheDictionaryCacheDictionaryEnumerator",
"T:LibreMetaverse.Color4": "crate::color4::Color4", "T:LibreMetaverse.Color4": "crate::color4::Color4",
@@ -64,6 +79,12 @@ NATIVE_TYPES = {
} }
NATIVE_MEMBER_BODIES = { NATIVE_MEMBER_BODIES = {
"M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.Byte[])":
"crate::dispatch::deserialize_bytes(data)",
"M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.IO.Stream)":
"crate::dispatch::deserialize_stream(stream)",
"M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.String)":
"crate::dispatch::deserialize_string(data)",
"M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32)": "M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32)":
"crate::byte_order::read_single_little_endian(src, pos)", "crate::byte_order::read_single_little_endian(src, pos)",
"M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single)": "M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single)":

View File

@@ -189,6 +189,14 @@ MEMBER_TYPE_OVERRIDES = {
), ),
} }
MEMBER_SIGNATURE_OVERRIDES = { MEMBER_SIGNATURE_OVERRIDES = {
"M:LibreMetaverse.StructuredData.OSDArray.CopyTo(LibreMetaverse.StructuredData.OSD[],System.Int32)": (
"pub fn copy_to(&self, array: &mut [libremetaverse_structured_data::OSD], index: i32) -> Result<(), crate::Error>",
"shared_self,mutable_borrow,owned",
),
"M:LibreMetaverse.StructuredData.OSDMap.CopyTo(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}[],System.Int32)": (
"pub fn copy_to(&self, array: &mut [(String, libremetaverse_structured_data::OSD)], index: i32) -> Result<(), crate::Error>",
"shared_self,mutable_borrow,owned",
),
"M:LibreMetaverse.CacheDictionary`2.CopyTo(System.Collections.Generic.KeyValuePair{`0,`1}[],System.Int32)": ( "M:LibreMetaverse.CacheDictionary`2.CopyTo(System.Collections.Generic.KeyValuePair{`0,`1}[],System.Int32)": (
"pub fn copy_to(&self, array: &mut [(TKey, Option<TValue>)], array_index: i32) -> Result<(), crate::Error>", "pub fn copy_to(&self, array: &mut [(TKey, Option<TValue>)], array_index: i32) -> Result<(), crate::Error>",
"shared_self,mutable_borrow,owned", "shared_self,mutable_borrow,owned",
@@ -972,6 +980,7 @@ def validate_generated_shims() -> None:
"not_implemented(", "not_implemented(",
"NotImplemented::new(", "NotImplemented::new(",
"crate::byte_order::", "crate::byte_order::",
"crate::dispatch::",
) )
): ):
raise ValueError(f"generated shim function does not use the standardized failure: {path.relative_to(ROOT)}") raise ValueError(f"generated shim function does not use the standardized failure: {path.relative_to(ROOT)}")