//! Native LLSD field codecs for the translated Linden message fixtures. #![allow(clippy::cast_possible_truncation)] // LLSD reals intentionally narrow to wire f32 fields. #![allow(clippy::too_many_arguments)] // Codec helpers mirror fixed message field sets. #![allow(clippy::type_complexity)] // Parsed tuples preserve the mapped message shape. #![allow(clippy::unnecessary_wraps)] // Handlers share the fallible registry signature. use crate::messages::linden::{ AgentDropGroupMessage, AgentDropGroupMessageAgentData, AgentGroupDataUpdateMessage, AgentGroupDataUpdateMessageGroupData, AgentGroupDataUpdateMessageNewGroupData, AgentStateUpdateMessage, BaseResourcesInfo, BulkUpdateInventoryMessage, BulkUpdateInventoryMessageFolderDataInfo, BulkUpdateInventoryMessageItemDataInfo, ChatSessionAcceptInvitation, ChatSessionRequestMessage, ChatSessionRequestMuteUpdate, ChatSessionRequestStartConference, ChatterBoxInvitationMessage, ChatterBoxSessionAgentListUpdatesMessage, ChatterBoxSessionAgentListUpdatesMessageAgentUpdatesBlock, ChatterBoxSessionStartReplyMessage, ChatterboxSessionEventReplyMessage, CopyInventoryFromNotecardMessage, CrossedRegionMessage, DirLandReplyMessage, DirLandReplyMessageQueryReply, DisplayNameUpdateMessage, EnableSimulatorMessage, EnableSimulatorMessageSimulatorInfoBlock, EstablishAgentCommunicationMessage, ForceCloseChatterBoxSessionMessage, GetDisplayNamesMessage, GetObjectCostMessage, GetObjectCostRequest, LandResourcesMessage, LandResourcesRequest, LandStatReplyMessage, LandStatReplyMessageReportDataBlock, MapLayerMessage, MapLayerReplyVariant, MapLayerReplyVariantLayerData, NavMeshStatusUpdateMessage, NewFileAgentInventoryMessage, NewFileAgentInventoryReplyMessage, NewFileAgentInventoryUploadReplyMessage, NewFileAgentInventoryVariablePriceMessage, NewFileAgentInventoryVariablePriceReplyMessage, ObjectMediaMessage, ObjectMediaNavigateMessage, ObjectMediaRequest, ObjectMediaResponse, ObjectMediaUpdate, ObjectPhysicsPropertiesMessage, ParcelObjectOwnersReplyMessage, ParcelObjectOwnersReplyMessagePrimOwner, ParcelPropertiesMessage, ParcelPropertiesUpdateMessage, ParcelVoiceInfoRequestMessage, PlacesReplyMessage, PlacesReplyMessageQueryData, ProvisionVoiceAccountRequestMessage, RegionInfoMessage, RemoteParcelRequestMessage, RemoteParcelRequestReply, RemoteParcelRequestRequest, RenderMaterialsMessage, RequiredVoiceVersionMessage, ScriptRunningReplyMessage, SearchStatRequestMessage, SearchStatRequestReply, SearchStatRequestRequest, SendPostcardMessage, SetDisplayNameMessage, SetDisplayNameReplyMessage, SimConsoleResponseMessage, TeleportFailedMessage, TeleportFinishMessage, UpdateAgentInformationMessage, UpdateAgentInventoryRequestMessage, UpdateAgentLanguageMessage, UpdateGestureAgentInventoryMessage, UpdateNotecardAgentInventoryMessage, UpdateNotecardTaskInventoryMessage, UpdateScriptAgentMessage, UpdateScriptAgentRequestMessage, UpdateScriptTaskMessage, UpdateScriptTaskUpdateMessage, UploadBakedTextureMessage, UploadObjectAssetMessage, UploadObjectAssetMessageObject, UploadObjectAssetMessageObjectExtraParam, UploadObjectAssetMessageObjectFace, UploadScriptTaskMessage, UploaderRequestComplete, UploaderRequestScriptComplete, UploaderRequestUpload, UploaderScriptRequestError, ViewerStatsMessage, }; use crate::{ AgentDisplayName, Error, GroupPowers, InstantMessageDialog, InstantMessageOnline, LandingType, MediaEntry, ParcelCategory, ParcelFlags, ParcelResult, ParcelStatus, PermissionMask, PrimitivePhysicsProperties, SimAccess, TeleportFlags, }; use flate2::read::ZlibDecoder; use libremetaverse_structured_data::{OSD, OSDMap, OSDParser}; use libremetaverse_types::compat::Uri; use libremetaverse_types::{ AssetType, Color4, ExtraParamType, FolderType, InventoryType, Material, Quaternion, SaleType, SculptType, UUID, Vector3, }; use std::collections::HashMap; use std::io::Read as _; use std::net::{IpAddr, Ipv4Addr}; use std::time::{SystemTime, UNIX_EPOCH}; pub(crate) trait MessageValue: Sized { fn message_default() -> Self; fn to_message_osd(&self) -> Result; fn from_message_osd(value: &OSD) -> Result; } pub(crate) trait GeneratedMessage: Sized { fn new_generated() -> Self; fn serialize_generated(&self) -> Result; fn deserialize_generated(&mut self, map: &OSDMap) -> Result<(), Error>; } pub(crate) fn generated_from_osd(data: OSD) -> Result { let OSD::Map(values) = data else { return Err(Error::Argument); }; let map = OSDMap::new_with_dictionary(values)?; let mut value = T::new_generated(); value.deserialize_generated(&map)?; Ok(value) } pub(crate) fn get_object_cost_handler( map: &OSDMap, ) -> Result>, Error> { if map.get("object_ids").is_some() { Ok(Some(Box::new(GetObjectCostRequest::new_generated()))) } else { Ok(Some(Box::new(GetObjectCostMessage::new_generated()))) } } pub(crate) fn land_resources_handler( map: &OSDMap, ) -> Result>, Error> { if map.get("parcel_id").is_some() { Ok(Some(Box::new(LandResourcesRequest::new_generated()))) } else if map.get("ScriptResourceSummary").is_some() { Ok(Some(Box::new(LandResourcesMessage::new_generated()))) } else { Ok(None) } } macro_rules! integer_value { ($type:ty, $from:ident, $as:ident) => { impl MessageValue for $type { fn message_default() -> Self { 0 } fn to_message_osd(&self) -> Result { OSD::$from(*self) } fn from_message_osd(value: &OSD) -> Result { value.$as() } } }; } integer_value!(i32, from_integer_with_int32, as_integer); integer_value!(u32, from_u_integer, as_u_integer); impl MessageValue for u64 { fn message_default() -> Self { 0 } fn to_message_osd(&self) -> Result { OSD::from_u_long(*self) } fn from_message_osd(value: &OSD) -> Result { let bytes = value.as_binary()?; let bytes: [u8; 8] = bytes.try_into().map_err(|_| Error::Argument)?; Ok(u64::from_be_bytes(bytes)) } } impl MessageValue for bool { fn message_default() -> Self { false } fn to_message_osd(&self) -> Result { OSD::from_boolean(*self) } fn from_message_osd(value: &OSD) -> Result { value.as_boolean() } } impl MessageValue for f32 { fn message_default() -> Self { 0.0 } fn to_message_osd(&self) -> Result { OSD::from_real_with_single(*self) } #[allow(clippy::cast_possible_truncation)] // Mirrors OSD.AsReal's C# double-to-float cast. fn from_message_osd(value: &OSD) -> Result { Ok(value.as_real()? as f32) } } impl MessageValue for f64 { fn message_default() -> Self { 0.0 } fn to_message_osd(&self) -> Result { OSD::from_real_with_double(*self) } fn from_message_osd(value: &OSD) -> Result { value.as_real() } } impl MessageValue for String { fn message_default() -> Self { String::new() } fn to_message_osd(&self) -> Result { OSD::from_string(self.clone()) } fn from_message_osd(value: &OSD) -> Result { value.as_string() } } impl MessageValue for UUID { fn message_default() -> Self { UUID::zero() } fn to_message_osd(&self) -> Result { OSD::from_uuid(*self) } fn from_message_osd(value: &OSD) -> Result { value.as_uuid() } } impl MessageValue for Vector3 { fn message_default() -> Self { Vector3::zero() } fn to_message_osd(&self) -> Result { OSD::from_vector3(*self) } fn from_message_osd(value: &OSD) -> Result { value.as_vector3() } } impl MessageValue for SystemTime { fn message_default() -> Self { UNIX_EPOCH } fn to_message_osd(&self) -> Result { OSD::from_date(*self) } fn from_message_osd(value: &OSD) -> Result { value.as_date() } } impl MessageValue for Uri { fn message_default() -> Self { Self("about:blank".into()) } fn to_message_osd(&self) -> Result { OSD::from_uri(self.clone()) } fn from_message_osd(value: &OSD) -> Result { Ok(value.as_uri()?.unwrap_or_else(|| Self(String::new()))) } } impl MessageValue for IpAddr { fn message_default() -> Self { Self::V4(Ipv4Addr::UNSPECIFIED) } fn to_message_osd(&self) -> Result { match self { Self::V4(address) if !address.is_unspecified() => { OSD::from_binary(address.octets().to_vec()) } Self::V4(_) | Self::V6(_) => Ok(OSD::Undefined), } } fn from_message_osd(value: &OSD) -> Result { let bytes = value.as_binary()?; match <[u8; 4]>::try_from(bytes) { Ok(octets) => Ok(Self::V4(Ipv4Addr::from(octets))), Err(_) => Ok(Self::V4(Ipv4Addr::UNSPECIFIED)), } } } impl MessageValue for OSD { fn message_default() -> Self { Self::Undefined } fn to_message_osd(&self) -> Result { Ok(self.clone()) } fn from_message_osd(value: &OSD) -> Result { Ok(value.clone()) } } impl MessageValue for OSDMap { fn message_default() -> Self { Self::new_with_constructor().expect("empty OSD map") } fn to_message_osd(&self) -> Result { self.copy() } fn from_message_osd(value: &OSD) -> Result { let OSD::Map(values) = value else { return Err(Error::Argument); }; Self::new_with_dictionary(values.clone()) } } impl MessageValue for HashMap { fn message_default() -> Self { Self::new() } fn to_message_osd(&self) -> Result { self.iter() .map(|(key, value)| Ok((key.clone(), value.to_message_osd()?))) .collect::, Error>>() .map(OSD::Map) } fn from_message_osd(value: &OSD) -> Result { as_map(value)? .iter() .map(|(key, value)| Ok((key.clone(), i32::from_message_osd(value)?))) .collect() } } impl MessageValue for AgentDisplayName { fn message_default() -> Self { Self::new().expect("AgentDisplayName construction is infallible") } fn to_message_osd(&self) -> Result { self.get_osd() } fn from_message_osd(value: &OSD) -> Result { Self::from_osd(value.clone()) } } impl MessageValue for PrimitivePhysicsProperties { fn message_default() -> Self { Self::new().expect("PrimitivePhysicsProperties construction is infallible") } fn to_message_osd(&self) -> Result { self.get_osd() } fn from_message_osd(value: &OSD) -> Result { Self::from_osd(value.clone()) } } impl MessageValue for MediaEntry { fn message_default() -> Self { Self::new().expect("MediaEntry construction is infallible") } fn to_message_osd(&self) -> Result { if self.is_undefined() { Ok(OSD::Undefined) } else { self.get_osd()?.copy() } } fn from_message_osd(value: &OSD) -> Result { Self::from_osd(value.clone()) } } macro_rules! transparent_value { ($type:ty, $inner:ty) => { impl MessageValue for $type { fn message_default() -> Self { Self(0) } fn to_message_osd(&self) -> Result { <$inner as MessageValue>::to_message_osd(&self.0) } fn from_message_osd(value: &OSD) -> Result { Ok(Self(<$inner as MessageValue>::from_message_osd(value)?)) } } }; } transparent_value!(ParcelFlags, u32); transparent_value!(TeleportFlags, u32); transparent_value!(PermissionMask, u32); impl MessageValue for InventoryType { fn message_default() -> Self { Self(0) } fn to_message_osd(&self) -> Result { OSD::from_integer_with_int32(i32::from(self.0)) } fn from_message_osd(value: &OSD) -> Result { Ok(Self( i8::try_from(value.as_integer()?).map_err(|_| Error::Argument)?, )) } } impl MessageValue for GroupPowers { fn message_default() -> Self { Self(0) } fn to_message_osd(&self) -> Result { OSD::from_long(self.0.cast_signed()) } fn from_message_osd(value: &OSD) -> Result { Ok(Self(value.as_long()?.cast_unsigned())) } } impl MessageValue for SimAccess { fn message_default() -> Self { Self::UNKNOWN } fn to_message_osd(&self) -> Result { OSD::from_integer_with_byte(self.0) } fn from_message_osd(value: &OSD) -> Result { Ok(Self( u8::try_from(value.as_integer()?).map_err(|_| Error::Argument)?, )) } } macro_rules! enum_value { ($type:ty, $default:path, [$($variant:path),+ $(,)?]) => { impl MessageValue for $type { fn message_default() -> Self { $default } fn to_message_osd(&self) -> Result { OSD::from_integer_with_int32(*self as i32) } fn from_message_osd(value: &OSD) -> Result { match value.as_integer()? { $(candidate if candidate == $variant as i32 => Ok($variant),)+ _ => Err(Error::Argument), } } } }; } enum_value!( InstantMessageDialog, InstantMessageDialog::MessageFromAgent, [ InstantMessageDialog::AcceptTeleport, InstantMessageDialog::BusyAutoResponse, InstantMessageDialog::ConsoleAndChatHistory, InstantMessageDialog::DenyTeleport, InstantMessageDialog::FriendshipAccepted, InstantMessageDialog::FriendshipDeclined, InstantMessageDialog::FriendshipOffered, InstantMessageDialog::FromTaskAsAlert, InstantMessageDialog::GodLikeRequestTeleport, InstantMessageDialog::GotoUrl, InstantMessageDialog::GroupInvitation, InstantMessageDialog::GroupInvitationAccept, InstantMessageDialog::GroupInvitationDecline, InstantMessageDialog::GroupNotice, InstantMessageDialog::GroupNoticeInventoryAccepted, InstantMessageDialog::GroupNoticeInventoryDeclined, InstantMessageDialog::GroupNoticeRequested, InstantMessageDialog::GroupVote, InstantMessageDialog::InventoryAccepted, InstantMessageDialog::InventoryDeclined, InstantMessageDialog::InventoryOffered, InstantMessageDialog::Lure911, InstantMessageDialog::MessageBox, InstantMessageDialog::MessageFromAgent, InstantMessageDialog::MessageFromObject, InstantMessageDialog::NewUserDefault, InstantMessageDialog::RequestLure, InstantMessageDialog::RequestTeleport, InstantMessageDialog::Session911Start, InstantMessageDialog::SessionAdd, InstantMessageDialog::SessionCardlessStart, InstantMessageDialog::SessionDrop, InstantMessageDialog::SessionGroupStart, InstantMessageDialog::SessionOfflineAdd, InstantMessageDialog::SessionSend, InstantMessageDialog::StartTyping, InstantMessageDialog::StopTyping, InstantMessageDialog::TaskInventoryAccepted, InstantMessageDialog::TaskInventoryDeclined, InstantMessageDialog::TaskInventoryOffered, ] ); enum_value!( SaleType, SaleType::Not, [ SaleType::Contents, SaleType::Copy, SaleType::Not, SaleType::Original ] ); enum_value!( AssetType, AssetType::Unknown, [ AssetType::Animation, AssetType::Bodypart, AssetType::CallingCard, AssetType::Clothing, AssetType::Folder, AssetType::Gesture, AssetType::ImageJPEG, AssetType::ImageTGA, AssetType::LSLBytecode, AssetType::LSLText, AssetType::Landmark, AssetType::Link, AssetType::LinkFolder, AssetType::Material, AssetType::Mesh, AssetType::Notecard, AssetType::Object, AssetType::Person, AssetType::Script, AssetType::Settings, AssetType::Simstate, AssetType::Sound, AssetType::SoundWAV, AssetType::Texture, AssetType::TextureTGA, AssetType::Unknown, AssetType::Widget ] ); enum_value!( FolderType, FolderType::None, [ FolderType::Animation, FolderType::BasicRoot, FolderType::BodyPart, FolderType::CallingCard, FolderType::Clothing, FolderType::CurrentOutfit, FolderType::EnsembleEnd, FolderType::EnsembleStart, FolderType::Favorites, FolderType::Gesture, FolderType::Inbox, FolderType::LSLText, FolderType::Landmark, FolderType::LostAndFound, FolderType::MarketplaceListings, FolderType::MarketplaceStock, FolderType::MarketplaceVersion, FolderType::Material, FolderType::Mesh, FolderType::MyOutfits, FolderType::None, FolderType::Notecard, FolderType::Object, FolderType::OldRoot, FolderType::Outbox, FolderType::Outfit, FolderType::Root, FolderType::Settings, FolderType::Snapshot, FolderType::Sound, FolderType::Suitcase, FolderType::Texture, FolderType::Trash ] ); enum_value!( InstantMessageOnline, InstantMessageOnline::Online, [InstantMessageOnline::Offline, InstantMessageOnline::Online,] ); enum_value!( LandingType, LandingType::None, [ LandingType::Direct, LandingType::LandingPoint, LandingType::None, ] ); enum_value!( ParcelCategory, ParcelCategory::None, [ ParcelCategory::Adult, ParcelCategory::Any, ParcelCategory::Arts, ParcelCategory::Business, ParcelCategory::Educational, ParcelCategory::Gaming, ParcelCategory::Hangout, ParcelCategory::Linden, ParcelCategory::Newcomer, ParcelCategory::None, ParcelCategory::Other, ParcelCategory::Park, ParcelCategory::Residential, ParcelCategory::Shopping, ParcelCategory::Stage, ] ); enum_value!( ParcelResult, ParcelResult::Single, [ ParcelResult::Multiple, ParcelResult::NoData, ParcelResult::Single, ] ); enum_value!( ParcelStatus, ParcelStatus::Leased, [ ParcelStatus::Abandoned, ParcelStatus::LeasePending, ParcelStatus::Leased, ParcelStatus::None, ] ); fn encode_array(values: &[T]) -> Result { values .iter() .map(MessageValue::to_message_osd) .collect::, _>>() .map(OSD::Array) } fn decode_array(value: &OSD) -> Result, Error> { let OSD::Array(values) = value else { return Err(Error::Argument); }; values.iter().map(T::from_message_osd).collect() } fn map_osd(entries: impl IntoIterator) -> OSD { OSD::Map( entries .into_iter() .map(|(key, value)| (key.to_owned(), value)) .collect(), ) } fn finish_map(entries: impl IntoIterator) -> Result { let OSD::Map(values) = map_osd(entries) else { unreachable!(); }; OSDMap::new_with_dictionary(values) } fn required(map: &OSDMap, key: &str) -> Result { map.get(key).ok_or(Error::Argument) } fn required_from<'a>(map: &'a HashMap, key: &str) -> Result<&'a OSD, Error> { map.get(key).ok_or(Error::Argument) } fn as_map(value: &OSD) -> Result<&HashMap, Error> { if let OSD::Map(values) = value { Ok(values) } else { Err(Error::Argument) } } fn as_array(value: &OSD) -> Result<&[OSD], Error> { if let OSD::Array(values) = value { Ok(values) } else { Err(Error::Argument) } } fn owned_block(map: &OSDMap, key: &str) -> Result, Error> { let value = required(map, key)?; let first = as_array(&value)?.first().ok_or(Error::Argument)?; Ok(as_map(first)?.clone()) } fn encode_struct_array(values: &[T]) -> Result { encode_array(values) } fn decode_struct_array(map: &OSDMap, key: &str) -> Result, Error> { decode_array(&required(map, key)?) } fn decode_value(map: &HashMap, key: &str) -> Result { T::from_message_osd(required_from(map, key)?) } fn decode_top(map: &OSDMap, key: &str) -> Result { T::from_message_osd(&required(map, key)?) } fn one_block_osd(entries: impl IntoIterator) -> OSD { OSD::Array(vec![map_osd(entries)]) } fn unix_seconds(value: SystemTime) -> u32 { value.duration_since(UNIX_EPOCH).map_or(0, |duration| { u32::try_from(duration.as_secs().min(u64::from(u32::MAX))).unwrap_or(u32::MAX) }) } fn time_from_unix(value: u32) -> SystemTime { UNIX_EPOCH + std::time::Duration::from_secs(u64::from(value)) } fn integer_from_u32(value: u32) -> Result { OSD::from_integer_with_u_int32(value) } #[allow(clippy::cast_precision_loss)] // C# ViewerStats explicitly converts AsInteger to float. fn integer_as_f32(value: &OSD) -> Result { Ok(value.as_integer()? as f32) } fn serialize_agent_state_update(value: &AgentStateUpdateMessage) -> Result { let access = map_osd([("max", value.max_access.to_message_osd()?)]); let preferences = map_osd([ ("access_prefs", access), ( "alter_navmesh_objects", value.alter_navmesh_objects.to_message_osd()?, ), ( "alter_permanent_objects", value.alter_permanent_objects.to_message_osd()?, ), ("god_level", value.god_level.to_message_osd()?), ("language", value.language.to_message_osd()?), ( "language_is_public", value.language_is_public.to_message_osd()?, ), ]); finish_map([ ( "can_modify_navmesh", value.can_modify_navmesh.to_message_osd()?, ), ( "has_modified_navmesh", value.has_modified_navmesh.to_message_osd()?, ), ("preferences", preferences), ]) } fn deserialize_agent_state_update( value: &mut AgentStateUpdateMessage, map: &OSDMap, ) -> Result<(), Error> { value.raw_data = map.clone(); value.can_modify_navmesh = map .get("can_modify_navmesh") .unwrap_or_default() .as_boolean()?; value.has_modified_navmesh = map .get("has_modified_navmesh") .unwrap_or_default() .as_boolean()?; let Some(OSD::Map(preferences)) = map.get("preferences") else { return Ok(()); }; let preferences = OSDMap::new_with_dictionary(preferences)?; value.alter_navmesh_objects = preferences .get("alter_navmesh_objects") .unwrap_or_default() .as_boolean()?; value.alter_permanent_objects = preferences .get("alter_permanent_objects") .unwrap_or_default() .as_boolean()?; value.god_level = preferences .get("god_level") .unwrap_or_default() .as_integer()?; value.language = preferences .get("language") .unwrap_or_default() .as_string()?; value.language_is_public = preferences .get("language_is_public") .unwrap_or_default() .as_boolean()?; if let Some(OSD::Map(access)) = preferences.get("access_prefs") { value.max_access = access.get("max").unwrap_or(&OSD::Undefined).as_string()?; } Ok(()) } fn serialize_crossed_region(value: &CrossedRegionMessage) -> Result { finish_map([ ( "Info", one_block_osd([ ("LookAt", value.look_at.to_message_osd()?), ("Position", value.position.to_message_osd()?), ]), ), ( "AgentData", one_block_osd([ ("AgentID", value.agent_id.to_message_osd()?), ("SessionID", value.session_id.to_message_osd()?), ]), ), ( "RegionData", one_block_osd([ ("RegionHandle", value.region_handle.to_message_osd()?), ("SeedCapability", value.seed_capability.to_message_osd()?), ("SimIP", value.ip.to_message_osd()?), ("SimPort", value.port.to_message_osd()?), ("RegionSizeX", value.region_size_x.to_message_osd()?), ("RegionSizeY", value.region_size_y.to_message_osd()?), ]), ), ]) } fn first_block(map: &OSDMap, key: &str) -> Result { let encoded = required(map, key)?; let blocks = as_array(&encoded)?; let block = as_map(blocks.first().ok_or(Error::Argument)?)?; OSDMap::new_with_dictionary(block.clone()) } fn deserialize_crossed_region(value: &mut CrossedRegionMessage, map: &OSDMap) -> Result<(), Error> { let info = first_block(map, "Info")?; value.look_at = decode_top(&info, "LookAt")?; value.position = decode_top(&info, "Position")?; let agent = first_block(map, "AgentData")?; value.agent_id = decode_top(&agent, "AgentID")?; value.session_id = decode_top(&agent, "SessionID")?; let region = first_block(map, "RegionData")?; value.region_handle = decode_top(®ion, "RegionHandle")?; value.seed_capability = decode_top(®ion, "SeedCapability")?; value.ip = decode_top(®ion, "SimIP")?; value.port = decode_top(®ion, "SimPort")?; if region.get("RegionSizeX").is_some() { value.region_size_x = decode_top(®ion, "RegionSizeX")?; } if region.get("RegionSizeY").is_some() { value.region_size_y = decode_top(®ion, "RegionSizeY")?; } Ok(()) } fn serialize_get_object_cost(value: &GetObjectCostMessage) -> Result { let values = HashMap::from([ ( "linked_set_resource_cost".to_owned(), OSD::Real(value.link_cost), ), ("resource_cost".to_owned(), OSD::Real(value.object_cost)), ("physics_cost".to_owned(), OSD::Real(value.physics_cost)), ( "linked_set_physics_cost".to_owned(), OSD::Real(value.link_physics_cost), ), ]); OSDMap::new_with_dictionary(HashMap::from([( value.object_id.to_string(), OSD::Map(values), )])) } fn deserialize_get_object_cost( value: &mut GetObjectCostMessage, map: &OSDMap, ) -> Result<(), Error> { let mut entries: Vec<_> = map.snapshot().into_iter().collect(); entries.sort_unstable_by(|left, right| left.0.cmp(&right.0)); let Some((id, OSD::Map(costs))) = entries.into_iter().next() else { return Err(Error::Argument); }; value.object_id = UUID::new_with_string(id).unwrap_or_else(|_| UUID::zero()); value.link_cost = costs .get("linked_set_resource_cost") .unwrap_or(&OSD::Undefined) .as_real()?; value.object_cost = costs .get("resource_cost") .unwrap_or(&OSD::Undefined) .as_real()?; value.physics_cost = costs .get("physics_cost") .unwrap_or(&OSD::Undefined) .as_real()?; value.link_physics_cost = costs .get("linked_set_physics_cost") .unwrap_or(&OSD::Undefined) .as_real()?; Ok(()) } fn serialize_base_resources(value: &BaseResourcesInfo) -> Result { fn entries(values: &HashMap) -> Result { let mut values: Vec<_> = values.iter().collect(); values.sort_unstable_by(|left, right| left.0.cmp(right.0)); values .into_iter() .map(|(type_, amount)| { Ok(map_osd([ ("type", type_.to_message_osd()?), ("amount", amount.to_message_osd()?), ])) }) .collect::, Error>>() .map(OSD::Array) } finish_map([( "summary", map_osd([ ("available", entries(&value.summary_available)?), ("used", entries(&value.summary_used)?), ]), )]) } fn deserialize_base_resources(value: &mut BaseResourcesInfo, map: &OSDMap) -> Result<(), Error> { fn entries(value: &OSD) -> Result, Error> { as_array(value)? .iter() .map(|entry| { let entry = as_map(entry)?; Ok(( String::from_message_osd(required_from(entry, "type")?)?, i32::from_message_osd(required_from(entry, "amount")?)?, )) }) .collect() } let summary_value = required(map, "summary")?; let summary = as_map(&summary_value)?; value.summary_available = entries(required_from(summary, "available")?)?; value.summary_used = entries(required_from(summary, "used")?)?; Ok(()) } fn serialize_nav_mesh_status(value: &NavMeshStatusUpdateMessage) -> Result { finish_map([ ("region_id", value.region_id.to_message_osd()?), ("status", value.status.to_message_osd()?), ("version", value.version.to_message_osd()?), ]) } fn deserialize_nav_mesh_status( value: &mut NavMeshStatusUpdateMessage, map: &OSDMap, ) -> Result<(), Error> { value.raw_data = map.clone(); value.region_id = decode_top(map, "region_id")?; value.status = decode_top(map, "status")?; value.version = decode_top(map, "version")?; Ok(()) } fn serialize_region_info(value: &RegionInfoMessage) -> Result { finish_map([ ("parcel_local_id", value.parcel_local_id.to_message_osd()?), ("region_name", value.region_name.to_message_osd()?), ( "voice_credentials", map_osd([("channel_uri", value.channel_uri.to_message_osd()?)]), ), ]) } fn deserialize_region_info(value: &mut RegionInfoMessage, map: &OSDMap) -> Result<(), Error> { value.parcel_local_id = decode_top(map, "parcel_local_id")?; value.region_name = decode_top(map, "region_name")?; let OSD::Map(voice) = required(map, "voice_credentials")? else { return Err(Error::Argument); }; value.channel_uri = voice .get("channel_uri") .unwrap_or(&OSD::Undefined) .as_string()?; Ok(()) } fn serialize_render_materials(_value: &RenderMaterialsMessage) -> Result { OSDMap::new_with_constructor() } #[allow(clippy::unnecessary_wraps)] // GeneratedMessage requires a fallible codec signature. fn deserialize_render_materials( value: &mut RenderMaterialsMessage, map: &OSDMap, ) -> Result<(), Error> { let decoded = map .get("Zipped") .ok_or(Error::Argument) .and_then(|value| value.as_binary()) .and_then(|bytes| { let mut output = Vec::new(); ZlibDecoder::new(bytes.as_slice()) .read_to_end(&mut output) .map_err(|_| Error::Argument)?; OSDParser::deserialize_llsd_binary_with_bytes(output) }); value.material_data = decoded.unwrap_or_else(|_| OSD::Map(HashMap::new())); Ok(()) } fn serialize_set_display_name(value: &SetDisplayNameMessage) -> Result { finish_map([( "display_name", OSD::Array(vec![ OSD::String(value.old_display_name.clone()), OSD::String(value.new_display_name.clone()), ]), )]) } fn deserialize_set_display_name( value: &mut SetDisplayNameMessage, map: &OSDMap, ) -> Result<(), Error> { let OSD::Array(names) = required(map, "display_name")? else { return Err(Error::Argument); }; value.old_display_name = names.first().unwrap_or(&OSD::Undefined).as_string()?; value.new_display_name = names.get(1).unwrap_or(&OSD::Undefined).as_string()?; Ok(()) } fn serialize_set_display_name_reply(value: &SetDisplayNameReplyMessage) -> Result { finish_map([ ("content", value.display_name.to_message_osd()?), ("reason", value.reason.to_message_osd()?), ("status", value.status.to_message_osd()?), ]) } fn deserialize_set_display_name_reply( value: &mut SetDisplayNameReplyMessage, map: &OSDMap, ) -> Result<(), Error> { value.display_name = decode_top(map, "content")?; value.reason = decode_top(map, "reason")?; value.status = decode_top(map, "status")?; Ok(()) } fn serialize_display_name_update(value: &DisplayNameUpdateMessage) -> Result { let OSD::Map(mut agent) = value.display_name.to_message_osd()? else { return Err(Error::Argument); }; agent.insert( "old_display_name".into(), OSD::String(value.old_display_name.clone()), ); finish_map([("agent", OSD::Map(agent))]) } fn deserialize_display_name_update( value: &mut DisplayNameUpdateMessage, map: &OSDMap, ) -> Result<(), Error> { let agent = required(map, "agent")?; let OSD::Map(agent_map) = &agent else { return Err(Error::Argument); }; value.display_name = AgentDisplayName::from_osd(agent.clone())?; value.old_display_name = agent_map .get("old_display_name") .unwrap_or(&OSD::Undefined) .as_string()?; Ok(()) } fn serialize_uploader_upload(value: &UploaderRequestUpload) -> Result { finish_map([ ("state", OSD::String("upload".into())), ("uploader", value.url.to_message_osd()?), ]) } fn deserialize_uploader_upload( value: &mut UploaderRequestUpload, map: &OSDMap, ) -> Result<(), Error> { if required(map, "state")?.as_string()? != "upload" { return Err(Error::Argument); } value.url = decode_top(map, "uploader")?; Ok(()) } fn serialize_uploader_complete(value: &UploaderRequestComplete) -> Result { finish_map([ ("state", OSD::String("complete".into())), ("new_asset", value.asset_id.to_message_osd()?), ]) } fn deserialize_uploader_complete( value: &mut UploaderRequestComplete, map: &OSDMap, ) -> Result<(), Error> { if required(map, "state")?.as_string()? != "complete" { return Err(Error::Argument); } value.asset_id = decode_top(map, "new_asset")?; Ok(()) } fn serialize_bulk_folder( value: &BulkUpdateInventoryMessageFolderDataInfo, ) -> Result { finish_map([ ("FolderID", value.folder_id.to_message_osd()?), ("ParentID", value.parent_id.to_message_osd()?), ("Name", value.name.to_message_osd()?), ("Type", value.type_.to_message_osd()?), ]) } fn deserialize_bulk_folder( value: &mut BulkUpdateInventoryMessageFolderDataInfo, map: &OSDMap, ) -> Result<(), Error> { value.folder_id = decode_top(map, "FolderID")?; value.parent_id = decode_top(map, "ParentID")?; value.name = decode_top(map, "Name")?; value.type_ = decode_top(map, "Type")?; Ok(()) } fn serialize_bulk_item(value: &BulkUpdateInventoryMessageItemDataInfo) -> Result { finish_map([ ("ItemID", value.item_id.to_message_osd()?), ("CallbackID", value.callback_id.to_message_osd()?), ("FolderID", value.folder_id.to_message_osd()?), ("CreatorID", value.creator_id.to_message_osd()?), ("OwnerID", value.owner_id.to_message_osd()?), ("GroupID", value.group_id.to_message_osd()?), ("BaseMask", value.base_mask.to_message_osd()?), ("OwnerMask", value.owner_mask.to_message_osd()?), ("GroupMask", value.group_mask.to_message_osd()?), ("EveryoneMask", value.everyone_mask.to_message_osd()?), ("NextOwnerMask", value.next_owner_mask.to_message_osd()?), ("GroupOwned", value.group_owned.to_message_osd()?), ("AssetID", value.asset_id.to_message_osd()?), ("Type", value.type_.to_message_osd()?), ("InvType", value.inv_type.to_message_osd()?), ("Flags", value.flags.to_message_osd()?), ("SaleType", value.sale_type.to_message_osd()?), ("SalePrice", value.sale_price.to_message_osd()?), ("Name", value.name.to_message_osd()?), ("Description", value.description.to_message_osd()?), ( "CreationDate", unix_seconds(value.creation_date).to_message_osd()?, ), ("CRC", value.crc.to_message_osd()?), ]) } fn deserialize_bulk_item( value: &mut BulkUpdateInventoryMessageItemDataInfo, map: &OSDMap, ) -> Result<(), Error> { value.item_id = decode_top(map, "ItemID")?; value.callback_id = decode_top(map, "CallbackID")?; value.folder_id = decode_top(map, "FolderID")?; value.creator_id = decode_top(map, "CreatorID")?; value.owner_id = decode_top(map, "OwnerID")?; value.group_id = decode_top(map, "GroupID")?; value.base_mask = decode_top(map, "BaseMask")?; value.owner_mask = decode_top(map, "OwnerMask")?; value.group_mask = decode_top(map, "GroupMask")?; value.everyone_mask = decode_top(map, "EveryoneMask")?; value.next_owner_mask = decode_top(map, "NextOwnerMask")?; value.group_owned = decode_top(map, "GroupOwned")?; value.asset_id = decode_top(map, "AssetID")?; value.type_ = decode_top(map, "Type")?; value.inv_type = decode_top(map, "InvType")?; value.flags = decode_top(map, "Flags")?; value.sale_type = decode_top(map, "SaleType")?; // Preserve the reference adapter's field selection for wire compatibility. value.sale_price = decode_top(map, "SaleType")?; value.name = decode_top(map, "Name")?; value.description = decode_top(map, "Description")?; value.creation_date = time_from_unix(decode_top(map, "CreationDate")?); value.crc = decode_top(map, "CRC")?; Ok(()) } fn serialize_bulk_inventory(value: &BulkUpdateInventoryMessage) -> Result { finish_map([ ( "AgentData", one_block_osd([ ("AgentID", value.agent_id.to_message_osd()?), ("TransactionID", value.transaction_id.to_message_osd()?), ]), ), ("FolderData", encode_struct_array(&value.folder_data)?), ("ItemData", encode_struct_array(&value.item_data)?), ]) } fn deserialize_bulk_inventory( value: &mut BulkUpdateInventoryMessage, map: &OSDMap, ) -> Result<(), Error> { let agent = first_block(map, "AgentData")?; value.agent_id = decode_top(&agent, "AgentID")?; value.transaction_id = decode_top(&agent, "TransactionID")?; value.folder_data = match map.get("FolderData") { Some(encoded) => decode_array(&encoded)?, None => Vec::new(), }; value.item_data = match map.get("ItemData") { Some(encoded) => decode_array(&encoded)?, None => Vec::new(), }; Ok(()) } macro_rules! map_message { ($type:ty { scalar: [$($scalar:ident => $scalar_key:literal),* $(,)?], array: [$($array:ident => $array_key:literal),* $(,)?], bytes: [$($bytes:ident => $bytes_key:literal),* $(,)?] } $(init $init:expr)?) => { impl GeneratedMessage for $type { fn new_generated() -> Self { #[allow(unused_mut)] let mut value = Self { $($scalar: MessageValue::message_default(),)* $($array: Vec::new(),)* $($bytes: Vec::new(),)* }; $($init(&mut value);)? value } fn serialize_generated(&self) -> Result { let map = OSDMap::new_with_constructor()?; $(map.add_with_string_osd($scalar_key.into(), self.$scalar.to_message_osd()?)?;)* $(map.add_with_string_osd($array_key.into(), encode_array(&self.$array)?)?;)* $(map.add_with_string_osd($bytes_key.into(), OSD::from_binary(self.$bytes.clone())?)?;)* Ok(map) } fn deserialize_generated(&mut self, map: &OSDMap) -> Result<(), Error> { $(if let Some(value) = map.get($scalar_key) { self.$scalar = MessageValue::from_message_osd(&value)?; })* $(if let Some(value) = map.get($array_key) { self.$array = decode_array(&value)?; })* $(if let Some(value) = map.get($bytes_key) { self.$bytes = value.as_binary()?; })* Ok(()) } } impl MessageValue for $type { fn message_default() -> Self { ::new_generated() } fn to_message_osd(&self) -> Result { self.serialize_generated()?.copy() } fn from_message_osd(value: &OSD) -> Result { let OSD::Map(values) = value else { return Err(Error::Argument); }; let map = OSDMap::new_with_dictionary(values.clone())?; let mut output = ::new_generated(); output.deserialize_generated(&map)?; Ok(output) } } }; } macro_rules! custom_message { ($type:ty { scalar: [$($scalar:ident),* $(,)?], array: [$($array:ident),* $(,)?], bytes: [$($bytes:ident),* $(,)?] } => $serialize:path, $deserialize:path $(, init $init:expr)?) => { impl GeneratedMessage for $type { fn new_generated() -> Self { #[allow(unused_mut)] let mut value = Self { $($scalar: MessageValue::message_default(),)* $($array: Vec::new(),)* $($bytes: Vec::new(),)* }; $($init(&mut value);)? value } fn serialize_generated(&self) -> Result { $serialize(self) } fn deserialize_generated(&mut self, map: &OSDMap) -> Result<(), Error> { $deserialize(self, map) } } impl MessageValue for $type { fn message_default() -> Self { ::new_generated() } fn to_message_osd(&self) -> Result { self.serialize_generated()?.copy() } fn from_message_osd(value: &OSD) -> Result { let OSD::Map(values) = value else { return Err(Error::Argument); }; let map = OSDMap::new_with_dictionary(values.clone())?; let mut output = ::new_generated(); output.deserialize_generated(&map)?; Ok(output) } } }; } macro_rules! wrapped_message { ($type:ty) => { impl GeneratedMessage for $type { fn new_generated() -> Self { Self { request: MessageValue::message_default(), } } fn serialize_generated(&self) -> Result { let OSD::Map(values) = self.request.to_message_osd()? else { return Err(Error::Argument); }; OSDMap::new_with_dictionary(values) } fn deserialize_generated(&mut self, map: &OSDMap) -> Result<(), Error> { self.request = MessageValue::from_message_osd(&map.copy()?)?; Ok(()) } } impl MessageValue for $type { fn message_default() -> Self { ::new_generated() } fn to_message_osd(&self) -> Result { self.serialize_generated()?.copy() } fn from_message_osd(value: &OSD) -> Result { let OSD::Map(values) = value else { return Err(Error::Argument); }; let map = OSDMap::new_with_dictionary(values.clone())?; let mut output = ::new_generated(); output.deserialize_generated(&map)?; Ok(output) } } }; } macro_rules! osd_entries { ($value:expr; $($field:ident => $key:literal),* $(,)?) => { vec![$(($key, $value.$field.to_message_osd()?)),*] }; } macro_rules! decode_fields { ($value:expr, $map:expr; $($field:ident => $key:literal),* $(,)?) => { $($value.$field = decode_value($map, $key)?;)* }; } map_message!(AgentGroupDataUpdateMessageGroupData { scalar: [ accept_notices => "AcceptNotices", contribution => "Contribution", group_id => "GroupID", group_insignia_id => "GroupInsigniaID", group_name => "GroupName", group_powers => "GroupPowers", group_title => "GroupTitle" ], array: [], bytes: [] }); map_message!(AgentGroupDataUpdateMessageNewGroupData { scalar: [list_in_profile => "ListInProfile"], array: [], bytes: [] }); map_message!(AgentDropGroupMessageAgentData { scalar: [agent_id => "AgentID", group_id => "GroupID"], array: [], bytes: [] }); map_message!(AgentDropGroupMessage { scalar: [], array: [agent_data_block => "AgentData"], bytes: [] }); custom_message!(BulkUpdateInventoryMessageFolderDataInfo { scalar: [folder_id, name, parent_id, type_], array: [], bytes: [] } => serialize_bulk_folder, deserialize_bulk_folder); custom_message!(BulkUpdateInventoryMessageItemDataInfo { scalar: [asset_id, base_mask, crc, callback_id, creation_date, creator_id, description, everyone_mask, flags, folder_id, group_id, group_mask, group_owned, inv_type, item_id, name, next_owner_mask, owner_id, owner_mask, sale_price, sale_type, type_], array: [], bytes: [] } => serialize_bulk_item, deserialize_bulk_item); custom_message!(BulkUpdateInventoryMessage { scalar: [agent_id, transaction_id], array: [folder_data, item_data], bytes: [] } => serialize_bulk_inventory, deserialize_bulk_inventory); custom_message!(AgentStateUpdateMessage { scalar: [alter_navmesh_objects, alter_permanent_objects, can_modify_navmesh, god_level, has_modified_navmesh, language, language_is_public, max_access, raw_data], array: [], bytes: [] } => serialize_agent_state_update, deserialize_agent_state_update); custom_message!(AgentGroupDataUpdateMessage { scalar: [agent_id, avatar_id], array: [group_data_block, new_group_data_block], bytes: [] } => serialize_agent_group, deserialize_agent_group); custom_message!(ChatSessionAcceptInvitation { scalar: [base, session_id], array: [], bytes: [] } => serialize_chat_accept, deserialize_chat_accept, init |value: &mut ChatSessionAcceptInvitation| value.base.method = "accept invitation".into()); custom_message!(ChatSessionRequestMuteUpdate { scalar: [base, agent_id, request_key, request_value, session_id], array: [], bytes: [] } => serialize_chat_mute, deserialize_chat_mute, init |value: &mut ChatSessionRequestMuteUpdate| value.base.method = "mute update".into()); custom_message!(ChatSessionRequestStartConference { scalar: [base, session_id], array: [agents_block], bytes: [] } => serialize_chat_start, deserialize_chat_start, init |value: &mut ChatSessionRequestStartConference| value.base.method = "start conference".into()); custom_message!(ChatterBoxInvitationMessage { scalar: [ dialog, from_agent_id, from_agent_name, group_im, im_session_id, message, offline, parent_estate_id, position, region_id, timestamp, to_agent_id, voice, voice_channel_credentials, voice_channel_uri ], array: [], bytes: [binary_bucket] } => serialize_chatter_invitation, deserialize_chatter_invitation); custom_message!(CrossedRegionMessage { scalar: [agent_id, ip, look_at, port, position, region_handle, region_size_x, region_size_y, seed_capability, session_id], array: [], bytes: [] } => serialize_crossed_region, deserialize_crossed_region, init |value: &mut CrossedRegionMessage| { value.region_size_x = crate::network_manager::Simulator::DEFAULT_REGION_SIZE_X; value.region_size_y = crate::network_manager::Simulator::DEFAULT_REGION_SIZE_Y; }); map_message!(ChatterBoxSessionAgentListUpdatesMessageAgentUpdatesBlock { scalar: [ agent_id => "agent_id", can_voice_chat => "can_voice_chat", is_moderator => "is_moderator", mute_text => "mute_text", mute_voice => "mute_voice", transition => "transition" ], array: [], bytes: [] }); custom_message!(ChatterBoxSessionAgentListUpdatesMessage { scalar: [session_id], array: [updates], bytes: [] } => serialize_chatter_updates, deserialize_chatter_updates); custom_message!(ChatterBoxSessionStartReplyMessage { scalar: [ moderated_voice, session_id, session_name, success, temp_session_id, type_, voice_enabled ], array: [], bytes: [] } => serialize_chatter_start_reply, deserialize_chatter_start_reply); map_message!(ChatterboxSessionEventReplyMessage { scalar: [session_id => "session_id", success => "success"], array: [], bytes: [] }); map_message!(CopyInventoryFromNotecardMessage { scalar: [ callback_id => "callback-id", folder_id => "folder-id", item_id => "item-id", notecard_id => "notecard-id", object_id => "object-id" ], array: [], bytes: [] }); map_message!(DirLandReplyMessageQueryReply { scalar: [ actual_area => "ActualArea", auction => "Auction", for_sale => "ForSale", name => "Name", parcel_id => "ParcelID", product_sku => "ProductSKU", sale_price => "SalePrice" ], array: [], bytes: [] }); custom_message!(DirLandReplyMessage { scalar: [agent_id, query_id], array: [query_replies], bytes: [] } => serialize_dir_land, deserialize_dir_land); custom_message!(EnableSimulatorMessageSimulatorInfoBlock { scalar: [ip, port, region_handle, region_size_x, region_size_y], array: [], bytes: [] } => serialize_simulator_info, deserialize_simulator_info); custom_message!(EnableSimulatorMessage { scalar: [], array: [simulators], bytes: [] } => serialize_enable_simulator, deserialize_enable_simulator); custom_message!(EstablishAgentCommunicationMessage { scalar: [address, agent_id, port, seed_capability], array: [], bytes: [] } => serialize_establish_agent, deserialize_establish_agent); custom_message!(LandStatReplyMessageReportDataBlock { scalar: [ location, mono_score, owner_name, score, task_id, task_local_id, task_name, time_stamp ], array: [], bytes: [] } => serialize_land_stat_block, deserialize_land_stat_block); custom_message!(LandStatReplyMessage { scalar: [report_type, request_flags, total_object_count], array: [report_data_blocks], bytes: [] } => serialize_land_stat, deserialize_land_stat); map_message!(MapLayerReplyVariantLayerData { scalar: [ bottom => "Bottom", image_id => "ImageID", left => "Left", right => "Right", top => "Top" ], array: [], bytes: [] }); custom_message!(MapLayerReplyVariant { scalar: [base], array: [layer_data_blocks], bytes: [] } => serialize_map_layer, deserialize_map_layer); map_message!(ParcelObjectOwnersReplyMessagePrimOwner { scalar: [ count => "Count", is_group_owned => "IsGroupOwned", online_status => "OnlineStatus", owner_id => "OwnerID", time_stamp => "TimeStamp" ], array: [], bytes: [] }); custom_message!(ParcelObjectOwnersReplyMessage { scalar: [], array: [prim_owners_block], bytes: [] } => serialize_parcel_owners, deserialize_parcel_owners); custom_message!(ParcelPropertiesMessage { scalar: [ aabb_max, aabb_min, any_av_sounds, area, auction_id, auth_buyer_id, category, claim_date, claim_price, desc, group_av_sounds, group_id, group_prims, is_group_owned, landing_type, local_id, max_prims, media_auto_scale, media_desc, media_height, media_id, media_loop, media_type, media_url, media_width, music_url, name, obscure_media, obscure_music, other_clean_time, other_count, other_prims, owner_id, owner_prims, parcel_flags, parcel_prim_bonus, pass_hours, pass_price, privacy, public_count, region_deny_age_unverified, region_deny_anonymous, region_deny_identified, region_deny_transacted, region_push_override, rent_price, request_result, sale_price, see_a_vs, selected_prims, self_count, sequence_id, sim_wide_max_prims, sim_wide_total_prims, snap_selection, snapshot_id, status, total_prims, user_location, user_look_at ], array: [], bytes: [bitmap] } => serialize_parcel_properties, deserialize_parcel_properties); custom_message!(ParcelPropertiesUpdateMessage { scalar: [ any_av_sounds, auth_buyer_id, category, desc, group_av_sounds, group_id, landing, local_id, media_auto_scale, media_desc, media_height, media_id, media_loop, media_type, media_url, media_width, music_url, name, obscure_media, obscure_music, parcel_flags, pass_hours, pass_price, privacy, sale_price, see_a_vs, snapshot_id, user_location, user_look_at ], array: [], bytes: [] } => serialize_parcel_update, deserialize_parcel_update); custom_message!(ParcelVoiceInfoRequestMessage { scalar: [parcel_id, region_name, sip_channel_uri], array: [], bytes: [] } => serialize_parcel_voice, deserialize_parcel_voice); map_message!(PlacesReplyMessageQueryData { scalar: [ actual_area => "ActualArea", billable_area => "BillableArea", description => "Desc", dwell => "Dwell", flags => "Flags", global_x => "GlobalX", global_y => "GlobalY", global_z => "GlobalZ", name => "Name", owner_id => "OwnerID", price => "Price", product_sku => "ProductSKU", sim_name => "SimName", snap_shot_id => "SnapshotID" ], array: [], bytes: [] }); custom_message!(PlacesReplyMessage { scalar: [agent_id, query_id, transaction_id], array: [query_data_blocks], bytes: [] } => serialize_places, deserialize_places); map_message!(ProvisionVoiceAccountRequestMessage { scalar: [password => "password", username => "username"], array: [], bytes: [] }); map_message!(RemoteParcelRequestReply { scalar: [parcel_id => "parcel_id"], array: [], bytes: [] }); map_message!(SearchStatRequestRequest { scalar: [classified_id => "classified_id"], array: [], bytes: [] }); map_message!(SearchStatRequestReply { scalar: [ map_clicks => "map_clicks", profile_clicks => "profile_clicks", search_map_clicks => "search_map_clicks", search_profile_clicks => "search_profile_clicks", search_teleport_clicks => "search_teleport_clicks", teleport_clicks => "teleport_clicks" ], array: [], bytes: [] }); map_message!(ForceCloseChatterBoxSessionMessage { scalar: [reason => "reason", session_id => "session_id"], array: [], bytes: [] }); map_message!(GetDisplayNamesMessage { scalar: [], array: [agents => "agents", bad_i_ds => "bad_ids"], bytes: [] }); custom_message!(GetObjectCostMessage { scalar: [link_cost, link_physics_cost, object_cost, object_id, physics_cost], array: [], bytes: [] } => serialize_get_object_cost, deserialize_get_object_cost); custom_message!(BaseResourcesInfo { scalar: [summary_available, summary_used], array: [], bytes: [] } => serialize_base_resources, deserialize_base_resources); map_message!(GetObjectCostRequest { scalar: [], array: [object_i_ds => "object_ids"], bytes: [] }); map_message!(LandResourcesMessage { scalar: [ script_resource_details => "ScriptResourceDetails", script_resource_summary => "ScriptResourceSummary" ], array: [], bytes: [] }); map_message!(LandResourcesRequest { scalar: [parcel_id => "parcel_id"], array: [], bytes: [] }); custom_message!(NavMeshStatusUpdateMessage { scalar: [raw_data, region_id, status, version], array: [], bytes: [] } => serialize_nav_mesh_status, deserialize_nav_mesh_status); map_message!(ObjectMediaNavigateMessage { scalar: [face => "texture_index", prim_id => "object_id", url => "current_url"], array: [], bytes: [] }); map_message!(ObjectMediaRequest { scalar: [prim_id => "object_id", verb => "verb"], array: [], bytes: [] } init |value: &mut ObjectMediaRequest| value.verb = "GET".into()); map_message!(ObjectMediaResponse { scalar: [prim_id => "object_id", version => "object_media_version"], array: [face_media => "object_media_data"], bytes: [] }); map_message!(ObjectMediaUpdate { scalar: [prim_id => "object_id", verb => "verb"], array: [face_media => "object_media_data"], bytes: [] } init |value: &mut ObjectMediaUpdate| value.verb = "UPDATE".into()); map_message!(ObjectPhysicsPropertiesMessage { scalar: [], array: [object_physics_properties => "ObjectData"], bytes: [] }); custom_message!(RegionInfoMessage { scalar: [channel_uri, parcel_local_id, region_name], array: [], bytes: [] } => serialize_region_info, deserialize_region_info); custom_message!(RenderMaterialsMessage { scalar: [material_data], array: [], bytes: [] } => serialize_render_materials, deserialize_render_materials); map_message!(RequiredVoiceVersionMessage { scalar: [ major_version => "major_version", minor_version => "minor_version", region_name => "region_name" ], array: [], bytes: [] }); custom_message!(ScriptRunningReplyMessage { scalar: [item_id, mono, object_id, running], array: [], bytes: [] } => serialize_script_running, deserialize_script_running); map_message!(SendPostcardMessage { scalar: [ from_name => "name", global_position => "pos-global", message => "msg", subject => "subject", to_email => "to" ], array: [], bytes: [] }); custom_message!(SetDisplayNameMessage { scalar: [new_display_name, old_display_name], array: [], bytes: [] } => serialize_set_display_name, deserialize_set_display_name); custom_message!(SetDisplayNameReplyMessage { scalar: [display_name, reason, status], array: [], bytes: [] } => serialize_set_display_name_reply, deserialize_set_display_name_reply); custom_message!(DisplayNameUpdateMessage { scalar: [display_name, old_display_name], array: [], bytes: [] } => serialize_display_name_update, deserialize_display_name_update); map_message!(SimConsoleResponseMessage { scalar: [body => "body"], array: [], bytes: [] }); custom_message!(TeleportFailedMessage { scalar: [agent_id, extra_params, message_key, reason], array: [], bytes: [] } => serialize_teleport_failed, deserialize_teleport_failed); custom_message!(TeleportFinishMessage { scalar: [ agent_id, flags, ip, location_id, port, region_handle, region_size_x, region_size_y, seed_capability, sim_access ], array: [], bytes: [] } => serialize_teleport_finish, deserialize_teleport_finish); custom_message!(UpdateAgentInformationMessage { scalar: [max_access], array: [], bytes: [] } => serialize_agent_information, deserialize_agent_information); map_message!(UpdateAgentInventoryRequestMessage { scalar: [item_id => "item_id"], array: [], bytes: [] }); map_message!(UpdateAgentLanguageMessage { scalar: [language => "language", language_public => "language_is_public"], array: [], bytes: [] }); map_message!(UpdateScriptAgentRequestMessage { scalar: [item_id => "item_id", target => "target"], array: [], bytes: [] } init |value: &mut UpdateScriptAgentRequestMessage| value.target = "lsl2".into()); map_message!(UpdateScriptTaskUpdateMessage { scalar: [ item_id => "item_id", script_running => "is_script_running", target => "target", task_id => "task_id" ], array: [], bytes: [] }); custom_message!(ViewerStatsMessage { scalar: [ agent_fps, agent_language, agent_memory_used, agent_ping, agent_runtime, agent_start_time, agent_version, agents_in_view, failures_invalid, failures_off_circuit, failures_resent, failures_send_packet, in_compressed_packets, in_kbytes, in_packets, in_savings, meters_traveled, misc_int1, misc_int2, misc_string1, misc_version, out_compressed_packets, out_kbytes, out_packets, out_savings, regions_visited, session_id, simulator_fps, stats_dropped, stats_failed_resends, system_cpu, system_gpu, system_gpu_class, system_gpu_vendor, system_gpu_version, system_installed_ram, system_os, vertex_buffers_enabled, object_kbytes, texture_kbytes, world_kbytes ], array: [], bytes: [] } => serialize_viewer_stats, deserialize_viewer_stats); custom_message!(UploaderRequestUpload { scalar: [url], array: [], bytes: [] } => serialize_uploader_upload, deserialize_uploader_upload); custom_message!(UploaderRequestComplete { scalar: [asset_id], array: [], bytes: [] } => serialize_uploader_complete, deserialize_uploader_complete); custom_message!(NewFileAgentInventoryMessage { scalar: [asset_type, description, everyone_mask, folder_id, group_mask, inventory_type, name, next_owner_mask], array: [], bytes: [] } => serialize_new_file_inventory, deserialize_new_file_inventory); custom_message!(NewFileAgentInventoryVariablePriceMessage { scalar: [asset_type, description, everyone_mask, folder_id, group_mask, inventory_type, name, next_owner_mask], array: [], bytes: [] } => serialize_new_file_inventory_variable, deserialize_new_file_inventory_variable); map_message!(NewFileAgentInventoryReplyMessage { scalar: [state => "state", uploader => "uploader"], array: [], bytes: [] } init |value: &mut NewFileAgentInventoryReplyMessage| value.state = "upload".into()); map_message!(NewFileAgentInventoryVariablePriceReplyMessage { scalar: [resource_cost => "resource_cost", rsvp => "rsvp", state => "state", upload_price => "upload_price"], array: [], bytes: [] } init |value: &mut NewFileAgentInventoryVariablePriceReplyMessage| value.state = "confirm_upload".into()); map_message!(NewFileAgentInventoryUploadReplyMessage { scalar: [ new_asset => "new_asset", new_base_mask => "new_base_mask", new_everyone_mask => "new_everyone_mask", new_inventory_item => "new_inventory_item", new_next_owner_mask => "new_next_owner_mask", new_owner_mask => "new_owner_mask", state => "state" ], array: [], bytes: [] } init |value: &mut NewFileAgentInventoryUploadReplyMessage| value.state = "complete".into()); map_message!(UpdateNotecardTaskInventoryMessage { scalar: [item_id => "item_id", task_id => "task_id"], array: [], bytes: [] }); custom_message!(UploaderRequestScriptComplete { scalar: [asset_id, compiled], array: [], bytes: [] } => serialize_uploader_script_complete, deserialize_uploader_script_complete); custom_message!(UploaderScriptRequestError { scalar: [asset_id, compiled, error], array: [], bytes: [] } => serialize_uploader_script_error, deserialize_uploader_script_error); map_message!(RemoteParcelRequestRequest { scalar: [location => "location", region_handle => "region_handle", region_id => "region_id"], array: [], bytes: [] }); wrapped_message!(ChatSessionRequestMessage); wrapped_message!(MapLayerMessage); wrapped_message!(ObjectMediaMessage); wrapped_message!(RemoteParcelRequestMessage); wrapped_message!(SearchStatRequestMessage); wrapped_message!(UpdateGestureAgentInventoryMessage); wrapped_message!(UpdateNotecardAgentInventoryMessage); wrapped_message!(UpdateScriptAgentMessage); wrapped_message!(UpdateScriptTaskMessage); wrapped_message!(UploadBakedTextureMessage); wrapped_message!(UploadScriptTaskMessage); fn serialize_new_file_inventory_fields( asset_type: AssetType, description: &str, everyone_mask: PermissionMask, folder_id: UUID, group_mask: PermissionMask, inventory_type: InventoryType, name: &str, next_owner_mask: PermissionMask, ) -> Result { finish_map([ ("folder_id", folder_id.to_message_osd()?), ( "asset_type", libremetaverse_types::Utils::asset_type_to_string(asset_type)?.to_message_osd()?, ), ( "inventory_type", libremetaverse_types::Utils::inventory_type_to_string(inventory_type)? .to_message_osd()?, ), ("name", name.to_owned().to_message_osd()?), ("description", description.to_owned().to_message_osd()?), ("everyone_mask", everyone_mask.to_message_osd()?), ("group_mask", group_mask.to_message_osd()?), ("next_owner_mask", next_owner_mask.to_message_osd()?), ]) } fn deserialize_new_file_inventory_fields( map: &OSDMap, ) -> Result< ( AssetType, String, PermissionMask, UUID, PermissionMask, InventoryType, String, PermissionMask, ), Error, > { Ok(( libremetaverse_types::Utils::string_to_asset_type(decode_top(map, "asset_type")?)?, decode_top(map, "description")?, decode_top(map, "everyone_mask")?, decode_top(map, "folder_id")?, decode_top(map, "group_mask")?, libremetaverse_types::Utils::string_to_inventory_type(decode_top(map, "inventory_type")?)?, decode_top(map, "name")?, decode_top(map, "next_owner_mask")?, )) } fn serialize_new_file_inventory(value: &NewFileAgentInventoryMessage) -> Result { serialize_new_file_inventory_fields( value.asset_type, &value.description, value.everyone_mask, value.folder_id, value.group_mask, value.inventory_type, &value.name, value.next_owner_mask, ) } fn deserialize_new_file_inventory( value: &mut NewFileAgentInventoryMessage, map: &OSDMap, ) -> Result<(), Error> { ( value.asset_type, value.description, value.everyone_mask, value.folder_id, value.group_mask, value.inventory_type, value.name, value.next_owner_mask, ) = deserialize_new_file_inventory_fields(map)?; Ok(()) } fn serialize_new_file_inventory_variable( value: &NewFileAgentInventoryVariablePriceMessage, ) -> Result { serialize_new_file_inventory_fields( value.asset_type, &value.description, value.everyone_mask, value.folder_id, value.group_mask, value.inventory_type, &value.name, value.next_owner_mask, ) } fn deserialize_new_file_inventory_variable( value: &mut NewFileAgentInventoryVariablePriceMessage, map: &OSDMap, ) -> Result<(), Error> { ( value.asset_type, value.description, value.everyone_mask, value.folder_id, value.group_mask, value.inventory_type, value.name, value.next_owner_mask, ) = deserialize_new_file_inventory_fields(map)?; Ok(()) } fn serialize_uploader_script_complete( value: &UploaderRequestScriptComplete, ) -> Result { finish_map([ ("state", "complete".to_owned().to_message_osd()?), ("new_asset", value.asset_id.to_message_osd()?), ("compiled", value.compiled.to_message_osd()?), ]) } fn deserialize_uploader_script_complete( value: &mut UploaderRequestScriptComplete, map: &OSDMap, ) -> Result<(), Error> { value.asset_id = decode_top(map, "new_asset")?; value.compiled = decode_top(map, "compiled")?; Ok(()) } fn serialize_uploader_script_error(value: &UploaderScriptRequestError) -> Result { finish_map([ ("state", "complete".to_owned().to_message_osd()?), ("new_asset", value.asset_id.to_message_osd()?), ("compiled", value.compiled.to_message_osd()?), ("errors", encode_array(std::slice::from_ref(&value.error))?), ]) } fn deserialize_uploader_script_error( value: &mut UploaderScriptRequestError, map: &OSDMap, ) -> Result<(), Error> { value.asset_id = decode_top(map, "new_asset")?; value.compiled = decode_top(map, "compiled")?; value.error = decode_array::(&required(map, "errors")?)? .into_iter() .next() .ok_or(Error::Argument)?; Ok(()) } fn serialize_agent_group(value: &AgentGroupDataUpdateMessage) -> Result { finish_map([ ( "AgentData", one_block_osd([("AgentID", value.agent_id.to_message_osd()?)]), ), ("GroupData", encode_struct_array(&value.group_data_block)?), ( "NewGroupData", encode_struct_array(&value.new_group_data_block)?, ), ]) } fn deserialize_agent_group( value: &mut AgentGroupDataUpdateMessage, map: &OSDMap, ) -> Result<(), Error> { let agent_blocks = required(map, "AgentData")?; let agent_blocks = as_array(&agent_blocks)?; let agent = as_map(agent_blocks.first().ok_or(Error::Argument)?)?; value.agent_id = decode_value(agent, "AgentID")?; value.avatar_id = if let Some(avatar) = agent_blocks.get(1) { decode_value(as_map(avatar)?, "AvatarID")? } else { value.agent_id }; value.group_data_block = decode_struct_array(map, "GroupData")?; value.new_group_data_block = if map.get("NewGroupData").is_some() { decode_struct_array(map, "NewGroupData")? } else { (0..value.group_data_block.len()) .map(|_| AgentGroupDataUpdateMessageNewGroupData { list_in_profile: false, }) .collect() }; Ok(()) } fn serialize_chat_accept(value: &ChatSessionAcceptInvitation) -> Result { finish_map([ ("method", value.base.method.to_message_osd()?), ("session-id", value.session_id.to_message_osd()?), ]) } fn deserialize_chat_accept( value: &mut ChatSessionAcceptInvitation, map: &OSDMap, ) -> Result<(), Error> { value.base.method = decode_top(map, "method")?; value.session_id = decode_top(map, "session-id")?; Ok(()) } fn serialize_chat_start(value: &ChatSessionRequestStartConference) -> Result { finish_map([ ("method", value.base.method.to_message_osd()?), ("params", encode_array(&value.agents_block)?), ("session-id", value.session_id.to_message_osd()?), ]) } fn deserialize_chat_start( value: &mut ChatSessionRequestStartConference, map: &OSDMap, ) -> Result<(), Error> { value.base.method = decode_top(map, "method")?; value.agents_block = decode_array(&required(map, "params")?)?; value.session_id = decode_top(map, "session-id")?; Ok(()) } fn serialize_chat_mute(value: &ChatSessionRequestMuteUpdate) -> Result { let mute = OSD::Map(HashMap::from([( value.request_key.clone(), value.request_value.to_message_osd()?, )])); finish_map([ ("method", value.base.method.to_message_osd()?), ( "params", map_osd([ ("agent_id", value.agent_id.to_message_osd()?), ("mute_info", mute), ]), ), ("session-id", value.session_id.to_message_osd()?), ]) } fn deserialize_chat_mute( value: &mut ChatSessionRequestMuteUpdate, map: &OSDMap, ) -> Result<(), Error> { value.base.method = decode_top(map, "method")?; value.session_id = decode_top(map, "session-id")?; let params_value = required(map, "params")?; let params = as_map(¶ms_value)?; value.agent_id = decode_value(params, "agent_id")?; let mute = as_map(required_from(params, "mute_info")?)?; value.request_key = if mute.contains_key("text") { "text".into() } else if mute.contains_key("voice") { "voice".into() } else { return Err(Error::Argument); }; value.request_value = decode_value(mute, &value.request_key)?; Ok(()) } fn serialize_chatter_invitation(value: &ChatterBoxInvitationMessage) -> Result { let data = map_osd([ ("timestamp", value.timestamp.to_message_osd()?), ("type", value.dialog.to_message_osd()?), ( "binary_bucket", OSD::from_binary(value.binary_bucket.clone())?, ), ]); let params = map_osd([ ("from_id", value.from_agent_id.to_message_osd()?), ("from_name", value.from_agent_name.to_message_osd()?), ("to_id", value.to_agent_id.to_message_osd()?), ( "parent_estate_id", integer_from_u32(value.parent_estate_id)?, ), ("region_id", value.region_id.to_message_osd()?), ("position", value.position.to_message_osd()?), ("from_group", value.group_im.to_message_osd()?), ("id", value.im_session_id.to_message_osd()?), ("message", value.message.to_message_osd()?), ("offline", value.offline.to_message_osd()?), ("data", data), ]); finish_map([("instantmessage", map_osd([("message_params", params)]))]) } fn deserialize_chatter_invitation( value: &mut ChatterBoxInvitationMessage, map: &OSDMap, ) -> Result<(), Error> { if map.get("voice").is_some() { value.from_agent_id = decode_top(map, "from_id")?; value.from_agent_name = decode_top(map, "from_name")?; value.im_session_id = decode_top(map, "session_id")?; value.binary_bucket = decode_top::(map, "session_name")?.into_bytes(); value.voice = true; let voice_value = required(map, "voice")?; let voice = as_map(&voice_value)?; value.voice_channel_uri = voice .get("channel_uri") .map(String::from_message_osd) .transpose()? .unwrap_or_default(); value.voice_channel_credentials = voice .get("channel_credentials") .map(String::from_message_osd) .transpose()? .unwrap_or_default(); return Ok(()); } let im_value = required(map, "instantmessage")?; let im = as_map(&im_value)?; let params = as_map(required_from(im, "message_params")?)?; let data = as_map(required_from(params, "data")?)?; value.from_agent_id = decode_value(params, "from_id")?; value.from_agent_name = decode_value(params, "from_name")?; value.to_agent_id = decode_value(params, "to_id")?; value.parent_estate_id = decode_value(params, "parent_estate_id")?; value.region_id = decode_value(params, "region_id")?; value.position = decode_value(params, "position")?; value.group_im = decode_value(params, "from_group")?; value.im_session_id = decode_value(params, "id")?; value.message = decode_value(params, "message")?; value.offline = decode_value(params, "offline")?; value.dialog = decode_value(data, "type")?; value.binary_bucket = required_from(data, "binary_bucket")?.as_binary()?; value.timestamp = decode_value(data, "timestamp")?; value.voice = false; Ok(()) } fn serialize_chatter_start_reply( value: &ChatterBoxSessionStartReplyMessage, ) -> Result { let moderated = map_osd([("voice", value.moderated_voice.to_message_osd()?)]); let session = map_osd([ ("type", value.type_.to_message_osd()?), ("session_name", value.session_name.to_message_osd()?), ("voice_enabled", value.voice_enabled.to_message_osd()?), ("moderated_mode", moderated), ]); finish_map([ ("session_id", value.session_id.to_message_osd()?), ("temp_session_id", value.temp_session_id.to_message_osd()?), ("success", value.success.to_message_osd()?), ("session_info", session), ]) } fn deserialize_chatter_start_reply( value: &mut ChatterBoxSessionStartReplyMessage, map: &OSDMap, ) -> Result<(), Error> { value.session_id = decode_top(map, "session_id")?; value.temp_session_id = decode_top(map, "temp_session_id")?; value.success = decode_top(map, "success")?; if !value.success { return Ok(()); } let session_value = required(map, "session_info")?; let session = as_map(&session_value)?; value.session_name = decode_value(session, "session_name")?; value.type_ = decode_value(session, "type")?; value.voice_enabled = decode_value(session, "voice_enabled")?; let moderated = as_map(required_from(session, "moderated_mode")?)?; value.moderated_voice = decode_value(moderated, "voice")?; Ok(()) } fn serialize_chatter_updates( value: &ChatterBoxSessionAgentListUpdatesMessage, ) -> Result { let mut updates = HashMap::with_capacity(value.updates.len()); for block in &value.updates { let mutes = map_osd([ ("text", block.mute_text.to_message_osd()?), ("voice", block.mute_voice.to_message_osd()?), ]); let info = map_osd([ ("can_voice_chat", block.can_voice_chat.to_message_osd()?), ("is_moderator", block.is_moderator.to_message_osd()?), ("mutes", mutes), ]); updates.insert( block.agent_id.to_string(), map_osd([ ("info", info), ("transition", block.transition.to_message_osd()?), ]), ); } finish_map([ ("agent_updates", OSD::Map(updates)), ("session_id", value.session_id.to_message_osd()?), ]) } fn deserialize_chatter_updates( value: &mut ChatterBoxSessionAgentListUpdatesMessage, map: &OSDMap, ) -> Result<(), Error> { value.session_id = decode_top(map, "session_id")?; let updates_value = required(map, "agent_updates")?; let updates = as_map(&updates_value)?; let mut output = Vec::with_capacity(updates.len()); for (agent_id, update) in updates { if agent_id == "updates" || agent_id == "session_id" { continue; } let update = as_map(update)?; let info = as_map(required_from(update, "info")?)?; let mut decoded = ChatterBoxSessionAgentListUpdatesMessageAgentUpdatesBlock { agent_id: UUID::new_with_string(agent_id.clone())?, can_voice_chat: decode_value(info, "can_voice_chat")?, is_moderator: decode_value(info, "is_moderator")?, mute_text: false, mute_voice: false, transition: decode_value(update, "transition")?, }; if let Some(mutes) = info.get("mutes") { let mutes = as_map(mutes)?; decoded.mute_text = decode_value(mutes, "text")?; decoded.mute_voice = decode_value(mutes, "voice")?; } output.push(decoded); } value.updates = output; Ok(()) } fn serialize_dir_land(value: &DirLandReplyMessage) -> Result { finish_map([ ( "AgentData", one_block_osd([("AgentID", value.agent_id.to_message_osd()?)]), ), ( "QueryData", one_block_osd([("QueryID", value.query_id.to_message_osd()?)]), ), ("QueryReplies", encode_struct_array(&value.query_replies)?), ]) } fn deserialize_dir_land(value: &mut DirLandReplyMessage, map: &OSDMap) -> Result<(), Error> { let agent = owned_block(map, "AgentData")?; value.agent_id = decode_value(&agent, "AgentID")?; let query = owned_block(map, "QueryData")?; value.query_id = decode_value(&query, "QueryID")?; value.query_replies = decode_struct_array(map, "QueryReplies")?; Ok(()) } fn serialize_enable_simulator(value: &EnableSimulatorMessage) -> Result { finish_map([("SimulatorInfo", encode_struct_array(&value.simulators)?)]) } fn serialize_simulator_info( value: &EnableSimulatorMessageSimulatorInfoBlock, ) -> Result { finish_map([ ("Handle", value.region_handle.to_message_osd()?), ("IP", value.ip.to_message_osd()?), ("Port", value.port.to_message_osd()?), ("RegionSizeX", value.region_size_x.to_message_osd()?), ("RegionSizeY", value.region_size_y.to_message_osd()?), ]) } fn deserialize_simulator_info( value: &mut EnableSimulatorMessageSimulatorInfoBlock, map: &OSDMap, ) -> Result<(), Error> { value.region_handle = decode_top(map, "Handle")?; value.ip = decode_top(map, "IP")?; value.port = decode_top(map, "Port")?; value.region_size_x = map .get("RegionSizeX") .map(|field| u32::from_message_osd(&field)) .transpose()? .unwrap_or(256); value.region_size_y = map .get("RegionSizeY") .map(|field| u32::from_message_osd(&field)) .transpose()? .unwrap_or(256); Ok(()) } fn deserialize_enable_simulator( value: &mut EnableSimulatorMessage, map: &OSDMap, ) -> Result<(), Error> { value.simulators = decode_struct_array(map, "SimulatorInfo")?; Ok(()) } fn serialize_establish_agent(value: &EstablishAgentCommunicationMessage) -> Result { finish_map([ ("agent-id", value.agent_id.to_message_osd()?), ( "sim-ip-and-port", OSD::from_string(format!("{}:{}", value.address, value.port))?, ), ("seed-capability", value.seed_capability.to_message_osd()?), ]) } fn deserialize_establish_agent( value: &mut EstablishAgentCommunicationMessage, map: &OSDMap, ) -> Result<(), Error> { value.agent_id = decode_top(map, "agent-id")?; let endpoint: String = decode_top(map, "sim-ip-and-port")?; let (address, port) = endpoint.rsplit_once(':').ok_or(Error::Argument)?; value.address = address.parse().map_err(|_| Error::Argument)?; value.port = port.parse().map_err(|_| Error::Argument)?; value.seed_capability = decode_top(map, "seed-capability")?; Ok(()) } fn serialize_map_layer(value: &MapLayerReplyVariant) -> Result { finish_map([ ( "AgentData", map_osd([("Flags", value.base.flags.to_message_osd()?)]), ), ("LayerData", encode_struct_array(&value.layer_data_blocks)?), ]) } fn deserialize_map_layer(value: &mut MapLayerReplyVariant, map: &OSDMap) -> Result<(), Error> { let agent_value = required(map, "AgentData")?; let agent = as_map(&agent_value)?; value.base.flags = decode_value(agent, "Flags")?; value.layer_data_blocks = decode_struct_array(map, "LayerData")?; Ok(()) } fn serialize_script_running(value: &ScriptRunningReplyMessage) -> Result { finish_map([( "Script", one_block_osd([ ("ItemID", value.item_id.to_message_osd()?), ("Mono", value.mono.to_message_osd()?), ("ObjectID", value.object_id.to_message_osd()?), ("Running", value.running.to_message_osd()?), ]), )]) } fn deserialize_script_running( value: &mut ScriptRunningReplyMessage, map: &OSDMap, ) -> Result<(), Error> { let script = owned_block(map, "Script")?; value.item_id = decode_value(&script, "ItemID")?; value.mono = decode_value(&script, "Mono")?; value.object_id = decode_value(&script, "ObjectID")?; value.running = decode_value(&script, "Running")?; Ok(()) } fn serialize_teleport_failed(value: &TeleportFailedMessage) -> Result { finish_map([ ( "AlertInfo", one_block_osd([ ("ExtraParams", value.extra_params.to_message_osd()?), ("Message", value.message_key.to_message_osd()?), ]), ), ( "Info", one_block_osd([ ("AgentID", value.agent_id.to_message_osd()?), ("Reason", value.reason.to_message_osd()?), ]), ), ]) } fn deserialize_teleport_failed( value: &mut TeleportFailedMessage, map: &OSDMap, ) -> Result<(), Error> { let alert = owned_block(map, "AlertInfo")?; value.extra_params = decode_value(&alert, "ExtraParams")?; value.message_key = decode_value(&alert, "Message")?; let info = owned_block(map, "Info")?; value.agent_id = decode_value(&info, "AgentID")?; value.reason = decode_value(&info, "Reason")?; Ok(()) } fn serialize_teleport_finish(value: &TeleportFinishMessage) -> Result { finish_map([( "Info", one_block_osd([ ("AgentID", value.agent_id.to_message_osd()?), ("LocationID", value.location_id.to_message_osd()?), ("RegionHandle", value.region_handle.to_message_osd()?), ("SeedCapability", value.seed_capability.to_message_osd()?), ("SimAccess", value.sim_access.to_message_osd()?), ("SimIP", value.ip.to_message_osd()?), ("SimPort", value.port.to_message_osd()?), ("TeleportFlags", value.flags.to_message_osd()?), ("RegionSizeX", value.region_size_x.to_message_osd()?), ("RegionSizeY", value.region_size_y.to_message_osd()?), ]), )]) } fn deserialize_teleport_finish( value: &mut TeleportFinishMessage, map: &OSDMap, ) -> Result<(), Error> { let info = owned_block(map, "Info")?; value.agent_id = decode_value(&info, "AgentID")?; value.location_id = decode_value(&info, "LocationID")?; value.region_handle = decode_value(&info, "RegionHandle")?; value.seed_capability = decode_value(&info, "SeedCapability")?; value.sim_access = decode_value(&info, "SimAccess")?; value.ip = decode_value(&info, "SimIP")?; value.port = decode_value(&info, "SimPort")?; value.flags = decode_value(&info, "TeleportFlags")?; value.region_size_x = info .get("RegionSizeX") .map(u32::from_message_osd) .transpose()? .unwrap_or(256); value.region_size_y = info .get("RegionSizeY") .map(u32::from_message_osd) .transpose()? .unwrap_or(256); Ok(()) } fn serialize_agent_information(value: &UpdateAgentInformationMessage) -> Result { finish_map([( "access_prefs", map_osd([("max", value.max_access.to_message_osd()?)]), )]) } fn deserialize_agent_information( value: &mut UpdateAgentInformationMessage, map: &OSDMap, ) -> Result<(), Error> { let prefs_value = required(map, "access_prefs")?; value.max_access = decode_value(as_map(&prefs_value)?, "max")?; Ok(()) } fn serialize_parcel_voice(value: &ParcelVoiceInfoRequestMessage) -> Result { finish_map([ ("parcel_local_id", value.parcel_id.to_message_osd()?), ("region_name", value.region_name.to_message_osd()?), ( "voice_credentials", map_osd([("channel_uri", value.sip_channel_uri.to_message_osd()?)]), ), ]) } fn deserialize_parcel_voice( value: &mut ParcelVoiceInfoRequestMessage, map: &OSDMap, ) -> Result<(), Error> { value.parcel_id = decode_top(map, "parcel_local_id")?; value.region_name = decode_top(map, "region_name")?; let credentials_value = required(map, "voice_credentials")?; value.sip_channel_uri = decode_value(as_map(&credentials_value)?, "channel_uri")?; Ok(()) } fn serialize_places(value: &PlacesReplyMessage) -> Result { finish_map([ ( "AgentData", one_block_osd([ ("AgentID", value.agent_id.to_message_osd()?), ("QueryID", value.query_id.to_message_osd()?), ]), ), ("QueryData", encode_struct_array(&value.query_data_blocks)?), ( "TransactionData", one_block_osd([("TransactionID", value.transaction_id.to_message_osd()?)]), ), ]) } fn deserialize_places(value: &mut PlacesReplyMessage, map: &OSDMap) -> Result<(), Error> { let agent = owned_block(map, "AgentData")?; value.agent_id = decode_value(&agent, "AgentID")?; value.query_id = decode_value(&agent, "QueryID")?; value.query_data_blocks = decode_struct_array(map, "QueryData")?; let transaction = owned_block(map, "TransactionData")?; value.transaction_id = decode_value(&transaction, "TransactionID")?; Ok(()) } fn serialize_land_stat_block(value: &LandStatReplyMessageReportDataBlock) -> Result { finish_map([ ("LocationX", value.location.x.to_message_osd()?), ("LocationY", value.location.y.to_message_osd()?), ("LocationZ", value.location.z.to_message_osd()?), ("OwnerName", value.owner_name.to_message_osd()?), ("Score", value.score.to_message_osd()?), ("TaskID", value.task_id.to_message_osd()?), ( "TaskLocalID", OSD::from_real_with_double(f64::from(value.task_local_id))?, ), ("TaskName", value.task_name.to_message_osd()?), ("MonoScore", value.mono_score.to_message_osd()?), ("TimeStamp", value.time_stamp.to_message_osd()?), ]) } fn deserialize_land_stat_block( value: &mut LandStatReplyMessageReportDataBlock, map: &OSDMap, ) -> Result<(), Error> { value.location = Vector3 { x: decode_top(map, "LocationX")?, y: decode_top(map, "LocationY")?, z: decode_top(map, "LocationZ")?, }; value.owner_name = decode_top(map, "OwnerName")?; value.score = decode_top(map, "Score")?; value.task_id = decode_top(map, "TaskID")?; value.task_local_id = decode_top(map, "TaskLocalID")?; value.task_name = decode_top(map, "TaskName")?; value.mono_score = decode_top(map, "MonoScore")?; value.time_stamp = decode_top(map, "TimeStamp")?; Ok(()) } fn serialize_land_stat(value: &LandStatReplyMessage) -> Result { let mut reports = Vec::with_capacity(value.report_data_blocks.len()); let mut extended = Vec::with_capacity(value.report_data_blocks.len()); for block in &value.report_data_blocks { reports.push(map_osd([ ("LocationX", block.location.x.to_message_osd()?), ("LocationY", block.location.y.to_message_osd()?), ("LocationZ", block.location.z.to_message_osd()?), ("OwnerName", block.owner_name.to_message_osd()?), ("Score", block.score.to_message_osd()?), ("TaskID", block.task_id.to_message_osd()?), ( "TaskLocalID", OSD::from_real_with_double(f64::from(block.task_local_id))?, ), ("TaskName", block.task_name.to_message_osd()?), ])); extended.push(map_osd([ ("MonoScore", block.mono_score.to_message_osd()?), ("TimeStamp", block.time_stamp.to_message_osd()?), ])); } finish_map([ ( "RequestData", one_block_osd([ ("ReportType", value.report_type.to_message_osd()?), ("RequestFlags", value.request_flags.to_message_osd()?), ( "TotalObjectCount", value.total_object_count.to_message_osd()?, ), ]), ), ("ReportData", OSD::Array(reports)), ("DataExtended", OSD::Array(extended)), ]) } fn deserialize_land_stat(value: &mut LandStatReplyMessage, map: &OSDMap) -> Result<(), Error> { let request = owned_block(map, "RequestData")?; value.report_type = decode_value(&request, "ReportType")?; value.request_flags = decode_value(&request, "RequestFlags")?; value.total_object_count = decode_value(&request, "TotalObjectCount")?; if value.total_object_count == 0 { value.report_data_blocks.clear(); return Ok(()); } let report_value = required(map, "ReportData")?; let reports = as_array(&report_value)?; let extended_value = required(map, "DataExtended")?; let extended = as_array(&extended_value)?; if reports.len() != extended.len() { return Err(Error::Argument); } let mut blocks = Vec::with_capacity(reports.len()); for (report, extra) in reports.iter().zip(extended) { let report = as_map(report)?; let extra = as_map(extra)?; blocks.push(LandStatReplyMessageReportDataBlock { location: Vector3 { x: decode_value(report, "LocationX")?, y: decode_value(report, "LocationY")?, z: decode_value(report, "LocationZ")?, }, mono_score: decode_value(extra, "MonoScore")?, owner_name: decode_value(report, "OwnerName")?, score: decode_value(report, "Score")?, task_id: decode_value(report, "TaskID")?, task_local_id: decode_value(report, "TaskLocalID")?, task_name: decode_value(report, "TaskName")?, time_stamp: time_from_unix(required_from(extra, "TimeStamp")?.as_u_integer()?), }); } value.report_data_blocks = blocks; Ok(()) } fn serialize_parcel_owners(value: &ParcelObjectOwnersReplyMessage) -> Result { let mut data = Vec::with_capacity(value.prim_owners_block.len()); let mut extended = Vec::with_capacity(value.prim_owners_block.len()); for owner in &value.prim_owners_block { data.push(map_osd([ ("OwnerID", owner.owner_id.to_message_osd()?), ("Count", owner.count.to_message_osd()?), ("IsGroupOwned", owner.is_group_owned.to_message_osd()?), ("OnlineStatus", owner.online_status.to_message_osd()?), ])); extended.push(map_osd([("TimeStamp", owner.time_stamp.to_message_osd()?)])); } let mut entries = vec![("Data", OSD::Array(data))]; if !extended.is_empty() { entries.push(("DataExtended", OSD::Array(extended))); } finish_map(entries) } fn deserialize_parcel_owners( value: &mut ParcelObjectOwnersReplyMessage, map: &OSDMap, ) -> Result<(), Error> { let data_value = required(map, "Data")?; let data = as_array(&data_value)?; let extended_value = map.get("DataExtended"); let extended = extended_value .as_ref() .map(as_array) .transpose()? .unwrap_or(&[]); let has_extended = extended.len() == data.len(); let mut owners = Vec::with_capacity(data.len()); for (index, item) in data.iter().enumerate() { let item = as_map(item)?; let time_stamp = if has_extended { let extra = as_map(&extended[index])?; time_from_unix(required_from(extra, "TimeStamp")?.as_u_integer()?) } else { UNIX_EPOCH }; owners.push(ParcelObjectOwnersReplyMessagePrimOwner { count: decode_value(item, "Count")?, is_group_owned: decode_value(item, "IsGroupOwned")?, online_status: decode_value(item, "OnlineStatus")?, owner_id: decode_value(item, "OwnerID")?, time_stamp, }); } value.prim_owners_block = owners; Ok(()) } fn serialize_parcel_update(value: &ParcelPropertiesUpdateMessage) -> Result { let mut entries = osd_entries!(value; auth_buyer_id => "auth_buyer_id", media_auto_scale => "auto_scale", category => "category", desc => "description", group_id => "group_id", landing => "landing_type", local_id => "local_id", media_desc => "media_desc", media_height => "media_height", media_id => "media_id", media_loop => "media_loop", media_type => "media_type", media_url => "media_url", media_width => "media_width", music_url => "music_url", name => "name", obscure_media => "obscure_media", obscure_music => "obscure_music", parcel_flags => "parcel_flags", pass_hours => "pass_hours", privacy => "privacy", snapshot_id => "snapshot_id", user_location => "user_location", user_look_at => "user_look_at", see_a_vs => "see_avs", any_av_sounds => "any_av_sounds", group_av_sounds => "group_av_sounds", ); entries.push(("flags", OSD::from_binary(Vec::new())?)); entries.push(("pass_price", integer_from_u32(value.pass_price)?)); entries.push(("sale_price", integer_from_u32(value.sale_price)?)); finish_map(entries) } fn deserialize_parcel_update( value: &mut ParcelPropertiesUpdateMessage, map: &OSDMap, ) -> Result<(), Error> { let values = map.snapshot(); decode_fields!(value, &values; auth_buyer_id => "auth_buyer_id", media_auto_scale => "auto_scale", category => "category", desc => "description", group_id => "group_id", landing => "landing_type", local_id => "local_id", media_desc => "media_desc", media_height => "media_height", media_id => "media_id", media_loop => "media_loop", media_type => "media_type", media_url => "media_url", media_width => "media_width", music_url => "music_url", name => "name", obscure_media => "obscure_media", obscure_music => "obscure_music", parcel_flags => "parcel_flags", pass_hours => "pass_hours", pass_price => "pass_price", privacy => "privacy", sale_price => "sale_price", snapshot_id => "snapshot_id", user_location => "user_location", user_look_at => "user_look_at", ); if values.contains_key("see_avs") { decode_fields!(value, &values; see_a_vs => "see_avs", any_av_sounds => "any_av_sounds", group_av_sounds => "group_av_sounds", ); } else { value.see_a_vs = true; value.any_av_sounds = true; value.group_av_sounds = true; } Ok(()) } fn serialize_parcel_properties(value: &ParcelPropertiesMessage) -> Result { let mut parcel = osd_entries!(value; local_id => "LocalID", aabb_max => "AABBMax", aabb_min => "AABBMin", area => "Area", auth_buyer_id => "AuthBuyerID", category => "Category", claim_date => "ClaimDate", claim_price => "ClaimPrice", desc => "Desc", parcel_flags => "ParcelFlags", group_id => "GroupID", group_prims => "GroupPrims", is_group_owned => "IsGroupOwned", landing_type => "LandingType", max_prims => "MaxPrims", media_id => "MediaID", media_url => "MediaURL", media_auto_scale => "MediaAutoScale", music_url => "MusicURL", name => "Name", other_clean_time => "OtherCleanTime", other_count => "OtherCount", other_prims => "OtherPrims", owner_id => "OwnerID", owner_prims => "OwnerPrims", parcel_prim_bonus => "ParcelPrimBonus", pass_hours => "PassHours", pass_price => "PassPrice", public_count => "PublicCount", privacy => "Privacy", region_deny_anonymous => "RegionDenyAnonymous", region_deny_identified => "RegionDenyIdentified", region_deny_transacted => "RegionDenyTransacted", region_push_override => "RegionPushOverride", rent_price => "RentPrice", request_result => "RequestResult", sale_price => "SalePrice", selected_prims => "SelectedPrims", self_count => "SelfCount", sequence_id => "SequenceID", sim_wide_max_prims => "SimWideMaxPrims", sim_wide_total_prims => "SimWideTotalPrims", snap_selection => "SnapSelection", snapshot_id => "SnapshotID", status => "Status", total_prims => "TotalPrims", user_location => "UserLocation", user_look_at => "UserLookAt", see_a_vs => "SeeAVs", any_av_sounds => "AnyAVSounds", group_av_sounds => "GroupAVSounds", ); parcel.push(("AuctionID", integer_from_u32(value.auction_id)?)); parcel.push(("Bitmap", OSD::from_binary(value.bitmap.clone())?)); let media = osd_entries!(value; media_desc => "MediaDesc", media_height => "MediaHeight", media_width => "MediaWidth", media_loop => "MediaLoop", media_type => "MediaType", obscure_media => "ObscureMedia", obscure_music => "ObscureMusic", ); finish_map([ ("ParcelData", OSD::Array(vec![map_osd(parcel)])), ("MediaData", OSD::Array(vec![map_osd(media)])), ( "AgeVerificationBlock", one_block_osd([( "RegionDenyAgeUnverified", value.region_deny_age_unverified.to_message_osd()?, )]), ), ]) } fn deserialize_parcel_properties( value: &mut ParcelPropertiesMessage, map: &OSDMap, ) -> Result<(), Error> { let parcel = owned_block(map, "ParcelData")?; decode_fields!(value, &parcel; local_id => "LocalID", aabb_max => "AABBMax", aabb_min => "AABBMin", area => "Area", auction_id => "AuctionID", auth_buyer_id => "AuthBuyerID", category => "Category", claim_date => "ClaimDate", claim_price => "ClaimPrice", desc => "Desc", parcel_flags => "ParcelFlags", group_id => "GroupID", group_prims => "GroupPrims", is_group_owned => "IsGroupOwned", landing_type => "LandingType", max_prims => "MaxPrims", media_id => "MediaID", media_url => "MediaURL", media_auto_scale => "MediaAutoScale", music_url => "MusicURL", name => "Name", other_clean_time => "OtherCleanTime", other_count => "OtherCount", other_prims => "OtherPrims", owner_id => "OwnerID", owner_prims => "OwnerPrims", parcel_prim_bonus => "ParcelPrimBonus", pass_hours => "PassHours", pass_price => "PassPrice", public_count => "PublicCount", privacy => "Privacy", region_deny_anonymous => "RegionDenyAnonymous", region_deny_identified => "RegionDenyIdentified", region_deny_transacted => "RegionDenyTransacted", region_push_override => "RegionPushOverride", rent_price => "RentPrice", request_result => "RequestResult", sale_price => "SalePrice", selected_prims => "SelectedPrims", self_count => "SelfCount", sequence_id => "SequenceID", sim_wide_max_prims => "SimWideMaxPrims", sim_wide_total_prims => "SimWideTotalPrims", snap_selection => "SnapSelection", snapshot_id => "SnapshotID", status => "Status", total_prims => "TotalPrims", user_location => "UserLocation", user_look_at => "UserLookAt", see_a_vs => "SeeAVs", any_av_sounds => "AnyAVSounds", group_av_sounds => "GroupAVSounds", ); value.bitmap = required_from(&parcel, "Bitmap")?.as_binary()?; if map.get("MediaData").is_some() { let media = owned_block(map, "MediaData")?; decode_fields!(value, &media; media_desc => "MediaDesc", media_height => "MediaHeight", media_width => "MediaWidth", media_loop => "MediaLoop", media_type => "MediaType", obscure_media => "ObscureMedia", obscure_music => "ObscureMusic", ); } let age = owned_block(map, "AgeVerificationBlock")?; value.region_deny_age_unverified = decode_value(&age, "RegionDenyAgeUnverified")?; Ok(()) } fn serialize_viewer_stats(value: &ViewerStatsMessage) -> Result { let agent = map_osd([ ("agents_in_view", value.agents_in_view.to_message_osd()?), ("fps", value.agent_fps.to_message_osd()?), ("language", value.agent_language.to_message_osd()?), ("mem_use", value.agent_memory_used.to_message_osd()?), ("meters_traveled", value.meters_traveled.to_message_osd()?), ("ping", value.agent_ping.to_message_osd()?), ("regions_visited", value.regions_visited.to_message_osd()?), ("run_time", value.agent_runtime.to_message_osd()?), ("sim_fps", value.simulator_fps.to_message_osd()?), ( "start_time", OSD::from_u_integer(unix_seconds(value.agent_start_time))?, ), ("version", value.agent_version.to_message_osd()?), ]); let downloads = map_osd([ ("object_kbytes", value.object_kbytes.to_message_osd()?), ("texture_kbytes", value.texture_kbytes.to_message_osd()?), ("world_kbytes", value.world_kbytes.to_message_osd()?), ]); let misc = map_osd([ ("Version", value.misc_version.to_message_osd()?), ( "Vertex Buffers Enabled", value.vertex_buffers_enabled.to_message_osd()?, ), ]); let failures = map_osd([ ("dropped", value.stats_dropped.to_message_osd()?), ( "failed_resends", value.stats_failed_resends.to_message_osd()?, ), ("invalid", value.failures_invalid.to_message_osd()?), ("off_circuit", value.failures_off_circuit.to_message_osd()?), ("resent", value.failures_resent.to_message_osd()?), ("send_packet", value.failures_send_packet.to_message_osd()?), ]); let stats_misc = map_osd([ ("int_1", value.misc_int1.to_message_osd()?), ("int_2", value.misc_int2.to_message_osd()?), ("string_1", value.misc_string1.to_message_osd()?), ]); let net_in = map_osd([ ( "compressed_packets", value.in_compressed_packets.to_message_osd()?, ), ("kbytes", value.in_kbytes.to_message_osd()?), ("packets", value.in_packets.to_message_osd()?), ("savings", value.in_savings.to_message_osd()?), ]); let net_out = map_osd([ ( "compressed_packets", value.out_compressed_packets.to_message_osd()?, ), ("kbytes", value.out_kbytes.to_message_osd()?), ("packets", value.out_packets.to_message_osd()?), ("savings", value.out_savings.to_message_osd()?), ]); let stats = map_osd([ ("failures", failures), ("misc", stats_misc), ("net", map_osd([("in", net_in), ("out", net_out)])), ]); let system = map_osd([ ("cpu", value.system_cpu.to_message_osd()?), ("gpu", value.system_gpu.to_message_osd()?), ("gpu_class", value.system_gpu_class.to_message_osd()?), ("gpu_vendor", value.system_gpu_vendor.to_message_osd()?), ("gpu_version", value.system_gpu_version.to_message_osd()?), ("os", value.system_os.to_message_osd()?), ("ram", value.system_installed_ram.to_message_osd()?), ]); finish_map([ ("session_id", value.session_id.to_message_osd()?), ("agent", agent), ("downloads", downloads), ("misc", misc), ("system", system), ("stats", stats), ]) } fn deserialize_viewer_stats(value: &mut ViewerStatsMessage, map: &OSDMap) -> Result<(), Error> { value.session_id = decode_top(map, "session_id")?; let agent_value = required(map, "agent")?; let agent = as_map(&agent_value)?; decode_fields!(value, agent; agents_in_view => "agents_in_view", agent_fps => "fps", agent_language => "language", agent_memory_used => "mem_use", agent_ping => "ping", regions_visited => "regions_visited", agent_runtime => "run_time", simulator_fps => "sim_fps", agent_version => "version", ); value.meters_traveled = integer_as_f32(required_from(agent, "meters_traveled")?)?; value.agent_start_time = time_from_unix(required_from(agent, "start_time")?.as_u_integer()?); let downloads_value = required(map, "downloads")?; decode_fields!(value, as_map(&downloads_value)?; object_kbytes => "object_kbytes", texture_kbytes => "texture_kbytes", world_kbytes => "world_kbytes", ); let misc_value = required(map, "misc")?; decode_fields!(value, as_map(&misc_value)?; misc_version => "Version", vertex_buffers_enabled => "Vertex Buffers Enabled", ); let stats_value = required(map, "stats")?; let stats = as_map(&stats_value)?; let failures = as_map(required_from(stats, "failures")?)?; decode_fields!(value, failures; stats_dropped => "dropped", stats_failed_resends => "failed_resends", failures_invalid => "invalid", failures_off_circuit => "off_circuit", failures_resent => "resent", failures_send_packet => "send_packet", ); let stats_misc = as_map(required_from(stats, "misc")?)?; decode_fields!(value, stats_misc; misc_int1 => "int_1", misc_int2 => "int_2", misc_string1 => "string_1", ); let net = as_map(required_from(stats, "net")?)?; let net_in = as_map(required_from(net, "in")?)?; value.in_compressed_packets = decode_value(net_in, "compressed_packets")?; value.in_kbytes = integer_as_f32(required_from(net_in, "kbytes")?)?; value.in_packets = integer_as_f32(required_from(net_in, "packets")?)?; value.in_savings = integer_as_f32(required_from(net_in, "savings")?)?; let net_out = as_map(required_from(net, "out")?)?; value.out_compressed_packets = decode_value(net_out, "compressed_packets")?; value.out_kbytes = integer_as_f32(required_from(net_out, "kbytes")?)?; value.out_packets = integer_as_f32(required_from(net_out, "packets")?)?; value.out_savings = integer_as_f32(required_from(net_out, "savings")?)?; let system_value = required(map, "system")?; decode_fields!(value, as_map(&system_value)?; system_cpu => "cpu", system_gpu => "gpu", system_gpu_class => "gpu_class", system_gpu_vendor => "gpu_vendor", system_gpu_version => "gpu_version", system_os => "os", system_installed_ram => "ram", ); Ok(()) } fn material_from_i32(value: i32) -> Result { match value { 0 => Ok(Material::Stone), 1 => Ok(Material::Metal), 2 => Ok(Material::Glass), 3 => Ok(Material::Wood), 4 => Ok(Material::Flesh), 5 => Ok(Material::Plastic), 6 => Ok(Material::Rubber), 7 => Ok(Material::Light), _ => Err(Error::Argument), } } fn sculpt_type_from_i32(value: i32) -> Result { match value { 0 => Ok(SculptType::None), 1 => Ok(SculptType::Sphere), 2 => Ok(SculptType::Torus), 3 => Ok(SculptType::Plane), 4 => Ok(SculptType::Cylinder), 5 => Ok(SculptType::Mesh), 64 => Ok(SculptType::Invert), 128 => Ok(SculptType::Mirror), _ => Err(Error::Argument), } } fn bumpiness_from_i32(value: i32) -> Result { use crate::Bumpiness; match value { 0 => Ok(Bumpiness::None), 1 => Ok(Bumpiness::Brightness), 2 => Ok(Bumpiness::Darkness), 3 => Ok(Bumpiness::Woodgrain), 4 => Ok(Bumpiness::Bark), 5 => Ok(Bumpiness::Bricks), 6 => Ok(Bumpiness::Checker), 7 => Ok(Bumpiness::Concrete), 8 => Ok(Bumpiness::Crustytile), 9 => Ok(Bumpiness::Cutstone), 10 => Ok(Bumpiness::Discs), 11 => Ok(Bumpiness::Gravel), 12 => Ok(Bumpiness::Petridish), 13 => Ok(Bumpiness::Siding), 14 => Ok(Bumpiness::Stonetile), 15 => Ok(Bumpiness::Stucco), 16 => Ok(Bumpiness::Suction), 17 => Ok(Bumpiness::Weave), _ => Err(Error::Argument), } } impl GeneratedMessage for UploadObjectAssetMessageObjectFace { fn new_generated() -> Self { Self { bump: crate::Bumpiness::None, color: Color4::default(), fullbright: false, glow: 0.0, image_id: UUID::zero(), image_rot: 0.0, media_flags: 0, offset_s: 0.0, offset_t: 0.0, scale_s: 0.0, scale_t: 0.0, } } fn serialize_generated(&self) -> Result { finish_map([ ("bump", OSD::Integer(self.bump as i32)), ("colors", OSD::from_color4(self.color)?), ("fullbright", OSD::Boolean(self.fullbright)), ("glow", OSD::Real(f64::from(self.glow))), ("imageid", OSD::UUID(self.image_id)), ("imagerot", OSD::Real(f64::from(self.image_rot))), ("media_flags", OSD::Integer(self.media_flags)), ("offsets", OSD::Real(f64::from(self.offset_s))), ("offsett", OSD::Real(f64::from(self.offset_t))), ("scales", OSD::Real(f64::from(self.scale_s))), ("scalet", OSD::Real(f64::from(self.scale_t))), ]) } fn deserialize_generated(&mut self, map: &OSDMap) -> Result<(), Error> { self.bump = bumpiness_from_i32(required(map, "bump")?.as_integer()?)?; self.color = required(map, "colors")?.as_color4()?; self.fullbright = required(map, "fullbright")?.as_boolean()?; self.glow = required(map, "glow")?.as_real()? as f32; self.image_id = required(map, "imageid")?.as_uuid()?; self.image_rot = required(map, "imagerot")?.as_real()? as f32; self.media_flags = required(map, "media_flags")?.as_integer()?; self.offset_s = required(map, "offsets")?.as_real()? as f32; self.offset_t = required(map, "offsett")?.as_real()? as f32; self.scale_s = required(map, "scales")?.as_real()? as f32; self.scale_t = required(map, "scalet")?.as_real()? as f32; Ok(()) } } impl GeneratedMessage for UploadObjectAssetMessageObjectExtraParam { fn new_generated() -> Self { Self { extra_param_data: Vec::new(), type_: ExtraParamType(0), } } fn serialize_generated(&self) -> Result { finish_map([ ("extra_parameter", OSD::Integer(i32::from(self.type_.0))), ("param_data", OSD::Binary(self.extra_param_data.clone())), ]) } fn deserialize_generated(&mut self, map: &OSDMap) -> Result<(), Error> { self.type_ = ExtraParamType( u16::try_from(required(map, "extra_parameter")?.as_integer()?) .map_err(|_| Error::Argument)?, ); self.extra_param_data = required(map, "param_data")?.as_binary()?; Ok(()) } } fn upload_object_default() -> UploadObjectAssetMessageObject { UploadObjectAssetMessageObject { extra_params: Vec::new(), faces: Vec::new(), group_id: UUID::zero(), material: Material::Stone, name: String::new(), path_begin: 0.0, path_curve: 0, path_end: 0.0, position: Vector3::zero(), profile_begin: 0.0, profile_curve: 0, profile_end: 0.0, profile_hollow: 0.0, radius_offset: 0.0, revolutions: 0.0, rotation: Quaternion::identity(), scale: Vector3::zero(), scale_x: 0.0, scale_y: 0.0, sculpt_id: UUID::zero(), sculpt_type: SculptType::None, shear_x: 0.0, shear_y: 0.0, skew: 0.0, taper_x: 0.0, taper_y: 0.0, twist: 0.0, twist_begin: 0.0, } } impl GeneratedMessage for UploadObjectAssetMessageObject { fn new_generated() -> Self { upload_object_default() } fn serialize_generated(&self) -> Result { let encode = |values: &[&dyn GeneratedMessageObject]| -> Result { Ok(OSD::Array( values .iter() .map(|value| value.encode()) .collect::>()?, )) }; let extras: Vec<&dyn GeneratedMessageObject> = self.extra_params.iter().map(|v| v as _).collect(); let faces: Vec<&dyn GeneratedMessageObject> = self.faces.iter().map(|v| v as _).collect(); let shape = map_osd([ ( "path", map_osd([ ("begin", OSD::Real(f64::from(self.path_begin))), ("curve", OSD::Integer(self.path_curve)), ("end", OSD::Real(f64::from(self.path_end))), ("radius_offset", OSD::Real(f64::from(self.radius_offset))), ("revolutions", OSD::Real(f64::from(self.revolutions))), ("scale_x", OSD::Real(f64::from(self.scale_x))), ("scale_y", OSD::Real(f64::from(self.scale_y))), ("shear_x", OSD::Real(f64::from(self.shear_x))), ("shear_y", OSD::Real(f64::from(self.shear_y))), ("skew", OSD::Real(f64::from(self.skew))), ("taper_x", OSD::Real(f64::from(self.taper_x))), ("taper_y", OSD::Real(f64::from(self.taper_y))), ("twist", OSD::Real(f64::from(self.twist))), ("twist_begin", OSD::Real(f64::from(self.twist_begin))), ]), ), ( "profile", map_osd([ ("begin", OSD::Real(f64::from(self.profile_begin))), ("curve", OSD::Integer(self.profile_curve)), ("end", OSD::Real(f64::from(self.profile_end))), ("hollow", OSD::Real(f64::from(self.profile_hollow))), ]), ), ( "sculpt", map_osd([ ("id", OSD::UUID(self.sculpt_id)), ("type", OSD::Integer(self.sculpt_type as i32)), ]), ), ]); finish_map([ ("group-id", OSD::UUID(self.group_id)), ("material", OSD::Integer(self.material as i32)), ("name", OSD::String(self.name.clone())), ("pos", OSD::from_vector3(self.position)?), ("rotation", OSD::from_quaternion(self.rotation)?), ("scale", OSD::from_vector3(self.scale)?), ("extra_parameters", encode(&extras)?), ("facelist", encode(&faces)?), ("shape", shape), ]) } #[allow(clippy::cast_possible_truncation)] fn deserialize_generated(&mut self, map: &OSDMap) -> Result<(), Error> { self.group_id = required(map, "group-id")?.as_uuid()?; self.material = material_from_i32(required(map, "material")?.as_integer()?)?; self.name = required(map, "name")?.as_string()?; self.position = required(map, "pos")?.as_vector3()?; self.rotation = required(map, "rotation")?.as_quaternion()?; self.scale = required(map, "scale")?.as_vector3()?; self.extra_params = decode_array(&required(map, "extra_parameters")?)?; self.faces = decode_array(&required(map, "facelist")?)?; let shape_value = required(map, "shape")?; let shape = as_map(&shape_value)?; let path = as_map(required_from(shape, "path")?)?; macro_rules! real { ($field:ident, $key:literal) => { self.$field = required_from(path, $key)?.as_real()? as f32; }; } real!(path_begin, "begin"); self.path_curve = required_from(path, "curve")?.as_integer()?; real!(path_end, "end"); real!(radius_offset, "radius_offset"); real!(revolutions, "revolutions"); real!(scale_x, "scale_x"); real!(scale_y, "scale_y"); real!(shear_x, "shear_x"); real!(shear_y, "shear_y"); real!(skew, "skew"); real!(taper_x, "taper_x"); real!(taper_y, "taper_y"); real!(twist, "twist"); real!(twist_begin, "twist_begin"); let profile = as_map(required_from(shape, "profile")?)?; self.profile_begin = required_from(profile, "begin")?.as_real()? as f32; self.profile_curve = required_from(profile, "curve")?.as_integer()?; self.profile_end = required_from(profile, "end")?.as_real()? as f32; self.profile_hollow = required_from(profile, "hollow")?.as_real()? as f32; let sculpt = as_map(required_from(shape, "sculpt")?)?; self.sculpt_id = required_from(sculpt, "id")?.as_uuid()?; self.sculpt_type = sculpt_type_from_i32(required_from(sculpt, "type")?.as_integer()?)?; Ok(()) } } trait GeneratedMessageObject { fn encode(&self) -> Result; } impl GeneratedMessageObject for UploadObjectAssetMessageObjectFace { fn encode(&self) -> Result { self.serialize_generated()?.copy() } } impl GeneratedMessageObject for UploadObjectAssetMessageObjectExtraParam { fn encode(&self) -> Result { self.serialize_generated()?.copy() } } impl GeneratedMessage for UploadObjectAssetMessage { fn new_generated() -> Self { Self { objects: Vec::new(), } } fn serialize_generated(&self) -> Result { finish_map([("objects", encode_struct_array(&self.objects)?)]) } fn deserialize_generated(&mut self, map: &OSDMap) -> Result<(), Error> { self.objects = decode_array(&required(map, "objects")?)?; Ok(()) } } macro_rules! upload_message_value { ($($type:ty),+ $(,)?) => { $(impl MessageValue for $type { fn message_default() -> Self { ::new_generated() } fn to_message_osd(&self) -> Result { self.serialize_generated()?.copy() } fn from_message_osd(value: &OSD) -> Result { let OSD::Map(values) = value else { return Err(Error::Argument); }; let map = OSDMap::new_with_dictionary(values.clone())?; let mut output = ::new_generated(); output.deserialize_generated(&map)?; Ok(output) } })+ }; } upload_message_value!( UploadObjectAssetMessage, UploadObjectAssetMessageObject, UploadObjectAssetMessageObjectExtraParam, UploadObjectAssetMessageObjectFace, ); #[cfg(test)] mod tests { use super::*; fn keys(value: &OSD) -> Vec { let mut keys: Vec<_> = as_map(value).unwrap().keys().cloned().collect(); keys.sort(); keys } #[test] fn postcard_uses_capability_wire_names() { let value = SendPostcardMessage::new_generated(); let serialized = value.serialize_generated().unwrap().copy().unwrap(); assert_eq!( keys(&serialized), ["msg", "name", "pos-global", "subject", "to"] ); } #[test] fn teleport_finish_uses_info_block_and_binary_ip() { let mut value = TeleportFinishMessage::new_generated(); value.ip = "192.0.2.9".parse().unwrap(); let serialized = value.serialize_generated().unwrap().copy().unwrap(); let outer = as_map(&serialized).unwrap(); assert_eq!(outer.len(), 1); let info = as_map(&as_array(outer.get("Info").unwrap()).unwrap()[0]).unwrap(); assert_eq!(info.get("SimIP"), Some(&OSD::Binary(vec![192, 0, 2, 9]))); assert!(info.contains_key("TeleportFlags")); assert!(!info.contains_key("flags")); } #[test] fn chat_mute_flattens_base_and_nests_dynamic_mute_key() { let mut value = ChatSessionRequestMuteUpdate::new_generated(); value.request_key = "voice".into(); value.request_value = true; let serialized = value.serialize_generated().unwrap().copy().unwrap(); let outer = as_map(&serialized).unwrap(); assert_eq!(keys(&serialized), ["method", "params", "session-id"]); let params = as_map(outer.get("params").unwrap()).unwrap(); let mute = as_map(params.get("mute_info").unwrap()).unwrap(); assert_eq!(mute.get("voice"), Some(&OSD::Boolean(true))); } #[test] fn viewer_stats_preserves_nested_golden_layout() { let value = ViewerStatsMessage::new_generated(); let serialized = value.serialize_generated().unwrap().copy().unwrap(); let outer = as_map(&serialized).unwrap(); assert_eq!( keys(&serialized), [ "agent", "downloads", "misc", "session_id", "stats", "system" ] ); let stats = as_map(outer.get("stats").unwrap()).unwrap(); assert_eq!( keys(outer.get("stats").unwrap()), ["failures", "misc", "net"] ); assert_eq!(keys(stats.get("net").unwrap()), ["in", "out"]); } #[test] fn enable_simulator_defaults_legacy_region_size_when_absent() { let map = finish_map([( "SimulatorInfo", OSD::Array(vec![map_osd([ ("Handle", OSD::from_u_long(7).unwrap()), ("IP", OSD::from_binary(vec![127, 0, 0, 1]).unwrap()), ("Port", OSD::from_integer_with_int32(9000).unwrap()), ])]), )]) .unwrap(); let mut value = EnableSimulatorMessage::new_generated(); value.deserialize_generated(&map).unwrap(); assert_eq!(value.simulators[0].region_size_x, 256); assert_eq!(value.simulators[0].region_size_y, 256); } }