//! Native LLSD/OSD value model. //! //! The model is deliberately independent of every wire-format implementation. //! Maps have no observable ordering contract; serializers that require stable //! output must impose their own ordering. Collection access returns owned //! snapshots, matching the value-oriented Rust boundary while preventing locks //! or borrows from escaping a call. #![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_possible_wrap)] // C# unchecked numeric conversions are reference behavior. #![allow(clippy::cast_precision_loss)] // C# converts integral bounds through Double for these APIs. #![allow(clippy::cast_sign_loss)] #![allow(clippy::format_collect)] // Hex conversion is small and mirrors the reference allocation. #![allow(clippy::inherent_to_string)] #![allow(clippy::match_same_arms)] // Unsupported mapped object variants intentionally become undefined. #![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_panics_doc)] // Infallible mapped operators and indexers have fixed signatures. #![allow(clippy::must_use_candidate)] #![allow(clippy::needless_pass_by_value)] #![allow(clippy::should_implement_trait)] use base64::Engine as _; use libremetaverse_types::compat::{ ArrayList, ExternalError, Hashtable, IDictionaryEnumerator, Object, TypeId, Uri, }; use libremetaverse_types::{Color4, Error, Quaternion, UUID, Vector2, Vector3, Vector3d, Vector4}; use std::collections::HashMap; use std::fmt::Write as _; use std::hash::{Hash, Hasher}; use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[repr(u8)] pub enum OSDType { Unknown = 0, Boolean = 1, Integer = 2, Real = 3, String = 4, UUID = 5, Date = 6, URI = 7, Binary = 8, Map = 9, Array = 10, LlsdXml = 11, } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[repr(i32)] pub enum OSDFormat { Xml = 0, Json = 1, Binary = 2, } /// A format-neutral LLSD value. /// /// Equality and hashing are structural. Real values compare by their IEEE-754 /// bit pattern so that hashing remains valid for NaNs and signed zero. Map /// hashing sorts keys first, because [`HashMap`] iteration order is unspecified. /// /// Typed accessors follow `LibreMetaverse`'s permissive conversion contract: /// unsupported conversions return the type's default (`false`, zero, an empty /// string or byte vector, the nil UUID, the Unix epoch, or no URI). Integer and /// real conversions retain the reference's unchecked wrapping, clamping, and /// round-to-even behavior. Binary integers use network byte order; date binary /// values use the reference's little-endian floating-point Unix timestamp. /// Owned variants make cloning structural and keep all borrowing local to the /// call, so no parser or serializer implementation controls this representation. #[non_exhaustive] #[derive(Clone, Debug, Default)] pub enum OSD { #[default] Undefined, Boolean(bool), Integer(i32), Real(f64), String(String), UUID(UUID), Date(SystemTime), Uri(Uri), Binary(Vec), Array(Vec), Map(HashMap), LlsdXml(String), } impl PartialEq for OSD { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Undefined, Self::Undefined) => true, (Self::Boolean(left), Self::Boolean(right)) => left == right, (Self::Integer(left), Self::Integer(right)) => left == right, (Self::Real(left), Self::Real(right)) => left.to_bits() == right.to_bits(), (Self::String(left), Self::String(right)) | (Self::LlsdXml(left), Self::LlsdXml(right)) => left == right, (Self::UUID(left), Self::UUID(right)) => left == right, (Self::Date(left), Self::Date(right)) => left == right, (Self::Uri(left), Self::Uri(right)) => left == right, (Self::Binary(left), Self::Binary(right)) => left == right, (Self::Array(left), Self::Array(right)) => left == right, (Self::Map(left), Self::Map(right)) => left == right, _ => false, } } } impl Eq for OSD {} impl Hash for OSD { fn hash(&self, state: &mut H) { std::mem::discriminant(self).hash(state); match self { Self::Undefined => {} Self::Boolean(value) => value.hash(state), Self::Integer(value) => value.hash(state), Self::Real(value) => value.to_bits().hash(state), Self::String(value) | Self::LlsdXml(value) => value.hash(state), Self::UUID(value) => value.hash(state), Self::Date(value) => value.hash(state), Self::Uri(value) => value.hash(state), Self::Binary(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); } } } } impl OSD { pub const DEFAULT_MAX_DEPTH: usize = 64; pub const DEFAULT_MAX_NODES: usize = 1_000_000; pub const DEFAULT_MAX_BINARY_BYTES: usize = 64 * 1024 * 1024; pub const fn new() -> Result { Ok(Self::Undefined) } /// Checks recursively allocated data before accepting a value from an /// untrusted parser boundary. Format parsers call this after decoding. pub fn validate_limits( &self, max_depth: usize, max_nodes: usize, max_binary_bytes: usize, ) -> Result<(), Error> { let mut nodes = 0_usize; let mut binary_bytes = 0_usize; let mut stack = vec![(self, 0_usize)]; while let Some((value, depth)) = stack.pop() { if depth > max_depth { return Err(Error::Argument); } nodes = nodes.checked_add(1).ok_or(Error::Argument)?; if nodes > max_nodes { return Err(Error::Argument); } match value { Self::Binary(bytes) => { binary_bytes = binary_bytes .checked_add(bytes.len()) .ok_or(Error::Argument)?; if binary_bytes > max_binary_bytes { return Err(Error::Argument); } } Self::Array(values) => { stack.extend(values.iter().map(|value| (value, depth + 1))); } Self::Map(values) => { stack.extend(values.values().map(|value| (value, depth + 1))); } _ => {} } } Ok(()) } pub fn as_binary(&self) -> Result, Error> { Ok(match self { Self::Boolean(value) => vec![if *value { b'1' } else { b'0' }], Self::Integer(value) => value.to_be_bytes().to_vec(), 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_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 .iter() .map(|value| value.as_integer().map(|integer| integer as u8)) .collect::, _>>()?, _ => Vec::new(), }) } pub fn as_boolean(&self) -> Result { Ok(match self { Self::Boolean(value) => *value, Self::Integer(value) => *value != 0, Self::Real(value) => !value.is_nan() && *value != 0.0, Self::String(value) => string_as_boolean(value), Self::UUID(value) => *value != UUID::zero(), Self::Array(value) => !value.is_empty(), Self::Map(value) => !value.is_empty(), _ => false, }) } pub fn as_integer(&self) -> Result { Ok(match self { Self::Boolean(value) => i32::from(*value), Self::Integer(value) => *value, Self::Real(value) => real_to_i32(*value), Self::String(value) => string_to_f64(value).map_or(0, floor_to_i32), Self::Date(value) => unix_seconds_u32(*value) as i32, _ => 0, }) } pub fn as_u_integer(&self) -> Result { Ok(match self { Self::Integer(value) => *value as u32, Self::Real(value) => real_to_u32(*value), Self::String(value) => string_to_f64(value).map_or(0, floor_to_u32), Self::Date(value) => unix_seconds_u32(*value), Self::Binary(value) => read_u32(value)?, Self::Array(value) => read_u32(&Self::Array(value.clone()).as_binary()?)?, _ => self.as_integer()? as u32, }) } pub fn as_long(&self) -> Result { Ok(match self { Self::Integer(value) => i64::from(*value), Self::Real(value) => real_to_i64(*value), Self::String(value) => string_to_f64(value).map_or(0, floor_to_i64), Self::Date(value) => i64::from(unix_seconds_u32(*value)), Self::Binary(value) => read_i64(value)?, Self::Array(value) => read_i64(&Self::Array(value.clone()).as_binary()?)?, _ => 0, }) } pub fn as_u_long(&self) -> Result { Ok(match self { Self::Integer(value) => *value as u64, Self::Real(value) => real_to_u64(*value), Self::String(value) => string_to_f64(value).map_or(0, floor_to_u64), Self::Date(value) => u64::from(unix_seconds_u32(*value)), Self::Binary(value) => read_u64(value)?, Self::Array(value) => read_u64(&Self::Array(value.clone()).as_binary()?)?, _ => 0, }) } pub fn as_real(&self) -> Result { Ok(match self { Self::Boolean(value) => f64::from(u8::from(*value)), Self::Integer(value) => f64::from(*value), Self::Real(value) => *value, Self::String(value) => string_to_f64(value).unwrap_or(0.0), _ => 0.0, }) } pub fn as_string(&self) -> Result { Ok(match self { Self::Boolean(value) => if *value { "1" } else { "0" }.to_owned(), Self::Integer(value) => value.to_string(), Self::Real(value) => format_real_for_codec(*value), Self::String(value) | Self::LlsdXml(value) => value.clone(), Self::UUID(value) => value.to_string(), Self::Date(value) => format_system_time_for_codec(*value), Self::Uri(value) => format_uri_for_codec(&value.0), Self::Binary(value) => base64::engine::general_purpose::STANDARD.encode(value), _ => String::new(), }) } pub fn as_uuid(&self) -> Result { match self { Self::UUID(value) => Ok(*value), Self::String(value) => { Ok(UUID::new_with_string(value.clone()).unwrap_or_else(|_| UUID::zero())) } _ => Ok(UUID::zero()), } } pub fn as_date(&self) -> Result { Ok(match self { Self::Date(value) => *value, Self::String(value) => parse_system_time_for_codec(value).unwrap_or(UNIX_EPOCH), _ => UNIX_EPOCH, }) } pub fn as_uri(&self) -> Result, Error> { Ok(match self { Self::Uri(value) => Some(value.clone()), Self::String(value) if valid_uri_reference(value) => Some(Uri(value.clone())), _ => None, }) } pub fn as_vector2(&self) -> Result { match self { Self::Array(value) if value.len() == 2 => Vector2::new_with_single_single( value[0].as_real()? as f32, value[1].as_real()? as f32, ), _ => Ok(Vector2::zero()), } } pub fn as_vector3(&self) -> Result { match self { Self::Array(value) if value.len() == 3 => Vector3::new_with_single_single_single( value[0].as_real()? as f32, value[1].as_real()? as f32, value[2].as_real()? as f32, ), _ => Ok(Vector3::zero()), } } pub fn as_vector3d(&self) -> Result { match self { Self::Array(value) if value.len() == 3 => Vector3d::new_with_double_double_double( value[0].as_real()?, value[1].as_real()?, value[2].as_real()?, ), _ => Ok(Vector3d::zero()), } } pub fn as_vector4(&self) -> Result { match self { Self::Array(value) if value.len() == 4 => { Vector4::new_with_single_single_single_single( value[0].as_real()? as f32, value[1].as_real()? as f32, value[2].as_real()? as f32, value[3].as_real()? as f32, ) } _ => Ok(Vector4::zero()), } } pub fn as_quaternion(&self) -> Result { match self { Self::Array(value) if value.len() == 4 => { Quaternion::new_with_single_single_single_single( value[0].as_real()? as f32, value[1].as_real()? as f32, value[2].as_real()? as f32, value[3].as_real()? as f32, ) } _ => Ok(Quaternion::identity()), } } pub fn as_color4(&self) -> Result { match self { Self::Array(value) if value.len() == 4 => Color4::new_with_single_single_single_single( (value[0].as_real()? as f32).clamp(0.0, 1.0), (value[1].as_real()? as f32).clamp(0.0, 1.0), (value[2].as_real()? as f32).clamp(0.0, 1.0), (value[3].as_real()? as f32).clamp(0.0, 1.0), ), _ => Ok(Color4::black()), } } pub fn copy(&self) -> Result { Ok(self.clone()) } pub fn from_boolean(value: bool) -> Result { Ok(Self::Boolean(value)) } pub fn from_integer_with_int32(value: i32) -> Result { Ok(Self::Integer(value)) } pub fn from_integer_with_u_int32(value: u32) -> Result { Ok(Self::Integer(value as i32)) } pub fn from_integer_with_int16(value: i16) -> Result { Ok(Self::Integer(i32::from(value))) } pub fn from_integer_with_u_int16(value: u16) -> Result { Ok(Self::Integer(i32::from(value))) } pub fn from_integer_with_s_byte(value: i8) -> Result { Ok(Self::Integer(i32::from(value))) } pub fn from_integer_with_byte(value: u8) -> Result { Ok(Self::Integer(i32::from(value))) } pub fn from_u_integer(value: u32) -> Result { Ok(Self::Binary(value.to_be_bytes().to_vec())) } pub fn from_long(value: i64) -> Result { Ok(Self::Binary(value.to_be_bytes().to_vec())) } pub fn from_u_long(value: u64) -> Result { Ok(Self::Binary(value.to_be_bytes().to_vec())) } pub fn from_real_with_double(value: f64) -> Result { Ok(Self::Real(value)) } pub fn from_real_with_single(value: f32) -> Result { Ok(Self::Real(f64::from(value))) } pub fn from_string(value: String) -> Result { Ok(Self::String(value)) } pub fn from_uuid(value: UUID) -> Result { Ok(Self::UUID(value)) } pub fn from_date(value: SystemTime) -> Result { Ok(Self::Date(value)) } pub fn from_uri(value: Uri) -> Result { Ok(Self::Uri(value)) } pub fn from_binary(value: Vec) -> Result { Ok(Self::Binary(value)) } pub fn from_vector2(value: Vector2) -> Result { Ok(Self::Array(vec![ Self::Real(f64::from(value.x)), Self::Real(f64::from(value.y)), ])) } pub fn from_vector3(value: Vector3) -> Result { Ok(Self::Array(vec![ Self::Real(f64::from(value.x)), Self::Real(f64::from(value.y)), Self::Real(f64::from(value.z)), ])) } pub fn from_vector3d(value: Vector3d) -> Result { Ok(Self::Array(vec![ Self::Real(value.x), Self::Real(value.y), Self::Real(value.z), ])) } pub fn from_vector4(value: Vector4) -> Result { Ok(Self::Array(vec![ Self::Real(f64::from(value.x)), Self::Real(f64::from(value.y)), Self::Real(f64::from(value.z)), Self::Real(f64::from(value.w)), ])) } pub fn from_quaternion(value: Quaternion) -> Result { Ok(Self::Array(vec![ Self::Real(f64::from(value.x)), Self::Real(f64::from(value.y)), Self::Real(f64::from(value.z)), Self::Real(f64::from(value.w)), ])) } pub fn from_color4(value: Color4) -> Result { Ok(Self::Array(vec![ Self::Real(f64::from(value.r)), Self::Real(f64::from(value.g)), Self::Real(f64::from(value.b)), Self::Real(f64::from(value.a)), ])) } pub fn from_object(value: Object) -> Result { object_to_osd(value) } pub fn to_object(type_: TypeId, value: Self) -> Result { let name = type_.0; Ok(match name { "System.String" | "string" => Object::String(value.as_string()?), "System.Boolean" | "bool" => Object::Boolean(value.as_boolean()?), "System.Int32" | "int" => Object::Integer(value.as_integer()?), "System.UInt32" | "uint" => Object::UInteger(value.as_u_integer()?), "System.Int64" | "long" => Object::Long(value.as_long()?), "System.UInt64" | "ulong" => Object::ULong(value.as_u_long()?), "System.Single" | "float" | "System.Double" | "double" => { Object::Real(value.as_real()?) } "LibreMetaverse.UUID" | "UUID" => Object::UUID(value.as_uuid()?), "LibreMetaverse.Vector2" | "Vector2" => Object::Vector2(value.as_vector2()?), "LibreMetaverse.Vector3" | "Vector3" => Object::Vector3(value.as_vector3()?), "LibreMetaverse.Vector3d" | "Vector3d" => Object::Vector3d(value.as_vector3d()?), "LibreMetaverse.Vector4" | "Vector4" => Object::Vector4(value.as_vector4()?), "LibreMetaverse.Quaternion" | "Quaternion" => { Object::Quaternion(value.as_quaternion()?) } "LibreMetaverse.Color4" | "Color4" => Object::Color4(value.as_color4()?), _ => osd_to_object(value)?, }) } pub fn serialize_members(obj: Object) -> Result { match obj { Object::Map(values) => OSDMap::new_with_dictionary( values .into_iter() .map(|(key, value)| Ok((key, object_to_osd(value)?))) .collect::>()?, ), value => { let map = OSDMap::new_with_int32(1)?; map.add_with_string_osd("value".to_owned(), object_to_osd(value)?)?; Ok(map) } } } pub fn deserialize_members(obj: &mut Object, serialized: OSDMap) -> Result<(), Error> { if matches!(obj, Object::Map(_)) { *obj = Object::Map( serialized .snapshot() .into_iter() .map(|(key, value)| Ok((key, osd_to_object(value)?))) .collect::>()?, ); } else if let Some(value) = serialized.get("value") { *obj = osd_to_object(value)?; } Ok(()) } pub fn to_string(&self) -> String { model_text(self) } pub const fn type_(&self) -> OSDType { match self { Self::Undefined => OSDType::Unknown, Self::Boolean(_) => OSDType::Boolean, Self::Integer(_) => OSDType::Integer, Self::Real(_) => OSDType::Real, Self::String(_) => OSDType::String, Self::UUID(_) => OSDType::UUID, Self::Date(_) => OSDType::Date, Self::Uri(_) => OSDType::URI, Self::Binary(_) => OSDType::Binary, Self::Map(_) => OSDType::Map, Self::Array(_) => OSDType::Array, Self::LlsdXml(_) => OSDType::LlsdXml, } } pub fn from_with_color4(value: Color4) -> Self { Self::from_color4(value).expect("infallible conversion") } pub fn from_with_quaternion(value: Quaternion) -> Self { Self::from_quaternion(value).expect("infallible conversion") } pub fn from_with_uuid(value: UUID) -> Self { Self::UUID(value) } pub fn from_with_vector2(value: Vector2) -> Self { Self::from_vector2(value).expect("infallible conversion") } pub fn from_with_vector3(value: Vector3) -> Self { Self::from_vector3(value).expect("infallible conversion") } pub fn from_with_vector3d(value: Vector3d) -> Self { Self::from_vector3d(value).expect("infallible conversion") } pub fn from_with_vector4(value: Vector4) -> Self { Self::from_vector4(value).expect("infallible conversion") } pub const fn from_with_boolean(value: bool) -> Self { Self::Boolean(value) } pub const fn from_with_byte(value: u8) -> Self { Self::Integer(value as i32) } pub fn from_with_bytes(value: Vec) -> Self { Self::Binary(value) } pub fn from_with_date_time(value: SystemTime) -> Self { Self::Date(value) } pub const fn from_with_double(value: f64) -> Self { Self::Real(value) } pub const fn from_with_int16(value: i16) -> Self { Self::Integer(value as i32) } pub const fn from_with_int32(value: i32) -> Self { Self::Integer(value) } pub fn from_with_int64(value: i64) -> Self { Self::Binary(value.to_be_bytes().to_vec()) } pub const fn from_with_s_byte(value: i8) -> Self { Self::Integer(value as i32) } pub const fn from_with_single(value: f32) -> Self { Self::Real(value as f64) } pub fn from_with_string(value: String) -> Self { Self::String(value) } pub const fn from_with_u_int16(value: u16) -> Self { Self::Integer(value as i32) } pub const fn from_with_u_int32(value: u32) -> Self { Self::Integer(value as i32) } pub fn from_with_u_int64(value: u64) -> Self { Self::Binary(value.to_be_bytes().to_vec()) } pub fn from_with_uri(value: Uri) -> Self { Self::Uri(value) } pub fn from_with_osd(value: Self) -> Color4 { value.as_color4().unwrap_or_else(|_| Color4::black()) } pub fn from_with_osd_b8ef4612(value: Self) -> Quaternion { value .as_quaternion() .unwrap_or_else(|_| Quaternion::identity()) } pub fn from_with_osd_eba15316(value: Self) -> UUID { value.as_uuid().unwrap_or_else(|_| UUID::zero()) } pub fn from_with_osd_e9aee905(value: Self) -> Vector2 { value.as_vector2().unwrap_or_else(|_| Vector2::zero()) } pub fn from_with_osd_93c123bc(value: Self) -> Vector3 { value.as_vector3().unwrap_or_else(|_| Vector3::zero()) } pub fn from_with_osd_d799abcc(value: Self) -> Vector3d { value.as_vector3d().unwrap_or_else(|_| Vector3d::zero()) } pub fn from_with_osd_866cd1d2(value: Self) -> Vector4 { value.as_vector4().unwrap_or_else(|_| Vector4::zero()) } pub fn from_with_osd_f50c5016(value: Self) -> bool { value.as_boolean().unwrap_or(false) } pub fn from_with_osd_ffa78bdc(value: Self) -> Vec { value.as_binary().unwrap_or_default() } pub fn from_with_osd_37aede2f(value: Self) -> SystemTime { value.as_date().unwrap_or(UNIX_EPOCH) } pub fn from_with_osd_1b5df47b(value: Self) -> f64 { value.as_real().unwrap_or(0.0) } pub fn from_with_osd_0d6b9c45(value: Self) -> i32 { value.as_integer().unwrap_or(0) } pub fn from_with_osd_75ba279e(value: Self) -> i64 { value.as_long().unwrap_or(0) } pub fn from_with_osd_232c9960(value: Self) -> f32 { value.as_real().unwrap_or(0.0) as f32 } pub fn from_with_osd_853f4d5d(value: Self) -> String { value.as_string().unwrap_or_default() } pub fn from_with_osd_6a01d439(value: Self) -> u32 { value.as_u_integer().unwrap_or(0) } pub fn from_with_osd_1ae3c138(value: Self) -> u64 { value.as_u_long().unwrap_or(0) } pub fn from_with_osd_8dbb79ec(value: Self) -> Option { value.as_uri().unwrap_or(None) } } /// Format-neutral parser/serializer namespace matching the C# static type. pub struct OSDParser; macro_rules! scalar_wrapper { ($name:ident, $value:ty, $variant:ident, $kind:ident) => { #[derive(Clone, Debug)] pub struct $name { value: $value, } impl $name { pub fn new(value: $value) -> Result { Ok(Self { value }) } pub fn copy(&self) -> Result { Ok(OSD::$variant(self.value.clone())) } pub fn type_(&self) -> OSDType { OSDType::$kind } pub fn to_string(&self) -> String { OSD::$variant(self.value.clone()).to_string() } } }; } scalar_wrapper!(OSDBoolean, bool, Boolean, Boolean); impl OSDBoolean { pub fn as_binary(&self) -> Result, Error> { OSD::Boolean(self.value).as_binary() } pub const fn as_boolean(&self) -> Result { Ok(self.value) } pub fn as_integer(&self) -> Result { Ok(i32::from(self.value)) } pub fn as_real(&self) -> Result { Ok(f64::from(u8::from(self.value))) } pub fn as_string(&self) -> Result { OSD::Boolean(self.value).as_string() } } scalar_wrapper!(OSDInteger, i32, Integer, Integer); impl OSDInteger { pub fn as_binary(&self) -> Result, Error> { Ok(self.value.to_be_bytes().to_vec()) } pub const fn as_boolean(&self) -> Result { Ok(self.value != 0) } pub const fn as_integer(&self) -> Result { Ok(self.value) } pub fn as_long(&self) -> Result { Ok(i64::from(self.value)) } pub fn as_real(&self) -> Result { Ok(f64::from(self.value)) } pub fn as_string(&self) -> Result { Ok(self.value.to_string()) } pub fn as_u_integer(&self) -> Result { Ok(self.value as u32) } pub fn as_u_long(&self) -> Result { Ok(self.value as u64) } } scalar_wrapper!(OSDReal, f64, Real, Real); impl OSDReal { pub fn as_binary(&self) -> Result, Error> { Ok(self.value.to_be_bytes().to_vec()) } pub fn as_boolean(&self) -> Result { OSD::Real(self.value).as_boolean() } pub fn as_integer(&self) -> Result { Ok(real_to_i32(self.value)) } pub fn as_long(&self) -> Result { Ok(real_to_i64(self.value)) } pub const fn as_real(&self) -> Result { Ok(self.value) } pub fn as_string(&self) -> Result { Ok(format_real_for_codec(self.value)) } pub fn as_u_integer(&self) -> Result { Ok(real_to_u32(self.value)) } pub fn as_u_long(&self) -> Result { Ok(real_to_u64(self.value)) } } scalar_wrapper!(OSDString, String, String, String); impl OSDString { pub fn as_binary(&self) -> Result, Error> { Ok(self.value.as_bytes().to_vec()) } pub fn as_boolean(&self) -> Result { Ok(string_as_boolean(&self.value)) } pub fn as_date(&self) -> Result { Ok(parse_system_time_for_codec(&self.value).unwrap_or(UNIX_EPOCH)) } pub fn as_integer(&self) -> Result { Ok(string_to_f64(&self.value).map_or(0, floor_to_i32)) } pub fn as_long(&self) -> Result { Ok(string_to_f64(&self.value).map_or(0, floor_to_i64)) } pub fn as_real(&self) -> Result { Ok(string_to_f64(&self.value).unwrap_or(0.0)) } pub fn as_string(&self) -> Result { Ok(self.value.clone()) } pub fn as_u_integer(&self) -> Result { Ok(string_to_f64(&self.value).map_or(0, floor_to_u32)) } pub fn as_u_long(&self) -> Result { Ok(string_to_f64(&self.value).map_or(0, floor_to_u64)) } pub fn as_uuid(&self) -> Result { OSD::String(self.value.clone()).as_uuid() } pub fn as_uri(&self) -> Result, Error> { OSD::String(self.value.clone()).as_uri() } } scalar_wrapper!(OSDUUID, UUID, UUID, UUID); impl OSDUUID { pub fn as_binary(&self) -> Result, Error> { self.value.get_bytes() } pub fn as_boolean(&self) -> Result { Ok(self.value != UUID::zero()) } pub fn as_string(&self) -> Result { Ok(self.value.to_string()) } pub const fn as_uuid(&self) -> Result { Ok(self.value) } } scalar_wrapper!(OSDDate, SystemTime, Date, Date); impl OSDDate { pub fn as_binary(&self) -> Result, Error> { Ok(unix_seconds_for_codec(self.value).to_le_bytes().to_vec()) } pub const fn as_date(&self) -> Result { Ok(self.value) } pub fn as_integer(&self) -> Result { Ok(unix_seconds_u32(self.value) as i32) } pub fn as_long(&self) -> Result { Ok(i64::from(unix_seconds_u32(self.value))) } pub fn as_string(&self) -> Result { Ok(format_system_time_for_codec(self.value)) } pub fn as_u_integer(&self) -> Result { Ok(unix_seconds_u32(self.value)) } pub fn as_u_long(&self) -> Result { Ok(u64::from(unix_seconds_u32(self.value))) } } #[derive(Clone, Debug)] pub struct OSDUri { value: Uri, } impl OSDUri { pub fn new(uri: Uri) -> Result { Ok(Self { value: uri }) } pub fn as_binary(&self) -> Result, Error> { Ok(self.value.0.as_bytes().to_vec()) } pub fn as_string(&self) -> Result { Ok(self.value.0.clone()) } pub fn as_uri(&self) -> Result { Ok(self.value.clone()) } pub fn copy(&self) -> Result { Ok(OSD::Uri(self.value.clone())) } pub fn to_string(&self) -> String { self.value.0.clone() } pub const fn type_(&self) -> OSDType { OSDType::URI } } #[derive(Clone, Debug)] pub struct OSDLlsdXml { pub value: String, } impl OSDLlsdXml { pub fn new(value: String) -> Result { Ok(Self { value }) } pub fn as_binary(&self) -> Result, Error> { Ok(self.value.as_bytes().to_vec()) } pub fn as_string(&self) -> Result { Ok(self.value.clone()) } pub fn copy(&self) -> Result { Ok(OSD::LlsdXml(self.value.clone())) } pub fn to_string(&self) -> String { self.value.clone() } pub const fn type_(&self) -> OSDType { OSDType::LlsdXml } } #[derive(Clone, Debug)] pub struct OSDBinary { value: Vec, } impl OSDBinary { pub fn new_with_bytes(value: Vec) -> Result { Ok(Self { value }) } pub fn new_with_int64(value: i64) -> Result { Ok(Self { value: value.to_be_bytes().to_vec(), }) } pub fn new_with_u_int32(value: u32) -> Result { Ok(Self { value: value.to_be_bytes().to_vec(), }) } pub fn new_with_u_int64(value: u64) -> Result { Ok(Self { value: value.to_be_bytes().to_vec(), }) } pub fn as_binary(&self) -> Result, Error> { Ok(self.value.clone()) } pub fn as_long(&self) -> Result { read_i64(&self.value) } pub fn as_string(&self) -> Result { Ok(base64::engine::general_purpose::STANDARD.encode(&self.value)) } pub fn as_u_integer(&self) -> Result { read_u32(&self.value) } pub fn as_u_long(&self) -> Result { read_u64(&self.value) } pub fn copy(&self) -> Result { Ok(OSD::Binary(self.value.clone())) } pub fn to_string(&self) -> String { self.value .iter() .map(|byte| format!("{byte:02X}")) .collect() } pub const fn type_(&self) -> OSDType { OSDType::Binary } } #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct OSDException { pub message: String, pub inner_exception: Option, } impl OSDException { pub fn new_with_constructor() -> Result { Ok(Self::default()) } pub fn new_with_string(message: String) -> Result { Ok(Self { message, inner_exception: None, }) } pub fn new_with_string_exception( message: String, inner_exception: ExternalError, ) -> Result { Ok(Self { message, inner_exception: Some(inner_exception), }) } } /// Mutable OSD sequence with C#-compatible indexing and conversion behavior. /// /// Reads and [`Self::copy`] return owned structural snapshots. Mutation is /// synchronized, but callers should not infer transactional behavior across /// multiple calls. pub struct OSDArray { values: RwLock>, } impl Clone for OSDArray { fn clone(&self) -> Self { Self { values: RwLock::new(self.snapshot()), } } } impl OSDArray { pub fn new_with_constructor() -> Result { Ok(Self { values: RwLock::new(Vec::new()), }) } pub fn new_with_list(value: Vec) -> Result { Ok(Self { values: RwLock::new(value), }) } pub fn new_with_int32(capacity: i32) -> Result { let capacity = usize::try_from(capacity).map_err(|_| Error::Argument)?; Ok(Self { values: RwLock::new(Vec::with_capacity(capacity)), }) } fn read(&self) -> RwLockReadGuard<'_, Vec> { self.values .read() .unwrap_or_else(std::sync::PoisonError::into_inner) } fn write(&self) -> RwLockWriteGuard<'_, Vec> { self.values .write() .unwrap_or_else(std::sync::PoisonError::into_inner) } pub fn snapshot(&self) -> Vec { self.read().clone() } pub fn add(&self, llsd: OSD) -> Result<(), Error> { self.write().push(llsd); Ok(()) } pub fn as_binary(&self) -> Result, Error> { OSD::Array(self.snapshot()).as_binary() } pub fn as_boolean(&self) -> Result { Ok(!self.read().is_empty()) } pub fn as_color4(&self) -> Result { OSD::Array(self.snapshot()).as_color4() } pub fn as_long(&self) -> Result { OSD::Array(self.snapshot()).as_long() } pub fn as_quaternion(&self) -> Result { OSD::Array(self.snapshot()).as_quaternion() } pub fn as_u_integer(&self) -> Result { OSD::Array(self.snapshot()).as_u_integer() } pub fn as_u_long(&self) -> Result { OSD::Array(self.snapshot()).as_u_long() } pub fn as_vector2(&self) -> Result { OSD::Array(self.snapshot()).as_vector2() } pub fn as_vector3(&self) -> Result { OSD::Array(self.snapshot()).as_vector3() } pub fn as_vector3d(&self) -> Result { OSD::Array(self.snapshot()).as_vector3d() } pub fn as_vector4(&self) -> Result { OSD::Array(self.snapshot()).as_vector4() } pub fn clear(&self) -> Result<(), Error> { self.write().clear(); Ok(()) } pub fn contains_with_osd(&self, llsd: OSD) -> Result { Ok(self.read().contains(&llsd)) } pub fn contains_with_string(&self, element: String) -> Result { Ok(self .read() .iter() .any(|value| matches!(value, OSD::String(text) if text == &element))) } pub fn copy(&self) -> Result { Ok(OSD::Array(self.snapshot())) } pub fn copy_to(&self, array: &mut [OSD], index: i32) -> Result<(), Error> { let index = usize::try_from(index).map_err(|_| Error::Argument)?; let values = self.read(); let destination = array .get_mut(index..index.checked_add(values.len()).ok_or(Error::Argument)?) .ok_or(Error::Argument)?; destination.clone_from_slice(&values); Ok(()) } pub fn index_of(&self, item: OSD) -> Result { Ok(self .read() .iter() .position(|value| value == &item) .map_or(-1, |index| index as i32)) } pub fn insert(&self, index: i32, item: OSD) -> Result<(), Error> { let index = usize::try_from(index).map_err(|_| Error::IndexOutOfRange)?; let mut values = self.write(); if index > values.len() { return Err(Error::IndexOutOfRange); } values.insert(index, item); Ok(()) } pub fn remove(&self, llsd: OSD) -> Result { let mut values = self.write(); if let Some(index) = values.iter().position(|value| value == &llsd) { values.remove(index); Ok(true) } else { Ok(false) } } pub fn remove_at(&self, index: i32) -> Result<(), Error> { let index = usize::try_from(index).map_err(|_| Error::IndexOutOfRange)?; let mut values = self.write(); if index >= values.len() { return Err(Error::IndexOutOfRange); } values.remove(index); Ok(()) } pub fn to_array_list(&self) -> Result { Ok(ArrayList( self.snapshot() .into_iter() .map(osd_to_object) .collect::>()?, )) } pub fn to_string(&self) -> String { model_text(&OSD::Array(self.snapshot())) } pub fn count(&self) -> i32 { self.read().len() as i32 } pub const fn is_read_only(&self) -> bool { false } pub fn item(&self, index: i32) -> OSD { self.read()[usize::try_from(index).expect("negative OSDArray index")].clone() } pub fn set_item(&mut self, index: i32, value: OSD) { self.write()[usize::try_from(index).expect("negative OSDArray index")] = value; } pub const fn type_(&self) -> OSDType { OSDType::Array } } /// Mutable string-keyed OSD map. /// /// Missing index lookups return [`OSD::Undefined`], duplicate `add` operations /// fail, and setters replace existing values. Key, value, copy, and conversion /// results are owned snapshots. The public map has no iteration-order promise; /// snapshot helpers sort keys when a stable order is useful. pub struct OSDMap { values: RwLock>, } impl Clone for OSDMap { fn clone(&self) -> Self { Self { values: RwLock::new(self.snapshot()), } } } impl OSDMap { pub fn new_with_constructor() -> Result { Ok(Self { values: RwLock::new(HashMap::new()), }) } pub fn new_with_dictionary(value: HashMap) -> Result { Ok(Self { values: RwLock::new(value), }) } pub fn new_with_int32(capacity: i32) -> Result { let capacity = usize::try_from(capacity).map_err(|_| Error::Argument)?; Ok(Self { values: RwLock::new(HashMap::with_capacity(capacity)), }) } fn read(&self) -> RwLockReadGuard<'_, HashMap> { self.values .read() .unwrap_or_else(std::sync::PoisonError::into_inner) } fn write(&self) -> RwLockWriteGuard<'_, HashMap> { self.values .write() .unwrap_or_else(std::sync::PoisonError::into_inner) } pub fn snapshot(&self) -> HashMap { self.read().clone() } pub fn get(&self, key: &str) -> Option { self.read().get(key).cloned() } pub fn add_with_key_value_pair(&self, kvp: (String, OSD)) -> Result<(), Error> { self.add_with_string_osd(kvp.0, kvp.1) } pub fn add_with_string_osd(&self, key: String, llsd: OSD) -> Result<(), Error> { let mut values = self.write(); if values.contains_key(&key) { return Err(Error::Argument); } values.insert(key, llsd); Ok(()) } pub fn as_boolean(&self) -> Result { Ok(!self.read().is_empty()) } pub fn clear(&self) -> Result<(), Error> { self.write().clear(); Ok(()) } pub fn contains(&self, kvp: (String, OSD)) -> Result { Ok(self.read().get(&kvp.0) == Some(&kvp.1)) } pub fn contains_key(&self, key: String) -> Result { Ok(self.read().contains_key(&key)) } pub fn copy(&self) -> Result { Ok(OSD::Map(self.snapshot())) } pub fn copy_to(&self, array: &mut [(String, OSD)], index: i32) -> Result<(), Error> { let index = usize::try_from(index).map_err(|_| Error::Argument)?; let mut entries: Vec<_> = self.snapshot().into_iter().collect(); entries.sort_unstable_by_key(|(key, _)| key.clone()); let destination = array .get_mut(index..index.checked_add(entries.len()).ok_or(Error::Argument)?) .ok_or(Error::Argument)?; destination.clone_from_slice(&entries); Ok(()) } pub const fn get_enumerator(&self) -> Result { Ok(IDictionaryEnumerator) } pub fn remove_with_key_value_pair(&self, kvp: (String, OSD)) -> Result { Ok(self.write().remove(&kvp.0).is_some()) } pub fn remove_with_string(&self, key: String) -> Result { Ok(self.write().remove(&key).is_some()) } pub fn to_hashtable(&self) -> Result { Ok(Hashtable( self.snapshot() .into_iter() .map(|(key, value)| Ok((Object::String(key), osd_to_object(value)?))) .collect::>()?, )) } pub fn to_string(&self) -> String { model_text(&OSD::Map(self.snapshot())) } pub fn try_get_value(&self, key: String, llsd: &mut OSD) -> bool { if let Some(value) = self.get(&key) { *llsd = value; true } else { false } } pub fn count(&self) -> i32 { self.read().len() as i32 } pub const fn is_read_only(&self) -> bool { false } pub fn item(&self, key: String) -> OSD { self.get(&key).unwrap_or_default() } pub fn set_item(&mut self, key: String, value: OSD) { self.write().insert(key, value); } pub fn keys(&self) -> Vec { let mut keys: Vec<_> = self.read().keys().cloned().collect(); keys.sort_unstable(); keys } pub const fn type_(&self) -> OSDType { OSDType::Map } pub fn values(&self) -> Vec { let mut entries: Vec<_> = self .read() .iter() .map(|(key, value)| (key.clone(), value.clone())) .collect(); entries.sort_unstable_by(|(left, _), (right, _)| left.cmp(right)); entries.into_iter().map(|(_, value)| value).collect() } } fn read_u32(bytes: &[u8]) -> Result { Ok(u32::from_be_bytes( bytes .get(..4) .ok_or(Error::IndexOutOfRange)? .try_into() .map_err(|_| Error::IndexOutOfRange)?, )) } fn read_i64(bytes: &[u8]) -> Result { Ok(i64::from_be_bytes( bytes .get(..8) .ok_or(Error::IndexOutOfRange)? .try_into() .map_err(|_| Error::IndexOutOfRange)?, )) } fn read_u64(bytes: &[u8]) -> Result { Ok(u64::from_be_bytes( bytes .get(..8) .ok_or(Error::IndexOutOfRange)? .try_into() .map_err(|_| Error::IndexOutOfRange)?, )) } fn string_as_boolean(value: &str) -> bool { !value.is_empty() && value != "0" && !value.eq_ignore_ascii_case("false") } fn string_to_f64(value: &str) -> Option { value.trim().parse().ok() } fn floor_to_i32(value: f64) -> i32 { if value.is_nan() { 0 } else { value .floor() .clamp(f64::from(i32::MIN), f64::from(i32::MAX)) as i32 } } fn floor_to_u32(value: f64) -> u32 { if value.is_nan() { 0 } else { value.floor().clamp(0.0, f64::from(u32::MAX)) as u32 } } fn floor_to_i64(value: f64) -> i64 { if value.is_nan() { 0 } else if value <= i64::MIN as f64 { i64::MIN } else if value >= i64::MAX as f64 { i64::MAX } else { value.floor() as i64 } } fn floor_to_u64(value: f64) -> u64 { if value.is_nan() || value <= 0.0 { 0 } else if value >= u64::MAX as f64 { u64::MAX } else { value.floor() as u64 } } fn real_to_i32(value: f64) -> i32 { if value.is_nan() { 0 } else { value .round_ties_even() .clamp(f64::from(i32::MIN), f64::from(i32::MAX)) as i32 } } fn real_to_u32(value: f64) -> u32 { if value.is_nan() { 0 } else { value.round_ties_even().clamp(0.0, f64::from(u32::MAX)) as u32 } } fn real_to_i64(value: f64) -> i64 { if value.is_nan() { 0 } else if value <= i64::MIN as f64 { i64::MIN } else if value >= i64::MAX as f64 { i64::MAX } else { value.round_ties_even() as i64 } } fn real_to_u64(value: f64) -> u64 { if value.is_nan() || value <= 0.0 { 0 } else if value > u64::MAX as f64 { i32::MAX as u64 } else { value.round_ties_even() as u64 } } pub(crate) fn format_real_for_codec(value: f64) -> String { if value.is_nan() { "NaN".to_owned() } else if value == f64::INFINITY { "Infinity".to_owned() } else if value == f64::NEG_INFINITY { "-Infinity".to_owned() } else { value.to_string() } } 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(), } } fn unix_seconds_u32(value: SystemTime) -> u32 { match value.duration_since(UNIX_EPOCH) { Ok(duration) => duration.as_secs() as u32, Err(error) => (-(error.duration().as_secs() as i64)) as u32, } } pub(crate) fn format_system_time_for_codec(value: SystemTime) -> String { let (whole, nanos) = match value.duration_since(UNIX_EPOCH) { Ok(duration) => (duration.as_secs() as i64, duration.subsec_nanos()), Err(error) => { let duration = error.duration(); if duration.subsec_nanos() == 0 { (-(duration.as_secs() as i64), 0) } else { ( -(duration.as_secs() as i64) - 1, 1_000_000_000 - duration.subsec_nanos(), ) } } }; let hundredths = (nanos / 10_000_000) as u8; let (year, month, day, hour, minute, second) = civil_from_unix(whole); if nanos / 1_000_000 > 0 { format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{hundredths:02}Z") } else { format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z") } } fn civil_from_unix(seconds: i64) -> (i32, u32, u32, u32, u32, u32) { let days = seconds.div_euclid(86_400); let day_seconds = seconds.rem_euclid(86_400); let z = days + 719_468; let era = if z >= 0 { z } else { z - 146_096 }.div_euclid(146_097); let doe = z - era * 146_097; let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365; let mut year = yoe + era * 400; let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let day = doy - (153 * mp + 2) / 5 + 1; let month = mp + if mp < 10 { 3 } else { -9 }; year += i64::from(month <= 2); ( year as i32, month as u32, day as u32, (day_seconds / 3_600) as u32, ((day_seconds % 3_600) / 60) as u32, (day_seconds % 60) as u32, ) } pub(crate) fn parse_system_time_for_codec(value: &str) -> Option { let value = value.trim(); let (date, time) = value.split_once('T').or_else(|| value.split_once(' '))?; let mut date_parts = date.split('-'); let year: i32 = date_parts.next()?.parse().ok()?; let month: u32 = date_parts.next()?.parse().ok()?; let day: u32 = date_parts.next()?.parse().ok()?; if date_parts.next().is_some() || !(1..=12).contains(&month) || !(1..=31).contains(&day) { return None; } let time = time.strip_suffix('Z').unwrap_or(time); let (clock, fraction) = time.split_once('.').map_or((time, ""), |parts| parts); let mut time_parts = clock.split(':'); let hour: u32 = time_parts.next()?.parse().ok()?; let minute: u32 = time_parts.next()?.parse().ok()?; let second: u32 = time_parts.next()?.parse().ok()?; if time_parts.next().is_some() || hour > 23 || minute > 59 || second > 59 { return None; } let days = days_from_civil(year, month, day)?; let whole = days .checked_mul(86_400)? .checked_add(i64::from(hour * 3_600 + minute * 60 + second))?; let nanos = if fraction.is_empty() { 0 } else { let digits = fraction.as_bytes(); if digits.len() > 9 || !digits.iter().all(u8::is_ascii_digit) { return None; } fraction .parse::() .ok()? .checked_mul(10_u32.pow((9 - digits.len()) as u32))? }; if whole >= 0 { UNIX_EPOCH.checked_add(Duration::new(whole as u64, nanos)) } else if nanos == 0 { UNIX_EPOCH.checked_sub(Duration::from_secs(whole.unsigned_abs())) } else { UNIX_EPOCH.checked_sub(Duration::new( whole.unsigned_abs() - 1, 1_000_000_000 - nanos, )) } } fn days_from_civil(year: i32, month: u32, day: u32) -> Option { let max_day = match month { 2 if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) => 29, 2 => 28, 4 | 6 | 9 | 11 => 30, 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, _ => return None, }; if day == 0 || day > max_day { return None; } let adjusted_year = i64::from(year) - i64::from(month <= 2); let era = adjusted_year.div_euclid(400); let yoe = adjusted_year - era * 400; let adjusted_month = i64::from(month) + if month > 2 { -3 } else { 9 }; let doy = (153 * adjusted_month + 2) / 5 + i64::from(day) - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; Some(era * 146_097 + doe - 719_468) } fn valid_uri_reference(value: &str) -> bool { !value.chars().any(char::is_whitespace) && !value.chars().any(char::is_control) } pub(crate) fn format_uri_for_codec(value: &str) -> String { if !value.contains("://") { return value.to_owned(); } let mut output = String::with_capacity(value.len()); for character in value.chars() { match character { ' ' => output.push_str("%20"), '"' => output.push_str("%22"), '<' => output.push_str("%3C"), '>' => output.push_str("%3E"), character if character.is_control() => { let mut bytes = [0_u8; 4]; for byte in character.encode_utf8(&mut bytes).as_bytes() { write!(output, "%{byte:02X}").expect("writing to String is infallible"); } } character => output.push(character), } } output } fn escape_text(value: &str) -> String { let mut output = String::with_capacity(value.len() + 2); output.push('"'); for character in value.chars() { match character { '"' => output.push_str("\\\""), '\\' => output.push_str("\\\\"), '\n' => output.push_str("\\n"), '\r' => output.push_str("\\r"), '\t' => output.push_str("\\t"), character if character.is_control() => { write!(output, "\\u{:04x}", character as u32) .expect("writing to String is infallible"); } character => output.push(character), } } output.push('"'); output } fn model_text(value: &OSD) -> String { match value { OSD::Undefined => "undef".to_owned(), OSD::Boolean(_) | OSD::Integer(_) | OSD::Real(_) | OSD::UUID(_) | OSD::Date(_) | OSD::Uri(_) | OSD::Binary(_) => value.as_string().unwrap_or_default(), OSD::String(text) | OSD::LlsdXml(text) => text.clone(), OSD::Array(_) | OSD::Map(_) => json_text_with_defaults(value), } } fn json_text_with_defaults(value: &OSD) -> String { match value { OSD::Undefined => "null".to_owned(), OSD::Boolean(value) => value.to_string(), OSD::Integer(value) => value.to_string(), OSD::Real(value) => format_real_for_codec(*value), OSD::String(value) => escape_text(value), OSD::LlsdXml(_) => "null".to_owned(), OSD::UUID(_) | OSD::Date(_) | OSD::Uri(_) => { escape_text(&value.as_string().unwrap_or_default()) } OSD::Binary(bytes) => format!( "[{}]", bytes .iter() .map(u8::to_string) .collect::>() .join(",") ), OSD::Array(values) => format!( "[{}]", values .iter() .map(json_text_with_defaults) .collect::>() .join(",") ), OSD::Map(values) => { let mut entries: Vec<_> = values.iter().collect(); entries.sort_unstable_by_key(|(key, _)| *key); format!( "{{{}}}", entries .into_iter() .map(|(key, value)| { format!("{}:{}", escape_text(key), json_text_with_defaults(value)) }) .collect::>() .join(",") ) } } } fn object_to_osd(value: Object) -> Result { Ok(match value { Object::Undefined => OSD::Undefined, Object::Boolean(value) => OSD::Boolean(value), Object::Integer(value) => OSD::Integer(value), Object::UInteger(value) => OSD::Binary(value.to_be_bytes().to_vec()), Object::Long(value) => OSD::Binary(value.to_be_bytes().to_vec()), Object::ULong(value) => OSD::Binary(value.to_be_bytes().to_vec()), Object::Real(value) => OSD::Real(value), Object::String(value) => OSD::String(value), Object::UUID(value) => OSD::UUID(value), Object::Date(value) => OSD::Date(value), Object::Bytes(value) => OSD::Binary(value), Object::Uri(value) => OSD::Uri(value), Object::Array(values) => OSD::Array( values .into_iter() .map(object_to_osd) .collect::>()?, ), Object::Map(values) => OSD::Map( values .into_iter() .map(|(key, value)| Ok((key, object_to_osd(value)?))) .collect::>()?, ), Object::Vector2(value) => OSD::from_vector2(value)?, Object::Vector3(value) => OSD::from_vector3(value)?, Object::Vector3d(value) => OSD::from_vector3d(value)?, Object::Vector4(value) => OSD::from_vector4(value)?, Object::Quaternion(value) => OSD::from_quaternion(value)?, Object::Color4(value) => OSD::from_color4(value)?, Object::Matrix4(_) | Object::Opaque(_) => OSD::Undefined, }) } fn osd_to_object(value: OSD) -> Result { Ok(match value { OSD::Undefined => Object::Undefined, OSD::Boolean(value) => Object::Boolean(value), OSD::Integer(value) => Object::Integer(value), OSD::Real(value) => Object::Real(value), OSD::String(value) | OSD::LlsdXml(value) => Object::String(value), OSD::UUID(value) => Object::UUID(value), OSD::Date(value) => Object::Date(value), OSD::Uri(value) => Object::Uri(value), OSD::Binary(value) => Object::Bytes(value), OSD::Array(values) => Object::Array( values .into_iter() .map(osd_to_object) .collect::>()?, ), OSD::Map(values) => Object::Map( values .into_iter() .map(|(key, value)| Ok((key, osd_to_object(value)?))) .collect::>()?, ), }) } #[cfg(test)] mod tests { use super::*; use std::collections::hash_map::DefaultHasher; #[test] fn scalar_conversions_match_reference_edges() { assert_eq!(OSD::Real(2.5).as_integer(), Ok(2)); assert_eq!(OSD::Real(3.5).as_integer(), Ok(4)); assert_eq!(OSD::Real(f64::NAN).as_integer(), Ok(0)); assert_eq!(OSD::String("-1.2".into()).as_integer(), Ok(-2)); assert_eq!(OSD::String("FALSE".into()).as_boolean(), Ok(false)); assert_eq!(OSD::Integer(-1).as_u_integer(), Ok(u32::MAX)); } #[test] fn binary_numeric_conversions_are_network_order() { let value = OSDBinary::new_with_u_int64(0x0102_0304_0506_0708).unwrap(); assert_eq!(value.as_binary().unwrap(), vec![1, 2, 3, 4, 5, 6, 7, 8]); assert_eq!(value.as_u_long(), Ok(0x0102_0304_0506_0708)); assert_eq!( OSDBinary::new_with_u_int32(0x0102_0304) .unwrap() .as_u_integer(), Ok(0x0102_0304) ); } #[test] fn arrays_mutate_and_convert_without_leaking_borrows() { let array = OSDArray::new_with_constructor().unwrap(); array.add(OSD::Real(1.0)).unwrap(); array.add(OSD::Real(2.0)).unwrap(); array.add(OSD::Real(3.0)).unwrap(); assert_eq!( array.as_vector3().unwrap(), Vector3 { x: 1.0, y: 2.0, z: 3.0 } ); assert_eq!(array.count(), 3); assert!(array.remove(OSD::Real(2.0)).unwrap()); assert_eq!(array.snapshot(), vec![OSD::Real(1.0), OSD::Real(3.0)]); } #[test] fn maps_have_undefined_index_default_and_snapshot_copy() { let mut map = OSDMap::new_with_constructor().unwrap(); map.add_with_string_osd("answer".into(), OSD::Integer(42)) .unwrap(); assert_eq!(map.item("missing".into()), OSD::Undefined); assert_eq!(map.item("answer".into()), OSD::Integer(42)); map.set_item("answer".into(), OSD::Integer(43)); assert_eq!(map.item("answer".into()), OSD::Integer(43)); assert!( map.add_with_string_osd("answer".into(), OSD::Integer(44)) .is_err() ); map.set_item( "values".into(), OSD::Array(vec![ OSD::Boolean(true), OSD::String("quoted\"text".into()), OSD::Binary(vec![1, 2]), OSD::Undefined, ]), ); assert_eq!( map.to_string(), r#"{"answer":43,"values":[true,"quoted\"text",[1,2],null]}"# ); } #[test] fn structural_map_hash_is_independent_of_hashmap_iteration() { let left = OSD::Map(HashMap::from([ ("a".into(), OSD::Integer(1)), ("b".into(), OSD::Integer(2)), ])); let right = OSD::Map(HashMap::from([ ("b".into(), OSD::Integer(2)), ("a".into(), OSD::Integer(1)), ])); let hash = |value: &OSD| { let mut hasher = DefaultHasher::new(); value.hash(&mut hasher); hasher.finish() }; assert_eq!(left, right); assert_eq!(hash(&left), hash(&right)); } #[test] fn nesting_limits_reject_depth_nodes_and_binary_allocation() { let nested = OSD::Array(vec![OSD::Array(vec![OSD::Binary(vec![0; 4])])]); assert_eq!(nested.validate_limits(2, 3, 4), Ok(())); assert_eq!(nested.validate_limits(1, 3, 4), Err(Error::Argument)); assert_eq!(nested.validate_limits(2, 2, 4), Err(Error::Argument)); assert_eq!(nested.validate_limits(2, 3, 3), Err(Error::Argument)); } #[test] fn utc_dates_round_trip_reference_text_and_little_endian_binary() { let date = UNIX_EPOCH + Duration::new(1_704_164_645, 120_000_000); let osd = OSDDate::new(date).unwrap(); assert_eq!(osd.as_string().unwrap(), "2024-01-02T03:04:05.12Z"); assert_eq!( OSDString::new("2024-01-02T03:04:05.12Z".into()) .unwrap() .as_date() .unwrap(), date ); assert_eq!(osd.as_binary().unwrap(), 1_704_164_645.12_f64.to_le_bytes()); let pre_epoch = OSDString::new("1969-12-31T23:59:59.50Z".into()) .unwrap() .as_date() .unwrap(); assert_eq!(pre_epoch, UNIX_EPOCH - Duration::from_millis(500)); assert_eq!( OSDDate::new(pre_epoch).unwrap().as_string().unwrap(), "1969-12-31T23:59:59.50Z" ); assert_eq!( OSDDate::new(UNIX_EPOCH + Duration::from_millis(1)) .unwrap() .as_string() .unwrap(), "1970-01-01T00:00:00.00Z" ); assert_eq!( OSDDate::new(UNIX_EPOCH - Duration::from_secs(1)) .unwrap() .as_u_integer(), Ok(u32::MAX) ); } #[test] fn object_bridge_preserves_unsigned_numeric_kinds() { assert_eq!( OSD::from_object(Object::UInteger(0xFEDC_BA98)).unwrap(), OSD::Binary(vec![0xFE, 0xDC, 0xBA, 0x98]) ); assert_eq!( OSD::to_object( TypeId("System.UInt64"), OSD::Binary(u64::MAX.to_be_bytes().to_vec()) ), Ok(Object::ULong(u64::MAX)) ); } }