Files
MetaCrate/crates/libremetaverse/src/network_manager.rs
Chili Palmer c9a1170a27
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
Complete first release candidate audit (#107)
2026-08-12 14:44:28 +00:00

4067 lines
138 KiB
Rust

//! Native network-manager, packet-dispatch, and simulator lifecycle runtime.
//!
//! The C# implementation uses reference-identity objects protected by several
//! locks and channel processors. Rust models those objects as cheap `Arc`
//! handles. Callback lists are snapshotted while locked and always invoked
//! after the lock is released.
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility map.
#![allow(clippy::large_enum_variant)] // Commands move through a bounded channel without cloning.
#![allow(clippy::needless_pass_by_value)] // Owned mapped parameters preserve the public API.
#![allow(clippy::too_many_arguments)] // Login overloads preserve the mapped public API.
#![allow(clippy::too_many_lines)] // Packet dispatch keeps the complete protocol switch together.
#![allow(clippy::type_complexity)] // Delegate signatures are fixed by the compatibility map.
use crate::interfaces::IMessage;
use crate::packets::{
AgentPausePacket, AgentResumePacket, CloseCircuitPacket, CompleteAgentMovementPacket,
CompletePingCheckPacket, GenericStreamingMessagePacket, KickUserPacket, LogoutReplyPacket,
LogoutRequestPacket, Packet, PacketType, RegionHandshakePacket, RegionHandshakeReplyPacket,
SimStatsPacket, StartPingCheckPacket, UseCircuitCodePacket,
};
use crate::udp_transport::{UDPBase, UDPPacketBuffer, UdpPacketHandler, UdpTransportConfig};
use crate::{
AccountLevelBenefits, Avatar, Caps, Error, GenericStreamingMethod, GridClient, Helpers,
LoginCredential, LoginParams, LoginProgressEventArgs, LoginResponseData, LoginStatus,
NetworkManagerLoginResponseCallback, Parcel, Primitive, RegionFlags, RegionProtocols,
SimAccess, SimulatorDataPool, SimulatorFeatures, SimulatorSimStats, TerrainPatch,
};
use libremetaverse_structured_data::{OSD, OSDFormat, OSDMap, OSDParser};
use libremetaverse_types::compat::{
CancellationToken, CancellationTokenSource, EventHandler, Object, Subscription, Uri,
};
use libremetaverse_types::{UUID, Vector2};
use std::collections::HashMap;
use std::fmt;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU32, AtomicU64, Ordering};
use std::sync::mpsc::{Receiver, RecvTimeoutError, SyncSender, sync_channel};
use std::sync::{Arc, Condvar, Mutex, OnceLock, RwLock, Weak};
use std::thread::{self, JoinHandle};
use std::time::Duration;
impl Clone for crate::packets::Header {
fn clone(&self) -> Self {
crate::packet_wire::clone_header(self)
}
}
impl Clone for Packet {
fn clone(&self) -> Self {
Self {
has_variable_blocks: self.has_variable_blocks,
header: self.header.clone(),
type_: self.type_,
native_body: self.native_body.clone(),
native_payload_length: self.native_payload_length,
}
}
}
fn mutex<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
value
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn read<T>(value: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
value
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write<T>(value: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
value
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn invoke_safely<T>(handler: &EventHandler<T>, value: T) {
let _ = catch_unwind(AssertUnwindSafe(|| handler(value)));
}
fn wait_for_cancellation(
token: &libremetaverse_types::compat::CancellationToken,
duration: Duration,
) -> bool {
let started = std::time::Instant::now();
while !token.is_cancellation_requested() {
let remaining = duration.saturating_sub(started.elapsed());
if remaining.is_zero() {
return false;
}
thread::sleep(remaining.min(Duration::from_millis(50)));
}
true
}
fn positive_millisecond_duration(value: i32) -> Duration {
Duration::from_millis(u64::try_from(value.max(1)).unwrap_or(1))
}
fn environment_tick_count() -> i32 {
static START: OnceLock<std::time::Instant> = OnceLock::new();
let elapsed = START.get_or_init(std::time::Instant::now).elapsed();
let wrapped = u32::try_from(elapsed.as_millis() & u128::from(u32::MAX)).unwrap_or_default();
wrapped.cast_signed()
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum LogoutWaitOutcome {
Reply,
Timeout,
Cancelled,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
enum NetworkLifecycleState {
Disconnected,
Connecting,
Connected,
LoggingOut,
ShuttingDown,
}
impl NetworkLifecycleState {
fn from_u8(value: u8) -> Self {
match value {
1 => Self::Connecting,
2 => Self::Connected,
3 => Self::LoggingOut,
4 => Self::ShuttingDown,
_ => Self::Disconnected,
}
}
}
fn wait_for_logout(
receiver: &Receiver<()>,
timeout: Duration,
cancellations: &[CancellationToken],
) -> LogoutWaitOutcome {
let started = std::time::Instant::now();
loop {
if receiver.try_recv().is_ok() {
return LogoutWaitOutcome::Reply;
}
if cancellations
.iter()
.any(CancellationToken::is_cancellation_requested)
{
return LogoutWaitOutcome::Cancelled;
}
let remaining = timeout.saturating_sub(started.elapsed());
if remaining.is_zero() {
return LogoutWaitOutcome::Timeout;
}
match receiver.recv_timeout(remaining.min(Duration::from_millis(20))) {
Ok(()) => return LogoutWaitOutcome::Reply,
Err(RecvTimeoutError::Timeout) => {}
Err(RecvTimeoutError::Disconnected) => return LogoutWaitOutcome::Cancelled,
}
}
}
fn validate_login_uri(value: &str) -> Result<Uri, Error> {
let parsed = reqwest::Url::parse(value).map_err(|_| Error::Argument)?;
if !matches!(parsed.scheme(), "http" | "https") || parsed.host_str().is_none() {
return Err(Error::Argument);
}
Ok(Uri(parsed.to_string()))
}
struct LoginWireSecrets {
password: String,
token: String,
mfa_hash: String,
}
impl Drop for LoginWireSecrets {
fn drop(&mut self) {
// Overwrite the live buffers before releasing them. This cannot erase
// allocator copies, but it prevents the owned request state surviving
// the login/redirect sequence.
self.password
.replace_range(.., &"\0".repeat(self.password.len()));
self.token.replace_range(.., &"\0".repeat(self.token.len()));
self.mfa_hash
.replace_range(.., &"\0".repeat(self.mfa_hash.len()));
self.password.clear();
self.token.clear();
self.mfa_hash.clear();
}
}
fn login_request(params: &LoginParams, secrets: &LoginWireSecrets) -> OSD {
let mut map = HashMap::from([
("first".to_owned(), OSD::String(params.first_name.clone())),
("last".to_owned(), OSD::String(params.last_name.clone())),
("passwd".to_owned(), OSD::String(secrets.password.clone())),
("start".to_owned(), OSD::String(params.start.clone())),
("channel".to_owned(), OSD::String(params.channel.clone())),
("version".to_owned(), OSD::String(params.version.clone())),
("platform".to_owned(), OSD::String(params.platform.clone())),
(
"platform_version".to_owned(),
OSD::String(params.platform_version.clone()),
),
("mac".to_owned(), OSD::String(params.mac.clone())),
("agree_to_tos".to_owned(), OSD::Boolean(params.agree_to_tos)),
(
"read_critical".to_owned(),
OSD::Boolean(params.read_critical),
),
(
"viewer_digest".to_owned(),
OSD::String(params.viewer_digest.clone()),
),
("id0".to_owned(), OSD::String(params.id0.clone())),
(
"last_exec_event".to_owned(),
OSD::Integer(params.last_exec_event as i32),
),
(
"options".to_owned(),
OSD::Array(params.options.iter().cloned().map(OSD::String).collect()),
),
]);
if params.mfa_enabled {
map.insert("token".to_owned(), OSD::String(secrets.token.clone()));
map.insert("mfa_hash".to_owned(), OSD::String(secrets.mfa_hash.clone()));
}
OSD::Map(map)
}
async fn run_blocking_compat<T: Send + 'static>(
name: &str,
operation: impl FnOnce() -> T + Send + 'static,
) -> Result<T, Error> {
let (sender, receiver) = tokio::sync::oneshot::channel();
thread::Builder::new()
.name(name.to_owned())
.spawn(move || {
let _ = sender.send(operation());
})
.map_err(|_| Error::InvalidOperation)?;
receiver.await.map_err(|_| Error::Cancelled)
}
struct EventRegistryState<T> {
next_id: AtomicU64,
handlers: Mutex<Vec<(u64, EventHandler<T>)>>,
}
struct EventRegistry<T> {
state: Arc<EventRegistryState<T>>,
}
impl<T> Default for EventRegistry<T> {
fn default() -> Self {
Self {
state: Arc::new(EventRegistryState {
next_id: AtomicU64::new(1),
handlers: Mutex::new(Vec::new()),
}),
}
}
}
impl<T: 'static> EventRegistry<T> {
fn subscribe(&self, handler: EventHandler<T>) -> Subscription {
let id = self.state.next_id.fetch_add(1, Ordering::Relaxed);
mutex(&self.state.handlers).push((id, handler));
let state = Arc::downgrade(&self.state);
Subscription::new(move || {
let Some(state) = state.upgrade() else {
return;
};
mutex(&state.handlers).retain(|(candidate, _)| *candidate != id);
})
}
fn emit_with(&self, mut value: impl FnMut() -> T) {
let handlers: Vec<_> = mutex(&self.state.handlers)
.iter()
.map(|(_, handler)| Arc::clone(handler))
.collect();
for handler in handlers {
invoke_safely(&handler, value());
}
}
}
/// C# `PacketReceivedEventArgs` with shared simulator identity.
#[derive(Clone)]
pub struct PacketReceivedEventArgs {
packet: Packet,
simulator: Simulator,
raw_data: Option<Vec<u8>>,
}
#[derive(Clone)]
pub(crate) struct RawPacketReceivedEventArgs {
pub packet_type: PacketType,
pub data: Vec<u8>,
pub simulator: Simulator,
}
impl PacketReceivedEventArgs {
pub fn new(packet: Packet, simulator: Simulator) -> Result<Self, Error> {
Self::new_with_raw_data(packet, simulator, None)
}
pub fn new_with_raw_data(
packet: Packet,
simulator: Simulator,
raw_data: Option<Vec<u8>>,
) -> Result<Self, Error> {
Ok(Self {
packet,
simulator,
raw_data,
})
}
#[must_use]
pub fn packet(&self) -> Packet {
self.packet.clone()
}
#[must_use]
pub fn simulator(&self) -> Simulator {
self.simulator.clone()
}
/// Returns the original datagram when this event came from the native UDP
/// receive path. Synthetic decoded events intentionally return `None`.
#[must_use]
pub fn raw_data(&self) -> Option<Vec<u8>> {
self.raw_data.clone()
}
}
#[derive(Clone)]
pub struct PacketSentEventArgs {
data: Vec<u8>,
sent_bytes: i32,
simulator: Simulator,
}
impl PacketSentEventArgs {
pub fn new(data: Vec<u8>, sent_bytes: i32, simulator: Simulator) -> Result<Self, Error> {
let sent = usize::try_from(sent_bytes).map_err(|_| Error::Argument)?;
if sent > data.len() {
return Err(Error::Argument);
}
Ok(Self {
data,
sent_bytes,
simulator,
})
}
#[must_use]
pub fn data(&self) -> Vec<u8> {
self.data.clone()
}
#[must_use]
pub const fn sent_bytes(&self) -> i32 {
self.sent_bytes
}
#[must_use]
pub fn simulator(&self) -> Simulator {
self.simulator.clone()
}
}
#[derive(Clone)]
pub struct SimChangedEventArgs {
previous_simulator: Option<Simulator>,
}
/// Inventory item identifiers returned by a successful logout handshake.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LoggedOutEventArgs {
pub inventory_items: Vec<UUID>,
}
impl LoggedOutEventArgs {
pub const fn new(inventory_items: Vec<UUID>) -> Result<Self, Error> {
Ok(Self { inventory_items })
}
}
impl SimChangedEventArgs {
pub fn new(previous_simulator: Option<Simulator>) -> Result<Self, Error> {
Ok(Self { previous_simulator })
}
#[must_use]
pub fn previous_simulator(&self) -> Option<Simulator> {
self.previous_simulator.clone()
}
}
#[derive(Clone)]
pub struct SimConnectedEventArgs {
simulator: Simulator,
}
/// Event arguments raised when a simulator's `EventQueueGet` poller is live.
#[derive(Clone)]
pub struct EventQueueRunningEventArgs {
simulator: Simulator,
}
impl EventQueueRunningEventArgs {
pub const fn new(simulator: Simulator) -> Result<Self, Error> {
Ok(Self { simulator })
}
#[must_use]
pub fn simulator(&self) -> Simulator {
self.simulator.clone()
}
}
impl SimConnectedEventArgs {
pub fn new(simulator: Simulator) -> Result<Self, Error> {
Ok(Self { simulator })
}
#[must_use]
pub fn simulator(&self) -> Simulator {
self.simulator.clone()
}
}
#[derive(Clone)]
pub struct SimConnectingEventArgs {
simulator: Simulator,
cancel: Arc<AtomicBool>,
}
impl SimConnectingEventArgs {
pub fn new(simulator: Simulator) -> Result<Self, Error> {
Ok(Self {
simulator,
cancel: Arc::new(AtomicBool::new(false)),
})
}
#[must_use]
pub fn cancel(&self) -> bool {
self.cancel.load(Ordering::Acquire)
}
pub fn set_cancel(&mut self, value: bool) {
self.cancel.store(value, Ordering::Release);
}
#[must_use]
pub fn simulator(&self) -> Simulator {
self.simulator.clone()
}
}
#[derive(Clone)]
pub struct SimDisconnectedEventArgs {
simulator: Simulator,
reason: crate::NetworkManagerDisconnectType,
was_connected: bool,
}
impl SimDisconnectedEventArgs {
pub fn new(
simulator: Simulator,
reason: crate::NetworkManagerDisconnectType,
was_connected: bool,
) -> Result<Self, Error> {
Ok(Self {
simulator,
reason,
was_connected,
})
}
#[must_use]
pub const fn reason(&self) -> crate::NetworkManagerDisconnectType {
self.reason
}
#[must_use]
pub fn simulator(&self) -> Simulator {
self.simulator.clone()
}
#[must_use]
pub const fn was_connected(&self) -> bool {
self.was_connected
}
}
#[derive(Clone)]
pub struct DisconnectedEventArgs {
reason: crate::NetworkManagerDisconnectType,
message: String,
}
impl DisconnectedEventArgs {
pub fn new(
reason: crate::NetworkManagerDisconnectType,
message: String,
) -> Result<Self, Error> {
Ok(Self { reason, message })
}
#[must_use]
pub fn message(&self) -> String {
self.message.clone()
}
#[must_use]
pub const fn reason(&self) -> crate::NetworkManagerDisconnectType {
self.reason
}
}
#[derive(Clone)]
pub struct GenericStreamingMessageEventArgs {
simulator: Simulator,
method: GenericStreamingMethod,
data: Vec<u8>,
}
impl GenericStreamingMessageEventArgs {
pub fn new(
simulator: Simulator,
method: GenericStreamingMethod,
data: Vec<u8>,
) -> Result<Self, Error> {
Ok(Self {
simulator,
method,
data,
})
}
#[must_use]
pub fn data(&self) -> Vec<u8> {
self.data.clone()
}
#[must_use]
pub const fn method(&self) -> GenericStreamingMethod {
self.method
}
#[must_use]
pub fn simulator(&self) -> Simulator {
self.simulator.clone()
}
}
#[derive(Clone, Default)]
struct RegisteredPacketCallbacks {
handlers: Vec<EventHandler<PacketReceivedEventArgs>>,
is_async: bool,
}
struct AsyncPacketBatch {
default_handlers: Vec<EventHandler<PacketReceivedEventArgs>>,
specific_handlers: Vec<EventHandler<PacketReceivedEventArgs>>,
packet: Packet,
simulator: Simulator,
raw_data: Option<Vec<u8>>,
}
enum PacketWorkerCommand {
Dispatch(AsyncPacketBatch),
Stop,
}
struct PacketEventState {
callbacks: Mutex<HashMap<PacketType, RegisteredPacketCallbacks>>,
async_sender: SyncSender<PacketWorkerCommand>,
worker: Mutex<Option<JoinHandle<()>>>,
}
impl Drop for PacketEventState {
fn drop(&mut self) {
let _ = self.async_sender.try_send(PacketWorkerCommand::Stop);
if let Some(worker) = self
.worker
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take()
&& worker.thread().id() != thread::current().id()
{
let _ = worker.join();
}
}
}
/// Ordered packet callback registry matching `PacketEventDictionary`.
pub struct PacketEventDictionary {
pub client: GridClient,
state: Arc<PacketEventState>,
}
impl PacketEventDictionary {
pub fn new(client: GridClient) -> Result<Self, Error> {
let (sender, receiver) = sync_channel(256);
let state = Arc::new(PacketEventState {
callbacks: Mutex::new(HashMap::new()),
async_sender: sender,
worker: Mutex::new(None),
});
let weak = Arc::downgrade(&state);
let worker = thread::Builder::new()
.name("libremetaverse-packet-events".to_owned())
.spawn(move || packet_event_worker(receiver, weak))
.map_err(|_| Error::InvalidOperation)?;
*mutex(&state.worker) = Some(worker);
Ok(Self { client, state })
}
pub fn register_event(
&self,
packet_type: PacketType,
event_handler: EventHandler<PacketReceivedEventArgs>,
is_async: bool,
) -> Result<(), Error> {
let mut table = mutex(&self.state.callbacks);
let callbacks = table.entry(packet_type).or_default();
callbacks.is_async |= is_async;
callbacks.handlers.push(event_handler);
Ok(())
}
pub fn subscribe(
&self,
packet_type: PacketType,
event_handler: EventHandler<PacketReceivedEventArgs>,
is_async: bool,
) -> Subscription {
let identity = Arc::as_ptr(&event_handler).cast::<()>() as usize;
let _ = self.register_event(packet_type, event_handler, is_async);
let state = Arc::downgrade(&self.state);
Subscription::new(move || {
let Some(state) = state.upgrade() else {
return;
};
let mut table = mutex(&state.callbacks);
let Some(callbacks) = table.get_mut(&packet_type) else {
return;
};
if let Some(index) = callbacks
.handlers
.iter()
.rposition(|handler| Arc::as_ptr(handler).cast::<()>() as usize == identity)
{
callbacks.handlers.remove(index);
}
if callbacks.handlers.is_empty() {
table.remove(&packet_type);
}
})
}
pub fn unregister_event(
&self,
packet_type: PacketType,
event_handler: EventHandler<PacketReceivedEventArgs>,
) -> Result<(), Error> {
let mut table = mutex(&self.state.callbacks);
let Some(callbacks) = table.get_mut(&packet_type) else {
return Ok(());
};
if let Some(index) = callbacks
.handlers
.iter()
.rposition(|handler| Arc::ptr_eq(handler, &event_handler))
{
callbacks.handlers.remove(index);
}
if callbacks.handlers.is_empty() {
table.remove(&packet_type);
}
Ok(())
}
pub fn invoke_raise_event(
&self,
packet_type: PacketType,
packet: Packet,
simulator: Simulator,
) -> Result<(), Error> {
self.raise_event(packet_type, packet, simulator, None)
}
/// Dispatches a decoded packet together with its original wire datagram.
/// This is primarily useful to deterministic packet-capture fixtures.
pub fn invoke_raise_event_with_raw_data(
&self,
packet_type: PacketType,
packet: Packet,
simulator: Simulator,
raw_data: Vec<u8>,
) -> Result<(), Error> {
self.raise_event(packet_type, packet, simulator, Some(raw_data))
}
fn raise_event(
&self,
packet_type: PacketType,
packet: Packet,
simulator: Simulator,
raw_data: Option<Vec<u8>>,
) -> Result<(), Error> {
let (defaults, specific) = {
let table = mutex(&self.state.callbacks);
(
table.get(&PacketType::Default).cloned().unwrap_or_default(),
table.get(&packet_type).cloned().unwrap_or_default(),
)
};
let default_async = defaults.is_async;
let specific_async = specific.is_async;
let args = PacketReceivedEventArgs::new_with_raw_data(
packet.clone(),
simulator.clone(),
raw_data.clone(),
)?;
if !default_async {
for handler in &defaults.handlers {
invoke_safely(handler, args.clone());
}
}
if !specific_async {
for handler in &specific.handlers {
invoke_safely(handler, args.clone());
}
// The reference implementation returns as soon as a concrete
// packet type has synchronous handlers. This also deliberately
// suppresses a default asynchronous chain for that dispatch.
if !specific.handlers.is_empty() {
return Ok(());
}
}
let default_handlers: Vec<EventHandler<PacketReceivedEventArgs>> = if default_async {
defaults.handlers
} else {
Vec::new()
};
let specific_handlers: Vec<EventHandler<PacketReceivedEventArgs>> = if specific_async {
specific.handlers
} else {
Vec::new()
};
if default_handlers.is_empty() && specific_handlers.is_empty() {
return Ok(());
}
self.state
.async_sender
.try_send(PacketWorkerCommand::Dispatch(AsyncPacketBatch {
default_handlers,
specific_handlers,
packet,
simulator,
raw_data,
}))
.map_err(|_| Error::InvalidOperation)
}
}
fn packet_event_worker(receiver: Receiver<PacketWorkerCommand>, state: Weak<PacketEventState>) {
while state.strong_count() != 0 {
match receiver.recv_timeout(Duration::from_millis(100)) {
Ok(PacketWorkerCommand::Dispatch(batch)) => {
let Ok(args) = PacketReceivedEventArgs::new_with_raw_data(
batch.packet,
batch.simulator,
batch.raw_data,
) else {
continue;
};
for handler in batch
.default_handlers
.into_iter()
.chain(batch.specific_handlers)
{
invoke_safely(&handler, args.clone());
}
}
Ok(PacketWorkerCommand::Stop) | Err(RecvTimeoutError::Disconnected) => break,
Err(RecvTimeoutError::Timeout) => {}
}
}
}
type CapsHandler = dyn Fn(&str, &dyn IMessage, Simulator) + Send + Sync;
#[derive(Clone)]
pub struct CapsEventQueueCallback {
id: u64,
handler: Arc<CapsHandler>,
}
impl CapsEventQueueCallback {
pub fn from_handler(
handler: impl Fn(&str, &dyn IMessage, Simulator) + Send + Sync + 'static,
) -> Self {
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
Self {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
handler: Arc::new(handler),
}
}
pub fn new(_object: Object, _method: isize) -> Result<Self, Error> {
Err(Error::InvalidOperation)
}
pub fn begin_invoke(
&self,
caps_key: String,
message: Box<dyn IMessage>,
simulator: Simulator,
callback: Box<dyn Fn(&dyn std::any::Any) + Send + Sync>,
object: Object,
) -> Result<Box<dyn std::any::Any + Send + Sync>, Error> {
(self.handler)(&caps_key, message.as_ref(), simulator);
callback(&object);
Ok(Box::new(object))
}
pub fn end_invoke(&self, _result: Box<dyn std::any::Any + Send + Sync>) -> Result<(), Error> {
Ok(())
}
pub fn invoke(
&self,
caps_key: String,
message: Box<dyn IMessage>,
simulator: Simulator,
) -> Result<(), Error> {
(self.handler)(&caps_key, message.as_ref(), simulator);
Ok(())
}
fn invoke_ref(&self, caps_key: &str, message: &dyn IMessage, simulator: Simulator) {
let _ = catch_unwind(AssertUnwindSafe(|| {
(self.handler)(caps_key, message, simulator);
}));
}
}
pub struct CapsEventDictionary {
pub client: GridClient,
callbacks: Arc<Mutex<HashMap<String, Vec<CapsEventQueueCallback>>>>,
}
impl CapsEventDictionary {
pub fn new(client: GridClient) -> Result<Self, Error> {
Ok(Self {
client,
callbacks: Arc::new(Mutex::new(HashMap::new())),
})
}
pub fn register_event(
&self,
caps_event: String,
event_handler: CapsEventQueueCallback,
) -> Result<(), Error> {
mutex(&self.callbacks)
.entry(caps_event)
.or_default()
.push(event_handler);
Ok(())
}
pub fn subscribe(
&self,
caps_event: String,
event_handler: CapsEventQueueCallback,
) -> Subscription {
let id = event_handler.id;
let _ = self.register_event(caps_event.clone(), event_handler);
let callbacks = Arc::downgrade(&self.callbacks);
Subscription::new(move || {
let Some(callbacks) = callbacks.upgrade() else {
return;
};
let mut table = mutex(&callbacks);
let Some(entries) = table.get_mut(&caps_event) else {
return;
};
entries.retain(|entry| entry.id != id);
if entries.is_empty() {
table.remove(&caps_event);
}
})
}
pub fn unregister_event(
&self,
caps_event: String,
event_handler: CapsEventQueueCallback,
) -> Result<(), Error> {
let mut table = mutex(&self.callbacks);
if let Some(entries) = table.get_mut(&caps_event) {
if let Some(index) = entries
.iter()
.rposition(|entry| entry.id == event_handler.id)
{
entries.remove(index);
}
if entries.is_empty() {
table.remove(&caps_event);
}
}
Ok(())
}
pub fn invoke_raise_event(
&self,
caps_event: &str,
message: &dyn IMessage,
simulator: Simulator,
) {
let (defaults, specific) = {
let table = mutex(&self.callbacks);
(
table.get("").cloned().unwrap_or_default(),
table.get(caps_event).cloned().unwrap_or_default(),
)
};
for callback in defaults.into_iter().chain(specific) {
callback.invoke_ref(caps_event, message, simulator.clone());
}
}
}
/// Mutable, thread-safe equivalent of the C# public simulator list.
#[derive(Clone, Default)]
pub struct SimulatorCollection(Arc<RwLock<Vec<Simulator>>>);
impl SimulatorCollection {
#[must_use]
pub fn snapshot(&self) -> Vec<Simulator> {
read(&self.0).clone()
}
#[must_use]
pub fn len(&self) -> usize {
read(&self.0).len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn push_if_missing(&self, simulator: Simulator) -> Simulator {
let mut simulators = write(&self.0);
if let Some(existing) = simulators
.iter()
.find(|existing| existing.ip_end_point() == simulator.ip_end_point())
{
return existing.clone();
}
simulators.push(simulator.clone());
simulator
}
fn remove(&self, simulator: &Simulator) -> bool {
let mut simulators = write(&self.0);
let old_len = simulators.len();
simulators.retain(|candidate| candidate != simulator);
old_len != simulators.len()
}
fn drain(&self) -> Vec<Simulator> {
std::mem::take(&mut *write(&self.0))
}
}
impl fmt::Debug for SimulatorCollection {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_list()
.entries(self.snapshot().iter())
.finish()
}
}
struct TransportThread {
shutdown: libremetaverse_types::compat::CancellationTokenSource,
handle: JoinHandle<()>,
}
/// Shared data exposed through the C# `Simulator` field surface.
pub struct SimulatorData {
pub access: SimAccess,
pub agent_movement_complete: bool,
pub billable_factor: f32,
pub cpu_class: i32,
pub cpu_ratio: i32,
pub client: GridClient,
pub colo_location: String,
pub data_pool: Option<SimulatorDataPool>,
pub features: SimulatorFeatures,
pub flags: RegionFlags,
pub global_to_local_id: RwLock<HashMap<UUID, u32>>,
pub handle: u64,
pub id: UUID,
pub is_estate_manager: bool,
pub name: String,
pub objects_avatars: RwLock<HashMap<u32, Avatar>>,
pub objects_primitives: RwLock<HashMap<u32, Primitive>>,
pub parcel_overlay: RwLock<Vec<u8>>,
pub parcel_overlays_received: AtomicI32,
parcel_overlay_segments: AtomicU8,
pub parcels: RwLock<HashMap<i32, Parcel>>,
pub parcel_map: RwLock<Vec<i32>>,
pub product_name: String,
pub product_sku: String,
pub protocols: RegionProtocols,
pub region_id: UUID,
pub sequence: i32,
pub sim_owner: UUID,
pub sim_version: String,
pub size_x: u32,
pub size_y: u32,
pub stats: SimulatorSimStats,
pub terrain: RwLock<Vec<TerrainPatch>>,
pub terrain_base0: UUID,
pub terrain_base1: UUID,
pub terrain_base2: UUID,
pub terrain_base3: UUID,
pub terrain_detail0: UUID,
pub terrain_detail1: UUID,
pub terrain_detail2: UUID,
pub terrain_detail3: UUID,
pub terrain_height_range00: f32,
pub terrain_height_range01: f32,
pub terrain_height_range10: f32,
pub terrain_height_range11: f32,
pub terrain_start_height00: f32,
pub terrain_start_height01: f32,
pub terrain_start_height10: f32,
pub terrain_start_height11: f32,
pub water_height: f32,
pub wind_speeds: RwLock<Option<Vec<Vector2>>>,
prey_id: RwLock<UUID>,
endpoint: std::net::SocketAddr,
connected: AtomicBool,
movement_complete: AtomicBool,
handshake_complete: AtomicBool,
handshake_wait: Condvar,
handshake_wait_lock: Mutex<()>,
disconnect_candidate: AtomicBool,
circuit_code: AtomicU32,
agent_id: RwLock<UUID>,
session_id: RwLock<UUID>,
seed_caps: RwLock<Option<Uri>>,
caps_state: RwLock<Option<Arc<crate::caps::CapsInner>>>,
event_queue_generation: Mutex<u64>,
event_queue_wait: Condvar,
manager: Mutex<Weak<NetworkManagerInner>>,
transport: UDPBase,
transport_thread: Mutex<Option<TransportThread>>,
pause_serial: AtomicU32,
}
impl Drop for SimulatorData {
fn drop(&mut self) {
if let Some(caps) = self
.caps_state
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take()
{
caps.disconnect(true);
}
let runtime = self
.transport_thread
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take();
if let Some(runtime) = runtime {
runtime.shutdown.cancel();
if runtime.handle.thread().id() != thread::current().id() {
let _ = runtime.handle.join();
}
}
}
}
/// Reference-identity simulator handle.
pub struct Simulator {
/// Capability client associated with this simulator.
///
/// This remains on the value surface, rather than behind `Deref`, because
/// existing translated code consumes the public C# field by value.
pub caps: Option<Box<Caps>>,
data: Arc<SimulatorData>,
}
impl Clone for Simulator {
fn clone(&self) -> Self {
let mut clone = self.native_clone_without_caps();
if let Some(inner) = read(&self.data.caps_state).clone() {
clone.caps = Some(Box::new(Caps::native_from_inner(
inner,
clone.native_clone_without_caps(),
)));
}
clone
}
}
impl std::ops::Deref for Simulator {
type Target = SimulatorData;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl PartialEq for Simulator {
fn eq(&self, other: &Self) -> bool {
self.ip_end_point() == other.ip_end_point()
}
}
impl Eq for Simulator {}
impl fmt::Debug for Simulator {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Simulator")
.field("endpoint", &self.native_ip_end_point())
.field("handle", &self.handle)
.field("connected", &self.native_is_connected())
.field("handshake_complete", &self.native_handshake_complete())
.finish_non_exhaustive()
}
}
impl Simulator {
pub(crate) fn native_prey_id(&self) -> UUID {
*read(&self.data.prey_id)
}
pub(crate) fn native_set_prey_id(&self, value: UUID) {
*write(&self.data.prey_id) = value;
}
pub fn native_new(
client: GridClient,
address: std::net::SocketAddr,
handle: u64,
size_x: Option<u32>,
size_y: Option<u32>,
) -> Result<Self, Error> {
let cancellation = client.cancellation_token();
let simulator_reference = Arc::new(Mutex::new(Weak::new()));
let handler: Arc<dyn UdpPacketHandler> = Arc::new(SimulatorUdpHandler {
simulator: Arc::clone(&simulator_reference),
});
let transport = UDPBase::client(
address,
UdpTransportConfig::default(),
handler,
cancellation,
)?;
let data = Arc::new(SimulatorData {
access: SimAccess::UNKNOWN,
agent_movement_complete: false,
billable_factor: 0.0,
cpu_class: 0,
cpu_ratio: 0,
client,
colo_location: String::new(),
data_pool: None,
features: SimulatorFeatures::default(),
flags: RegionFlags(0),
global_to_local_id: RwLock::new(HashMap::new()),
handle,
id: UUID::zero(),
is_estate_manager: false,
name: String::new(),
objects_avatars: RwLock::new(HashMap::new()),
objects_primitives: RwLock::new(HashMap::new()),
parcel_overlay: RwLock::new(vec![0; 4096]),
parcel_overlays_received: AtomicI32::new(0),
parcel_overlay_segments: AtomicU8::new(0),
parcels: RwLock::new(HashMap::new()),
parcel_map: RwLock::new(vec![0; 4096]),
product_name: String::new(),
product_sku: String::new(),
protocols: RegionProtocols(0),
region_id: UUID::zero(),
sequence: 0,
sim_owner: UUID::zero(),
sim_version: String::new(),
size_x: size_x.unwrap_or(Self::DEFAULT_REGION_SIZE_X),
size_y: size_y.unwrap_or(Self::DEFAULT_REGION_SIZE_Y),
stats: SimulatorSimStats::default(),
terrain: RwLock::new(Vec::new()),
terrain_base0: UUID::zero(),
terrain_base1: UUID::zero(),
terrain_base2: UUID::zero(),
terrain_base3: UUID::zero(),
terrain_detail0: UUID::zero(),
terrain_detail1: UUID::zero(),
terrain_detail2: UUID::zero(),
terrain_detail3: UUID::zero(),
terrain_height_range00: 0.0,
terrain_height_range01: 0.0,
terrain_height_range10: 0.0,
terrain_height_range11: 0.0,
terrain_start_height00: 0.0,
terrain_start_height01: 0.0,
terrain_start_height10: 0.0,
terrain_start_height11: 0.0,
water_height: 0.0,
wind_speeds: RwLock::new(None),
prey_id: RwLock::new(UUID::zero()),
endpoint: address,
connected: AtomicBool::new(false),
movement_complete: AtomicBool::new(false),
handshake_complete: AtomicBool::new(false),
handshake_wait: Condvar::new(),
handshake_wait_lock: Mutex::new(()),
disconnect_candidate: AtomicBool::new(false),
circuit_code: AtomicU32::new(0),
agent_id: RwLock::new(UUID::zero()),
session_id: RwLock::new(UUID::zero()),
seed_caps: RwLock::new(None),
caps_state: RwLock::new(None),
event_queue_generation: Mutex::new(0),
event_queue_wait: Condvar::new(),
manager: Mutex::new(Weak::new()),
transport,
transport_thread: Mutex::new(None),
pause_serial: AtomicU32::new(0),
});
*mutex(&simulator_reference) = Arc::downgrade(&data);
Ok(Self { caps: None, data })
}
pub(crate) fn native_clone_without_caps(&self) -> Self {
Self {
caps: None,
data: Arc::clone(&self.data),
}
}
pub(crate) fn native_data_weak(&self) -> Weak<SimulatorData> {
Arc::downgrade(&self.data)
}
pub(crate) fn native_data_arc(&self) -> Arc<SimulatorData> {
Arc::clone(&self.data)
}
pub(crate) fn native_caps(&self) -> Option<Caps> {
read(&self.data.caps_state)
.clone()
.map(|inner| Caps::native_from_inner(inner, self.native_clone_without_caps()))
}
/// Looks up one capability without exposing the simulator's mutable caps state.
///
/// Capability URLs are bearer credentials. Callers must not log or persist
/// the returned URI.
pub fn native_capability_uri(&self, capability: &str) -> Result<Option<Uri>, Error> {
let Some(caps) = self.native_caps() else {
return Ok(None);
};
caps.capability_uri(capability.to_owned())
}
pub(crate) fn native_id(&self) -> UUID {
self.data.id
}
pub(crate) fn native_store_terrain_patch(
&self,
patch: TerrainPatch,
large_region: bool,
) -> Result<(), Error> {
let (width, height) = if large_region {
(
usize::try_from(self.size_x.max(16) / 16).map_err(|_| Error::Argument)?,
usize::try_from(self.size_y.max(16) / 16).map_err(|_| Error::Argument)?,
)
} else {
(16, 16)
};
let x = usize::try_from(patch.x).map_err(|_| Error::Argument)?;
let y = usize::try_from(patch.y).map_err(|_| Error::Argument)?;
if x >= width || y >= height || width > 256 || height > 256 {
return Err(Error::Argument);
}
let index = y
.checked_mul(width)
.and_then(|value| value.checked_add(x))
.ok_or(Error::Argument)?;
let patch_count = width.checked_mul(height).ok_or(Error::Argument)?;
let mut terrain = write(&self.terrain);
terrain.resize_with(patch_count, || TerrainPatch {
data: Vec::new(),
x: 0,
y: 0,
});
terrain[index] = patch;
Ok(())
}
pub(crate) fn native_store_wind(&self, values: Vec<Vector2>) {
*write(&self.wind_speeds) = Some(values);
}
pub(crate) fn native_apply_parcel_overlay(
&self,
sequence_id: i32,
data: Vec<u8>,
) -> Result<bool, Error> {
if !(0..4).contains(&sequence_id) {
return Err(Error::Argument);
}
let mut overlay = write(&self.parcel_overlay);
let segment_size = overlay.len().checked_div(4).ok_or(Error::Argument)?;
if data.len() != segment_size {
return Err(Error::Argument);
}
let sequence = usize::try_from(sequence_id).map_err(|_| Error::Argument)?;
let offset = sequence.checked_mul(segment_size).ok_or(Error::Argument)?;
let end = offset.checked_add(segment_size).ok_or(Error::Argument)?;
overlay[offset..end].copy_from_slice(&data);
// The overlay may be retransmitted or arrive out of order. Count unique
// sequence IDs so duplicates cannot publish an incomplete map.
let bit = 1_u8 << sequence;
let old_segments = self.parcel_overlay_segments.load(Ordering::Acquire);
let segments = old_segments | bit;
self.parcel_overlay_segments
.store(segments, Ordering::Release);
let received = i32::try_from(segments.count_ones()).map_err(|_| Error::Argument)?;
self.parcel_overlays_received
.store(received, Ordering::Release);
if segments == 0b1111 {
self.parcel_overlay_segments.store(0, Ordering::Release);
self.parcel_overlays_received.store(0, Ordering::Release);
Ok(true)
} else {
Ok(false)
}
}
pub(crate) fn native_store_parcel(&self, parcel: Parcel) {
if parcel.bitmap.len() == 512 {
let mut map = write(&self.parcel_map);
for cell in 0..4096 {
if parcel.bitmap[cell / 8] & (1 << (cell % 8)) != 0 {
map[cell] = parcel.local_id;
}
}
}
write(&self.parcels).insert(parcel.local_id, parcel);
}
pub(crate) fn native_parcel(&self, local_id: i32) -> Option<Parcel> {
read(&self.parcels).get(&local_id).cloned()
}
pub(crate) fn native_parcel_map_at(&self, x: usize, y: usize) -> Option<i32> {
if x >= 64 || y >= 64 {
return None;
}
read(&self.parcel_map).get(y * 64 + x).copied()
}
pub(crate) fn native_parcels_snapshot(&self) -> HashMap<i32, Parcel> {
read(&self.parcels).clone()
}
pub(crate) fn native_parcel_map_snapshot(&self) -> Vec<i32> {
read(&self.parcel_map).clone()
}
pub(crate) fn native_is_parcel_map_full(&self) -> bool {
read(&self.parcel_map).iter().all(|value| *value > 0)
}
pub(crate) fn native_terrain_height_at_point(
&self,
x: i32,
y: i32,
height: &mut f32,
) -> Result<bool, Error> {
let (Ok(x), Ok(y)) = (u32::try_from(x), u32::try_from(y)) else {
return Ok(false);
};
if x >= self.size_x || y >= self.size_y {
return Ok(false);
}
let per_edge = usize::try_from(self.size_x.max(16) / 16).map_err(|_| Error::Argument)?;
let patch_x = usize::try_from(x / 16).map_err(|_| Error::Argument)?;
let patch_y = usize::try_from(y / 16).map_err(|_| Error::Argument)?;
let point = usize::try_from((y % 16) * 16 + (x % 16)).map_err(|_| Error::Argument)?;
let terrain = read(&self.terrain);
let Some(patch) = terrain.get(patch_y * per_edge + patch_x) else {
return Ok(false);
};
let Some(value) = patch.data.get(point) else {
return Ok(false);
};
*height = *value;
Ok(true)
}
pub(crate) fn native_clear_parcels(&self) {
write(&self.parcels).clear();
write(&self.parcel_map).fill(0);
}
pub(crate) fn native_update_parcel_dwell(&self, local_id: i32, dwell: f32) {
if let Some(parcel) = write(&self.parcels).get_mut(&local_id) {
parcel.dwell = dwell;
}
}
pub(crate) fn native_update_parcel_access(
&self,
local_id: i32,
flags: u32,
entries: Vec<crate::ParcelManagerParcelAccessEntry>,
) {
if let Some(parcel) = write(&self.parcels).get_mut(&local_id) {
if flags & crate::AccessList::BAN.0 != 0 {
parcel.access_black_list = entries;
} else {
parcel.access_white_list = entries;
}
}
}
pub(crate) fn native_send_parcel_clean_time(
&self,
local_id: i32,
clean_time: i32,
) -> Result<(), Error> {
let mut packet = crate::packets::ParcelSetOtherCleanTimePacket::new_with_constructor()?;
packet.agent_data.agent_id = *read(&self.data.agent_id);
packet.agent_data.session_id = *read(&self.data.session_id);
packet.parcel_data.local_id = local_id;
packet.parcel_data.other_clean_time = clean_time;
let data = packet.to_bytes_with_method()?;
self.native_send_packet_data(
data.clone(),
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
PacketType::ParcelSetOtherCleanTime,
false,
)
}
pub(crate) fn native_from_weak(data: &Weak<SimulatorData>) -> Option<Self> {
data.upgrade().map(|data| Self { caps: None, data })
}
pub(crate) fn native_from_data(data: Arc<SimulatorData>) -> Self {
Self { caps: None, data }
}
fn attach_manager(&self, manager: &Arc<NetworkManagerInner>, circuit_code: u32) {
*mutex(&self.manager) = Arc::downgrade(manager);
self.circuit_code.store(circuit_code, Ordering::Release);
*write(&self.agent_id) = *read(&manager.agent_id);
*write(&self.session_id) = *read(&manager.session_id);
}
pub fn native_connect(&self, move_to_sim: bool) -> Result<bool, Error> {
if self.native_is_connected() {
self.native_use_circuit_code(true)?;
if move_to_sim {
self.native_complete_agent_movement()?;
}
return Ok(true);
}
let mut transport_thread = mutex(&self.transport_thread);
if transport_thread.is_none() {
let shutdown = libremetaverse_types::compat::CancellationTokenSource::default();
let shutdown_token = shutdown.token();
let transport = self.transport.clone();
let (ready_sender, ready_receiver) = sync_channel(1);
let handle = thread::Builder::new()
.name(format!("libremetaverse-udp-{}", self.endpoint))
.spawn(move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build();
let Ok(runtime) = runtime else {
let _ =
ready_sender.send(Err(crate::UdpTransportError::RuntimeUnavailable));
return;
};
runtime.block_on(async move {
let result = transport.start_transport();
let started = result.is_ok();
let _ = ready_sender.send(result);
if started {
shutdown_token.cancelled().await;
transport.stop_async().await;
}
});
})
.map_err(|_| Error::InvalidOperation)?;
let timeout =
positive_millisecond_duration(self.client.settings_ref().timing.login_timeout);
match ready_receiver.recv_timeout(timeout) {
Ok(Ok(())) => {
*transport_thread = Some(TransportThread { shutdown, handle });
}
Ok(Err(error)) => {
let _ = handle.join();
return Err(error.into());
}
Err(_) => {
shutdown.cancel();
let _ = handle.join();
return Ok(false);
}
}
}
self.connected.store(true, Ordering::Release);
self.stats.set_connect_time(environment_tick_count())?;
self.handshake_complete.store(false, Ordering::Release);
drop(transport_thread);
self.native_use_circuit_code(true)?;
if move_to_sim {
self.native_complete_agent_movement()?;
}
let timeout =
positive_millisecond_duration(self.client.settings_ref().timing.login_timeout);
let guard = mutex(&self.handshake_wait_lock);
let (guard, _) = self
.handshake_wait
.wait_timeout_while(guard, timeout, |()| {
!self.handshake_complete.load(Ordering::Acquire)
&& !self.client.cancellation_token().is_cancellation_requested()
})
.unwrap_or_else(std::sync::PoisonError::into_inner);
drop(guard);
if !self.handshake_complete.load(Ordering::Acquire)
&& let Some(manager) = mutex(&self.manager).upgrade()
{
manager.simulators.remove(self);
}
Ok(true)
}
pub async fn native_connect_async(&self, move_to_sim: bool) -> Result<bool, Error> {
let simulator = self.clone();
run_blocking_compat("libremetaverse-simulator-connect", move || {
simulator.native_connect(move_to_sim)
})
.await?
}
pub fn native_disconnect(&self, send_close_circuit: bool) -> Result<(), Error> {
if let Some(caps) = write(&self.caps_state).take() {
caps.disconnect(true);
}
*write(&self.seed_caps) = None;
self.disconnect_candidate.store(false, Ordering::Release);
let was_connected = self.connected.swap(false, Ordering::AcqRel);
self.handshake_complete.store(false, Ordering::Release);
self.handshake_wait.notify_all();
if !was_connected {
return Ok(());
}
if send_close_circuit {
let close = CloseCircuitPacket::new_with_constructor()?;
let data = close.to_bytes_with_method()?;
let _ = self.native_send_packet_data(
data.clone(),
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
PacketType::CloseCircuit,
false,
);
}
if let Some(runtime) = mutex(&self.transport_thread).take() {
runtime.shutdown.cancel();
if runtime.handle.thread().id() != thread::current().id() {
let _ = runtime.handle.join();
}
}
Ok(())
}
pub fn native_dispose(&self) -> Result<(), Error> {
self.native_disconnect(false)
}
#[must_use]
pub fn native_equals(&self, obj: Option<Object>) -> bool {
obj.and_then(|value| value.downcast_arc::<Simulator>())
.is_some_and(|other| *self == *other)
}
#[must_use]
pub fn native_hash_code(&self) -> i32 {
let folded = self.handle ^ (self.handle >> 32);
let low = u32::try_from(folded & u64::from(u32::MAX)).unwrap_or_default();
low.cast_signed()
}
pub fn native_pause(&self) -> Result<(), Error> {
let mut packet = AgentPausePacket::new_with_constructor()?;
packet.agent_data.agent_id = UUID::zero();
packet.agent_data.session_id = UUID::zero();
packet.agent_data.serial_num = self.pause_serial.fetch_add(1, Ordering::AcqRel);
let data = packet.to_bytes_with_method()?;
self.native_send_packet_data(
data.clone(),
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
PacketType::AgentPause,
false,
)
}
pub fn native_resume(&self) -> Result<(), Error> {
let mut packet = AgentResumePacket::new_with_constructor()?;
packet.agent_data.agent_id = UUID::zero();
packet.agent_data.session_id = UUID::zero();
packet.agent_data.serial_num = self.pause_serial.fetch_add(1, Ordering::AcqRel);
let data = packet.to_bytes_with_method()?;
self.native_send_packet_data(
data.clone(),
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
PacketType::AgentResume,
false,
)
}
pub fn native_send_packet(&self, packet: Packet) -> Result<(), Error> {
let data = crate::packet_wire::encode_base_packet(&packet)?;
self.native_send_packet_data(
data.clone(),
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
packet.type_,
packet.header.zerocoded,
)
}
pub fn native_send_packet_data(
&self,
data: Vec<u8>,
data_length: i32,
packet_type: PacketType,
do_zerocode: bool,
) -> Result<(), Error> {
let length = usize::try_from(data_length).map_err(|_| Error::Argument)?;
let payload = data.get(..length).ok_or(Error::Argument)?.to_vec();
self.transport
.try_send_packet(payload, self.endpoint, packet_type, do_zerocode)
.map(|_| ())
.map_err(Into::into)
}
pub fn native_send_ping(&self) -> Result<(), Error> {
let mut ping = StartPingCheckPacket::new_with_constructor()?;
ping.ping_id.ping_id = self.stats.get_and_increment_last_ping_id()?.to_le_bytes()[0];
ping.ping_id.oldest_unacked = 0;
self.stats.set_last_ping_sent(environment_tick_count())?;
let data = ping.to_bytes_with_method()?;
self.native_send_packet_data(
data.clone(),
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
PacketType::StartPingCheck,
false,
)
}
pub fn native_set_seed_caps(
&self,
seed_caps: Option<Uri>,
changed_sim: Option<bool>,
) -> Result<(), Error> {
if !changed_sim.unwrap_or(false) && *read(&self.seed_caps) == seed_caps {
return Ok(());
}
if let Some(previous) = write(&self.caps_state).take() {
previous.disconnect(true);
}
write(&self.seed_caps).clone_from(&seed_caps);
if let Some(seed_caps) = seed_caps {
let caps = Caps::native_create(self, seed_caps)?;
*write(&self.caps_state) = Some(caps);
}
Ok(())
}
pub fn native_is_event_queue_running(
&self,
block_until_running: Option<bool>,
) -> Result<bool, Error> {
if self.caps_running() {
return Ok(true);
}
if !block_until_running.unwrap_or(false) {
return Ok(false);
}
let mut generation = mutex(&self.event_queue_generation);
for delay in [1_u64, 2, 4, 8] {
if self.caps_running() || self.current_sim_caps_running() {
return Ok(true);
}
self.start_current_event_queue();
let observed = *generation;
let (next, _) = self
.event_queue_wait
.wait_timeout_while(generation, Duration::from_secs(delay), |value| {
*value == observed
&& !self.client.cancellation_token().is_cancellation_requested()
})
.unwrap_or_else(std::sync::PoisonError::into_inner);
generation = next;
if *generation != observed {
break;
}
}
Ok(self.caps_running() || self.current_sim_caps_running())
}
pub(crate) fn native_start_event_queue(&self) {
self.start_current_event_queue();
}
fn caps_running(&self) -> bool {
read(&self.caps_state).as_ref().is_some_and(|inner| {
Caps::native_from_inner(Arc::clone(inner), self.native_clone_without_caps())
.is_event_queue_running()
})
}
fn current_sim_caps_running(&self) -> bool {
mutex(&self.manager)
.upgrade()
.and_then(|manager| read(&manager.current_sim).clone())
.is_some_and(|simulator| simulator.caps_running())
}
fn start_current_event_queue(&self) {
let simulator = mutex(&self.manager)
.upgrade()
.and_then(|manager| read(&manager.current_sim).clone())
.unwrap_or_else(|| self.native_clone_without_caps());
if let Some(inner) = read(&simulator.caps_state).clone() {
let caps = Caps::native_from_inner(inner, simulator.native_clone_without_caps());
if let Some(queue) = caps.event_queue() {
let _ = queue.start();
}
}
}
pub(crate) fn native_raise_event_queue_running(&self) {
let mut generation = mutex(&self.event_queue_generation);
*generation = generation.wrapping_add(1);
drop(generation);
self.event_queue_wait.notify_all();
if let Some(manager) = mutex(&self.manager).upgrade() {
let args = EventQueueRunningEventArgs {
simulator: self.clone(),
};
manager
.events
.event_queue_running
.emit_with(|| args.clone());
}
}
pub(crate) fn native_dispatch_caps_event(&self, event_name: &str, message: &dyn IMessage) {
if let Some(manager) = mutex(&self.manager).upgrade() {
manager
.caps_events
.invoke_raise_event(event_name, message, self.clone());
}
}
pub(crate) fn native_enqueue_caps_packet(&self, packet: Packet) -> Result<(), Error> {
let manager = mutex(&self.manager)
.upgrade()
.ok_or(Error::InvalidOperation)?;
manager.enqueue_incoming(NetworkManagerIncomingPacket {
packet: Some(packet),
simulator: Some(self.clone()),
raw_data: None,
})
}
#[must_use]
pub fn seed_capability(&self) -> Option<Uri> {
read(&self.seed_caps).clone()
}
pub fn native_use_circuit_code(&self, wait_for_ack: bool) -> Result<(), Error> {
let data = self.use_circuit_code_bytes()?;
if wait_for_ack {
let receiver = self
.transport
.try_send_packet_wait_ack(data, self.endpoint, PacketType::UseCircuitCode, false)
.map_err(Error::from)?;
let timeout =
positive_millisecond_duration(self.client.settings_ref().timing.login_timeout);
let _ = receiver.recv_timeout(timeout);
} else {
self.transport
.try_send_packet(data, self.endpoint, PacketType::UseCircuitCode, false)
.map_err(Error::from)?;
}
Ok(())
}
pub async fn native_use_circuit_code_async(&self, wait_for_ack: bool) -> Result<bool, Error> {
let data = self.use_circuit_code_bytes()?;
if !wait_for_ack {
self.transport
.try_send_packet(data, self.endpoint, PacketType::UseCircuitCode, false)
.map_err(Error::from)?;
return Ok(true);
}
let receiver = self
.transport
.try_send_packet_wait_ack(data, self.endpoint, PacketType::UseCircuitCode, false)
.map_err(Error::from)?;
let timeout =
positive_millisecond_duration(self.client.settings_ref().timing.login_timeout);
run_blocking_compat("libremetaverse-circuit-ack", move || {
receiver.recv_timeout(timeout).is_ok()
})
.await
}
fn use_circuit_code_bytes(&self) -> Result<Vec<u8>, Error> {
let mut packet = UseCircuitCodePacket::new_with_constructor()?;
packet.circuit_code.code = self.circuit_code.load(Ordering::Acquire);
packet.circuit_code.id = *read(&self.agent_id);
packet.circuit_code.session_id = *read(&self.session_id);
packet.to_bytes_with_method()
}
fn native_complete_agent_movement(&self) -> Result<(), Error> {
let mut packet = CompleteAgentMovementPacket::new_with_constructor()?;
packet.agent_data.agent_id = *read(&self.agent_id);
packet.agent_data.session_id = *read(&self.session_id);
packet.agent_data.circuit_code = self.circuit_code.load(Ordering::Acquire);
let mut data = packet.to_bytes_with_method()?;
// Match the reference packet constructor: this transition is reliable.
let flags = data.first_mut().ok_or(Error::Argument)?;
*flags |= Helpers::MSG_RELIABLE;
self.native_send_packet_data(
data.clone(),
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
PacketType::CompleteAgentMovement,
false,
)
}
#[must_use]
pub fn native_is_connected(&self) -> bool {
self.connected.load(Ordering::Acquire)
}
#[must_use]
pub(crate) fn native_agent_movement_complete(&self) -> bool {
self.movement_complete.load(Ordering::Acquire)
}
pub(crate) fn native_set_agent_movement_complete(&self, value: bool) {
self.movement_complete.store(value, Ordering::Release);
}
#[cfg(test)]
pub(crate) fn native_set_connected_for_tests(&self, connected: bool) {
self.connected.store(connected, Ordering::Release);
}
#[must_use]
pub fn native_handshake_complete(&self) -> bool {
self.handshake_complete.load(Ordering::Acquire)
}
#[must_use]
pub fn native_ip_end_point(&self) -> std::net::SocketAddr {
self.data.endpoint
}
#[must_use]
pub fn native_to_string(&self) -> String {
if self.name.is_empty() {
format!("({})", self.endpoint)
} else {
format!("{} ({})", self.name, self.endpoint)
}
}
fn note_packet_received(&self, _packet_type: PacketType) {
self.disconnect_candidate.store(false, Ordering::Release);
}
fn mark_disconnect_candidate(&self) -> bool {
self.disconnect_candidate.swap(true, Ordering::AcqRel)
}
fn send_buffer(&self, buffer: UDPPacketBuffer) -> Result<(), Error> {
self.transport.async_begin_send(buffer)
}
}
struct SimulatorUdpHandler {
simulator: Arc<Mutex<Weak<SimulatorData>>>,
}
impl UdpPacketHandler for SimulatorUdpHandler {
fn packet_received(&self, buffer: UDPPacketBuffer) {
let Some(data) = mutex(&self.simulator).upgrade() else {
return;
};
let simulator = Simulator { caps: None, data };
let Ok(length) = usize::try_from(buffer.data_length) else {
return;
};
let Some(payload) = buffer.data.get(..length) else {
return;
};
let Ok(payload_length) = i32::try_from(payload.len()) else {
return;
};
let mut packet_end = payload_length - 1;
let mut zero_buffer = vec![0_u8; UdpTransportConfig::default().max_decoded_packet_size];
let Ok(packet) =
crate::packet_wire::build_packet_from_bytes(payload, &mut packet_end, &mut zero_buffer)
else {
return;
};
let _ = simulator
.stats
.add_recv_bytes(i64::from(buffer.data_length));
let _ = simulator.stats.increment_recv_packets();
if payload
.first()
.is_some_and(|flags| flags & Helpers::MSG_RESENT != 0)
{
let _ = simulator.stats.increment_received_resends();
}
simulator.note_packet_received(packet.type_);
let manager = mutex(&simulator.manager).upgrade();
if let Some(manager) = manager {
let _ = manager.enqueue_incoming(NetworkManagerIncomingPacket {
simulator: Some(simulator),
packet: Some(packet),
raw_data: Some(payload.to_vec()),
});
}
}
fn packet_sent(&self, buffer: UDPPacketBuffer, bytes_sent: usize) {
let Some(data) = mutex(&self.simulator).upgrade() else {
return;
};
let simulator = Simulator { caps: None, data };
let _ = simulator
.stats
.add_sent_bytes(i64::try_from(bytes_sent).unwrap_or(i64::MAX));
let _ = simulator.stats.increment_sent_packets();
if buffer
.data
.first()
.is_some_and(|flags| flags & Helpers::MSG_RESENT != 0)
{
let _ = simulator.stats.increment_resent_packets();
}
let manager = mutex(&simulator.manager).upgrade();
if let Some(manager) = manager {
manager.raise_packet_sent(buffer.data, bytes_sent, simulator);
}
}
fn packet_dropped(&self) {
let Some(data) = mutex(&self.simulator).upgrade() else {
return;
};
let _ = data.stats.increment_dropped_packets();
}
}
#[derive(Clone)]
pub struct NetworkManagerIncomingPacket {
pub packet: Option<Packet>,
pub simulator: Option<Simulator>,
raw_data: Option<Vec<u8>>,
}
impl NetworkManagerIncomingPacket {
pub fn new() -> Result<Self, Error> {
Ok(Self {
packet: None,
simulator: None,
raw_data: None,
})
}
}
#[derive(Clone)]
pub struct NetworkManagerOutgoingPacket {
pub buffer: UDPPacketBuffer,
pub resend_count: i32,
pub sequence_number: u32,
pub simulator: Simulator,
pub tick_count: i32,
pub type_: PacketType,
}
impl NetworkManagerOutgoingPacket {
pub fn new(
simulator: Simulator,
buffer: UDPPacketBuffer,
type_: PacketType,
) -> Result<Self, Error> {
Ok(Self {
simulator,
buffer,
sequence_number: 0,
resend_count: 0,
tick_count: 0,
type_,
})
}
}
struct NetworkWorkers {
incoming: SyncSender<NetworkManagerIncomingPacket>,
outgoing: SyncSender<NetworkManagerOutgoingPacket>,
cancellation: libremetaverse_types::compat::CancellationTokenSource,
handles: Vec<JoinHandle<()>>,
}
#[derive(Default)]
struct NetworkEvents {
disconnected: EventRegistry<DisconnectedEventArgs>,
event_queue_running: EventRegistry<EventQueueRunningEventArgs>,
generic_streaming_message: EventRegistry<GenericStreamingMessageEventArgs>,
logged_out: EventRegistry<LoggedOutEventArgs>,
login_progress: EventRegistry<LoginProgressEventArgs>,
packet_sent: EventRegistry<PacketSentEventArgs>,
raw_packet_received: EventRegistry<RawPacketReceivedEventArgs>,
sim_changed: EventRegistry<SimChangedEventArgs>,
sim_connected: EventRegistry<SimConnectedEventArgs>,
sim_connecting: EventRegistry<SimConnectingEventArgs>,
sim_disconnected: EventRegistry<SimDisconnectedEventArgs>,
}
struct LoginRuntimeState {
next_generation: u64,
active: Option<(u64, CancellationTokenSource)>,
response_callbacks: Vec<(NetworkManagerLoginResponseCallback, Option<Vec<String>>)>,
}
impl Default for LoginRuntimeState {
fn default() -> Self {
Self {
next_generation: 1,
active: None,
response_callbacks: Vec::new(),
}
}
}
pub(crate) struct NetworkManagerInner {
client: GridClient,
simulators: SimulatorCollection,
packet_events: PacketEventDictionary,
caps_events: CapsEventDictionary,
events: NetworkEvents,
current_sim: RwLock<Option<Simulator>>,
connected: AtomicBool,
circuit_code: AtomicU32,
agent_id: RwLock<UUID>,
session_id: RwLock<UUID>,
login_status: RwLock<LoginStatus>,
login_error_key: RwLock<String>,
login_message: RwLock<String>,
login_seed_capability: RwLock<Option<Uri>>,
login_response: LoginResponseData,
login_runtime: Mutex<LoginRuntimeState>,
logout_cancellation: Mutex<CancellationTokenSource>,
inbox_count: AtomicI32,
outbox_count: AtomicI32,
workers: Mutex<Option<NetworkWorkers>>,
disconnect_lock: Mutex<()>,
lifecycle_state: AtomicU8,
logout_task_count: AtomicI32,
last_logout_error: Mutex<Option<Error>>,
}
impl NetworkManagerInner {
fn lifecycle_state(&self) -> NetworkLifecycleState {
NetworkLifecycleState::from_u8(self.lifecycle_state.load(Ordering::Acquire))
}
fn set_lifecycle_state(&self, state: NetworkLifecycleState) {
self.lifecycle_state.store(state as u8, Ordering::Release);
}
fn ensure_workers(self: &Arc<Self>) -> Result<(), Error> {
let mut workers = mutex(&self.workers);
if workers.is_some() {
return Ok(());
}
let capacity = usize::try_from(crate::Settings::udp_receive_queue_capacity())
.map_err(|_| Error::InvalidOperation)?
.max(1);
let (incoming_sender, incoming_receiver) =
sync_channel::<NetworkManagerIncomingPacket>(capacity);
let (outgoing_sender, outgoing_receiver) =
sync_channel::<NetworkManagerOutgoingPacket>(capacity);
let cancellation = libremetaverse_types::compat::CancellationTokenSource::default();
let incoming_token = cancellation.token();
let outgoing_token = cancellation.token();
let keepalive_token = cancellation.token();
let incoming_weak = Arc::downgrade(self);
let outgoing_weak = Arc::downgrade(self);
let keepalive_weak = Arc::downgrade(self);
let incoming_handle = thread::Builder::new()
.name("libremetaverse-network-incoming".to_owned())
.spawn(move || {
while !incoming_token.is_cancellation_requested() {
match incoming_receiver.recv_timeout(Duration::from_millis(50)) {
Ok(incoming) => {
let Some(inner) = incoming_weak.upgrade() else {
break;
};
inner.inbox_count.fetch_sub(1, Ordering::AcqRel);
let raw_data = incoming.raw_data;
let (Some(packet), Some(simulator)) =
(incoming.packet, incoming.simulator)
else {
continue;
};
inner.process_internal_packet(&packet, &simulator, raw_data.as_deref());
if let Some(data) = raw_data.as_ref() {
inner.events.raw_packet_received.emit_with(|| {
RawPacketReceivedEventArgs {
packet_type: packet.type_,
data: data.clone(),
simulator: simulator.clone(),
}
});
}
let _ = inner.packet_events.raise_event(
packet.type_,
packet,
simulator,
raw_data,
);
}
Err(RecvTimeoutError::Timeout) => {}
Err(RecvTimeoutError::Disconnected) => break,
}
}
})
.map_err(|_| Error::InvalidOperation)?;
let outgoing_handle = thread::Builder::new()
.name("libremetaverse-network-outgoing".to_owned())
.spawn(move || {
while !outgoing_token.is_cancellation_requested() {
match outgoing_receiver.recv_timeout(Duration::from_millis(50)) {
Ok(outgoing) => {
let Some(inner) = outgoing_weak.upgrade() else {
break;
};
inner.outbox_count.fetch_sub(1, Ordering::AcqRel);
let _ = outgoing.simulator.send_buffer(outgoing.buffer);
}
Err(RecvTimeoutError::Timeout) => {}
Err(RecvTimeoutError::Disconnected) => break,
}
}
})
.map_err(|_| Error::InvalidOperation)?;
let timeout =
positive_millisecond_duration(self.client.settings_ref().timing.simulator_timeout);
let keepalive_handle = thread::Builder::new()
.name("libremetaverse-network-keepalive".to_owned())
.spawn(move || {
while !wait_for_cancellation(&keepalive_token, timeout) {
let Some(inner) = keepalive_weak.upgrade() else {
break;
};
inner.keepalive_tick();
}
})
.map_err(|_| Error::InvalidOperation)?;
*workers = Some(NetworkWorkers {
incoming: incoming_sender,
outgoing: outgoing_sender,
cancellation,
handles: vec![incoming_handle, outgoing_handle, keepalive_handle],
});
Ok(())
}
fn process_internal_packet(
&self,
packet: &Packet,
simulator: &Simulator,
raw_data: Option<&[u8]>,
) {
match packet.type_ {
PacketType::RegionHandshake => {
let Some(raw_data) = raw_data else {
return;
};
let mut position = 0;
let Ok(_incoming) =
RegionHandshakePacket::new_with_bytes_int32(raw_data.to_vec(), &mut position)
else {
return;
};
let Ok(mut reply) = RegionHandshakeReplyPacket::new_with_constructor() else {
return;
};
reply.agent_data.agent_id = *read(&self.agent_id);
reply.agent_data.session_id = *read(&self.session_id);
reply.region_info.flags = 0x1 | 0x2 | 0x4;
let Ok(data) = reply.to_bytes_with_method() else {
return;
};
let Ok(length) = i32::try_from(data.len()) else {
return;
};
if simulator
.native_send_packet_data(data, length, PacketType::RegionHandshakeReply, true)
.is_err()
{
return;
}
simulator.connected.store(true, Ordering::Release);
simulator.handshake_complete.store(true, Ordering::Release);
simulator.handshake_wait.notify_all();
}
PacketType::CompletePingCheck => {
simulator.stats.record_pong(environment_tick_count());
}
PacketType::StartPingCheck => {
let Some(raw_data) = raw_data else {
return;
};
let Ok(mut incoming) = StartPingCheckPacket::new_with_constructor() else {
return;
};
let Ok(mut packet_end) = i32::try_from(raw_data.len()) else {
return;
};
packet_end -= 1;
let mut position = 0;
let mut zero_buffer =
vec![0_u8; UdpTransportConfig::default().max_decoded_packet_size];
if incoming
.from_bytes_with_bytes_int32_int32_bytes(
raw_data.to_vec(),
&mut position,
&mut packet_end,
Some(&mut zero_buffer),
)
.is_err()
{
return;
}
if incoming.ping_id.oldest_unacked > 0 {
let _ = simulator.transport.try_flush_acks(simulator.endpoint);
}
let Ok(mut response) = CompletePingCheckPacket::new_with_constructor() else {
return;
};
response.ping_id.ping_id = incoming.ping_id.ping_id;
let Ok(mut data) = response.to_bytes_with_method() else {
return;
};
if let Some(flags) = data.first_mut() {
*flags &= !Helpers::MSG_RELIABLE;
}
let Ok(length) = i32::try_from(data.len()) else {
return;
};
let _ = simulator.native_send_packet_data(
data,
length,
PacketType::CompletePingCheck,
false,
);
}
PacketType::LogoutReply => {
let Some(raw_data) = raw_data else {
return;
};
let Ok(mut incoming) = LogoutReplyPacket::new_with_constructor() else {
return;
};
let Ok(mut packet_end) = i32::try_from(raw_data.len()) else {
return;
};
packet_end -= 1;
let mut position = 0;
let mut zero_buffer =
vec![0_u8; UdpTransportConfig::default().max_decoded_packet_size];
if incoming
.from_bytes_with_bytes_int32_int32_bytes(
raw_data.to_vec(),
&mut position,
&mut packet_end,
Some(&mut zero_buffer),
)
.is_err()
{
return;
}
if incoming.agent_data.agent_id != *read(&self.agent_id)
|| incoming.agent_data.session_id != *read(&self.session_id)
{
return;
}
let Ok(args) = LoggedOutEventArgs::new(
incoming
.inventory_data
.into_iter()
.map(|item| item.item_id)
.collect(),
) else {
return;
};
self.events.logged_out.emit_with(|| args.clone());
let _ = self.shutdown(
crate::NetworkManagerDisconnectType::ClientInitiated,
"ClientInitiated".to_owned(),
);
}
PacketType::SimStats => {
if !self.client.settings_ref().packets.enable_sim_stats {
return;
}
let Some(raw_data) = raw_data else {
return;
};
let Ok(mut incoming) = SimStatsPacket::new_with_constructor() else {
return;
};
let Ok(mut packet_end) = i32::try_from(raw_data.len()) else {
return;
};
packet_end -= 1;
let mut position = 0;
let mut zero_buffer =
vec![0_u8; UdpTransportConfig::default().max_decoded_packet_size];
if incoming
.from_bytes_with_bytes_int32_int32_bytes(
raw_data.to_vec(),
&mut position,
&mut packet_end,
Some(&mut zero_buffer),
)
.is_err()
{
return;
}
for stat in incoming.stat {
simulator
.stats
.apply_server_stat(stat.stat_id, stat.stat_value);
}
}
PacketType::DisableSimulator => {
let _ = self.disconnect_simulator(simulator.clone(), false);
}
PacketType::KickUser => {
let Some(raw_data) = raw_data else {
return;
};
let Ok(mut incoming) = KickUserPacket::new_with_constructor() else {
return;
};
let Ok(mut packet_end) = i32::try_from(raw_data.len()) else {
return;
};
packet_end -= 1;
let mut position = 0;
let mut zero_buffer =
vec![0_u8; UdpTransportConfig::default().max_decoded_packet_size];
if incoming
.from_bytes_with_bytes_int32_int32_bytes(
raw_data.to_vec(),
&mut position,
&mut packet_end,
Some(&mut zero_buffer),
)
.is_err()
{
return;
}
let message = String::from_utf8_lossy(&incoming.user_info.reason)
.trim_end_matches('\0')
.to_owned();
let _ = self.shutdown(
crate::NetworkManagerDisconnectType::ServerInitiated,
message,
);
}
PacketType::GenericStreamingMessage => {
let Some(raw_data) = raw_data else {
return;
};
let Ok(mut incoming) = GenericStreamingMessagePacket::new_with_constructor() else {
return;
};
let Ok(mut packet_end) = i32::try_from(raw_data.len()) else {
return;
};
packet_end -= 1;
let mut position = 0;
let mut zero_buffer =
vec![0_u8; UdpTransportConfig::default().max_decoded_packet_size];
if incoming
.from_bytes_with_bytes_int32_int32_bytes(
raw_data.to_vec(),
&mut position,
&mut packet_end,
Some(&mut zero_buffer),
)
.is_err()
{
return;
}
let method = if incoming.method_data.method
== GenericStreamingMethod::GltfMaterialOverride as u16
{
GenericStreamingMethod::GltfMaterialOverride
} else {
GenericStreamingMethod::Unknown
};
let Ok(args) = GenericStreamingMessageEventArgs::new(
simulator.clone(),
method,
incoming.data_block.data,
) else {
return;
};
self.events
.generic_streaming_message
.emit_with(|| args.clone());
}
_ => {}
}
}
fn disconnect_simulator(
&self,
simulator: Simulator,
send_close_circuit: bool,
) -> Result<(), Error> {
let disconnect_guard = mutex(&self.disconnect_lock);
let was_connected = simulator.connected();
simulator.native_disconnect(send_close_circuit)?;
if !self.simulators.remove(&simulator) {
return Ok(());
}
if read(&self.current_sim).as_ref() == Some(&simulator) {
*write(&self.current_sim) = None;
}
let args = SimDisconnectedEventArgs::new(
simulator.clone(),
crate::NetworkManagerDisconnectType::NetworkTimeout,
was_connected,
)?;
drop(disconnect_guard);
self.events.sim_disconnected.emit_with(|| args.clone());
if self.simulators.is_empty() {
self.shutdown(
crate::NetworkManagerDisconnectType::SimShutdown,
format!(
"You have disconnected from {}.",
simulator.native_to_string()
),
)?;
}
Ok(())
}
fn enqueue_incoming(&self, incoming: NetworkManagerIncomingPacket) -> Result<(), Error> {
let workers = mutex(&self.workers);
let Some(workers) = workers.as_ref() else {
return Err(Error::InvalidOperation);
};
workers
.incoming
.try_send(incoming)
.map_err(|_| Error::InvalidOperation)?;
self.inbox_count.fetch_add(1, Ordering::AcqRel);
Ok(())
}
fn enqueue_outgoing(&self, outgoing: NetworkManagerOutgoingPacket) -> Result<(), Error> {
let workers = mutex(&self.workers);
let Some(workers) = workers.as_ref() else {
return Err(Error::InvalidOperation);
};
workers
.outgoing
.try_send(outgoing)
.map_err(|_| Error::InvalidOperation)?;
self.outbox_count.fetch_add(1, Ordering::AcqRel);
Ok(())
}
fn stop_workers(&self) {
let workers = mutex(&self.workers).take();
let Some(workers) = workers else {
return;
};
workers.cancellation.cancel();
drop(workers.incoming);
drop(workers.outgoing);
for handle in workers.handles {
if handle.thread().id() != thread::current().id() {
let _ = handle.join();
}
}
self.inbox_count.store(0, Ordering::Release);
self.outbox_count.store(0, Ordering::Release);
}
fn keepalive_tick(&self) {
if !self.connected.load(Ordering::Acquire)
|| self.lifecycle_state() != NetworkLifecycleState::Connected
{
return;
}
let current = read(&self.current_sim).clone();
let Some(current) = current else {
return;
};
if current.mark_disconnect_candidate() {
let _ = self.shutdown(
crate::NetworkManagerDisconnectType::NetworkTimeout,
"NetworkTimeout".to_owned(),
);
} else if current.native_send_ping().is_ok() {
let _ = current.stats.increment_sent_pings();
}
}
fn raise_packet_sent(&self, data: Vec<u8>, bytes_sent: usize, simulator: Simulator) {
let Ok(bytes_sent) = i32::try_from(bytes_sent) else {
return;
};
let Ok(args) = PacketSentEventArgs::new(data, bytes_sent, simulator) else {
return;
};
self.events.packet_sent.emit_with(|| args.clone());
}
fn set_current_sim(&self, simulator: Simulator, seed_caps: Option<Uri>) -> Result<(), Error> {
let transition = mutex(&self.disconnect_lock);
let previous = read(&self.current_sim).clone();
if previous.as_ref() == Some(&simulator) {
drop(transition);
simulator.native_set_seed_caps(seed_caps, Some(false))?;
return Ok(());
}
simulator.native_set_seed_caps(seed_caps, Some(previous.as_ref() != Some(&simulator)))?;
*write(&self.current_sim) = Some(simulator.clone());
drop(transition);
let args = SimChangedEventArgs::new(previous)?;
self.events.sim_changed.emit_with(|| args.clone());
Ok(())
}
fn shutdown(
&self,
reason: crate::NetworkManagerDisconnectType,
message: String,
) -> Result<(), Error> {
loop {
let state = self.lifecycle_state();
if matches!(
state,
NetworkLifecycleState::Disconnected | NetworkLifecycleState::ShuttingDown
) {
return Ok(());
}
if self
.lifecycle_state
.compare_exchange(
state as u8,
NetworkLifecycleState::ShuttingDown as u8,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
break;
}
}
if let Some((_, cancellation)) = mutex(&self.login_runtime).active.take() {
cancellation.cancel();
}
mutex(&self.logout_cancellation).cancel();
let current = write(&self.current_sim).take();
let mut simulators: Vec<_> = self
.simulators
.drain()
.into_iter()
.filter(|simulator| current.as_ref() != Some(simulator))
.collect();
if let Some(current) = current {
simulators.push(current);
}
let send_close = matches!(
reason,
crate::NetworkManagerDisconnectType::ClientInitiated
| crate::NetworkManagerDisconnectType::NetworkTimeout
);
for simulator in simulators {
let was_connected = simulator.connected();
let _ = simulator.native_disconnect(send_close);
if let Ok(args) = SimDisconnectedEventArgs::new(simulator, reason, was_connected) {
self.events.sim_disconnected.emit_with(|| args.clone());
}
}
self.stop_workers();
self.connected.store(false, Ordering::Release);
self.circuit_code.store(0, Ordering::Release);
*write(&self.agent_id) = UUID::zero();
*write(&self.session_id) = UUID::zero();
*write(&self.login_seed_capability) = None;
self.login_response.clear_secrets();
let event_result = DisconnectedEventArgs::new(reason, message);
if let Ok(args) = &event_result {
self.events.disconnected.emit_with(|| args.clone());
}
self.set_lifecycle_state(NetworkLifecycleState::Disconnected);
event_result.map(|_| ())
}
}
impl Drop for NetworkManagerInner {
fn drop(&mut self) {
mutex(&self.logout_cancellation).cancel();
self.stop_workers();
for simulator in self.simulators.drain() {
let _ = simulator.native_disconnect(false);
}
}
}
/// Native implementation of C# `NetworkManager`.
#[derive(Clone)]
pub struct NetworkManager {
pub login_response_data: Option<LoginResponseData>,
pub simulators: SimulatorCollection,
inner: Arc<NetworkManagerInner>,
}
impl fmt::Debug for NetworkManager {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("NetworkManager")
.field("connected", &self.native_connected())
.field("circuit_code", &self.native_circuit_code())
.field("simulators", &self.simulators)
.finish_non_exhaustive()
}
}
impl NetworkManager {
pub(crate) fn native_from_inner(inner: Arc<NetworkManagerInner>) -> Self {
Self {
login_response_data: Some(inner.login_response.clone()),
simulators: inner.simulators.clone(),
inner,
}
}
pub(crate) fn native_inner_weak(&self) -> Weak<NetworkManagerInner> {
Arc::downgrade(&self.inner)
}
pub(crate) fn native_agent_id(&self) -> UUID {
*read(&self.inner.agent_id)
}
pub(crate) fn native_session_id(&self) -> UUID {
*read(&self.inner.session_id)
}
pub fn native_new(client: GridClient) -> Result<Self, Error> {
let simulators = SimulatorCollection::default();
let packet_events = PacketEventDictionary::new(client.clone())?;
let caps_events = CapsEventDictionary::new(client.clone())?;
let login_response = LoginResponseData::new()?;
let inner = Arc::new(NetworkManagerInner {
client,
simulators: simulators.clone(),
packet_events,
caps_events,
events: NetworkEvents::default(),
current_sim: RwLock::new(None),
connected: AtomicBool::new(false),
circuit_code: AtomicU32::new(0),
agent_id: RwLock::new(UUID::zero()),
session_id: RwLock::new(UUID::zero()),
login_status: RwLock::new(LoginStatus::None),
login_error_key: RwLock::new(String::new()),
login_message: RwLock::new(String::new()),
login_seed_capability: RwLock::new(None),
login_response: login_response.clone(),
login_runtime: Mutex::new(LoginRuntimeState::default()),
logout_cancellation: Mutex::new(CancellationTokenSource::new()),
inbox_count: AtomicI32::new(0),
outbox_count: AtomicI32::new(0),
workers: Mutex::new(None),
disconnect_lock: Mutex::new(()),
lifecycle_state: AtomicU8::new(NetworkLifecycleState::Disconnected as u8),
logout_task_count: AtomicI32::new(0),
last_logout_error: Mutex::new(None),
});
let weak_inner = Arc::downgrade(&inner);
inner.caps_events.register_event(
"EnableSimulator".to_owned(),
CapsEventQueueCallback::from_handler(move |_, message, _| {
let Some(inner) = weak_inner.upgrade() else {
return;
};
if !inner.client.settings_ref().agent.multiple_sims {
return;
}
let any_message = message as &dyn std::any::Any;
let Some(message) =
any_message.downcast_ref::<crate::messages::linden::EnableSimulatorMessage>()
else {
return;
};
let manager = NetworkManager {
login_response_data: Some(inner.login_response.clone()),
simulators: inner.simulators.clone(),
inner: Arc::clone(&inner),
};
for simulator in &message.simulators {
let Ok(port) = u16::try_from(simulator.port) else {
continue;
};
let endpoint = std::net::SocketAddr::new(simulator.ip, port);
if manager.native_find_endpoint(endpoint).is_some() {
continue;
}
let _ = manager.native_connect(
endpoint,
simulator.region_handle,
false,
None,
simulator.region_size_x,
simulator.region_size_y,
);
}
}),
)?;
Ok(Self {
login_response_data: Some(login_response),
simulators,
inner,
})
}
pub fn native_subscribe_login_progress(
&self,
handler: EventHandler<LoginProgressEventArgs>,
) -> Subscription {
self.inner.events.login_progress.subscribe(handler)
}
pub fn native_subscribe_logged_out(
&self,
handler: EventHandler<LoggedOutEventArgs>,
) -> Subscription {
self.inner.events.logged_out.subscribe(handler)
}
fn update_login_status(&self, status: LoginStatus, message: String) {
if status == LoginStatus::Failed {
*write(&self.inner.agent_id) = UUID::zero();
*write(&self.inner.session_id) = UUID::zero();
*write(&self.inner.login_seed_capability) = None;
self.inner.login_response.clear_secrets();
}
*write(&self.inner.login_status) = status;
write(&self.inner.login_message).clone_from(&message);
let fail_reason = read(&self.inner.login_error_key).clone();
if let Ok(args) = LoginProgressEventArgs::new(status, message, fail_reason) {
self.inner.events.login_progress.emit_with(|| args.clone());
}
}
fn set_login_error(&self, value: &str) {
let mut error = write(&self.inner.login_error_key);
error.clear();
error.push_str(value);
}
#[must_use]
pub fn native_login_status_code(&self) -> LoginStatus {
*read(&self.inner.login_status)
}
pub fn native_set_login_status_code(&self, value: LoginStatus) {
*write(&self.inner.login_status) = value;
}
#[must_use]
pub fn native_login_error_key(&self) -> String {
read(&self.inner.login_error_key).clone()
}
pub fn native_set_login_error_key(&self, value: String) {
*write(&self.inner.login_error_key) = value;
}
#[must_use]
pub fn native_login_message(&self) -> String {
read(&self.inner.login_message).clone()
}
pub fn native_set_login_message(&self, value: String) {
*write(&self.inner.login_message) = value;
}
#[must_use]
pub fn native_login_seed_capability(&self) -> Option<Uri> {
read(&self.inner.login_seed_capability).clone()
}
pub fn native_set_login_seed_capability(&self, value: Option<Uri>) {
*write(&self.inner.login_seed_capability) = value;
}
#[must_use]
pub fn native_account_level_benefits(&self) -> Option<AccountLevelBenefits> {
self.inner.login_response.account_level_benefits()
}
#[must_use]
pub fn native_agent_appearance_service_url(&self) -> Option<String> {
let value = self.inner.login_response.agent_appearance_service_url();
(!value.is_empty()).then_some(value)
}
#[must_use]
pub fn native_max_agent_groups(&self) -> i32 {
let fallback = self.inner.login_response.max_agent_groups();
self.inner
.login_response
.account_level_benefits()
.map_or(fallback, |benefits| {
let limit = benefits.group_membership_limit();
if limit > 0 { limit } else { fallback }
})
}
pub fn native_default_login_params(
&self,
first_name: String,
last_name: String,
password: String,
channel: String,
version: String,
) -> Result<LoginParams, Error> {
LoginParams::new_with_grid_client_string_string_string_string_string(
self.inner.client.clone(),
first_name,
last_name,
password,
channel,
version,
)
}
pub fn native_register_login_response_callback(
&self,
callback: NetworkManagerLoginResponseCallback,
options: Option<Vec<String>>,
) -> Result<(), Error> {
let mut state = mutex(&self.inner.login_runtime);
if state
.response_callbacks
.iter()
.any(|(candidate, _)| candidate.ptr_eq(&callback))
{
return Err(Error::Argument);
}
state.response_callbacks.push((callback, options));
Ok(())
}
pub fn native_unregister_login_response_callback(
&self,
callback: &NetworkManagerLoginResponseCallback,
) -> Result<(), Error> {
let mut state = mutex(&self.inner.login_runtime);
state
.response_callbacks
.retain(|(candidate, _)| !candidate.ptr_eq(callback));
Ok(())
}
fn reserve_login(
&self,
cancellation_token: Option<CancellationToken>,
) -> Result<(u64, CancellationTokenSource), Error> {
let mut runtime = mutex(&self.inner.login_runtime);
if runtime.active.is_some() {
return Err(Error::InvalidOperation);
}
let caller = cancellation_token.unwrap_or_default();
let source =
CancellationTokenSource::new_linked(&[caller, self.inner.client.cancellation_token()]);
let generation = runtime.next_generation;
runtime.next_generation = runtime.next_generation.wrapping_add(1).max(1);
runtime.active = Some((generation, source.clone()));
Ok((generation, source))
}
fn release_login(&self, generation: u64) {
let mut runtime = mutex(&self.inner.login_runtime);
if runtime
.active
.as_ref()
.is_some_and(|(active, _)| *active == generation)
{
runtime.active = None;
}
}
pub fn native_abort_login(&self) -> Result<(), Error> {
let active = mutex(&self.inner.login_runtime).active.take();
if let Some((_, source)) = active {
source.cancel();
}
self.update_login_status(LoginStatus::Failed, "Abort Requested".to_owned());
Ok(())
}
pub fn native_begin_login(&self, login_params: LoginParams) -> Result<(), Error> {
let (generation, source) = self.reserve_login(None)?;
let manager = Self {
login_response_data: Some(self.inner.login_response.clone()),
simulators: self.simulators.clone(),
inner: Arc::clone(&self.inner),
};
thread::Builder::new()
.name("libremetaverse-login".to_owned())
.spawn(move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build();
let result = match runtime {
Ok(runtime) => {
runtime.block_on(manager.perform_login(login_params, source.token()))
}
Err(_) => Err(Error::InvalidOperation),
};
if let Err(error) = result {
manager.login_transport_failure(&error);
}
manager.release_login(generation);
})
.map_err(|_| {
self.release_login(generation);
Error::InvalidOperation
})?;
Ok(())
}
pub async fn native_login(
&self,
login_params: LoginParams,
cancellation_token: Option<CancellationToken>,
) -> Result<bool, Error> {
Ok(self
.native_login_response_async(login_params, cancellation_token)
.await?
.is_some_and(|response| response.success()))
}
pub async fn native_login_response_async(
&self,
login_params: LoginParams,
cancellation_token: Option<CancellationToken>,
) -> Result<Option<LoginResponseData>, Error> {
let timeout = positive_millisecond_duration(login_params.timeout);
let (generation, source) = self.reserve_login(cancellation_token)?;
let token = source.token();
let result = tokio::select! {
result = tokio::time::timeout(timeout, self.perform_login(login_params, token.clone())) => {
if let Ok(result) = result {
result
} else {
source.cancel();
self.set_login_error("timeout");
self.update_login_status(LoginStatus::Failed, "Login timed out".to_owned());
Ok(None)
}
}
() = token.cancelled() => {
self.set_login_error("canceled");
self.update_login_status(LoginStatus::Failed, "Canceled".to_owned());
Ok(None)
}
};
self.release_login(generation);
match result {
Ok(value) => Ok(value),
Err(error) => {
self.login_transport_failure(&error);
Ok(None)
}
}
}
pub async fn native_login_credential(
&self,
credential: LoginCredential,
channel: String,
start: Option<String>,
version: String,
cancellation_token: Option<CancellationToken>,
) -> Result<bool, Error> {
let mut login_params = LoginParams::new_with_grid_client_login_credential_string_string(
self.inner.client.clone(),
credential,
channel,
version,
)?;
if let Some(start) = start {
login_params.start = start;
}
self.native_login(login_params, cancellation_token).await
}
pub async fn native_login_credential_response(
&self,
credential: LoginCredential,
channel: String,
start: Option<String>,
version: String,
cancellation_token: Option<CancellationToken>,
) -> Result<Option<LoginResponseData>, Error> {
let mut login_params = LoginParams::new_with_grid_client_login_credential_string_string(
self.inner.client.clone(),
credential,
channel,
version,
)?;
if let Some(start) = start {
login_params.start = start;
}
self.native_login_response_async(login_params, cancellation_token)
.await
}
pub async fn native_login_strings(
&self,
first_name: String,
last_name: String,
password: String,
channel: String,
start: String,
version: String,
cancellation_token: Option<CancellationToken>,
) -> Result<bool, Error> {
let mut params =
self.native_default_login_params(first_name, last_name, password, channel, version)?;
params.start = start;
self.native_login(params, cancellation_token).await
}
pub async fn native_login_strings_response(
&self,
first_name: String,
last_name: String,
password: String,
channel: String,
start: String,
version: String,
cancellation_token: Option<CancellationToken>,
) -> Result<Option<LoginResponseData>, Error> {
let mut params =
self.native_default_login_params(first_name, last_name, password, channel, version)?;
params.start = start;
self.native_login_response_async(params, cancellation_token)
.await
}
async fn perform_login(
&self,
mut params: LoginParams,
cancellation_token: CancellationToken,
) -> Result<Option<LoginResponseData>, Error> {
if params.password.len() != 35 && !params.password.starts_with("$1$") {
params.password = libremetaverse_types::Utils::md5_with_string(params.password)?;
}
if params.channel.trim().is_empty() {
params.channel = crate::Settings::user_agent();
}
if params.version.trim().is_empty() {
params.version = String::from("?.?.?");
}
let wire_secrets = LoginWireSecrets {
password: std::mem::take(&mut params.password),
token: std::mem::take(&mut params.token),
mfa_hash: std::mem::take(&mut params.mfa_hash),
};
params.clear_secrets();
let requested_start = if params.login_location.trim().is_empty() {
params.start.clone()
} else {
params.login_location.clone()
};
params.start = crate::login::normalize_start(&requested_start)?;
let callback_options: Vec<String> = mutex(&self.inner.login_runtime)
.response_callbacks
.iter()
.filter_map(|(_, options)| options.as_ref())
.flatten()
.cloned()
.collect();
for option in callback_options {
if !params.options.contains(&option) {
params.options.push(option);
}
}
let mut redirects = 0_usize;
loop {
cancellation_token.throw_if_cancellation_requested()?;
let login_uri = validate_login_uri(&params.uri)?;
self.update_login_status(
LoginStatus::ConnectingToLogin,
format!(
"Logging in as {} {}...",
params.first_name, params.last_name
),
);
let request = login_request(&params, &wire_secrets);
let response = self
.inner
.client
.native_http_caps_client()
.post_with_uri_osd_format_osd_cancellation_token_i_progress(
login_uri,
OSDFormat::Xml,
request,
cancellation_token.clone(),
None,
)
.await;
let (http_response, response_data) = match response {
Ok(response) => response,
Err(Error::Cancelled) => {
self.set_login_error("canceled");
self.update_login_status(LoginStatus::Failed, "Login canceled".to_owned());
return Ok(None);
}
Err(error) => {
self.login_transport_failure(&error);
return Ok(None);
}
};
if !http_response.is_success_status_code() {
self.set_login_error("bad response");
self.update_login_status(
LoginStatus::Failed,
format!("Login service returned HTTP {}", http_response.status_code),
);
return Ok(None);
}
self.update_login_status(
LoginStatus::ReadingResponse,
"Reading login response...".to_owned(),
);
let parsed = match OSDParser::deserialize_with_bytes(response_data) {
Ok(OSD::Map(map)) => OSDMap::new_with_dictionary(map),
_ => Err(Error::Argument),
};
let Ok(parsed) = parsed else {
self.set_login_error("bad response");
self.update_login_status(
LoginStatus::Failed,
"Empty or corrupt login response".to_owned(),
);
return Ok(None);
};
let has_login = parsed.get("login").is_some();
self.inner.login_response.parse_with_osd_map(parsed)?;
let response = self.inner.login_response.clone();
if !has_login {
self.update_login_status(
LoginStatus::Failed,
"Login parameter missing in the response".to_owned(),
);
return Ok(None);
}
match response.login() {
crate::LoginState::Indeterminate => {
redirects += 1;
if redirects > 10 {
self.set_login_error("redirect loop");
self.update_login_status(
LoginStatus::Failed,
"Too many login redirects".to_owned(),
);
return Ok(None);
}
self.update_login_status(LoginStatus::Redirecting, response.message());
params.uri = response.next_url();
validate_login_uri(&params.uri)?;
let delay = Duration::from_secs(
u64::try_from(response.next_duration().clamp(0, 300)).unwrap_or(0),
);
tokio::select! {
() = tokio::time::sleep(delay) => {}
() = cancellation_token.cancelled() => {
self.set_login_error("canceled");
self.update_login_status(LoginStatus::Failed, "Login canceled".to_owned());
return Ok(None);
}
}
}
crate::LoginState::True => {
self.invoke_login_response(true, false, &response);
if response.seed_capability().is_empty() {
self.set_login_error("bad response");
self.update_login_status(
LoginStatus::Failed,
"Login response did not include a seed capability".to_owned(),
);
return Ok(None);
}
if self.initialize_login_response(
response.clone(),
response.message(),
"Unable to establish a UDP connection to the simulator",
"Login server did not return a simulator address",
)? {
return Ok(Some(response));
}
return Ok(None);
}
crate::LoginState::False => {
let reason = response.reason();
self.set_login_error(if reason.is_empty() {
"unknown"
} else {
&reason
});
self.update_login_status(LoginStatus::Failed, response.message());
return Ok(None);
}
}
}
}
fn invoke_login_response(&self, success: bool, redirect: bool, response: &LoginResponseData) {
let callbacks: Vec<_> = mutex(&self.inner.login_runtime)
.response_callbacks
.iter()
.map(|(callback, _)| callback.clone())
.collect();
for callback in callbacks {
let _ = callback.invoke(
success,
redirect,
response.message(),
response.reason(),
Some(response.clone()),
);
}
}
fn login_transport_failure(&self, error: &Error) {
let (key, message) = match error {
Error::Cancelled => ("canceled", "Login canceled"),
Error::HttpRequest => ("no connection", "Unable to contact login service"),
Error::Argument => ("bad response", "Invalid login request or response"),
_ => ("no connection", "Login failed"),
};
self.set_login_error(key);
self.update_login_status(LoginStatus::Failed, message.to_owned());
}
pub fn native_login_response(&self, response: LoginResponseData) -> Result<bool, Error> {
self.initialize_login_response(
response,
"Login success".to_owned(),
"Unable to connect to simulator",
"Unable to connect to simulator",
)
}
fn initialize_login_response(
&self,
response: LoginResponseData,
success_message: String,
connect_failure_message: &str,
address_failure_message: &str,
) -> Result<bool, Error> {
self.inner.login_response.copy_from(&response);
self.inner
.circuit_code
.store(response.circuit_code().cast_unsigned(), Ordering::Release);
*write(&self.inner.agent_id) = response.agent_id();
*write(&self.inner.session_id) = response.session_id();
let seed = if response.seed_capability().is_empty() {
None
} else {
Some(validate_login_uri(&response.seed_capability())?)
};
write(&self.inner.login_seed_capability).clone_from(&seed);
self.update_login_status(
LoginStatus::ConnectingToSim,
"Connecting to simulator...".to_owned(),
);
let Some(ip) = response.sim_ip() else {
self.set_login_error("bad response");
self.update_login_status(LoginStatus::Failed, address_failure_message.to_owned());
return Ok(false);
};
if response.sim_port() == 0 {
self.set_login_error("bad response");
self.update_login_status(LoginStatus::Failed, address_failure_message.to_owned());
return Ok(false);
}
let handle = (u64::from(response.region_x()) << 32) | u64::from(response.region_y());
let endpoint = std::net::SocketAddr::new(ip, response.sim_port());
let Ok(connected) = self.native_connect(
endpoint,
handle,
true,
seed,
response.region_size_x(),
response.region_size_y(),
) else {
self.cleanup_failed_login_connection(endpoint);
self.set_login_error("no connection");
self.update_login_status(LoginStatus::Failed, connect_failure_message.to_owned());
return Ok(false);
};
if let Some(simulator) = connected {
let economy_request =
Packet::build_packet_with_packet_type(PacketType::EconomyDataRequest);
if economy_request
.and_then(|packet| simulator.native_send_packet(packet))
.is_err()
{
self.cleanup_failed_login_connection(endpoint);
self.set_login_error("no connection");
self.update_login_status(LoginStatus::Failed, connect_failure_message.to_owned());
return Ok(false);
}
self.update_login_status(LoginStatus::Success, success_message);
Ok(true)
} else {
self.cleanup_failed_login_connection(endpoint);
self.update_login_status(LoginStatus::Failed, connect_failure_message.to_owned());
Ok(false)
}
}
fn cleanup_failed_login_connection(&self, endpoint: std::net::SocketAddr) {
if let Some(simulator) = self.native_find_endpoint(endpoint) {
let _ = simulator.native_disconnect(false);
self.simulators.remove(&simulator);
let mut current = write(&self.inner.current_sim);
if current.as_ref() == Some(&simulator) {
*current = None;
}
}
if self.simulators.is_empty() {
self.inner.connected.store(false, Ordering::Release);
self.inner
.set_lifecycle_state(NetworkLifecycleState::Disconnected);
}
}
pub fn native_subscribe_disconnected(
&self,
handler: EventHandler<DisconnectedEventArgs>,
) -> Subscription {
self.inner.events.disconnected.subscribe(handler)
}
pub fn native_subscribe_event_queue_running(
&self,
handler: EventHandler<EventQueueRunningEventArgs>,
) -> Subscription {
self.inner.events.event_queue_running.subscribe(handler)
}
pub fn native_subscribe_generic_streaming_message(
&self,
handler: EventHandler<GenericStreamingMessageEventArgs>,
) -> Subscription {
self.inner
.events
.generic_streaming_message
.subscribe(handler)
}
pub fn native_subscribe_packet_sent(
&self,
handler: EventHandler<PacketSentEventArgs>,
) -> Subscription {
self.inner.events.packet_sent.subscribe(handler)
}
pub fn native_subscribe_sim_changed(
&self,
handler: EventHandler<SimChangedEventArgs>,
) -> Subscription {
self.inner.events.sim_changed.subscribe(handler)
}
pub fn native_subscribe_sim_connected(
&self,
handler: EventHandler<SimConnectedEventArgs>,
) -> Subscription {
self.inner.events.sim_connected.subscribe(handler)
}
pub fn native_subscribe_sim_connecting(
&self,
handler: EventHandler<SimConnectingEventArgs>,
) -> Subscription {
self.inner.events.sim_connecting.subscribe(handler)
}
pub fn native_subscribe_sim_disconnected(
&self,
handler: EventHandler<SimDisconnectedEventArgs>,
) -> Subscription {
self.inner.events.sim_disconnected.subscribe(handler)
}
pub fn subscribe_packet(
&self,
packet_type: PacketType,
handler: EventHandler<PacketReceivedEventArgs>,
is_async: bool,
) -> Subscription {
self.inner
.packet_events
.subscribe(packet_type, handler, is_async)
}
pub(crate) fn subscribe_raw_packet(
&self,
handler: EventHandler<RawPacketReceivedEventArgs>,
) -> Subscription {
self.inner.events.raw_packet_received.subscribe(handler)
}
pub fn subscribe_caps(
&self,
caps_event: String,
handler: CapsEventQueueCallback,
) -> Subscription {
self.inner.caps_events.subscribe(caps_event, handler)
}
/// Dispatches a decoded CAPS event through the default and named handler
/// chains. This is the native boundary used by a CAPS event-queue client.
pub fn dispatch_caps_event(
&self,
caps_event: &str,
message: &dyn IMessage,
simulator: Simulator,
) {
self.inner
.caps_events
.invoke_raise_event(caps_event, message, simulator);
}
pub fn native_register_callback(
&self,
packet_type: PacketType,
callback: EventHandler<PacketReceivedEventArgs>,
is_async: bool,
) -> Result<(), Error> {
self.inner
.packet_events
.register_event(packet_type, callback, is_async)
}
pub fn native_unregister_callback(
&self,
packet_type: PacketType,
callback: EventHandler<PacketReceivedEventArgs>,
) -> Result<(), Error> {
self.inner
.packet_events
.unregister_event(packet_type, callback)
}
pub fn native_register_caps(
&self,
caps_event: String,
callback: CapsEventQueueCallback,
) -> Result<(), Error> {
self.inner.caps_events.register_event(caps_event, callback)
}
pub fn native_unregister_caps(
&self,
caps_event: String,
callback: CapsEventQueueCallback,
) -> Result<(), Error> {
self.inner
.caps_events
.unregister_event(caps_event, callback)
}
pub fn native_connect(
&self,
endpoint: std::net::SocketAddr,
handle: u64,
set_default: bool,
seed_caps: Option<Uri>,
size_x: u32,
size_y: u32,
) -> Result<Option<Simulator>, Error> {
if matches!(
self.inner.lifecycle_state(),
NetworkLifecycleState::LoggingOut | NetworkLifecycleState::ShuttingDown
) {
return Err(Error::InvalidOperation);
}
self.inner.ensure_workers()?;
let existing = self.native_find_endpoint(endpoint);
let newly_created = existing.is_none();
let simulator = existing.unwrap_or(Simulator::native_new(
self.inner.client.clone(),
endpoint,
handle,
Some(size_x),
Some(size_y),
)?);
simulator.attach_manager(&self.inner, self.inner.circuit_code.load(Ordering::Acquire));
let simulator = self.simulators.push_if_missing(simulator);
if !simulator.connected() {
self.inner
.set_lifecycle_state(NetworkLifecycleState::Connecting);
self.inner.connected.store(true, Ordering::Release);
self.inner
.set_lifecycle_state(NetworkLifecycleState::Connected);
let args = SimConnectingEventArgs::new(simulator.clone())?;
self.inner.events.sim_connecting.emit_with(|| args.clone());
if args.cancel() {
self.simulators.remove(&simulator);
return Ok(None);
}
match simulator.native_connect(set_default) {
Ok(true) => {}
Ok(false) => {
self.simulators.remove(&simulator);
return Ok(None);
}
Err(error) => {
let _ = simulator.native_disconnect(false);
self.simulators.remove(&simulator);
return Err(error);
}
}
if set_default
&& let Err(error) = self.inner.set_current_sim(simulator.clone(), seed_caps)
{
if newly_created {
let _ = simulator.native_disconnect(false);
self.simulators.remove(&simulator);
}
return Err(error);
}
let args = SimConnectedEventArgs::new(simulator.clone())?;
self.inner.events.sim_connected.emit_with(|| args.clone());
} else if set_default {
simulator.native_use_circuit_code(true)?;
simulator.native_complete_agent_movement()?;
self.inner.set_current_sim(simulator.clone(), seed_caps)?;
}
Ok(Some(simulator))
}
pub async fn native_connect_async(
&self,
endpoint: std::net::SocketAddr,
handle: u64,
set_default: bool,
seed_caps: Option<Uri>,
size_x: u32,
size_y: u32,
) -> Result<Option<Simulator>, Error> {
let manager = Self {
login_response_data: Some(self.inner.login_response.clone()),
simulators: self.simulators.clone(),
inner: Arc::clone(&self.inner),
};
run_blocking_compat("libremetaverse-manager-connect", move || {
manager.native_connect(endpoint, handle, set_default, seed_caps, size_x, size_y)
})
.await?
}
pub fn native_disconnect_sim(
&self,
simulator: Simulator,
send_close_circuit: bool,
) -> Result<(), Error> {
self.inner
.disconnect_simulator(simulator, send_close_circuit)
}
pub fn native_enqueue_incoming(
&self,
incoming: NetworkManagerIncomingPacket,
) -> Result<(), Error> {
self.inner.enqueue_incoming(incoming)
}
pub fn native_enqueue_outgoing(
&self,
outgoing: NetworkManagerOutgoingPacket,
) -> Result<(), Error> {
self.inner.enqueue_outgoing(outgoing)
}
#[must_use]
pub fn native_find_endpoint(&self, endpoint: std::net::SocketAddr) -> Option<Simulator> {
self.simulators
.snapshot()
.into_iter()
.find(|simulator| simulator.ip_end_point() == endpoint)
}
#[must_use]
pub fn native_find_handle(&self, handle: u64) -> Option<Simulator> {
self.simulators
.snapshot()
.into_iter()
.find(|simulator| simulator.handle == handle)
}
pub fn native_send_packet(
&self,
packet: Packet,
simulator: Option<Simulator>,
) -> Result<(), Error> {
let simulator = simulator
.or_else(|| self.native_current_sim())
.or_else(|| self.simulators.snapshot().into_iter().next())
.ok_or(Error::InvalidOperation)?;
simulator.native_send_packet(packet)
}
fn send_logout_request_if_needed(&self) -> Result<(), Error> {
let Some(simulator) = self.native_current_sim() else {
return Ok(());
};
if !self.native_connected() {
return Ok(());
}
loop {
match self.inner.lifecycle_state() {
NetworkLifecycleState::Connected => {
if self
.inner
.lifecycle_state
.compare_exchange(
NetworkLifecycleState::Connected as u8,
NetworkLifecycleState::LoggingOut as u8,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
break;
}
}
NetworkLifecycleState::LoggingOut
| NetworkLifecycleState::Disconnected
| NetworkLifecycleState::Connecting
| NetworkLifecycleState::ShuttingDown => return Ok(()),
}
}
{
let mut cancellation = mutex(&self.inner.logout_cancellation);
if cancellation.token().is_cancellation_requested() {
*cancellation = CancellationTokenSource::new();
}
}
let result = (|| {
let mut packet = LogoutRequestPacket::new_with_constructor()?;
packet.agent_data.agent_id = *read(&self.inner.agent_id);
packet.agent_data.session_id = *read(&self.inner.session_id);
let data = packet.to_bytes_with_method()?;
simulator.native_send_packet_data(
data.clone(),
i32::try_from(data.len()).map_err(|_| Error::Argument)?,
PacketType::LogoutRequest,
false,
)
})();
if result.is_err() {
let _ = self.inner.lifecycle_state.compare_exchange(
NetworkLifecycleState::LoggingOut as u8,
NetworkLifecycleState::Connected as u8,
Ordering::AcqRel,
Ordering::Acquire,
);
}
result
}
fn logout_signal(&self) -> (Receiver<()>, Subscription) {
let (sender, receiver) = sync_channel(1);
let subscription = self.native_subscribe_logged_out(Arc::new(move |_| {
let _ = sender.try_send(());
}));
(receiver, subscription)
}
fn finish_logout_wait(
&self,
outcome: LogoutWaitOutcome,
emit_timeout_logout: bool,
) -> Result<(), Error> {
match outcome {
LogoutWaitOutcome::Reply => {
self.native_shutdown(
crate::NetworkManagerDisconnectType::ClientInitiated,
"ClientInitiated".to_owned(),
)?;
let deadline = std::time::Instant::now()
+ positive_millisecond_duration(
self.inner.client.settings_ref().timing.logout_timeout,
);
while self.inner.lifecycle_state() != NetworkLifecycleState::Disconnected
&& std::time::Instant::now() < deadline
{
thread::sleep(Duration::from_millis(1));
}
Ok(())
}
LogoutWaitOutcome::Timeout => {
self.native_shutdown(
crate::NetworkManagerDisconnectType::NetworkTimeout,
"NetworkTimeout".to_owned(),
)?;
if emit_timeout_logout {
let args = LoggedOutEventArgs::new(Vec::new())?;
self.inner.events.logged_out.emit_with(|| args.clone());
}
Ok(())
}
LogoutWaitOutcome::Cancelled => {
let _ = self.inner.lifecycle_state.compare_exchange(
NetworkLifecycleState::LoggingOut as u8,
NetworkLifecycleState::Connected as u8,
Ordering::AcqRel,
Ordering::Acquire,
);
Err(Error::Cancelled)
}
}
}
fn logout_blocking(&self, emit_timeout_logout: bool) -> Result<(), Error> {
if !self.native_connected() || self.native_current_sim().is_none() {
return Ok(());
}
let (receiver, _subscription) = self.logout_signal();
self.send_logout_request_if_needed()?;
let timeout =
positive_millisecond_duration(self.inner.client.settings_ref().timing.logout_timeout);
let lifecycle_cancellation = mutex(&self.inner.logout_cancellation).token();
self.finish_logout_wait(
wait_for_logout(&receiver, timeout, &[lifecycle_cancellation]),
emit_timeout_logout,
)
}
pub fn native_begin_logout(&self) -> Result<(), Error> {
if !self.native_connected() || self.native_current_sim().is_none() {
return Ok(());
}
*mutex(&self.inner.last_logout_error) = None;
let manager = Self {
login_response_data: Some(self.inner.login_response.clone()),
simulators: self.simulators.clone(),
inner: Arc::clone(&self.inner),
};
self.inner.logout_task_count.fetch_add(1, Ordering::AcqRel);
let result = thread::Builder::new()
.name("libremetaverse-logout".to_owned())
.spawn(move || {
if let Err(error) = manager.logout_blocking(true) {
*mutex(&manager.inner.last_logout_error) = Some(error);
}
manager
.inner
.logout_task_count
.fetch_sub(1, Ordering::AcqRel);
})
.map(|_| ())
.map_err(|_| Error::InvalidOperation);
if result.is_err() {
self.inner.logout_task_count.fetch_sub(1, Ordering::AcqRel);
*mutex(&self.inner.last_logout_error) = Some(Error::InvalidOperation);
}
result
}
pub fn native_logout(&self) -> Result<(), Error> {
self.logout_blocking(false)
}
pub async fn native_logout_async(
&self,
cancellation_token: Option<CancellationToken>,
) -> Result<(), Error> {
if !self.native_connected() || self.native_current_sim().is_none() {
return Ok(());
}
let (receiver, _subscription) = self.logout_signal();
self.send_logout_request_if_needed()?;
let timeout =
positive_millisecond_duration(self.inner.client.settings_ref().timing.logout_timeout);
let cancellations = vec![
cancellation_token.unwrap_or_default(),
mutex(&self.inner.logout_cancellation).token(),
];
let outcome = run_blocking_compat("libremetaverse-logout-wait", move || {
wait_for_logout(&receiver, timeout, &cancellations)
})
.await?;
self.finish_logout_wait(outcome, false)
}
pub fn native_request_logout(&self) -> Result<(), Error> {
self.send_logout_request_if_needed()
}
/// Number of nonblocking logout waiters still owned by this manager.
#[must_use]
pub fn pending_logout_tasks(&self) -> i32 {
self.inner.logout_task_count.load(Ordering::Acquire)
}
/// Last error produced by a detached [`Self::native_begin_logout`] waiter.
#[must_use]
pub fn last_logout_error(&self) -> Option<Error> {
*mutex(&self.inner.last_logout_error)
}
pub fn native_shutdown(
&self,
reason: crate::NetworkManagerDisconnectType,
message: String,
) -> Result<(), Error> {
self.inner.shutdown(reason, message)
}
pub async fn native_shutdown_async(
&self,
reason: crate::NetworkManagerDisconnectType,
message: String,
) -> Result<(), Error> {
let inner = Arc::clone(&self.inner);
run_blocking_compat("libremetaverse-manager-shutdown", move || {
inner.shutdown(reason, message)
})
.await?
}
#[must_use]
pub fn native_circuit_code(&self) -> u32 {
self.inner.circuit_code.load(Ordering::Acquire)
}
pub fn native_set_circuit_code(&self, value: u32) {
self.inner.circuit_code.store(value, Ordering::Release);
for simulator in self.simulators.snapshot() {
simulator.circuit_code.store(value, Ordering::Release);
}
}
#[must_use]
pub fn native_connected(&self) -> bool {
self.inner.connected.load(Ordering::Acquire)
}
pub fn native_set_connected(&self, value: bool) {
self.inner.connected.store(value, Ordering::Release);
if value && self.inner.lifecycle_state() == NetworkLifecycleState::Disconnected {
self.inner
.set_lifecycle_state(NetworkLifecycleState::Connected);
}
}
#[must_use]
pub fn native_current_sim(&self) -> Option<Simulator> {
read(&self.inner.current_sim).clone()
}
pub fn native_set_current_sim(&self, value: Option<Simulator>) {
*write(&self.inner.current_sim) = value;
}
#[must_use]
pub fn native_inbox_count(&self) -> i32 {
self.inner.inbox_count.load(Ordering::Acquire)
}
#[must_use]
pub fn native_outbox_count(&self) -> i32 {
self.inner.outbox_count.load(Ordering::Acquire)
}
/// Executes one deterministic keepalive transition without sleeping.
pub fn poll_keepalive(&self) {
self.inner.keepalive_tick();
}
}
#[cfg(test)]
mod tests {
use super::*;
use libremetaverse_structured_data::{OSD, OSDMap};
use std::sync::mpsc;
#[test]
fn manager_workers_and_callback_registries_do_not_retain_the_client() {
let client = GridClient::new().expect("client");
let probe = client.retention_probe();
let manager = NetworkManager::new(client.clone()).expect("network manager");
manager.inner.ensure_workers().expect("manager workers");
let subscription = manager.subscribe_packet(PacketType::Default, Arc::new(|_| {}), true);
drop(subscription);
drop(manager);
drop(client);
assert!(probe.is_released());
}
#[test]
fn caps_packet_fallback_uses_the_bounded_udp_dispatch_pipeline() {
let client = GridClient::new().expect("client");
let manager = NetworkManager::new(client.clone()).expect("network manager");
manager.inner.ensure_workers().expect("workers");
let simulator =
Simulator::new(client, "127.0.0.1:14000".parse().unwrap(), 10, None, None).unwrap();
simulator.attach_manager(&manager.inner, 0);
manager.simulators.push_if_missing(simulator.clone());
let (sender, receiver) = mpsc::channel();
let _subscription = manager.subscribe_packet(
PacketType::PacketAck,
Arc::new(move |args| {
sender
.send((args.packet().type_, args.simulator().handle))
.unwrap();
}),
false,
);
let body = OSDMap::new_with_constructor().unwrap();
body.add_with_string_osd("Packets".to_owned(), OSD::Array(Vec::new()))
.unwrap();
let packet = Packet::build_packet_with_string_osd_map("PacketAck".to_owned(), body)
.unwrap()
.expect("packet fallback");
simulator.native_enqueue_caps_packet(packet).unwrap();
assert_eq!(
receiver.recv_timeout(Duration::from_secs(2)).unwrap(),
(PacketType::PacketAck, 10)
);
}
#[test]
fn event_queue_running_and_typed_caps_events_use_manager_registries() {
let client = GridClient::new().expect("client");
let manager = NetworkManager::new(client.clone()).expect("network manager");
let simulator =
Simulator::new(client, "127.0.0.1:14001".parse().unwrap(), 11, None, None).unwrap();
simulator.attach_manager(&manager.inner, 0);
let (running_sender, running_receiver) = mpsc::channel();
let _running = manager.native_subscribe_event_queue_running(Arc::new(move |args| {
running_sender.send(args.simulator().handle).unwrap();
}));
simulator.native_raise_event_queue_running();
assert_eq!(
running_receiver
.recv_timeout(Duration::from_secs(1))
.unwrap(),
11
);
let (caps_sender, caps_receiver) = mpsc::channel();
let _caps = manager.subscribe_caps(
"UpdateAgentLanguage".to_owned(),
CapsEventQueueCallback::from_handler(move |name, message, simulator| {
let typed = (message as &dyn std::any::Any)
.downcast_ref::<crate::messages::linden::UpdateAgentLanguageMessage>();
caps_sender
.send((name.to_owned(), typed.is_some(), simulator.handle))
.unwrap();
}),
);
let body = OSDMap::new_with_dictionary(HashMap::from([
("language".to_owned(), OSD::String("en".to_owned())),
("language_is_public".to_owned(), OSD::Boolean(true)),
]))
.unwrap();
let message = crate::message_decoder::decode_event("UpdateAgentLanguage", &body)
.unwrap()
.unwrap();
simulator.native_dispatch_caps_event("UpdateAgentLanguage", message.as_ref());
assert_eq!(
caps_receiver.recv_timeout(Duration::from_secs(1)).unwrap(),
("UpdateAgentLanguage".to_owned(), true, 11)
);
}
}