Complete first release candidate audit (#107)
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

This commit is contained in:
2026-08-12 14:44:28 +00:00
parent dceb394378
commit c9a1170a27
140 changed files with 82175 additions and 27179 deletions

View File

@@ -1,11 +1,17 @@
//! 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, BulkUpdateInventoryMessage, BulkUpdateInventoryMessageFolderDataInfo,
BulkUpdateInventoryMessageItemDataInfo, ChatSessionAcceptInvitation, ChatSessionRequestBlock,
ChatSessionRequestMuteUpdate, ChatSessionRequestStartConference, ChatterBoxInvitationMessage,
AgentStateUpdateMessage, BaseResourcesInfo, BulkUpdateInventoryMessage,
BulkUpdateInventoryMessageFolderDataInfo, BulkUpdateInventoryMessageItemDataInfo,
ChatSessionAcceptInvitation, ChatSessionRequestMessage, ChatSessionRequestMuteUpdate,
ChatSessionRequestStartConference, ChatterBoxInvitationMessage,
ChatterBoxSessionAgentListUpdatesMessage,
ChatterBoxSessionAgentListUpdatesMessageAgentUpdatesBlock, ChatterBoxSessionStartReplyMessage,
ChatterboxSessionEventReplyMessage, CopyInventoryFromNotecardMessage, CrossedRegionMessage,
@@ -13,20 +19,29 @@ use crate::messages::linden::{
EnableSimulatorMessage, EnableSimulatorMessageSimulatorInfoBlock,
EstablishAgentCommunicationMessage, ForceCloseChatterBoxSessionMessage, GetDisplayNamesMessage,
GetObjectCostMessage, GetObjectCostRequest, LandResourcesMessage, LandResourcesRequest,
LandStatReplyMessage, LandStatReplyMessageReportDataBlock, MapLayerMessageBase,
LandStatReplyMessage, LandStatReplyMessageReportDataBlock, MapLayerMessage,
MapLayerReplyVariant, MapLayerReplyVariantLayerData, NavMeshStatusUpdateMessage,
ObjectMediaNavigateMessage, ObjectMediaRequest, ObjectMediaResponse, ObjectMediaUpdate,
ObjectPhysicsPropertiesMessage, ParcelObjectOwnersReplyMessage,
ParcelObjectOwnersReplyMessagePrimOwner, ParcelPropertiesMessage,
ParcelPropertiesUpdateMessage, ParcelVoiceInfoRequestMessage, PlacesReplyMessage,
PlacesReplyMessageQueryData, ProvisionVoiceAccountRequestMessage, RegionInfoMessage,
RemoteParcelRequestReply, RenderMaterialsMessage, RequiredVoiceVersionMessage,
ScriptRunningReplyMessage, SearchStatRequestReply, SearchStatRequestRequest,
SendPostcardMessage, SetDisplayNameMessage, SetDisplayNameReplyMessage,
SimConsoleResponseMessage, TeleportFailedMessage, TeleportFinishMessage,
UpdateAgentInformationMessage, UpdateAgentInventoryRequestMessage, UpdateAgentLanguageMessage,
UpdateScriptAgentRequestMessage, UpdateScriptTaskUpdateMessage, UploaderRequestComplete,
UploaderRequestUpload, ViewerStatsMessage,
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,
@@ -36,7 +51,10 @@ use crate::{
use flate2::read::ZlibDecoder;
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
use libremetaverse_types::compat::Uri;
use libremetaverse_types::{AssetType, FolderType, InventoryType, SaleType, UUID, Vector3};
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};
@@ -54,6 +72,38 @@ pub(crate) trait GeneratedMessage: Sized {
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 {
@@ -232,6 +282,24 @@ impl MessageValue for OSDMap {
}
}
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")
@@ -813,6 +881,50 @@ fn deserialize_get_object_cost(
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()?),
@@ -1172,6 +1284,50 @@ macro_rules! custom_message {
};
}
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()?)),*]
@@ -1237,11 +1393,6 @@ custom_message!(AgentGroupDataUpdateMessage {
array: [group_data_block, new_group_data_block],
bytes: []
} => serialize_agent_group, deserialize_agent_group);
map_message!(ChatSessionRequestBlock {
scalar: [method => "method"],
array: [],
bytes: []
});
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());
@@ -1372,11 +1523,6 @@ custom_message!(LandStatReplyMessage {
array: [report_data_blocks],
bytes: []
} => serialize_land_stat, deserialize_land_stat);
map_message!(MapLayerMessageBase {
scalar: [flags => "Flags"],
array: [],
bytes: []
});
map_message!(MapLayerReplyVariantLayerData {
scalar: [
bottom => "Bottom",
@@ -1582,6 +1728,11 @@ custom_message!(GetObjectCostMessage {
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"],
@@ -1793,6 +1944,229 @@ custom_message!(UploaderRequestComplete {
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([
@@ -2918,6 +3292,329 @@ fn deserialize_viewer_stats(value: &mut ViewerStatsMessage, map: &OSDMap) -> Res
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::*;