Files
MetaCrate/crates/libremetaverse/src/asset_models.rs
Chili Palmer a9927fcfcb
Some checks failed
Native code generation / deterministic (push) Failing after 2m19s
Imaging and meshing gate / native (push) Failing after 1m26s
Native Rust workspace compile / compile (push) Failing after 2m11s
Implement appearance and current outfit services (#65)
2026-08-10 08:58:29 +00:00

678 lines
21 KiB
Rust

//! 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<u8>,
pub temporary: bool,
asset_id: UUID,
asset_type: AssetType,
}
impl Asset {
pub(crate) fn native_new(
asset_type: AssetType,
asset_id: UUID,
data: Vec<u8>,
) -> Result<Self, Error> {
validate_bytes(&data)?;
Ok(Self {
asset_data: data,
temporary: false,
asset_id,
asset_type,
})
}
pub fn decode(&self) -> Result<bool, Error> {
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<RwLock<Asset>>);
impl RawAsset {
fn empty(asset_type: AssetType) -> Result<Self, Error> {
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<u8>) -> Result<Self, Error> {
Ok(Self(Arc::new(RwLock::new(Asset::native_new(
asset_type, id, data,
)?))))
}
fn decode(&self) -> Result<bool, Error> {
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<u8> {
self.0
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.asset_data
.clone()
}
fn replace(&self, bytes: Vec<u8>) -> 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<Self, Error> {
Ok(Self {
raw: RawAsset::empty($kind)?,
})
}
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
Ok(Self {
raw: RawAsset::with_data($kind, asset_id, asset_data)?,
})
}
pub fn decode(&self) -> Result<bool, Error> {
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<u8>,
sample_rate: i32,
channels: i32,
bits_per_sample: Option<i32>,
) -> Result<Vec<u8>, 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<Self, Error> {
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<u8>,
) -> Result<Self, Error> {
Ok(Self {
raw: RawAsset::with_data(type_, asset_id, asset_data)?,
current_type: type_,
})
}
pub fn decode(&self) -> Result<bool, Error> {
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, Error> {
Self::new_with_uuid(UUID::zero())
}
pub fn new_with_uuid(avatar_id: UUID) -> Result<Self, Error> {
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<u8>) -> Result<Self, Error> {
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<bool, Error> {
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<Self, Error> {
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<u8>) -> Result<Self, Error> {
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<f32> = text
.lines()
.find_map(|line| line.strip_prefix("local_pos "))
.ok_or(Error::Argument)?
.split_whitespace()
.map(str::parse)
.collect::<Result<_, _>>()
.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<bool, Error> {
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<String>,
}
impl AssetScriptText {
pub fn new_with_constructor() -> Result<Self, Error> {
Ok(Self {
raw: RawAsset::empty(AssetType::LSLText)?,
source: None,
})
}
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
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<bool, Error> {
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<OSD>,
}
impl AssetSettings {
pub fn new_with_constructor() -> Result<Self, Error> {
Ok(Self {
raw: RawAsset::empty(AssetType::Settings)?,
settings: None,
})
}
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
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<bool, Error> {
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<Self, Error> {
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<u8>) -> Result<Self, Error> {
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<bool, Error> {
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<ManagedImage>,
}
impl AssetTexture {
pub fn new_with_constructor() -> Result<Self, Error> {
Ok(Self {
raw: RawAsset::empty(AssetType::Texture)?,
components: 0,
image: None,
})
}
pub fn new_with_managed_image(image: ManagedImage) -> Result<Self, Error> {
Ok(Self {
raw: RawAsset::empty(AssetType::Texture)?,
components: 0,
image: Some(image),
})
}
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
Ok(Self {
raw: RawAsset::with_data(AssetType::Texture, asset_id, asset_data)?,
components: 0,
image: None,
})
}
pub fn decode(&self) -> Result<bool, Error> {
#[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<i32, f32>,
pub permissions: Permissions,
pub sale_price: i32,
pub textures: HashMap<crate::AvatarTextureIndex, UUID>,
pub wearable_type: WearableType,
}
impl AssetWearable {
fn empty(kind: AssetType) -> Result<Self, Error> {
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<bool, Error> {
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<Self, Error> {
Ok(Self {
wearable: AssetWearable::empty(AssetType::Bodypart)?,
})
}
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
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<bool, Error> {
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<Self, Error> {
Ok(Self {
wearable: AssetWearable::empty(AssetType::Clothing)?,
})
}
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
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<bool, Error> {
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<crate::InventoryItem>,
}
impl AssetNotecard {
pub fn new_with_constructor() -> Result<Self, Error> {
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<u8>) -> Result<Self, Error> {
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<bool, Error> {
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
}
}