Files
MetaCrate/crates/libremetaverse-structured-data/src/model.rs
Chili Palmer 16501f2331
Some checks failed
JPEG 2000 feature / linux (push) Has been cancelled
JPEG 2000 feature / macos (push) Has been cancelled
JPEG 2000 feature / windows (push) Has been cancelled
Implement optional JPEG2000 codec adapter (#40)
2026-08-09 03:42:18 +00:00

1882 lines
63 KiB
Rust

//! 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<u8>),
Array(Vec<OSD>),
Map(HashMap<String, OSD>),
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<H: Hasher>(&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<Self, Error> {
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<Vec<u8>, 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::<Result<Vec<_>, _>>()?,
_ => Vec::new(),
})
}
pub fn as_boolean(&self) -> Result<bool, Error> {
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<i32, Error> {
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<u32, Error> {
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<i64, Error> {
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<u64, Error> {
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<f64, Error> {
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<String, Error> {
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<UUID, Error> {
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<SystemTime, Error> {
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<Option<Uri>, 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<Vector2, Error> {
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<Vector3, Error> {
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<Vector3d, Error> {
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<Vector4, Error> {
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<Quaternion, Error> {
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<Color4, Error> {
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<Self, Error> {
Ok(self.clone())
}
pub fn from_boolean(value: bool) -> Result<Self, Error> {
Ok(Self::Boolean(value))
}
pub fn from_integer_with_int32(value: i32) -> Result<Self, Error> {
Ok(Self::Integer(value))
}
pub fn from_integer_with_u_int32(value: u32) -> Result<Self, Error> {
Ok(Self::Integer(value as i32))
}
pub fn from_integer_with_int16(value: i16) -> Result<Self, Error> {
Ok(Self::Integer(i32::from(value)))
}
pub fn from_integer_with_u_int16(value: u16) -> Result<Self, Error> {
Ok(Self::Integer(i32::from(value)))
}
pub fn from_integer_with_s_byte(value: i8) -> Result<Self, Error> {
Ok(Self::Integer(i32::from(value)))
}
pub fn from_integer_with_byte(value: u8) -> Result<Self, Error> {
Ok(Self::Integer(i32::from(value)))
}
pub fn from_u_integer(value: u32) -> Result<Self, Error> {
Ok(Self::Binary(value.to_be_bytes().to_vec()))
}
pub fn from_long(value: i64) -> Result<Self, Error> {
Ok(Self::Binary(value.to_be_bytes().to_vec()))
}
pub fn from_u_long(value: u64) -> Result<Self, Error> {
Ok(Self::Binary(value.to_be_bytes().to_vec()))
}
pub fn from_real_with_double(value: f64) -> Result<Self, Error> {
Ok(Self::Real(value))
}
pub fn from_real_with_single(value: f32) -> Result<Self, Error> {
Ok(Self::Real(f64::from(value)))
}
pub fn from_string(value: String) -> Result<Self, Error> {
Ok(Self::String(value))
}
pub fn from_uuid(value: UUID) -> Result<Self, Error> {
Ok(Self::UUID(value))
}
pub fn from_date(value: SystemTime) -> Result<Self, Error> {
Ok(Self::Date(value))
}
pub fn from_uri(value: Uri) -> Result<Self, Error> {
Ok(Self::Uri(value))
}
pub fn from_binary(value: Vec<u8>) -> Result<Self, Error> {
Ok(Self::Binary(value))
}
pub fn from_vector2(value: Vector2) -> Result<Self, Error> {
Ok(Self::Array(vec![
Self::Real(f64::from(value.x)),
Self::Real(f64::from(value.y)),
]))
}
pub fn from_vector3(value: Vector3) -> Result<Self, Error> {
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<Self, Error> {
Ok(Self::Array(vec![
Self::Real(value.x),
Self::Real(value.y),
Self::Real(value.z),
]))
}
pub fn from_vector4(value: Vector4) -> Result<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
object_to_osd(value)
}
pub fn to_object(type_: TypeId, value: Self) -> Result<Object, Error> {
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<OSDMap, Error> {
match obj {
Object::Map(values) => OSDMap::new_with_dictionary(
values
.into_iter()
.map(|(key, value)| Ok((key, object_to_osd(value)?)))
.collect::<Result<_, Error>>()?,
),
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::<Result<_, Error>>()?,
);
} 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<u8>) -> 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<u8> {
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<Uri> {
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<Self, Error> {
Ok(Self { value })
}
pub fn copy(&self) -> Result<OSD, Error> {
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<Vec<u8>, Error> {
OSD::Boolean(self.value).as_binary()
}
pub const fn as_boolean(&self) -> Result<bool, Error> {
Ok(self.value)
}
pub fn as_integer(&self) -> Result<i32, Error> {
Ok(i32::from(self.value))
}
pub fn as_real(&self) -> Result<f64, Error> {
Ok(f64::from(u8::from(self.value)))
}
pub fn as_string(&self) -> Result<String, Error> {
OSD::Boolean(self.value).as_string()
}
}
scalar_wrapper!(OSDInteger, i32, Integer, Integer);
impl OSDInteger {
pub fn as_binary(&self) -> Result<Vec<u8>, Error> {
Ok(self.value.to_be_bytes().to_vec())
}
pub const fn as_boolean(&self) -> Result<bool, Error> {
Ok(self.value != 0)
}
pub const fn as_integer(&self) -> Result<i32, Error> {
Ok(self.value)
}
pub fn as_long(&self) -> Result<i64, Error> {
Ok(i64::from(self.value))
}
pub fn as_real(&self) -> Result<f64, Error> {
Ok(f64::from(self.value))
}
pub fn as_string(&self) -> Result<String, Error> {
Ok(self.value.to_string())
}
pub fn as_u_integer(&self) -> Result<u32, Error> {
Ok(self.value as u32)
}
pub fn as_u_long(&self) -> Result<u64, Error> {
Ok(self.value as u64)
}
}
scalar_wrapper!(OSDReal, f64, Real, Real);
impl OSDReal {
pub fn as_binary(&self) -> Result<Vec<u8>, Error> {
Ok(self.value.to_be_bytes().to_vec())
}
pub fn as_boolean(&self) -> Result<bool, Error> {
OSD::Real(self.value).as_boolean()
}
pub fn as_integer(&self) -> Result<i32, Error> {
Ok(real_to_i32(self.value))
}
pub fn as_long(&self) -> Result<i64, Error> {
Ok(real_to_i64(self.value))
}
pub const fn as_real(&self) -> Result<f64, Error> {
Ok(self.value)
}
pub fn as_string(&self) -> Result<String, Error> {
Ok(format_real_for_codec(self.value))
}
pub fn as_u_integer(&self) -> Result<u32, Error> {
Ok(real_to_u32(self.value))
}
pub fn as_u_long(&self) -> Result<u64, Error> {
Ok(real_to_u64(self.value))
}
}
scalar_wrapper!(OSDString, String, String, String);
impl OSDString {
pub fn as_binary(&self) -> Result<Vec<u8>, Error> {
Ok(self.value.as_bytes().to_vec())
}
pub fn as_boolean(&self) -> Result<bool, Error> {
Ok(string_as_boolean(&self.value))
}
pub fn as_date(&self) -> Result<SystemTime, Error> {
Ok(parse_system_time_for_codec(&self.value).unwrap_or(UNIX_EPOCH))
}
pub fn as_integer(&self) -> Result<i32, Error> {
Ok(string_to_f64(&self.value).map_or(0, floor_to_i32))
}
pub fn as_long(&self) -> Result<i64, Error> {
Ok(string_to_f64(&self.value).map_or(0, floor_to_i64))
}
pub fn as_real(&self) -> Result<f64, Error> {
Ok(string_to_f64(&self.value).unwrap_or(0.0))
}
pub fn as_string(&self) -> Result<String, Error> {
Ok(self.value.clone())
}
pub fn as_u_integer(&self) -> Result<u32, Error> {
Ok(string_to_f64(&self.value).map_or(0, floor_to_u32))
}
pub fn as_u_long(&self) -> Result<u64, Error> {
Ok(string_to_f64(&self.value).map_or(0, floor_to_u64))
}
pub fn as_uuid(&self) -> Result<UUID, Error> {
OSD::String(self.value.clone()).as_uuid()
}
pub fn as_uri(&self) -> Result<Option<Uri>, Error> {
OSD::String(self.value.clone()).as_uri()
}
}
scalar_wrapper!(OSDUUID, UUID, UUID, UUID);
impl OSDUUID {
pub fn as_binary(&self) -> Result<Vec<u8>, Error> {
self.value.get_bytes()
}
pub fn as_boolean(&self) -> Result<bool, Error> {
Ok(self.value != UUID::zero())
}
pub fn as_string(&self) -> Result<String, Error> {
Ok(self.value.to_string())
}
pub const fn as_uuid(&self) -> Result<UUID, Error> {
Ok(self.value)
}
}
scalar_wrapper!(OSDDate, SystemTime, Date, Date);
impl OSDDate {
pub fn as_binary(&self) -> Result<Vec<u8>, Error> {
Ok(unix_seconds_for_codec(self.value).to_le_bytes().to_vec())
}
pub const fn as_date(&self) -> Result<SystemTime, Error> {
Ok(self.value)
}
pub fn as_integer(&self) -> Result<i32, Error> {
Ok(unix_seconds_u32(self.value) as i32)
}
pub fn as_long(&self) -> Result<i64, Error> {
Ok(i64::from(unix_seconds_u32(self.value)))
}
pub fn as_string(&self) -> Result<String, Error> {
Ok(format_system_time_for_codec(self.value))
}
pub fn as_u_integer(&self) -> Result<u32, Error> {
Ok(unix_seconds_u32(self.value))
}
pub fn as_u_long(&self) -> Result<u64, Error> {
Ok(u64::from(unix_seconds_u32(self.value)))
}
}
#[derive(Clone, Debug)]
pub struct OSDUri {
value: Uri,
}
impl OSDUri {
pub fn new(uri: Uri) -> Result<Self, Error> {
Ok(Self { value: uri })
}
pub fn as_binary(&self) -> Result<Vec<u8>, Error> {
Ok(self.value.0.as_bytes().to_vec())
}
pub fn as_string(&self) -> Result<String, Error> {
Ok(self.value.0.clone())
}
pub fn as_uri(&self) -> Result<Uri, Error> {
Ok(self.value.clone())
}
pub fn copy(&self) -> Result<OSD, Error> {
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<Self, Error> {
Ok(Self { value })
}
pub fn as_binary(&self) -> Result<Vec<u8>, Error> {
Ok(self.value.as_bytes().to_vec())
}
pub fn as_string(&self) -> Result<String, Error> {
Ok(self.value.clone())
}
pub fn copy(&self) -> Result<OSD, Error> {
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<u8>,
}
impl OSDBinary {
pub fn new_with_bytes(value: Vec<u8>) -> Result<Self, Error> {
Ok(Self { value })
}
pub fn new_with_int64(value: i64) -> Result<Self, Error> {
Ok(Self {
value: value.to_be_bytes().to_vec(),
})
}
pub fn new_with_u_int32(value: u32) -> Result<Self, Error> {
Ok(Self {
value: value.to_be_bytes().to_vec(),
})
}
pub fn new_with_u_int64(value: u64) -> Result<Self, Error> {
Ok(Self {
value: value.to_be_bytes().to_vec(),
})
}
pub fn as_binary(&self) -> Result<Vec<u8>, Error> {
Ok(self.value.clone())
}
pub fn as_long(&self) -> Result<i64, Error> {
read_i64(&self.value)
}
pub fn as_string(&self) -> Result<String, Error> {
Ok(base64::engine::general_purpose::STANDARD.encode(&self.value))
}
pub fn as_u_integer(&self) -> Result<u32, Error> {
read_u32(&self.value)
}
pub fn as_u_long(&self) -> Result<u64, Error> {
read_u64(&self.value)
}
pub fn copy(&self) -> Result<OSD, Error> {
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<ExternalError>,
}
impl OSDException {
pub fn new_with_constructor() -> Result<Self, Error> {
Ok(Self::default())
}
pub fn new_with_string(message: String) -> Result<Self, Error> {
Ok(Self {
message,
inner_exception: None,
})
}
pub fn new_with_string_exception(
message: String,
inner_exception: ExternalError,
) -> Result<Self, Error> {
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<Vec<OSD>>,
}
impl Clone for OSDArray {
fn clone(&self) -> Self {
Self {
values: RwLock::new(self.snapshot()),
}
}
}
impl OSDArray {
pub fn new_with_constructor() -> Result<Self, Error> {
Ok(Self {
values: RwLock::new(Vec::new()),
})
}
pub fn new_with_list(value: Vec<OSD>) -> Result<Self, Error> {
Ok(Self {
values: RwLock::new(value),
})
}
pub fn new_with_int32(capacity: i32) -> Result<Self, Error> {
let capacity = usize::try_from(capacity).map_err(|_| Error::Argument)?;
Ok(Self {
values: RwLock::new(Vec::with_capacity(capacity)),
})
}
fn read(&self) -> RwLockReadGuard<'_, Vec<OSD>> {
self.values
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write(&self) -> RwLockWriteGuard<'_, Vec<OSD>> {
self.values
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
pub fn snapshot(&self) -> Vec<OSD> {
self.read().clone()
}
pub fn add(&self, llsd: OSD) -> Result<(), Error> {
self.write().push(llsd);
Ok(())
}
pub fn as_binary(&self) -> Result<Vec<u8>, Error> {
OSD::Array(self.snapshot()).as_binary()
}
pub fn as_boolean(&self) -> Result<bool, Error> {
Ok(!self.read().is_empty())
}
pub fn as_color4(&self) -> Result<Color4, Error> {
OSD::Array(self.snapshot()).as_color4()
}
pub fn as_long(&self) -> Result<i64, Error> {
OSD::Array(self.snapshot()).as_long()
}
pub fn as_quaternion(&self) -> Result<Quaternion, Error> {
OSD::Array(self.snapshot()).as_quaternion()
}
pub fn as_u_integer(&self) -> Result<u32, Error> {
OSD::Array(self.snapshot()).as_u_integer()
}
pub fn as_u_long(&self) -> Result<u64, Error> {
OSD::Array(self.snapshot()).as_u_long()
}
pub fn as_vector2(&self) -> Result<Vector2, Error> {
OSD::Array(self.snapshot()).as_vector2()
}
pub fn as_vector3(&self) -> Result<Vector3, Error> {
OSD::Array(self.snapshot()).as_vector3()
}
pub fn as_vector3d(&self) -> Result<Vector3d, Error> {
OSD::Array(self.snapshot()).as_vector3d()
}
pub fn as_vector4(&self) -> Result<Vector4, Error> {
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<bool, Error> {
Ok(self.read().contains(&llsd))
}
pub fn contains_with_string(&self, element: String) -> Result<bool, Error> {
Ok(self
.read()
.iter()
.any(|value| matches!(value, OSD::String(text) if text == &element)))
}
pub fn copy(&self) -> Result<OSD, Error> {
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<i32, Error> {
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<bool, Error> {
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<ArrayList, Error> {
Ok(ArrayList(
self.snapshot()
.into_iter()
.map(osd_to_object)
.collect::<Result<_, _>>()?,
))
}
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<HashMap<String, OSD>>,
}
impl Clone for OSDMap {
fn clone(&self) -> Self {
Self {
values: RwLock::new(self.snapshot()),
}
}
}
impl OSDMap {
pub fn new_with_constructor() -> Result<Self, Error> {
Ok(Self {
values: RwLock::new(HashMap::new()),
})
}
pub fn new_with_dictionary(value: HashMap<String, OSD>) -> Result<Self, Error> {
Ok(Self {
values: RwLock::new(value),
})
}
pub fn new_with_int32(capacity: i32) -> Result<Self, Error> {
let capacity = usize::try_from(capacity).map_err(|_| Error::Argument)?;
Ok(Self {
values: RwLock::new(HashMap::with_capacity(capacity)),
})
}
fn read(&self) -> RwLockReadGuard<'_, HashMap<String, OSD>> {
self.values
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write(&self) -> RwLockWriteGuard<'_, HashMap<String, OSD>> {
self.values
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
pub fn snapshot(&self) -> HashMap<String, OSD> {
self.read().clone()
}
pub fn get(&self, key: &str) -> Option<OSD> {
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<bool, Error> {
Ok(!self.read().is_empty())
}
pub fn clear(&self) -> Result<(), Error> {
self.write().clear();
Ok(())
}
pub fn contains(&self, kvp: (String, OSD)) -> Result<bool, Error> {
Ok(self.read().get(&kvp.0) == Some(&kvp.1))
}
pub fn contains_key(&self, key: String) -> Result<bool, Error> {
Ok(self.read().contains_key(&key))
}
pub fn copy(&self) -> Result<OSD, Error> {
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<IDictionaryEnumerator, Error> {
Ok(IDictionaryEnumerator)
}
pub fn remove_with_key_value_pair(&self, kvp: (String, OSD)) -> Result<bool, Error> {
Ok(self.write().remove(&kvp.0).is_some())
}
pub fn remove_with_string(&self, key: String) -> Result<bool, Error> {
Ok(self.write().remove(&key).is_some())
}
pub fn to_hashtable(&self) -> Result<Hashtable, Error> {
Ok(Hashtable(
self.snapshot()
.into_iter()
.map(|(key, value)| Ok((Object::String(key), osd_to_object(value)?)))
.collect::<Result<_, Error>>()?,
))
}
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<String> {
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<OSD> {
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<u32, Error> {
Ok(u32::from_be_bytes(
bytes
.get(..4)
.ok_or(Error::IndexOutOfRange)?
.try_into()
.map_err(|_| Error::IndexOutOfRange)?,
))
}
fn read_i64(bytes: &[u8]) -> Result<i64, Error> {
Ok(i64::from_be_bytes(
bytes
.get(..8)
.ok_or(Error::IndexOutOfRange)?
.try_into()
.map_err(|_| Error::IndexOutOfRange)?,
))
}
fn read_u64(bytes: &[u8]) -> Result<u64, Error> {
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<f64> {
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<SystemTime> {
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::<u32>()
.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<i64> {
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::<Vec<_>>()
.join(",")
),
OSD::Array(values) => format!(
"[{}]",
values
.iter()
.map(json_text_with_defaults)
.collect::<Vec<_>>()
.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::<Vec<_>>()
.join(",")
)
}
}
}
fn object_to_osd(value: Object) -> Result<OSD, Error> {
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::<Result<_, _>>()?,
),
Object::Map(values) => OSD::Map(
values
.into_iter()
.map(|(key, value)| Ok((key, object_to_osd(value)?)))
.collect::<Result<_, Error>>()?,
),
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<Object, Error> {
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::<Result<_, _>>()?,
),
OSD::Map(values) => Object::Map(
values
.into_iter()
.map(|(key, value)| Ok((key, osd_to_object(value)?)))
.collect::<Result<_, Error>>()?,
),
})
}
#[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))
);
}
}