implement room bans

This commit is contained in:
Nikita Vilunov 2023-02-16 22:49:17 +01:00
parent 204126b9fb
commit e813fb7c69
4 changed files with 88 additions and 19 deletions

View File

@ -8,7 +8,7 @@
//! A player actor is a serial handler of commands from a single player. It is preferable to run all per-player validations in the player actor, //! A player actor is a serial handler of commands from a single player. It is preferable to run all per-player validations in the player actor,
//! so that they don't overload the room actor. //! so that they don't overload the room actor.
use std::{ use std::{
collections::HashMap, collections::{HashMap, HashSet},
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
@ -58,7 +58,7 @@ impl PlayerConnection {
.await .await
} }
pub async fn join_room(&mut self, room_id: RoomId) -> Result<RoomInfo> { pub async fn join_room(&mut self, room_id: RoomId) -> Result<JoinResult> {
self.player_handle self.player_handle
.join_room(room_id, self.connection_id.clone()) .join_room(room_id, self.connection_id.clone())
.await .await
@ -96,7 +96,9 @@ impl PlayerConnection {
pub async fn get_rooms(&self) -> Result<Vec<RoomInfo>> { pub async fn get_rooms(&self) -> Result<Vec<RoomInfo>> {
let (promise, deferred) = oneshot(); let (promise, deferred) = oneshot();
self.player_handle.send(PlayerCommand::GetRooms(promise)).await; self.player_handle
.send(PlayerCommand::GetRooms(promise))
.await;
Ok(deferred.await?) Ok(deferred.await?)
} }
} }
@ -141,7 +143,7 @@ impl PlayerHandle {
&self, &self,
room_id: RoomId, room_id: RoomId,
connection_id: ConnectionId, connection_id: ConnectionId,
) -> Result<RoomInfo> { ) -> Result<JoinResult> {
let (promise, deferred) = oneshot(); let (promise, deferred) = oneshot();
let cmd = Cmd::JoinRoom { let cmd = Cmd::JoinRoom {
room_id, room_id,
@ -177,7 +179,7 @@ enum PlayerCommand {
pub enum Cmd { pub enum Cmd {
JoinRoom { JoinRoom {
room_id: RoomId, room_id: RoomId,
promise: Promise<RoomInfo>, promise: Promise<JoinResult>,
}, },
LeaveRoom { LeaveRoom {
room_id: RoomId, room_id: RoomId,
@ -195,6 +197,11 @@ pub enum Cmd {
}, },
} }
pub enum JoinResult {
Success(RoomInfo),
Banned,
}
/// Player update event type which is sent to a player actor and from there to a connection handler. /// Player update event type which is sent to a player actor and from there to a connection handler.
#[derive(Clone)] #[derive(Clone)]
pub enum Updates { pub enum Updates {
@ -215,6 +222,8 @@ pub enum Updates {
room_id: RoomId, room_id: RoomId,
former_member_id: PlayerId, former_member_id: PlayerId,
}, },
/// The player was banned from the room and left it immediately.
BannedFrom(RoomId),
} }
/// Handle to a player registry — a shared data structure containing information about players. /// Handle to a player registry — a shared data structure containing information about players.
@ -266,6 +275,7 @@ struct Player {
player_id: PlayerId, player_id: PlayerId,
connections: AnonTable<Sender<Updates>>, connections: AnonTable<Sender<Updates>>,
my_rooms: HashMap<RoomId, RoomHandle>, my_rooms: HashMap<RoomId, RoomHandle>,
banned_from: HashSet<RoomId>,
rx: Receiver<PlayerCommand>, rx: Receiver<PlayerCommand>,
handle: PlayerHandle, handle: PlayerHandle,
rooms: RoomRegistry, rooms: RoomRegistry,
@ -279,6 +289,7 @@ impl Player {
player_id, player_id,
connections: AnonTable::new(), connections: AnonTable::new(),
my_rooms: HashMap::new(), my_rooms: HashMap::new(),
banned_from: HashSet::from([RoomId::from_bytes(b"empty".to_vec()).unwrap()]),
rx, rx,
handle, handle,
rooms, rooms,
@ -312,6 +323,13 @@ impl Player {
"Player received an update, broadcasting to {} connections", "Player received an update, broadcasting to {} connections",
self.connections.len() self.connections.len()
); );
match update {
Updates::BannedFrom(ref room_id) => {
self.banned_from.insert(room_id.clone());
self.my_rooms.remove(room_id);
}
_ => {}
}
for (_, connection) in &self.connections { for (_, connection) in &self.connections {
connection.send(update.clone()).await; connection.send(update.clone()).await;
} }
@ -331,12 +349,17 @@ impl Player {
async fn handle_cmd(&mut self, cmd: Cmd, connection_id: ConnectionId) { async fn handle_cmd(&mut self, cmd: Cmd, connection_id: ConnectionId) {
match cmd { match cmd {
Cmd::JoinRoom { room_id, promise } => { Cmd::JoinRoom { room_id, promise } => {
if self.banned_from.contains(&room_id) {
promise.send(JoinResult::Banned);
return;
}
let room = self.rooms.get_or_create_room(room_id.clone()); let room = self.rooms.get_or_create_room(room_id.clone());
room.subscribe(self.player_id.clone(), self.handle.clone()) room.subscribe(self.player_id.clone(), self.handle.clone())
.await; .await;
self.my_rooms.insert(room_id.clone(), room.clone()); self.my_rooms.insert(room_id.clone(), room.clone());
let room_info = room.get_room_info().await; let room_info = room.get_room_info().await;
promise.send(room_info); promise.send(JoinResult::Success(room_info));
let update = Updates::RoomJoined { let update = Updates::RoomJoined {
room_id, room_id,
new_member_id: self.player_id.clone(), new_member_id: self.player_id.clone(),

View File

@ -7,7 +7,7 @@ use tokio::net::tcp::{ReadHalf, WriteHalf};
use tokio::net::{TcpListener, TcpStream}; use tokio::net::{TcpListener, TcpStream};
use tokio::sync::oneshot::channel; use tokio::sync::oneshot::channel;
use crate::core::player::{PlayerConnection, PlayerId, PlayerRegistry, Updates}; use crate::core::player::*;
use crate::core::room::{RoomId, RoomInfo, RoomRegistry}; use crate::core::room::{RoomId, RoomInfo, RoomRegistry};
use crate::prelude::*; use crate::prelude::*;
use crate::protos::irc::client::{client_message, ClientMessage}; use crate::protos::irc::client::{client_message, ClientMessage};
@ -320,6 +320,17 @@ async fn handle_update(
.await?; .await?;
writer.flush().await? writer.flush().await?
} }
Updates::BannedFrom(room_id) => {
// TODO think about the case when the user was banned, but was not in the room - no need to send PART in this case
ServerMessage {
tags: vec![],
sender: Some(player_id.as_bytes().clone()),
body: ServerMessageBody::Part(Chan::Global(room_id.as_bytes().clone())),
}
.write_async(writer)
.await?;
writer.flush().await?
}
} }
Ok(()) Ok(())
} }
@ -453,7 +464,10 @@ async fn handle_incoming_message(
ServerMessage { ServerMessage {
tags: vec![], tags: vec![],
sender: Some(config.server_name.as_bytes().to_vec()), sender: Some(config.server_name.as_bytes().to_vec()),
body: ServerMessageBody::N221UserModeIs { client: user.nickname.clone(), modes: b"+r".to_vec() }, body: ServerMessageBody::N221UserModeIs {
client: user.nickname.clone(),
modes: b"+r".to_vec(),
},
} }
.write_async(writer) .write_async(writer)
.await?; .await?;
@ -461,12 +475,12 @@ async fn handle_incoming_message(
} else { } else {
// TODO send 502 (not 401) if the user is not the sender // TODO send 502 (not 401) if the user is not the sender
} }
}, }
Recipient::Chan(_) => { Recipient::Chan(_) => {
// TODO handle chan mode handling // TODO handle chan mode handling
}, }
} }
}, }
cmd => { cmd => {
log::warn!("Not implemented handler for client command: {cmd:?}"); log::warn!("Not implemented handler for client command: {cmd:?}");
} }
@ -478,7 +492,11 @@ async fn handle_incoming_message(
Ok(()) Ok(())
} }
fn user_to_who_msg(config: &ServerConfig, requestor: &RegisteredUser, target_user_nickname: &ByteVec) -> ServerMessageBody { fn user_to_who_msg(
config: &ServerConfig,
requestor: &RegisteredUser,
target_user_nickname: &ByteVec,
) -> ServerMessageBody {
// Username is equal to nickname // Username is equal to nickname
let mut username = Vec::with_capacity(target_user_nickname.len() + 1); let mut username = Vec::with_capacity(target_user_nickname.len() + 1);
username.push(b'~'); username.push(b'~');
@ -511,8 +529,21 @@ async fn handle_join(
match chan { match chan {
Chan::Global(chan_name) => { Chan::Global(chan_name) => {
let room_id = RoomId::from_bytes(chan_name.clone())?; let room_id = RoomId::from_bytes(chan_name.clone())?;
let room_info = user_handle.join_room(room_id).await?; if let JoinResult::Success(room_info) = user_handle.join_room(room_id).await? {
produce_on_join_cmd_messages(&config, &user, chan, &room_info, writer).await?; produce_on_join_cmd_messages(&config, &user, chan, &room_info, writer).await?;
} else {
ServerMessage {
tags: vec![],
sender: Some(config.server_name.as_bytes().to_vec()),
body: ServerMessageBody::N474BannedFromChan {
client: user.nickname.clone(),
chan: chan.clone(),
message: b"U dun goofed".to_vec(),
},
}
.write_async(writer)
.await?;
}
writer.flush().await?; writer.flush().await?;
} }
Chan::Local(_) => {} Chan::Local(_) => {}

View File

@ -143,7 +143,7 @@ fn client_message_mode(input: &[u8]) -> IResult<&[u8], ClientMessage> {
let (input, _) = tag("MODE ")(input)?; let (input, _) = tag("MODE ")(input)?;
let (input, target) = recipient(input)?; let (input, target) = recipient(input)?;
Ok((input, ClientMessage::Mode { target } )) Ok((input, ClientMessage::Mode { target }))
} }
fn client_message_who(input: &[u8]) -> IResult<&[u8], ClientMessage> { fn client_message_who(input: &[u8]) -> IResult<&[u8], ClientMessage> {

View File

@ -124,6 +124,11 @@ pub enum ServerMessageBody {
client: ByteVec, client: ByteVec,
chan: Chan, chan: Chan,
}, },
N474BannedFromChan {
client: ByteVec,
chan: Chan,
message: ByteVec,
},
} }
impl ServerMessageBody { impl ServerMessageBody {
@ -192,9 +197,7 @@ impl ServerMessageBody {
writer.write_all(&hostname).await?; writer.write_all(&hostname).await?;
writer.write_all(b" ").await?; writer.write_all(b" ").await?;
writer.write_all(&softname).await?; writer.write_all(&softname).await?;
writer writer.write_all(b" r CFILPQbcefgijklmnopqrstvz").await?;
.write_all(b" r CFILPQbcefgijklmnopqrstvz")
.await?;
// TODO remove hardcoded modes // TODO remove hardcoded modes
} }
ServerMessageBody::N005ISupport { client, params } => { ServerMessageBody::N005ISupport { client, params } => {
@ -209,7 +212,7 @@ impl ServerMessageBody {
writer.write_all(&client).await?; writer.write_all(&client).await?;
writer.write_all(b" ").await?; writer.write_all(b" ").await?;
writer.write_all(&modes).await?; writer.write_all(&modes).await?;
}, }
ServerMessageBody::N315EndOfWho { client, mask, msg } => { ServerMessageBody::N315EndOfWho { client, mask, msg } => {
writer.write_all(b"315 ").await?; writer.write_all(b"315 ").await?;
writer.write_all(&client).await?; writer.write_all(&client).await?;
@ -279,6 +282,18 @@ impl ServerMessageBody {
chan.write_async(writer).await?; chan.write_async(writer).await?;
writer.write_all(b" :End of /NAMES list").await?; writer.write_all(b" :End of /NAMES list").await?;
} }
ServerMessageBody::N474BannedFromChan {
client,
chan,
message,
} => {
writer.write_all(b"474 ").await?;
writer.write_all(&client).await?;
writer.write_all(b" ").await?;
chan.write_async(writer).await?;
writer.write_all(b" :").await?;
writer.write_all(&message).await?;
}
} }
Ok(()) Ok(())
} }