887 lines
34 KiB
Rust
887 lines
34 KiB
Rust
//! 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));
|
|
}
|
|
}
|