//! Extended and legacy environment capability support. #![allow(clippy::missing_errors_doc)] #![allow(clippy::must_use_candidate)] #![allow(clippy::needless_pass_by_value)] use crate::agent_manager::EventRegistry; use crate::{Error, GridClient}; use futures_channel::oneshot; use futures_util::future::{Either, select}; use futures_util::pin_mut; use libremetaverse_structured_data::{OSD, OSDMap, OSDParser}; use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri}; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; const MAX_ENVIRONMENT_BYTES: usize = 16 * 1024 * 1024; fn read(value: &RwLock) -> std::sync::RwLockReadGuard<'_, T> { value .read() .unwrap_or_else(std::sync::PoisonError::into_inner) } fn write(value: &RwLock) -> std::sync::RwLockWriteGuard<'_, T> { value .write() .unwrap_or_else(std::sync::PoisonError::into_inner) } fn integer(map: &OSDMap, key: &str, default: i32) -> i32 { map.get(key) .and_then(|value| value.as_integer().ok()) .unwrap_or(default) } fn boolean(map: &OSDMap, key: &str, default: bool) -> bool { map.get(key) .and_then(|value| value.as_boolean().ok()) .unwrap_or(default) } fn string(map: &OSDMap, key: &str) -> String { map.get(key) .and_then(|value| value.as_string().ok()) .unwrap_or_default() } #[derive(Clone, Debug, PartialEq)] pub struct EnvironmentData { day_cycle: Option, day_length: i32, day_offset: i32, flags: crate::messages::linden::EnvironmentFlags, is_default: bool, } impl EnvironmentData { pub fn new() -> Result { Ok(Self { day_cycle: None, day_length: 14_400, day_offset: 57_600, flags: crate::messages::linden::EnvironmentFlags::NONE, is_default: false, }) } pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.day_length = integer(&map, "day_length", 14_400); self.day_offset = integer(&map, "day_offset", 57_600); self.flags = crate::messages::linden::EnvironmentFlags( u32::try_from(integer(&map, "flags", 0)).unwrap_or_default(), ); self.is_default = boolean(&map, "is_default", false); self.day_cycle = map .get("day_cycle") .filter(|value| !matches!(value, OSD::Undefined)); Ok(()) } pub fn serialize(&self) -> Result { let mut values = HashMap::from([ ("day_length".into(), OSD::Integer(self.day_length)), ("day_offset".into(), OSD::Integer(self.day_offset)), ( "flags".into(), OSD::Integer(i32::try_from(self.flags.0).unwrap_or(i32::MAX)), ), ]); if let Some(day_cycle) = self.day_cycle.clone() { values.insert("day_cycle".into(), day_cycle); } OSDMap::new_with_dictionary(values) } pub fn day_cycle(&self) -> Option { self.day_cycle.clone() } pub fn set_day_cycle(&mut self, value: Option) { self.day_cycle = value; } pub fn day_length(&self) -> i32 { self.day_length } pub fn set_day_length(&mut self, value: i32) { self.day_length = value; } pub fn day_offset(&self) -> i32 { self.day_offset } pub fn set_day_offset(&mut self, value: i32) { self.day_offset = value; } pub fn flags(&self) -> crate::messages::linden::EnvironmentFlags { self.flags } pub fn set_flags(&mut self, value: crate::messages::linden::EnvironmentFlags) { self.flags = value; } pub fn is_default(&self) -> bool { self.is_default } pub fn set_is_default(&mut self, value: bool) { self.is_default = value; } } impl crate::interfaces::IMessage for EnvironmentData { fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.deserialize(map) } fn serialize(&self) -> Result { self.serialize() } } #[derive(Clone, Debug, PartialEq)] pub struct ExtEnvironmentMessage { environment: Option, message: String, parcel_id: i32, success: bool, version: i32, } impl ExtEnvironmentMessage { pub fn new() -> Result { Ok(Self { environment: None, message: String::new(), parcel_id: -1, success: false, version: 0, }) } pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.success = boolean(&map, "success", false); self.parcel_id = integer(&map, "parcel_id", -1); self.version = integer(&map, "version", 0); self.message = string(&map, "message"); self.environment = match map.get("environment") { Some(OSD::Map(values)) => { let mut value = EnvironmentData::new()?; value.deserialize(OSDMap::new_with_dictionary(values)?)?; Some(value) } _ => None, }; Ok(()) } pub fn serialize(&self) -> Result { let mut values = HashMap::new(); values.insert("parcel_id".into(), OSD::Integer(self.parcel_id)); if let Some(environment) = &self.environment { values.insert( "environment".into(), OSD::Map(environment.serialize()?.snapshot()), ); } if self.version != 0 { values.insert("version".into(), OSD::Integer(self.version)); } OSDMap::new_with_dictionary(values) } pub fn environment(&self) -> Option { self.environment.clone() } pub fn set_environment(&mut self, value: Option) { self.environment = value; } pub fn message(&self) -> String { self.message.clone() } pub fn set_message(&mut self, value: String) { self.message = value; } pub fn parcel_id(&self) -> i32 { self.parcel_id } pub fn set_parcel_id(&mut self, value: i32) { self.parcel_id = value; } pub fn success(&self) -> bool { self.success } pub fn set_success(&mut self, value: bool) { self.success = value; } pub fn version(&self) -> i32 { self.version } pub fn set_version(&mut self, value: i32) { self.version = value; } } impl crate::interfaces::IMessage for ExtEnvironmentMessage { fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.deserialize(map) } fn serialize(&self) -> Result { self.serialize() } } #[derive(Clone, Debug, PartialEq)] pub struct LegacyEnvironmentMessage { settings: Option, } impl LegacyEnvironmentMessage { pub fn new() -> Result { Ok(Self { settings: None }) } pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.settings = Some(OSD::Map(map.snapshot())); Ok(()) } pub fn serialize(&self) -> Result { match &self.settings { Some(OSD::Map(values)) => OSDMap::new_with_dictionary(values.clone()), _ => OSDMap::new_with_constructor(), } } pub fn settings(&self) -> Option { self.settings.clone() } pub fn set_settings(&mut self, value: Option) { self.settings = value; } } impl crate::interfaces::IMessage for LegacyEnvironmentMessage { fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.deserialize(map) } fn serialize(&self) -> Result { self.serialize() } } #[derive(Clone)] pub struct RegionEnvironmentEventArgs { environment: ExtEnvironmentMessage, } impl RegionEnvironmentEventArgs { pub fn new(environment: ExtEnvironmentMessage) -> Result { Ok(Self { environment }) } pub fn environment(&self) -> ExtEnvironmentMessage { self.environment.clone() } } #[derive(Clone)] pub struct ParcelEnvironmentEventArgs { parcel_id: i32, environment: ExtEnvironmentMessage, } impl ParcelEnvironmentEventArgs { pub fn new(parcel_id: i32, environment: ExtEnvironmentMessage) -> Result { Ok(Self { parcel_id, environment, }) } pub fn parcel_id(&self) -> i32 { self.parcel_id } pub fn environment(&self) -> ExtEnvironmentMessage { self.environment.clone() } } #[derive(Clone)] pub struct LegacyEnvironmentEventArgs { environment: LegacyEnvironmentMessage, } impl LegacyEnvironmentEventArgs { pub fn new(environment: LegacyEnvironmentMessage) -> Result { Ok(Self { environment }) } pub fn environment(&self) -> LegacyEnvironmentMessage { self.environment.clone() } } pub(crate) struct EnvironmentManagerInner { client: Arc, disposed: AtomicBool, region: RwLock>, legacy: RwLock>, region_events: EventRegistry, parcel_events: EventRegistry, legacy_events: EventRegistry, } #[derive(Clone)] pub struct EnvironmentManager { pub(crate) inner: Arc, } impl EnvironmentManager { pub fn new(client: GridClient) -> Result { Self::native_new(Arc::new(client)) } #[allow(clippy::unnecessary_wraps)] // The mapped public constructor has a fixed fallible shape. pub(crate) fn native_new(client: Arc) -> Result { let candidate = Arc::new(EnvironmentManagerInner { client: Arc::clone(&client), disposed: AtomicBool::new(false), region: RwLock::new(None), legacy: RwLock::new(None), region_events: EventRegistry::default(), parcel_events: EventRegistry::default(), legacy_events: EventRegistry::default(), }); Ok(Self { inner: client.install_environment_manager_inner(candidate), }) } async fn capability( &self, name: &str, token: &CancellationToken, ) -> Result, Error> { if self.inner.disposed.load(Ordering::Acquire) { return Err(Error::InvalidOperation); } let Some(caps) = self .inner .client .network() .current_sim() .and_then(|sim| sim.native_caps()) else { return Ok(None); }; if let Some(uri) = caps.capability_uri(name.to_owned())? { return Ok(Some(uri)); } if caps.seed_request_finished() { return Ok(None); } let (sender, receiver) = oneshot::channel(); let sender = Arc::new(std::sync::Mutex::new(Some(sender))); let wake = Arc::clone(&sender); let _subscription = caps.subscribe_capabilities_received(Some(Arc::new(move |_| { if let Some(sender) = wake .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .take() { let _ = sender.send(()); } }))); if let Some(uri) = caps.capability_uri(name.to_owned())? { return Ok(Some(uri)); } let cancellation = token.cancelled(); pin_mut!(receiver, cancellation); match select(receiver, cancellation).await { Either::Left(_) => caps.capability_uri(name.to_owned()), Either::Right(_) => Err(Error::Cancelled), } } async fn request( &self, method: &str, uri: Uri, body: Vec, token: Option, ) -> Result>, Error> { if body.len() > MAX_ENVIRONMENT_BYTES { return Err(Error::Argument); } let http = self.inner.client.native_http_caps_client(); let token = token.unwrap_or_default(); let (response, bytes) = match method { "GET" => http.get(uri, token, None).await?, "POST" => { http.post_with_uri_string_bytes_cancellation_token_i_progress( uri, "application/llsd+xml".into(), body, token, None, ) .await? } "DELETE" => { http.delete_with_uri_string_bytes_cancellation_token_i_progress( uri, "application/llsd+xml".into(), body, token, None, ) .await? } _ => return Err(Error::Argument), }; if bytes.len() > MAX_ENVIRONMENT_BYTES { return Err(Error::Argument); } Ok((200..300).contains(&response.status_code).then_some(bytes)) } fn parcel_uri(uri: Uri, parcel_id: i32) -> Uri { let separator = if uri.0.contains('?') { '&' } else { '?' }; Uri(format!("{}{separator}parcelid={parcel_id}", uri.0)) } fn decode_ext(bytes: Vec) -> Result { let OSD::Map(values) = OSDParser::deserialize_with_bytes(bytes)? else { return Err(Error::Argument); }; let mut message = ExtEnvironmentMessage::new()?; message.deserialize(OSDMap::new_with_dictionary(values)?)?; Ok(message) } pub async fn get_region_environment( &self, token: Option, ) -> Result, Error> { let resolved = token.clone().unwrap_or_default(); let Some(uri) = self.capability("ExtEnvironment", &resolved).await? else { return Ok(None); }; let Some(bytes) = self.request("GET", uri, Vec::new(), token).await? else { return Ok(None); }; let message = Self::decode_ext(bytes)?; *write(&self.inner.region) = Some(message.clone()); self.inner .region_events .emit(RegionEnvironmentEventArgs::new(message.clone())?); Ok(Some(message)) } pub async fn get_parcel_environment( &self, parcel_id: i32, token: Option, ) -> Result, Error> { let resolved = token.clone().unwrap_or_default(); let Some(uri) = self.capability("ExtEnvironment", &resolved).await? else { return Ok(None); }; let Some(bytes) = self .request("GET", Self::parcel_uri(uri, parcel_id), Vec::new(), token) .await? else { return Ok(None); }; let message = Self::decode_ext(bytes)?; self.inner .parcel_events .emit(ParcelEnvironmentEventArgs::new(parcel_id, message.clone())?); Ok(Some(message)) } async fn set_environment( &self, parcel_id: Option, environment: EnvironmentData, token: Option, ) -> Result, Error> { let resolved = token.clone().unwrap_or_default(); let Some(mut uri) = self.capability("ExtEnvironment", &resolved).await? else { return Ok(None); }; if let Some(parcel_id) = parcel_id { uri = Self::parcel_uri(uri, parcel_id); } let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([( "environment".into(), OSD::Map(environment.serialize()?.snapshot()), )])))?; let Some(bytes) = self.request("POST", uri, body, token).await? else { return Ok(None); }; Self::decode_ext(bytes).map(Some) } pub async fn set_parcel_environment( &self, parcel_id: i32, environment: EnvironmentData, token: Option, ) -> Result, Error> { self.set_environment(Some(parcel_id), environment, token) .await } pub async fn set_region_environment_with_environment_data_cancellation_token( &self, environment: EnvironmentData, token: Option, ) -> Result, Error> { self.set_environment(None, environment, token).await } async fn reset_environment( &self, parcel_id: Option, token: Option, ) -> Result { let resolved = token.clone().unwrap_or_default(); let Some(mut uri) = self.capability("ExtEnvironment", &resolved).await? else { return Ok(false); }; if let Some(parcel_id) = parcel_id { uri = Self::parcel_uri(uri, parcel_id); } let Some(bytes) = self.request("DELETE", uri, Vec::new(), token).await? else { return Ok(false); }; Ok(Self::decode_ext(bytes)?.success()) } pub async fn reset_parcel_environment( &self, parcel_id: i32, token: Option, ) -> Result { self.reset_environment(Some(parcel_id), token).await } pub async fn reset_region_environment( &self, token: Option, ) -> Result { self.reset_environment(None, token).await } pub async fn get_legacy_environment( &self, token: Option, ) -> Result, Error> { let resolved = token.clone().unwrap_or_default(); let Some(uri) = self.capability("EnvironmentSettings", &resolved).await? else { return Ok(None); }; let Some(bytes) = self.request("GET", uri, Vec::new(), token).await? else { return Ok(None); }; let OSD::Map(values) = OSDParser::deserialize_with_bytes(bytes)? else { return Err(Error::Argument); }; let mut message = LegacyEnvironmentMessage::new()?; message.deserialize(OSDMap::new_with_dictionary(values)?)?; *write(&self.inner.legacy) = Some(message.clone()); self.inner .legacy_events .emit(LegacyEnvironmentEventArgs::new(message.clone())?); Ok(Some(message)) } pub async fn set_legacy_environment_with_osd_cancellation_token( &self, settings: OSD, token: Option, ) -> Result { let resolved = token.clone().unwrap_or_default(); let Some(uri) = self.capability("EnvironmentSettings", &resolved).await? else { return Ok(false); }; let body = OSDParser::serialize_llsd_xml_bytes(settings)?; let Some(bytes) = self.request("POST", uri, body, token).await? else { return Ok(false); }; let OSD::Map(values) = OSDParser::deserialize_with_bytes(bytes)? else { return Ok(false); }; Ok(values .get("success") .and_then(|value| value.as_boolean().ok()) .unwrap_or(true)) } pub fn region_environment(&self) -> Option { read(&self.inner.region).clone() } pub fn set_region_environment(&mut self, value: Option) { *write(&self.inner.region) = value; } pub fn legacy_environment(&self) -> Option { read(&self.inner.legacy).clone() } pub fn set_legacy_environment(&mut self, value: Option) { *write(&self.inner.legacy) = value; } pub fn subscribe_region_environment_updated( &self, handler: EventHandler, ) -> Subscription { self.inner.region_events.subscribe(handler) } pub fn subscribe_parcel_environment_updated( &self, handler: EventHandler, ) -> Subscription { self.inner.parcel_events.subscribe(handler) } pub fn subscribe_legacy_environment_updated( &self, handler: EventHandler, ) -> Subscription { self.inner.legacy_events.subscribe(handler) } pub fn dispose(&self) -> Result<(), Error> { self.inner.disposed.store(true, Ordering::Release); Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn environment_defaults_and_wire_shape_match_eep() { let mut value = EnvironmentData::new().unwrap(); assert_eq!(value.day_length(), 14_400); assert_eq!(value.day_offset(), 57_600); value.set_is_default(true); let map = value.serialize().unwrap(); assert!(!map.contains_key("is_default".into()).unwrap()); assert!(!map.contains_key("day_cycle".into()).unwrap()); } #[test] fn ext_environment_round_trip_preserves_correlation_fields() { let mut value = ExtEnvironmentMessage::new().unwrap(); value.set_parcel_id(17); value.set_version(9); value.set_environment(Some(EnvironmentData::new().unwrap())); let mut decoded = ExtEnvironmentMessage::new().unwrap(); decoded.deserialize(value.serialize().unwrap()).unwrap(); assert_eq!(decoded.parcel_id(), 17); assert_eq!(decoded.version(), 9); assert!(decoded.environment().is_some()); } }