Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
3705 lines
125 KiB
Rust
3705 lines
125 KiB
Rust
//! 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<OSD, Error>;
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error>;
|
|
}
|
|
|
|
pub(crate) trait GeneratedMessage: Sized {
|
|
fn new_generated() -> Self;
|
|
fn serialize_generated(&self) -> Result<OSDMap, Error>;
|
|
fn deserialize_generated(&mut self, map: &OSDMap) -> Result<(), Error>;
|
|
}
|
|
|
|
pub(crate) fn generated_from_osd<T: GeneratedMessage>(data: OSD) -> Result<T, Error> {
|
|
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<Option<Box<dyn crate::interfaces::IMessage>>, 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<Option<Box<dyn crate::interfaces::IMessage>>, 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, Error> {
|
|
OSD::$from(*self)
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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, Error> {
|
|
OSD::from_u_long(*self)
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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, Error> {
|
|
OSD::from_boolean(*self)
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
value.as_boolean()
|
|
}
|
|
}
|
|
|
|
impl MessageValue for f32 {
|
|
fn message_default() -> Self {
|
|
0.0
|
|
}
|
|
fn to_message_osd(&self) -> Result<OSD, Error> {
|
|
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<Self, Error> {
|
|
Ok(value.as_real()? as f32)
|
|
}
|
|
}
|
|
|
|
impl MessageValue for f64 {
|
|
fn message_default() -> Self {
|
|
0.0
|
|
}
|
|
fn to_message_osd(&self) -> Result<OSD, Error> {
|
|
OSD::from_real_with_double(*self)
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
value.as_real()
|
|
}
|
|
}
|
|
|
|
impl MessageValue for String {
|
|
fn message_default() -> Self {
|
|
String::new()
|
|
}
|
|
fn to_message_osd(&self) -> Result<OSD, Error> {
|
|
OSD::from_string(self.clone())
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
value.as_string()
|
|
}
|
|
}
|
|
|
|
impl MessageValue for UUID {
|
|
fn message_default() -> Self {
|
|
UUID::zero()
|
|
}
|
|
fn to_message_osd(&self) -> Result<OSD, Error> {
|
|
OSD::from_uuid(*self)
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
value.as_uuid()
|
|
}
|
|
}
|
|
|
|
impl MessageValue for Vector3 {
|
|
fn message_default() -> Self {
|
|
Vector3::zero()
|
|
}
|
|
fn to_message_osd(&self) -> Result<OSD, Error> {
|
|
OSD::from_vector3(*self)
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
value.as_vector3()
|
|
}
|
|
}
|
|
|
|
impl MessageValue for SystemTime {
|
|
fn message_default() -> Self {
|
|
UNIX_EPOCH
|
|
}
|
|
fn to_message_osd(&self) -> Result<OSD, Error> {
|
|
OSD::from_date(*self)
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
value.as_date()
|
|
}
|
|
}
|
|
|
|
impl MessageValue for Uri {
|
|
fn message_default() -> Self {
|
|
Self("about:blank".into())
|
|
}
|
|
fn to_message_osd(&self) -> Result<OSD, Error> {
|
|
OSD::from_uri(self.clone())
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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<OSD, Error> {
|
|
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<Self, Error> {
|
|
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<OSD, Error> {
|
|
Ok(self.clone())
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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<OSD, Error> {
|
|
self.copy()
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
let OSD::Map(values) = value else {
|
|
return Err(Error::Argument);
|
|
};
|
|
Self::new_with_dictionary(values.clone())
|
|
}
|
|
}
|
|
|
|
impl MessageValue for HashMap<String, i32> {
|
|
fn message_default() -> Self {
|
|
Self::new()
|
|
}
|
|
fn to_message_osd(&self) -> Result<OSD, Error> {
|
|
self.iter()
|
|
.map(|(key, value)| Ok((key.clone(), value.to_message_osd()?)))
|
|
.collect::<Result<HashMap<_, _>, Error>>()
|
|
.map(OSD::Map)
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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<OSD, Error> {
|
|
self.get_osd()
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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<OSD, Error> {
|
|
self.get_osd()
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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<OSD, Error> {
|
|
if self.is_undefined() {
|
|
Ok(OSD::Undefined)
|
|
} else {
|
|
self.get_osd()?.copy()
|
|
}
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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<OSD, Error> {
|
|
<$inner as MessageValue>::to_message_osd(&self.0)
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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, Error> {
|
|
OSD::from_integer_with_int32(i32::from(self.0))
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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, Error> {
|
|
OSD::from_long(self.0.cast_signed())
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
Ok(Self(value.as_long()?.cast_unsigned()))
|
|
}
|
|
}
|
|
|
|
impl MessageValue for SimAccess {
|
|
fn message_default() -> Self {
|
|
Self::UNKNOWN
|
|
}
|
|
fn to_message_osd(&self) -> Result<OSD, Error> {
|
|
OSD::from_integer_with_byte(self.0)
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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, Error> {
|
|
OSD::from_integer_with_int32(*self as i32)
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
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<T: MessageValue>(values: &[T]) -> Result<OSD, Error> {
|
|
values
|
|
.iter()
|
|
.map(MessageValue::to_message_osd)
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.map(OSD::Array)
|
|
}
|
|
|
|
fn decode_array<T: MessageValue>(value: &OSD) -> Result<Vec<T>, 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<Item = (&'static str, OSD)>) -> OSD {
|
|
OSD::Map(
|
|
entries
|
|
.into_iter()
|
|
.map(|(key, value)| (key.to_owned(), value))
|
|
.collect(),
|
|
)
|
|
}
|
|
|
|
fn finish_map(entries: impl IntoIterator<Item = (&'static str, OSD)>) -> Result<OSDMap, Error> {
|
|
let OSD::Map(values) = map_osd(entries) else {
|
|
unreachable!();
|
|
};
|
|
OSDMap::new_with_dictionary(values)
|
|
}
|
|
|
|
fn required(map: &OSDMap, key: &str) -> Result<OSD, Error> {
|
|
map.get(key).ok_or(Error::Argument)
|
|
}
|
|
|
|
fn required_from<'a>(map: &'a HashMap<String, OSD>, key: &str) -> Result<&'a OSD, Error> {
|
|
map.get(key).ok_or(Error::Argument)
|
|
}
|
|
|
|
fn as_map(value: &OSD) -> Result<&HashMap<String, OSD>, 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<HashMap<String, OSD>, 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<T: MessageValue>(values: &[T]) -> Result<OSD, Error> {
|
|
encode_array(values)
|
|
}
|
|
|
|
fn decode_struct_array<T: MessageValue>(map: &OSDMap, key: &str) -> Result<Vec<T>, Error> {
|
|
decode_array(&required(map, key)?)
|
|
}
|
|
|
|
fn decode_value<T: MessageValue>(map: &HashMap<String, OSD>, key: &str) -> Result<T, Error> {
|
|
T::from_message_osd(required_from(map, key)?)
|
|
}
|
|
|
|
fn decode_top<T: MessageValue>(map: &OSDMap, key: &str) -> Result<T, Error> {
|
|
T::from_message_osd(&required(map, key)?)
|
|
}
|
|
|
|
fn one_block_osd(entries: impl IntoIterator<Item = (&'static str, OSD)>) -> 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, Error> {
|
|
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<f32, Error> {
|
|
Ok(value.as_integer()? as f32)
|
|
}
|
|
|
|
fn serialize_agent_state_update(value: &AgentStateUpdateMessage) -> Result<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
fn entries(values: &HashMap<String, i32>) -> Result<OSD, Error> {
|
|
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::<Result<Vec<_>, 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<HashMap<String, i32>, 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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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 { <Self as GeneratedMessage>::new_generated() }
|
|
fn to_message_osd(&self) -> Result<OSD, Error> {
|
|
self.serialize_generated()?.copy()
|
|
}
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
let OSD::Map(values) = value else { return Err(Error::Argument); };
|
|
let map = OSDMap::new_with_dictionary(values.clone())?;
|
|
let mut output = <Self as GeneratedMessage>::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<OSDMap, Error> { $serialize(self) }
|
|
fn deserialize_generated(&mut self, map: &OSDMap) -> Result<(), Error> {
|
|
$deserialize(self, map)
|
|
}
|
|
}
|
|
impl MessageValue for $type {
|
|
fn message_default() -> Self { <Self as GeneratedMessage>::new_generated() }
|
|
fn to_message_osd(&self) -> Result<OSD, Error> { self.serialize_generated()?.copy() }
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
let OSD::Map(values) = value else { return Err(Error::Argument); };
|
|
let map = OSDMap::new_with_dictionary(values.clone())?;
|
|
let mut output = <Self as GeneratedMessage>::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<OSDMap, Error> {
|
|
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 {
|
|
<Self as GeneratedMessage>::new_generated()
|
|
}
|
|
|
|
fn to_message_osd(&self) -> Result<OSD, Error> {
|
|
self.serialize_generated()?.copy()
|
|
}
|
|
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
let OSD::Map(values) = value else {
|
|
return Err(Error::Argument);
|
|
};
|
|
let map = OSDMap::new_with_dictionary(values.clone())?;
|
|
let mut output = <Self as GeneratedMessage>::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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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::<String>(&required(map, "errors")?)?
|
|
.into_iter()
|
|
.next()
|
|
.ok_or(Error::Argument)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn serialize_agent_group(value: &AgentGroupDataUpdateMessage) -> Result<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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::<String>(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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
finish_map([("SimulatorInfo", encode_struct_array(&value.simulators)?)])
|
|
}
|
|
|
|
fn serialize_simulator_info(
|
|
value: &EnableSimulatorMessageSimulatorInfoBlock,
|
|
) -> Result<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<Material, Error> {
|
|
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<SculptType, Error> {
|
|
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<crate::Bumpiness, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
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<OSDMap, Error> {
|
|
let encode = |values: &[&dyn GeneratedMessageObject]| -> Result<OSD, Error> {
|
|
Ok(OSD::Array(
|
|
values
|
|
.iter()
|
|
.map(|value| value.encode())
|
|
.collect::<Result<_, _>>()?,
|
|
))
|
|
};
|
|
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<OSD, Error>;
|
|
}
|
|
impl GeneratedMessageObject for UploadObjectAssetMessageObjectFace {
|
|
fn encode(&self) -> Result<OSD, Error> {
|
|
self.serialize_generated()?.copy()
|
|
}
|
|
}
|
|
impl GeneratedMessageObject for UploadObjectAssetMessageObjectExtraParam {
|
|
fn encode(&self) -> Result<OSD, Error> {
|
|
self.serialize_generated()?.copy()
|
|
}
|
|
}
|
|
|
|
impl GeneratedMessage for UploadObjectAssetMessage {
|
|
fn new_generated() -> Self {
|
|
Self {
|
|
objects: Vec::new(),
|
|
}
|
|
}
|
|
fn serialize_generated(&self) -> Result<OSDMap, Error> {
|
|
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 { <Self as GeneratedMessage>::new_generated() }
|
|
fn to_message_osd(&self) -> Result<OSD, Error> { self.serialize_generated()?.copy() }
|
|
fn from_message_osd(value: &OSD) -> Result<Self, Error> {
|
|
let OSD::Map(values) = value else { return Err(Error::Argument); };
|
|
let map = OSDMap::new_with_dictionary(values.clone())?;
|
|
let mut output = <Self as GeneratedMessage>::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<String> {
|
|
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);
|
|
}
|
|
}
|