Implement social and group managers (#72)
All checks were successful
Native code generation / deterministic (push) Successful in 15m31s
Imaging and meshing gate / native (push) Successful in 5m21s
Native Rust workspace compile / compile (push) Successful in 5m17s

This commit is contained in:
2026-08-10 18:35:09 +00:00
parent 66fbb87b3f
commit c22eef5d0e
8 changed files with 2852 additions and 1345 deletions

View File

@@ -126,6 +126,8 @@ struct ClientRuntime {
grid_manager: Mutex<Option<Arc<crate::grid_manager::GridManagerInner>>>,
directory_manager: Mutex<Option<Arc<crate::directory_manager::DirectoryManagerInner>>>,
interest_list_manager: Mutex<Option<Arc<crate::interest_list::InterestListManagerInner>>>,
friends_manager: Mutex<Option<Arc<crate::friends_manager::FriendsManagerInner>>>,
group_manager: Mutex<Option<Arc<crate::group_manager::GroupManagerInner>>>,
appearance_manager: Mutex<Option<Arc<crate::appearance_manager::AppearanceManagerInner>>>,
avatar_manager: Mutex<Option<Arc<crate::avatar_manager::AvatarManagerInner>>>,
animesh_manager: Mutex<Option<Arc<crate::animesh_runtime::AnimeshManagerInner>>>,
@@ -168,6 +170,8 @@ impl ClientRuntime {
grid_manager: Mutex::new(None),
directory_manager: Mutex::new(None),
interest_list_manager: Mutex::new(None),
friends_manager: Mutex::new(None),
group_manager: Mutex::new(None),
appearance_manager: Mutex::new(None),
avatar_manager: Mutex::new(None),
animesh_manager: Mutex::new(None),
@@ -270,6 +274,14 @@ impl ClientRuntime {
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take();
self.friends_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take();
self.group_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take();
self.http_caps_client
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
@@ -977,6 +989,82 @@ impl GridClient {
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner);
}
pub(crate) fn cached_friends_manager_inner(
&self,
) -> Option<Arc<crate::friends_manager::FriendsManagerInner>> {
self.runtime
.friends_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
pub(crate) fn install_friends_manager_inner(
&self,
candidate: Arc<crate::friends_manager::FriendsManagerInner>,
) -> Arc<crate::friends_manager::FriendsManagerInner> {
let mut cached = self
.runtime
.friends_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inner) = cached.as_ref() {
return Arc::clone(inner);
}
*cached = Some(Arc::clone(&candidate));
candidate
}
pub(crate) fn native_friends(&self) -> Result<crate::FriendsManager, crate::Error> {
if let Some(inner) = self.cached_friends_manager_inner() {
Ok(crate::FriendsManager { inner })
} else {
crate::friends_manager::FriendsManager::native_new(Arc::new(self.clone()))
}
}
pub(crate) fn native_set_friends(&self, value: crate::FriendsManager) {
*self
.runtime
.friends_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner);
}
pub(crate) fn cached_group_manager_inner(
&self,
) -> Option<Arc<crate::group_manager::GroupManagerInner>> {
self.runtime
.group_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
pub(crate) fn install_group_manager_inner(
&self,
candidate: Arc<crate::group_manager::GroupManagerInner>,
) -> Arc<crate::group_manager::GroupManagerInner> {
let mut cached = self
.runtime
.group_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inner) = cached.as_ref() {
return Arc::clone(inner);
}
*cached = Some(Arc::clone(&candidate));
candidate
}
pub(crate) fn native_groups(&self) -> crate::GroupManager {
if let Some(inner) = self.cached_group_manager_inner() {
crate::GroupManager { inner }
} else {
crate::group_manager::GroupManager::native_new(Arc::new(self.clone()))
}
}
pub(crate) fn native_set_groups(&self, value: crate::GroupManager) {
*self
.runtime
.group_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner);
}
pub(crate) fn native_set_caps_rate_limiter(
&mut self,

View File

@@ -0,0 +1,886 @@
//! Friend presence, rights, offers, calling cards, and map tracking.
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::inherent_to_string)] // Fixed mapped CLR ToString method.
use crate::agent_manager::EventRegistry;
use crate::network_manager::RawPacketReceivedEventArgs;
use crate::packet_catalog::{GeneratedPacket, PacketType};
use crate::{Error, FriendRights, GridClient, InstantMessageDialog};
use libremetaverse_structured_data::{OSD, OSDFormat, OSDParser};
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription};
use libremetaverse_types::{FolderType, UUID, Vector3};
use std::collections::{BTreeMap, HashMap};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
const MAX_FRIENDS: usize = 100_000;
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 wire_string(value: &[u8]) -> String {
String::from_utf8_lossy(value.split(|b| *b == 0).next().unwrap_or_default()).into_owned()
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FriendInfo {
id: UUID,
name: String,
online: bool,
their_rights: FriendRights,
my_rights: FriendRights,
}
impl FriendInfo {
pub(crate) fn native_new(
id: UUID,
their_rights: FriendRights,
my_rights: FriendRights,
) -> Self {
Self {
id,
name: String::new(),
online: false,
their_rights,
my_rights,
}
}
pub fn uuid(&self) -> UUID {
self.id
}
pub fn name(&self) -> String {
self.name.clone()
}
pub fn set_name(&mut self, value: String) {
self.name = value;
}
pub fn is_online(&self) -> bool {
self.online
}
pub fn set_is_online(&mut self, value: bool) {
self.online = value;
}
pub fn their_friend_rights(&self) -> FriendRights {
self.their_rights
}
pub fn set_their_friend_rights(&mut self, value: FriendRights) {
self.their_rights = value;
}
pub fn my_friend_rights(&self) -> FriendRights {
self.my_rights
}
pub fn set_my_friend_rights(&mut self, value: FriendRights) {
self.my_rights = value;
}
pub fn can_see_me_online(&self) -> bool {
self.their_rights.0 & FriendRights::CAN_SEE_ONLINE.0 != 0
}
pub fn set_can_see_me_online(&mut self, value: bool) {
set_right(&mut self.their_rights, FriendRights::CAN_SEE_ONLINE, value);
if !value {
set_right(&mut self.their_rights, FriendRights::CAN_SEE_ON_MAP, false);
}
}
pub fn can_see_me_on_map(&self) -> bool {
self.their_rights.0 & FriendRights::CAN_SEE_ON_MAP.0 != 0
}
pub fn set_can_see_me_on_map(&mut self, value: bool) {
if self.can_see_me_online() {
set_right(&mut self.their_rights, FriendRights::CAN_SEE_ON_MAP, value);
}
}
pub fn can_modify_my_objects(&self) -> bool {
self.their_rights.0 & FriendRights::CAN_MODIFY_OBJECTS.0 != 0
}
pub fn set_can_modify_my_objects(&mut self, value: bool) {
set_right(
&mut self.their_rights,
FriendRights::CAN_MODIFY_OBJECTS,
value,
);
}
pub fn can_see_them_online(&self) -> bool {
self.my_rights.0 & FriendRights::CAN_SEE_ONLINE.0 != 0
}
pub fn set_can_see_them_online(&mut self, value: bool) {
set_right(&mut self.my_rights, FriendRights::CAN_SEE_ONLINE, value);
}
pub fn can_see_them_on_map(&self) -> bool {
self.my_rights.0 & FriendRights::CAN_SEE_ON_MAP.0 != 0
}
pub fn set_can_see_them_on_map(&mut self, value: bool) {
set_right(&mut self.my_rights, FriendRights::CAN_SEE_ON_MAP, value);
}
pub fn can_modify_their_objects(&self) -> bool {
self.my_rights.0 & FriendRights::CAN_MODIFY_OBJECTS.0 != 0
}
pub fn set_can_modify_their_objects(&mut self, value: bool) {
set_right(&mut self.my_rights, FriendRights::CAN_MODIFY_OBJECTS, value);
}
pub fn to_string(&self) -> String {
let who = if self.name.is_empty() {
self.id.to_string()
} else {
self.name.clone()
};
format!(
"{who} (Their Rights: {}, My Rights: {})",
self.their_rights.0, self.my_rights.0
)
}
}
fn set_right(rights: &mut FriendRights, flag: FriendRights, enabled: bool) {
if enabled {
rights.0 |= flag.0;
} else {
rights.0 &= !flag.0;
}
}
macro_rules! simple_event {
($name:ident { $($field:ident : $ty:ty => $getter:ident),+ $(,)? }) => {
#[derive(Clone)] pub struct $name { $( $field: $ty ),+ }
impl $name {
pub fn new($($field: $ty),+) -> Result<Self, Error> { Ok(Self { $($field),+ }) }
$(pub fn $getter(&self) -> $ty { self.$field.clone() })+
}
};
}
simple_event!(CallingCardOfferedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, transaction_id: UUID => transaction_id });
simple_event!(FriendFoundReplyEventArgs { agent_id: UUID => agent_id, region_handle: u64 => region_handle, location: Vector3 => location });
simple_event!(FriendInfoEventArgs { friend: FriendInfo => friend });
simple_event!(FriendNamesEventArgs { names: HashMap<UUID, String> => names });
simple_event!(FriendsReadyEventArgs { count: i32 => count });
simple_event!(FriendshipOfferedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, im_session_id: UUID => session_id });
simple_event!(FriendshipResponseEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, accepted: bool => accepted });
simple_event!(FriendshipTerminatedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name });
#[derive(Default)]
struct FriendEvents {
calling_card: EventRegistry<CallingCardOfferedEventArgs>,
found: EventRegistry<FriendFoundReplyEventArgs>,
names: EventRegistry<FriendNamesEventArgs>,
offline: EventRegistry<FriendInfoEventArgs>,
online: EventRegistry<FriendInfoEventArgs>,
rights: EventRegistry<FriendInfoEventArgs>,
offered: EventRegistry<FriendshipOfferedEventArgs>,
response: EventRegistry<FriendshipResponseEventArgs>,
terminated: EventRegistry<FriendshipTerminatedEventArgs>,
ready: EventRegistry<FriendsReadyEventArgs>,
}
pub(crate) struct FriendsManagerInner {
client: Arc<GridClient>,
friends: RwLock<HashMap<UUID, FriendInfo>>,
requests: RwLock<HashMap<UUID, UUID>>,
events: FriendEvents,
subscriptions: Mutex<Vec<Subscription>>,
login_callback: Mutex<Option<crate::NetworkManagerLoginResponseCallback>>,
disposed: AtomicBool,
}
#[derive(Clone)]
pub struct FriendsManager {
pub(crate) inner: Arc<FriendsManagerInner>,
}
impl FriendsManager {
pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
if let Some(inner) = client.cached_friends_manager_inner() {
return Ok(Self { inner });
}
let inner = Arc::new(FriendsManagerInner {
client: Arc::clone(&client),
friends: RwLock::new(HashMap::new()),
requests: RwLock::new(HashMap::new()),
events: FriendEvents::default(),
subscriptions: Mutex::new(Vec::new()),
login_callback: Mutex::new(None),
disposed: AtomicBool::new(false),
});
let weak = Arc::downgrade(&inner);
mutex(&inner.subscriptions).push(client.network().subscribe_raw_packet(Arc::new(
move |e| {
if let Some(inner) = weak.upgrade() {
inner.handle_packet(e);
}
},
)));
let weak = Arc::downgrade(&inner);
let mut callback_client = (*client).clone();
mutex(&inner.subscriptions).push(callback_client.self_().subscribe_im(Arc::new(
move |e| {
if let Some(inner) = weak.upgrade() {
inner.handle_im(e);
}
},
)));
let weak = Arc::downgrade(&inner);
let callback = crate::NetworkManagerLoginResponseCallback::from_handler(
move |success, _, _, _, response| {
if success && let Some(inner) = weak.upgrade() {
inner.load_login(response);
}
},
);
client.network().native_register_login_response_callback(
callback.clone(),
Some(vec!["buddy-list".into()]),
)?;
*mutex(&inner.login_callback) = Some(callback);
Ok(Self {
inner: client.install_friends_manager_inner(inner),
})
}
fn ids(&self) -> (UUID, UUID) {
let mut c = (*self.inner.client).clone();
let a = c.self_();
(a.agent_id(), a.session_id())
}
fn send<T: GeneratedPacket>(&self, p: &T, ty: PacketType) -> Result<(), Error> {
if self.inner.disposed.load(Ordering::Acquire) {
return Err(Error::InvalidOperation);
}
let sim = self
.inner
.client
.network()
.current_sim()
.ok_or(Error::InvalidOperation)?;
let bytes = p.encode_packet()?;
let result = sim.native_send_packet_data(
bytes.clone(),
i32::try_from(bytes.len()).map_err(|_| Error::Argument)?,
ty,
false,
);
match result {
Err(Error::InvalidOperation) if !sim.connected() => Ok(()),
result => result,
}
}
pub fn friend_list(&self) -> HashMap<UUID, FriendInfo> {
read(&self.inner.friends).clone()
}
pub fn friend_requests(&self) -> HashMap<UUID, UUID> {
read(&self.inner.requests).clone()
}
pub fn dispose(&self) -> Result<(), Error> {
self.inner.dispose();
Ok(())
}
pub fn subscribe_calling_card_offered(
&self,
h: EventHandler<CallingCardOfferedEventArgs>,
) -> Subscription {
self.inner.events.calling_card.subscribe(h)
}
pub fn subscribe_friend_found_reply(
&self,
h: EventHandler<FriendFoundReplyEventArgs>,
) -> Subscription {
self.inner.events.found.subscribe(h)
}
pub fn subscribe_friend_names(&self, h: EventHandler<FriendNamesEventArgs>) -> Subscription {
self.inner.events.names.subscribe(h)
}
pub fn subscribe_friend_offline(&self, h: EventHandler<FriendInfoEventArgs>) -> Subscription {
self.inner.events.offline.subscribe(h)
}
pub fn subscribe_friend_online(&self, h: EventHandler<FriendInfoEventArgs>) -> Subscription {
self.inner.events.online.subscribe(h)
}
pub fn subscribe_friend_rights_update(
&self,
h: EventHandler<FriendInfoEventArgs>,
) -> Subscription {
self.inner.events.rights.subscribe(h)
}
pub fn subscribe_friendship_offered(
&self,
h: EventHandler<FriendshipOfferedEventArgs>,
) -> Subscription {
self.inner.events.offered.subscribe(h)
}
pub fn subscribe_friendship_response(
&self,
h: EventHandler<FriendshipResponseEventArgs>,
) -> Subscription {
self.inner.events.response.subscribe(h)
}
pub fn subscribe_friendship_terminated(
&self,
h: EventHandler<FriendshipTerminatedEventArgs>,
) -> Subscription {
self.inner.events.terminated.subscribe(h)
}
pub fn subscribe_friends_list_ready(
&self,
h: EventHandler<FriendsReadyEventArgs>,
) -> Subscription {
self.inner.events.ready.subscribe(h)
}
pub fn accept_friendship(&self, from: UUID, transaction: UUID) -> Result<(), Error> {
let (agent, session) = self.ids();
let mut p = crate::packets::AcceptFriendshipPacket::new_with_constructor()?;
p.agent_data.agent_id = agent;
p.agent_data.session_id = session;
p.transaction_block.transaction_id = transaction;
let mut folder =
crate::packets::AcceptFriendshipPacketFolderDataBlock::new_with_constructor()?;
folder.folder_id = self
.inner
.client
.inventory()
.find_folder_for_type_with_folder_type(FolderType::CallingCard)?;
p.folder_data.push(folder);
self.send(&p, PacketType::AcceptFriendship)?;
self.inner.accept(from);
Ok(())
}
pub fn decline_friendship(&self, from: UUID, transaction: UUID) -> Result<(), Error> {
let (agent, session) = self.ids();
let mut p = crate::packets::DeclineFriendshipPacket::new_with_constructor()?;
p.agent_data.agent_id = agent;
p.agent_data.session_id = session;
p.transaction_block.transaction_id = transaction;
self.send(&p, PacketType::DeclineFriendship)?;
write(&self.inner.requests).remove(&from);
Ok(())
}
pub async fn accept_friendship_via_cap(
&self,
from: UUID,
ct: Option<CancellationToken>,
) -> Result<(), Error> {
self.friend_cap("AcceptFriendship", "POST", from, ct)
.await?;
self.inner.accept(from);
Ok(())
}
pub async fn decline_friendship_via_cap(
&self,
from: UUID,
ct: Option<CancellationToken>,
) -> Result<(), Error> {
self.friend_cap("DeclineFriendship", "DELETE", from, ct)
.await?;
write(&self.inner.requests).remove(&from);
Ok(())
}
async fn friend_cap(
&self,
cap: &str,
method: &str,
from: UUID,
ct: Option<CancellationToken>,
) -> Result<(), Error> {
let token = ct.unwrap_or_else(|| self.inner.client.cancellation_token());
token.throw_if_cancellation_requested()?;
let caps = self
.inner
.client
.network()
.current_sim()
.and_then(|s| s.native_caps())
.ok_or(Error::InvalidOperation)?;
let uri = caps
.capability_uri(cap.into())?
.ok_or(Error::InvalidOperation)?;
let mut url = reqwest::Url::parse(&uri.0).map_err(|_| Error::Argument)?;
url.query_pairs_mut().append_pair("from", &from.to_string());
let http = self.inner.client.native_http_caps_client();
let (response, bytes) = if method == "POST" {
http.post_with_uri_osd_format_osd_cancellation_token_i_progress(
libremetaverse_types::compat::Uri(url.into()),
OSDFormat::Xml,
OSD::Undefined,
token,
None,
)
.await?
} else {
http.send_custom(
"DELETE",
libremetaverse_types::compat::Uri(url.into()),
BTreeMap::new(),
None,
Vec::new(),
token,
)
.await?
};
if !response.is_success_status_code() || bytes.len() > 1024 * 1024 {
return Err(Error::InvalidOperation);
}
if !bytes.is_empty() {
let OSD::Map(map) = OSDParser::deserialize_with_bytes(bytes)? else {
return Err(Error::Argument);
};
if !map
.get("success")
.and_then(|v| v.as_boolean().ok())
.unwrap_or(false)
{
return Err(Error::InvalidOperation);
}
}
Ok(())
}
pub fn offer_friendship_with_uuid(&self, id: UUID) -> Result<(), Error> {
self.offer_friendship_with_uuid_string(id, "Do ya wanna be my buddy?".into())
}
pub fn offer_friendship_with_uuid_string(
&self,
id: UUID,
message: String,
) -> Result<(), Error> {
if message.len() > 1024 {
return Err(Error::Argument);
}
let mut client = (*self.inner.client).clone();
let region = client
.network()
.current_sim()
.map_or_else(UUID::zero, |s| s.id);
let me = client.self_();
me.native_instant_message_full(
me.name(),
id,
message,
UUID::random()?,
InstantMessageDialog::FriendshipOffered,
crate::InstantMessageOnline::Offline,
me.sim_position(),
region,
Vec::new(),
)
}
pub fn terminate_friendship(&self, id: UUID) -> Result<(), Error> {
let (agent, session) = self.ids();
let mut p = crate::packets::TerminateFriendshipPacket::new_with_constructor()?;
p.agent_data.agent_id = agent;
p.agent_data.session_id = session;
p.ex_block.other_id = id;
self.send(&p, PacketType::TerminateFriendship)?;
write(&self.inner.friends).remove(&id);
Ok(())
}
pub fn grant_rights(&self, id: UUID, rights: FriendRights) -> Result<(), Error> {
let (agent, session) = self.ids();
let mut p = crate::packets::GrantUserRightsPacket::new_with_constructor()?;
p.agent_data.agent_id = agent;
p.agent_data.session_id = session;
let mut b = crate::packets::GrantUserRightsPacketRightsBlock::new_with_constructor()?;
b.agent_related = id;
b.related_rights = rights.0;
p.rights.push(b);
self.send(&p, PacketType::GrantUserRights)
}
pub fn map_friend(&self, id: UUID) -> Result<(), Error> {
let (agent, _) = self.ids();
let mut p = crate::packets::FindAgentPacket::new_with_constructor()?;
p.agent_block.hunter = agent;
p.agent_block.prey = id;
let b = crate::packets::FindAgentPacketLocationBlockBlock::new_with_constructor()?;
p.location_block.push(b);
self.send(&p, PacketType::FindAgent)
}
pub fn track_friend(&self, id: UUID) -> Result<(), Error> {
let (agent, session) = self.ids();
let mut p = crate::packets::TrackAgentPacket::new_with_constructor()?;
p.agent_data.agent_id = agent;
p.agent_data.session_id = session;
p.target_data.prey_id = id;
self.send(&p, PacketType::TrackAgent)
}
pub fn request_online_notification(&self, id: UUID) -> Result<(), Error> {
let (agent, session) = self.ids();
let mut p = crate::packets::GenericMessagePacket::new_with_constructor()?;
p.agent_data.agent_id = agent;
p.agent_data.session_id = session;
p.method_data.method = b"requestonlinenotification".to_vec();
let mut b = crate::packets::GenericMessagePacketParamListBlock::new_with_constructor()?;
b.parameter = id.to_string().into_bytes();
p.param_list.push(b);
self.send(&p, PacketType::GenericMessage)
}
pub fn give_calling_card(&self, id: UUID) -> Result<(), Error> {
let (agent, session) = self.ids();
let mut p = crate::packets::OfferCallingCardPacket::new_with_constructor()?;
p.agent_data.agent_id = agent;
p.agent_data.session_id = session;
p.agent_block.dest_id = id;
p.agent_block.transaction_id = UUID::random()?;
self.send(&p, PacketType::OfferCallingCard)
}
pub fn accept_calling_card(&self, _from: UUID, transaction: UUID) -> Result<(), Error> {
let (agent, session) = self.ids();
let mut p = crate::packets::AcceptCallingCardPacket::new_with_constructor()?;
p.agent_data.agent_id = agent;
p.agent_data.session_id = session;
p.transaction_block.transaction_id = transaction;
let mut b = crate::packets::AcceptCallingCardPacketFolderDataBlock::new_with_constructor()?;
b.folder_id = self
.inner
.client
.inventory()
.find_folder_for_type_with_folder_type(FolderType::CallingCard)?;
p.folder_data.push(b);
self.send(&p, PacketType::AcceptCallingCard)
}
pub fn decline_calling_card(&self, _from: UUID, transaction: UUID) -> Result<(), Error> {
let (agent, session) = self.ids();
let mut p = crate::packets::DeclineCallingCardPacket::new_with_constructor()?;
p.agent_data.agent_id = agent;
p.agent_data.session_id = session;
p.transaction_block.transaction_id = transaction;
self.send(&p, PacketType::DeclineCallingCard)
}
pub fn on_find_agent_reply_handler(
&self,
_sender: Option<libremetaverse_types::compat::Object>,
e: crate::PacketReceivedEventArgs,
) -> Result<(), Error> {
// The native network pipeline dispatches the payload-bearing raw packet
// before its metadata-only compatibility callback. `handle_packet`
// performs the decode and event emission; this public callback retains
// the upstream type guard without emitting a duplicate event.
if e.packet().type_ == PacketType::FindAgent {
Ok(())
} else {
Err(Error::Argument)
}
}
}
impl FriendsManagerInner {
fn accept(&self, id: UUID) {
let mut friends = write(&self.friends);
if friends.len() < MAX_FRIENDS || friends.contains_key(&id) {
friends.entry(id).or_insert_with(|| {
FriendInfo::native_new(
id,
FriendRights::CAN_SEE_ONLINE,
FriendRights::CAN_SEE_ONLINE,
)
});
}
drop(friends);
write(&self.requests).remove(&id);
}
fn dispose(&self) {
if self.disposed.swap(true, Ordering::AcqRel) {
return;
}
mutex(&self.subscriptions).clear();
if let Some(cb) = mutex(&self.login_callback).take() {
let _ = self
.client
.network()
.native_unregister_login_response_callback(&cb);
}
}
fn load_login(&self, response: Option<crate::LoginResponseData>) {
let Some(response) = response else { return };
let mut friends = write(&self.friends);
for b in response.buddy_list().into_iter().take(MAX_FRIENDS) {
if let Ok(id) = UUID::parse(b.buddy_id.chars().take(36).collect::<String>()) {
friends.entry(id).or_insert_with(|| {
FriendInfo::native_new(
id,
FriendRights(b.buddy_rights_given),
FriendRights(b.buddy_rights_has),
)
});
}
}
let count = i32::try_from(friends.len()).unwrap_or(i32::MAX);
drop(friends);
self.events.ready.emit(FriendsReadyEventArgs { count });
}
fn handle_im(&self, e: crate::InstantMessageEventArgs) {
let im = e.im();
match im.dialog {
InstantMessageDialog::FriendshipOffered => {
let mut requests = write(&self.requests);
if requests.len() < MAX_FRIENDS || requests.contains_key(&im.from_agent_id) {
requests.insert(im.from_agent_id, im.im_session_id);
}
drop(requests);
self.events.offered.emit(FriendshipOfferedEventArgs {
agent_id: im.from_agent_id,
agent_name: im.from_agent_name,
im_session_id: im.im_session_id,
});
}
InstantMessageDialog::FriendshipAccepted => {
let mut f = FriendInfo::native_new(
im.from_agent_id,
FriendRights::CAN_SEE_ONLINE,
FriendRights::CAN_SEE_ONLINE,
);
f.name.clone_from(&im.from_agent_name);
f.online = true;
write(&self.friends).insert(f.id, f.clone());
self.events.response.emit(FriendshipResponseEventArgs {
agent_id: f.id,
agent_name: f.name,
accepted: true,
});
}
InstantMessageDialog::FriendshipDeclined => {
self.events.response.emit(FriendshipResponseEventArgs {
agent_id: im.from_agent_id,
agent_name: im.from_agent_name,
accepted: false,
});
}
_ => {}
}
}
#[allow(
clippy::too_many_lines,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss
)]
fn handle_packet(&self, e: RawPacketReceivedEventArgs) {
if self.disposed.load(Ordering::Acquire) || e.data.len() > 8 * 1024 * 1024 {
return;
}
let mut pos = 0;
match e.packet_type {
PacketType::OnlineNotification | PacketType::OfflineNotification => {
let online = e.packet_type == PacketType::OnlineNotification;
let ids = if online {
crate::packets::OnlineNotificationPacket::new_from_bytes(&e.data, &mut pos).map(
|p| {
p.agent_block
.into_iter()
.map(|b| b.agent_id)
.collect::<Vec<_>>()
},
)
} else {
crate::packets::OfflineNotificationPacket::new_from_bytes(&e.data, &mut pos)
.map(|p| {
p.agent_block
.into_iter()
.map(|b| b.agent_id)
.collect::<Vec<_>>()
})
};
if let Ok(ids) = ids {
for id in ids.into_iter().take(MAX_FRIENDS) {
let event = {
let mut fs = write(&self.friends);
let f = fs.entry(id).or_insert_with(|| {
FriendInfo::native_new(
id,
FriendRights::CAN_SEE_ONLINE,
FriendRights::CAN_SEE_ONLINE,
)
});
let changed = f.online != online;
f.online = online;
changed.then(|| f.clone())
};
if let Some(friend) = event {
if online {
self.events.online.emit(FriendInfoEventArgs { friend });
} else {
self.events.offline.emit(FriendInfoEventArgs { friend });
}
}
}
}
}
PacketType::TerminateFriendship => {
if let Ok(p) =
crate::packets::TerminateFriendshipPacket::new_from_bytes(&e.data, &mut pos)
{
let id = p.ex_block.other_id;
let name = write(&self.friends)
.remove(&id)
.map_or_else(String::new, |f| f.name);
self.events.terminated.emit(FriendshipTerminatedEventArgs {
agent_id: id,
agent_name: name,
});
}
}
PacketType::ChangeUserRights => {
if let Ok(p) =
crate::packets::ChangeUserRightsPacket::new_from_bytes(&e.data, &mut pos)
{
let self_id = {
let mut client = (*self.client).clone();
client.self_().agent_id()
};
for b in p.rights.into_iter().take(MAX_FRIENDS) {
let id = if b.agent_related == self_id {
p.agent_data.agent_id
} else {
b.agent_related
};
let event = {
let mut fs = write(&self.friends);
fs.get_mut(&id).map(|f| {
if b.agent_related == id {
f.their_rights = FriendRights(b.related_rights);
} else {
f.my_rights = FriendRights(b.related_rights);
}
f.clone()
})
};
if let Some(friend) = event {
self.events.rights.emit(FriendInfoEventArgs { friend });
}
}
}
}
PacketType::FindAgent => {
if let Ok(p) = crate::packets::FindAgentPacket::new_from_bytes(&e.data, &mut pos)
&& let Some(loc) = p.location_block.first()
{
let gx = loc.global_x.max(0.0) as u32;
let gy = loc.global_y.max(0.0) as u32;
let handle = (u64::from(gx & !255) << 32) | u64::from(gy & !255);
self.events.found.emit(FriendFoundReplyEventArgs {
agent_id: p.agent_block.prey,
region_handle: handle,
location: Vector3 {
x: (gx & 255) as f32,
y: (gy & 255) as f32,
z: 0.0,
},
});
}
}
PacketType::OfferCallingCard => {
if let Ok(p) =
crate::packets::OfferCallingCardPacket::new_from_bytes(&e.data, &mut pos)
{
let id = p.agent_data.agent_id;
let name = read(&self.friends).get(&id).map_or_else(
|| id.to_string(),
|f| {
if f.name.is_empty() {
id.to_string()
} else {
f.name.clone()
}
},
);
self.events.calling_card.emit(CallingCardOfferedEventArgs {
agent_id: id,
agent_name: name,
transaction_id: p.agent_block.transaction_id,
});
}
}
PacketType::UUIDNameReply => {
if let Ok(p) =
crate::packets::UUIDNameReplyPacket::new_from_bytes(&e.data, &mut pos)
{
let mut changed = HashMap::new();
{
let mut fs = write(&self.friends);
for b in p.uuid_name_block.into_iter().take(MAX_FRIENDS) {
if let Some(f) = fs.get_mut(&b.id) {
let name = format!(
"{} {}",
wire_string(&b.first_name),
wire_string(&b.last_name)
)
.trim()
.to_owned();
if f.name != name {
f.name.clone_from(&name);
changed.insert(b.id, name);
}
}
}
}
if !changed.is_empty() {
self.events
.names
.emit(FriendNamesEventArgs { names: changed });
}
}
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
#[test]
fn rights_enforce_online_before_map() {
let mut f = FriendInfo::native_new(UUID::zero(), FriendRights::NONE, FriendRights::NONE);
f.set_can_see_me_on_map(true);
assert!(!f.can_see_me_on_map());
f.set_can_see_me_online(true);
f.set_can_see_me_on_map(true);
assert!(f.can_see_me_on_map());
f.set_can_see_me_online(false);
assert!(!f.can_see_me_on_map());
}
#[test]
fn presence_packet_updates_state_before_callback() {
let client = GridClient::new().unwrap();
let manager = FriendsManager::native_new(Arc::new(client.clone())).unwrap();
let friend_id =
UUID::new_with_string("77777777-7777-7777-7777-777777777777".into()).unwrap();
write(&manager.inner.friends).insert(
friend_id,
FriendInfo::native_new(friend_id, FriendRights::NONE, FriendRights::NONE),
);
let callback_manager = manager.clone();
let observed = Arc::new(AtomicBool::new(false));
let observed_copy = Arc::clone(&observed);
let _subscription = manager.subscribe_friend_online(Arc::new(move |event| {
assert!(callback_manager.friend_list()[&event.friend().uuid()].is_online());
observed_copy.store(true, Ordering::Release);
}));
let mut packet = crate::packets::OnlineNotificationPacket::new_with_constructor().unwrap();
let mut row =
crate::packets::OnlineNotificationPacketAgentBlockBlock::new_with_constructor()
.unwrap();
row.agent_id = friend_id;
packet.agent_block = vec![row];
let simulator = crate::Simulator::native_new(
client,
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13973),
0,
Some(256),
Some(256),
)
.unwrap();
manager.inner.handle_packet(RawPacketReceivedEventArgs {
packet_type: PacketType::OnlineNotification,
data: packet.encode_packet().unwrap(),
simulator,
});
assert!(observed.load(Ordering::Acquire));
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,9 @@ mod current_outfit;
mod download_manager;
mod environment_manager;
mod event_queue;
mod friends_manager;
mod gesture;
mod group_manager;
mod initial_outfit;
mod inventory;
mod inventory_ais;