feat: stabilize OpenSim interactions and landmarks
Some checks failed
CI / rust-skia (Rust only) (push) Has been cancelled
CI / required (push) Has been cancelled

This commit is contained in:
2026-08-21 22:46:10 +02:00
parent adf5165033
commit e4e84dcdcb
27 changed files with 536 additions and 101 deletions

View File

@@ -32,7 +32,7 @@ use crate::{
};
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
use libremetaverse_types::compat::{EventHandler, Subscription, Uri};
use libremetaverse_types::{Color4, UUID, Vector3, Vector3d, Vector4};
use libremetaverse_types::{Color4, UUID, Utils, Vector3, Vector3d, Vector4};
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::panic::{AssertUnwindSafe, catch_unwind};
@@ -82,6 +82,13 @@ fn send_simulator_packet(
simulator.native_send_packet_data(data, length, packet_type, zerocoded)
}
fn open_sim_im_wire(mut data: Vec<u8>) -> Result<Vec<u8>, Error> {
// OpenSim's own IM sender deliberately disables protocol zerocoding.
let flags = data.first_mut().ok_or(Error::Argument)?;
*flags &= !crate::Helpers::MSG_ZEROCODED;
Ok(data)
}
fn parse_mute_list(text: &str) -> HashMap<String, MuteEntry> {
let mut result = HashMap::new();
for line in text.lines().map(str::trim).filter(|line| !line.is_empty()) {
@@ -2331,10 +2338,10 @@ impl AgentManager {
packet.agent_data.agent_id = self.native_agent_id();
packet.agent_data.session_id = self.native_session_id();
packet.message_block.dialog = dialog as u8;
packet.message_block.from_agent_name = from_name.as_bytes().to_vec();
packet.message_block.from_agent_name = Utils::string_to_bytes(from_name.clone())?;
packet.message_block.from_group = false;
packet.message_block.id = im_session_id;
packet.message_block.message = message.into_bytes();
packet.message_block.message = Utils::string_to_bytes(message)?;
packet.message_block.offline = offline as u8;
packet.message_block.to_agent_id = target;
packet
@@ -2346,7 +2353,7 @@ impl AgentManager {
packet.message_block.region_id = region_id;
self.send_packet(
crate::packets::PacketType::ImprovedInstantMessage,
packet.to_bytes_with_method()?,
open_sim_im_wire(packet.to_bytes_with_method()?)?,
)?;
}
Ok(())
@@ -4767,6 +4774,39 @@ mod tests {
encoded
}
#[test]
fn open_sim_im_wire_uses_legacy_unencoded_layout() {
let packet = crate::packets::ImprovedInstantMessagePacket::new_with_constructor().unwrap();
let generated = packet.to_bytes_with_method().unwrap();
let wire = open_sim_im_wire(generated.clone()).unwrap();
assert_eq!(wire.len(), generated.len());
assert_eq!(wire[0] & crate::Helpers::MSG_ZEROCODED, 0);
assert_eq!(&wire[wire.len() - 5..], &[0, 0, 0, 0, 0]);
}
#[test]
fn open_sim_im_without_metadata_count_reaches_subscribers() {
let client = GridClient::new().unwrap();
let inner = AgentManagerInner::default();
let received = Arc::new(Mutex::new(Vec::new()));
let values = Arc::clone(&received);
let _subscription = inner.instant_message.subscribe(Arc::new(move |event| {
values.lock().unwrap().push(event.im().message);
}));
let mut packet =
crate::packets::ImprovedInstantMessagePacket::new_with_constructor().unwrap();
packet.message_block.dialog = InstantMessageDialog::MessageFromAgent as u8;
packet.message_block.message = b"legacy OpenSim IM\0".to_vec();
let mut wire = open_sim_im_wire(packet.to_bytes_with_method().unwrap()).unwrap();
assert_eq!(wire.pop(), Some(0));
inner
.handle_instant_message_packet(&wire, simulator(&client, loopback(13000)))
.unwrap();
assert_eq!(*received.lock().unwrap(), ["legacy OpenSim IM"]);
}
#[test]
fn chat_uses_utf8_safe_protocol_chunks_and_exact_identity() {
let client = Arc::new(GridClient::new().expect("client"));

View File

@@ -21,8 +21,8 @@ use crate::packets::{
use crate::{
AgentFlags, AgentManagerControlFlags, AgentManagerCrossingFailureReason,
AgentManagerCrossingState, AgentState, BorderCrossingDirection, Error, GridClient,
InstantMessageDialog, InstantMessageOnline, NetworkManager, Simulator, TeleportFlags,
TeleportStatus,
GridLayerType, InstantMessageDialog, InstantMessageOnline, NetworkManager, Simulator,
TeleportFlags, TeleportStatus,
};
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri};
use libremetaverse_types::{Quaternion, UUID, Vector3, Vector3d, Vector4};
@@ -2779,7 +2779,7 @@ impl AgentManager {
);
return Ok(false);
};
let handle = if current.name.eq_ignore_ascii_case(&sim_name) {
let connected_handle = if current.name.eq_ignore_ascii_case(&sim_name) {
Some(current.handle)
} else {
self.client
@@ -2790,6 +2790,20 @@ impl AgentManager {
.find(|simulator| simulator.name.eq_ignore_ascii_case(&sim_name))
.map(|simulator| simulator.handle)
};
let handle = if let Some(handle) = connected_handle {
Some(handle)
} else {
self.client
.grid()
.get_grid_region_with_string_grid_layer_type_cancellation_token(
sim_name.clone(),
GridLayerType::Objects,
cancellation_token.clone(),
)
.await?
.flatten()
.map(|region| region.region_handle)
};
let Some(handle) = handle else {
self.movement.runtime.teleport_event(
format!("Unable to resolve simulator named: {sim_name}"),

View File

@@ -570,7 +570,7 @@ impl GridManager {
p.agent_data.agent_id = agent;
p.agent_data.session_id = session;
p.agent_data.flags = layer as u32;
p.name_data.name = Utils::string_to_bytes(name.to_owned())?;
p.name_data.name = Utils::string_to_bytes(name.to_ascii_lowercase())?;
self.send(&p, PacketType::MapNameRequest)
}
pub fn request_map_items(

View File

@@ -3393,11 +3393,14 @@ impl InventoryManager {
packet_type: PacketType,
bytes: Vec<u8>,
) -> Result<(), Error> {
let zerocoded = bytes
.first()
.is_some_and(|flags| flags & crate::Helpers::MSG_ZEROCODED != 0);
simulator.native_send_packet_data(
bytes.clone(),
i32::try_from(bytes.len()).map_err(|_| Error::Argument)?,
packet_type,
false,
zerocoded,
)
}

View File

@@ -149272,7 +149272,11 @@ impl GeneratedPacket for crate::packets::ImprovedInstantMessagePacket {
GeneratedBlock::decode_payload(&mut self.agent_data, reader)?;
GeneratedBlock::decode_payload(&mut self.message_block, reader)?;
GeneratedBlock::decode_payload(&mut self.estate_block, reader)?;
let meta_data_count = usize::from(reader.read_u8()?);
let meta_data_count = if reader.is_empty() {
0
} else {
usize::from(reader.read_u8()?)
};
let mut meta_data = Vec::new();
meta_data
.try_reserve_exact(meta_data_count)

View File

@@ -61,6 +61,10 @@ impl<'a> WireReader<'a> {
Ok(())
}
pub(crate) const fn is_empty(&self) -> bool {
self.position == self.end
}
pub(crate) fn read_u8(&mut self) -> Result<u8, Error> {
Ok(self.take(1, "truncated u8")?[0])
}