//! Native asset value models and their format boundaries. #![allow( clippy::format_push_string, clippy::missing_errors_doc, clippy::must_use_candidate, clippy::needless_pass_by_value, clippy::struct_field_names )] use crate::{Error, Permissions}; use libremetaverse_imaging::ManagedImage; use libremetaverse_structured_data::{OSD, OSDMap, OSDParser}; use libremetaverse_types::{AssetType, SaleType, UUID, Vector3, WearableType}; use std::collections::HashMap; use std::sync::{Arc, RwLock}; /// Maximum accepted in-memory asset payload. Network limits are enforced before /// this boundary too, but constructors are public and must defend themselves. pub const MAX_ASSET_BYTES: usize = 64 * 1024 * 1024; fn validate_bytes(bytes: &[u8]) -> Result<(), Error> { if bytes.len() > MAX_ASSET_BYTES { Err(Error::Argument) } else { Ok(()) } } #[derive(Clone, Debug)] pub struct Asset { pub asset_data: Vec, pub temporary: bool, asset_id: UUID, asset_type: AssetType, } impl Asset { pub(crate) fn native_new( asset_type: AssetType, asset_id: UUID, data: Vec, ) -> Result { validate_bytes(&data)?; Ok(Self { asset_data: data, temporary: false, asset_id, asset_type, }) } pub fn decode(&self) -> Result { validate_bytes(&self.asset_data)?; Ok(true) } pub fn encode(&self) -> Result<(), Error> { validate_bytes(&self.asset_data) } pub const fn asset_id(&self) -> UUID { self.asset_id } pub fn set_asset_id(&mut self, value: UUID) { self.asset_id = value; } pub const fn asset_type(&self) -> AssetType { self.asset_type } } #[derive(Clone, Debug)] struct RawAsset(Arc>); impl RawAsset { fn empty(asset_type: AssetType) -> Result { Ok(Self(Arc::new(RwLock::new(Asset::native_new( asset_type, UUID::zero(), Vec::new(), )?)))) } fn with_data(asset_type: AssetType, id: UUID, data: Vec) -> Result { Ok(Self(Arc::new(RwLock::new(Asset::native_new( asset_type, id, data, )?)))) } fn decode(&self) -> Result { self.0 .read() .unwrap_or_else(std::sync::PoisonError::into_inner) .decode() } fn encode(&self) -> Result<(), Error> { self.0 .read() .unwrap_or_else(std::sync::PoisonError::into_inner) .encode() } fn bytes(&self) -> Vec { self.0 .read() .unwrap_or_else(std::sync::PoisonError::into_inner) .asset_data .clone() } fn replace(&self, bytes: Vec) -> Result<(), Error> { validate_bytes(&bytes)?; self.0 .write() .unwrap_or_else(std::sync::PoisonError::into_inner) .asset_data = bytes; Ok(()) } } macro_rules! raw_asset { ($name:ident, $kind:expr) => { #[derive(Clone, Debug)] pub struct $name { raw: RawAsset, } impl $name { pub fn new_with_constructor() -> Result { Ok(Self { raw: RawAsset::empty($kind)?, }) } pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { Ok(Self { raw: RawAsset::with_data($kind, asset_id, asset_data)?, }) } pub fn decode(&self) -> Result { self.raw.decode() } pub fn encode(&self) -> Result<(), Error> { self.raw.encode() } pub const fn asset_type(&self) -> AssetType { $kind } } }; } raw_asset!(AssetAnimation, AssetType::Animation); raw_asset!(AssetScriptBinary, AssetType::LSLBytecode); raw_asset!(AssetSound, AssetType::Sound); impl AssetSound { pub fn pcm_to_ogg( pcm_data: Vec, sample_rate: i32, channels: i32, bits_per_sample: Option, ) -> Result, Error> { validate_bytes(&pcm_data)?; if pcm_data.is_empty() || sample_rate <= 0 || !matches!(channels, 1 | 2) || !matches!(bits_per_sample.unwrap_or(16), 8 | 16) { return Err(Error::Argument); } let bits = bits_per_sample.unwrap_or(16); let bytes_per_sample = usize::try_from(bits / 8).map_err(|_| Error::Argument)?; let channels = usize::try_from(channels).map_err(|_| Error::Argument)?; let frame_size = bytes_per_sample .checked_mul(channels) .ok_or(Error::Argument)?; if !pcm_data.len().is_multiple_of(frame_size) { return Err(Error::Argument); } let frequency = std::num::NonZeroU32::new(u32::try_from(sample_rate).map_err(|_| Error::Argument)?) .ok_or(Error::Argument)?; let channel_count = std::num::NonZeroU8::new(u8::try_from(channels).map_err(|_| Error::Argument)?) .ok_or(Error::Argument)?; let mut builder = vorbis_rs::VorbisEncoderBuilder::new_with_serial( frequency, channel_count, Vec::new(), 0x4d43_4154, ); let mut encoder = builder.build().map_err(|_| Error::InvalidOperation)?; let frame_count = pcm_data.len() / frame_size; for first_frame in (0..frame_count).step_by(1024) { let end_frame = first_frame.saturating_add(1024).min(frame_count); let mut planar = vec![Vec::with_capacity(end_frame - first_frame); channels]; for frame in first_frame..end_frame { for (channel, samples) in planar.iter_mut().enumerate() { let offset = frame * frame_size + channel * bytes_per_sample; let sample = if bits == 8 { (f32::from(pcm_data[offset]) - 128.0) / 128.0 } else { f32::from(i16::from_le_bytes([pcm_data[offset], pcm_data[offset + 1]])) / 32768.0 }; samples.push(sample); } } encoder .encode_audio_block(&planar) .map_err(|_| Error::InvalidOperation)?; } let ogg_data = encoder.finish().map_err(|_| Error::InvalidOperation)?; validate_bytes(&ogg_data)?; Ok(ogg_data) } } #[derive(Clone, Debug)] pub struct AssetMutable { raw: RawAsset, pub current_type: AssetType, } impl AssetMutable { pub fn new_with_asset_type(type_: AssetType) -> Result { Ok(Self { raw: RawAsset::empty(type_)?, current_type: type_, }) } pub fn new_with_asset_type_uuid_bytes( type_: AssetType, asset_id: UUID, asset_data: Vec, ) -> Result { Ok(Self { raw: RawAsset::with_data(type_, asset_id, asset_data)?, current_type: type_, }) } pub fn decode(&self) -> Result { self.raw.decode() } pub fn encode(&self) -> Result<(), Error> { self.raw.encode() } pub const fn asset_type(&self) -> AssetType { self.current_type } } #[derive(Clone, Debug)] pub struct AssetCallingCard { raw: RawAsset, pub avatar_id: UUID, } impl AssetCallingCard { pub fn new_with_constructor() -> Result { Self::new_with_uuid(UUID::zero()) } pub fn new_with_uuid(avatar_id: UUID) -> Result { let value = Self { raw: RawAsset::empty(AssetType::CallingCard)?, avatar_id, }; value.encode()?; Ok(value) } pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { let raw = RawAsset::with_data(AssetType::CallingCard, asset_id, asset_data)?; let text = String::from_utf8(raw.bytes()).map_err(|_| Error::Argument)?; let id = text .lines() .find_map(|line| line.strip_prefix("avatar_id ")) .ok_or(Error::Argument) .and_then(|id| UUID::new_with_string(id.trim().to_owned()))?; Ok(Self { raw, avatar_id: id }) } pub fn decode(&self) -> Result { let text = String::from_utf8(self.raw.bytes()).map_err(|_| Error::Argument)?; Ok(text.lines().any(|line| { line.strip_prefix("avatar_id ") .and_then(|id| UUID::new_with_string(id.trim().to_owned()).ok()) == Some(self.avatar_id) })) } pub fn encode(&self) -> Result<(), Error> { self.raw.replace( format!( "Linden text version 2\n{{\navatar_id {}\n}}\n", self.avatar_id ) .into_bytes(), ) } pub const fn asset_type(&self) -> AssetType { AssetType::CallingCard } } #[derive(Clone, Debug)] pub struct AssetLandmark { raw: RawAsset, pub position: Vector3, pub region_id: UUID, } impl AssetLandmark { pub fn new_with_constructor() -> Result { Ok(Self { raw: RawAsset::empty(AssetType::Landmark)?, position: Vector3::zero(), region_id: UUID::zero(), }) } pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { let raw = RawAsset::with_data(AssetType::Landmark, asset_id, asset_data)?; let text = String::from_utf8(raw.bytes()).map_err(|_| Error::Argument)?; let region = text .lines() .find_map(|line| line.strip_prefix("region_id ")) .ok_or(Error::Argument) .and_then(|id| UUID::new_with_string(id.trim().to_owned()))?; let values: Vec = text .lines() .find_map(|line| line.strip_prefix("local_pos ")) .ok_or(Error::Argument)? .split_whitespace() .map(str::parse) .collect::>() .map_err(|_| Error::Argument)?; if values.len() != 3 { return Err(Error::Argument); } Ok(Self { raw, region_id: region, position: Vector3 { x: values[0], y: values[1], z: values[2], }, }) } pub fn decode(&self) -> Result { Ok(Self::new_with_uuid_bytes(UUID::zero(), self.raw.bytes()).is_ok()) } pub fn encode(&self) -> Result<(), Error> { self.raw.replace( format!( "Landmark version 2\nregion_id {}\nlocal_pos {} {} {}\n", self.region_id, self.position.x, self.position.y, self.position.z ) .into_bytes(), ) } pub const fn asset_type(&self) -> AssetType { AssetType::Landmark } } #[derive(Clone, Debug)] pub struct AssetScriptText { raw: RawAsset, pub source: Option, } impl AssetScriptText { pub fn new_with_constructor() -> Result { Ok(Self { raw: RawAsset::empty(AssetType::LSLText)?, source: None, }) } pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { let source = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?; Ok(Self { raw: RawAsset::with_data(AssetType::LSLText, asset_id, asset_data)?, source: Some(source), }) } pub fn decode(&self) -> Result { Ok(String::from_utf8(self.raw.bytes()).is_ok()) } pub fn encode(&self) -> Result<(), Error> { self.raw .replace(self.source.clone().unwrap_or_default().into_bytes()) } pub const fn asset_type(&self) -> AssetType { AssetType::LSLText } } #[derive(Clone, Debug)] pub struct AssetSettings { raw: RawAsset, pub settings: Option, } impl AssetSettings { pub fn new_with_constructor() -> Result { Ok(Self { raw: RawAsset::empty(AssetType::Settings)?, settings: None, }) } pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { let text = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?; let settings = OSDParser::deserialize_with_string(text)?; Ok(Self { raw: RawAsset::with_data(AssetType::Settings, asset_id, asset_data)?, settings: Some(settings), }) } pub fn decode(&self) -> Result { Ok(OSDParser::deserialize_with_bytes(self.raw.bytes()).is_ok()) } pub fn encode(&self) -> Result<(), Error> { let value = self.settings.clone().unwrap_or_default(); self.raw.replace(value.as_string()?.into_bytes()) } pub const fn asset_type(&self) -> AssetType { AssetType::Settings } } pub struct AssetMesh { raw: RawAsset, pub mesh_data: OSDMap, } impl AssetMesh { pub fn new_with_constructor() -> Result { Ok(Self { raw: RawAsset::empty(AssetType::Mesh)?, mesh_data: OSDMap::new_with_constructor()?, }) } pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { let raw = RawAsset::with_data(AssetType::Mesh, asset_id, asset_data.clone())?; let mesh_data = match OSDParser::deserialize_llsd_binary_with_bytes(asset_data)? { OSD::Map(map) => OSDMap::new_with_dictionary(map)?, _ => return Err(Error::Argument), }; Ok(Self { raw, mesh_data }) } pub fn decode(&self) -> Result { Ok(matches!( OSDParser::deserialize_llsd_binary_with_bytes(self.raw.bytes()), Ok(OSD::Map(_)) )) } pub fn encode(&self) -> Result<(), Error> { self.raw .replace(OSDParser::serialize_llsd_binary_with_osd(OSD::Map( self.mesh_data.snapshot(), ))?) } pub const fn asset_type(&self) -> AssetType { AssetType::Mesh } } pub struct AssetTexture { raw: RawAsset, pub components: i32, pub image: Option, } impl AssetTexture { pub fn new_with_constructor() -> Result { Ok(Self { raw: RawAsset::empty(AssetType::Texture)?, components: 0, image: None, }) } pub fn new_with_managed_image(image: ManagedImage) -> Result { Ok(Self { raw: RawAsset::empty(AssetType::Texture)?, components: 0, image: Some(image), }) } pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { Ok(Self { raw: RawAsset::with_data(AssetType::Texture, asset_id, asset_data)?, components: 0, image: None, }) } pub fn decode(&self) -> Result { #[cfg(feature = "jpeg2000")] { Ok(libremetaverse_imaging::J2kCodec::decode_bytes( &self.raw.bytes(), libremetaverse_imaging::J2kDecodeOptions::default(), ) .is_ok()) } #[cfg(not(feature = "jpeg2000"))] { validate_bytes(&self.raw.bytes())?; Err(Error::InvalidOperation) } } pub fn encode(&self) -> Result<(), Error> { let image = self.image.as_ref().ok_or(Error::InvalidOperation)?; #[cfg(feature = "jpeg2000")] { let encoded = libremetaverse_imaging::J2kCodec::encode( image, libremetaverse_imaging::J2kEncodeOptions::default(), )?; self.raw.replace(encoded) } #[cfg(not(feature = "jpeg2000"))] { let _ = image; Err(Error::InvalidOperation) } } pub const fn asset_type(&self) -> AssetType { AssetType::Texture } } #[derive(Clone, Debug)] pub struct AssetWearable { raw: RawAsset, pub creator: UUID, pub description: String, pub for_sale: SaleType, pub group: UUID, pub group_owned: bool, pub last_owner: UUID, pub name: String, pub owner: UUID, pub params: HashMap, pub permissions: Permissions, pub sale_price: i32, pub textures: HashMap, pub wearable_type: WearableType, } impl AssetWearable { fn empty(kind: AssetType) -> Result { Ok(Self { raw: RawAsset::empty(kind)?, creator: UUID::zero(), description: String::new(), for_sale: SaleType::Not, group: UUID::zero(), group_owned: false, last_owner: UUID::zero(), name: String::new(), owner: UUID::zero(), params: HashMap::new(), permissions: Permissions::default(), sale_price: 0, textures: HashMap::new(), wearable_type: WearableType::Invalid, }) } pub fn decode(&self) -> Result { Ok(std::str::from_utf8(&self.raw.bytes()).is_ok()) } pub fn encode(&self) -> Result<(), Error> { let mut text = format!( "LLWearable version 22\n{}\n{}\ntype {}\nparameters {}\n", self.name, self.description, self.wearable_type as i32, self.params.len() ); let mut params: Vec<_> = self.params.iter().collect(); params.sort_unstable_by_key(|(id, _)| **id); for (id, value) in params { text.push_str(&format!("{id} {value}\n")); } text.push_str(&format!("textures {}\n", self.textures.len())); self.raw.replace(text.into_bytes()) } } pub struct AssetBodypart { wearable: AssetWearable, } impl AssetBodypart { pub fn new_with_constructor() -> Result { Ok(Self { wearable: AssetWearable::empty(AssetType::Bodypart)?, }) } pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { let mut wearable = AssetWearable::empty(AssetType::Bodypart)?; wearable.raw = RawAsset::with_data(AssetType::Bodypart, asset_id, asset_data)?; Ok(Self { wearable }) } pub const fn asset_type(&self) -> AssetType { AssetType::Bodypart } pub fn decode(&self) -> Result { self.wearable.decode() } pub fn encode(&self) -> Result<(), Error> { self.wearable.encode() } } pub struct AssetClothing { wearable: AssetWearable, } impl AssetClothing { pub fn new_with_constructor() -> Result { Ok(Self { wearable: AssetWearable::empty(AssetType::Clothing)?, }) } pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { let mut wearable = AssetWearable::empty(AssetType::Clothing)?; wearable.raw = RawAsset::with_data(AssetType::Clothing, asset_id, asset_data)?; Ok(Self { wearable }) } pub const fn asset_type(&self) -> AssetType { AssetType::Clothing } pub fn decode(&self) -> Result { self.wearable.decode() } pub fn encode(&self) -> Result<(), Error> { self.wearable.encode() } } pub struct AssetNotecard { raw: RawAsset, pub body_text: String, pub embedded_items: Vec, } impl AssetNotecard { pub fn new_with_constructor() -> Result { Ok(Self { raw: RawAsset::empty(AssetType::Notecard)?, body_text: String::new(), embedded_items: Vec::new(), }) } pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { let text = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?; let marker = "Text length "; let start = text.find(marker).ok_or(Error::Argument)? + marker.len(); let line_end = text[start..].find('\n').ok_or(Error::Argument)? + start; let length: usize = text[start..line_end] .trim() .parse() .map_err(|_| Error::Argument)?; let body_start = line_end + 1; let body_end = body_start.checked_add(length).ok_or(Error::Argument)?; let body_text = text .get(body_start..body_end) .ok_or(Error::Argument)? .to_owned(); Ok(Self { raw: RawAsset::with_data(AssetType::Notecard, asset_id, asset_data)?, body_text, embedded_items: Vec::new(), }) } pub fn decode(&self) -> Result { Ok(Self::new_with_uuid_bytes(UUID::zero(), self.raw.bytes()).is_ok()) } pub fn encode(&self) -> Result<(), Error> { if !self.embedded_items.is_empty() { return Err(Error::InvalidOperation); } let bytes = self.body_text.as_bytes(); self.raw.replace(format!("Linden text version 2\n{{\nLLEmbeddedItems version 1\n{{\ncount 0\n}}\nText length {}\n{}\n}}\n", bytes.len(), self.body_text).into_bytes()) } pub const fn asset_type(&self) -> AssetType { AssetType::Notecard } }