Skip to content

Commit

Permalink
Implement WHOWAS
Browse files Browse the repository at this point in the history
  • Loading branch information
progval authored and spb committed Apr 15, 2024
1 parent 28e8164 commit 901b26b
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 11 deletions.
2 changes: 1 addition & 1 deletion sable_ircd/src/command/handlers/whois.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fn whois_handler(
response.numeric(make_numeric!(WhoisUser, &target));

if let Ok(Some(account)) = target.account() {
response.numeric(make_numeric!(WhoisAccount, &target, &account.name()));
response.numeric(make_numeric!(WhoisAccount, &target.nick(), &account.name()));
}

if let Some(away_reason) = target.away_reason() {
Expand Down
52 changes: 52 additions & 0 deletions sable_ircd/src/command/handlers/whowas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use super::*;

const DEFAULT_COUNT: usize = 8; // Arbitrary value, that happens to match the capacity of
// historic_nick_users

#[command_handler("WHOWAS")]
/// Syntax: WHOIS <target> [<count>]
fn whowas_handler(
network: &Network,
response: &dyn CommandResponse,
source: UserSource,
server: &ClientServer,
target: Nickname,
count: Option<u32>,
) -> CommandResult {
// "If given, <count> SHOULD be a positive number. Otherwise, a full search is done."
let count = match count {
None | Some(0) => DEFAULT_COUNT,
Some(count) => count.try_into().unwrap_or(usize::MAX),
};
let historic_users: Vec<_> = network
.historic_users_by_nick(&target)
.cloned()
.unwrap_or_default()
.into_iter()
.take(count)
.collect();

if historic_users.is_empty() {
response.numeric(make_numeric!(WasNoSuchNick, &target));
} else {
for historic_user in historic_users {
let user: sable_network::network::wrapper::User<'_> =
wrapper::ObjectWrapper::wrap(network, &historic_user.user);
response.numeric(make_numeric!(WhowasUser, &historic_user));

if let Ok(Some(account)) = user.account() {
response.numeric(make_numeric!(WhoisAccount, &target, &account.name()));
}

if server.policy().can_see_connection_info(&source, &user) {
for conn in user.connections() {
response.numeric(make_numeric!(WhoisServer, &user, &conn.server()?));
response.numeric(make_numeric!(WhoisHost, &user, conn.hostname(), conn.ip()));
}
}
}
}

response.numeric(make_numeric!(EndOfWhowas, &target));
Ok(())
}
1 change: 1 addition & 0 deletions sable_ircd/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ mod handlers {
mod userhost;
mod who;
mod whois;
mod whowas;

// Interim solutions that need refinement
mod session;
Expand Down
8 changes: 7 additions & 1 deletion sable_ircd/src/messages/numeric.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::*;
use sable_macros::define_messages;
use sable_network::network::update::HistoricUser;
use sable_network::network::wrapper::{Channel, ChannelMode, ListModeEntry, Server, User};

define_messages! {
Expand All @@ -19,6 +20,8 @@ define_messages! {
=> "{nick} {user} {host} * :{realname}" },
312(WhoisServer) => { (nick: &User.nick(), server: &Server.name(), info=server.id())
=> "{nick} {server} :{info:?}"},
314(WhowasUser) => { (nick: &HistoricUser.nickname, user=nick.user.user, host=nick.user.visible_host, realname=nick.user.realname)
=> "{nick} {user} {host} * :{realname}" },
315(EndOfWho) => { (arg: &str) => "{arg} :End of /WHO list" },
318(EndOfWhois) => { (user: &User.nick()) => "{user} :End of /WHOIS" },
319(WhoisChannels) => { (user: &User.nick(), chanlist: &str)
Expand All @@ -29,7 +32,7 @@ define_messages! {
324(ChannelModeIs) => { (chan: &Channel.name(), modes: &ChannelMode.format())
=> "{chan} {modes}" },

330(WhoisAccount) => { (nick: &User.nick(), account: &Nickname)
330(WhoisAccount) => { (nick: &Nickname, account: &Nickname)
=> "{nick} {account} :is logged in as" },

331(NoTopic) => { (chan: &Channel.name()) => "{chan} :No topic is set"},
Expand All @@ -48,6 +51,8 @@ define_messages! {
=> "{is_pub} {chan} :{content}" },
366(EndOfNames) => { (chname: &str) => "{chname} :End of names list" },

369(EndOfWhowas) => { (nick: &Nickname) => "{nick} :End of /WHOWAS" },

256(AdminMe) => { (server_name: &ServerName) => "{server_name} :Administrative Info"},
257(AdminLocation1) => { (server_location: &str) => ":{server_location}" },
258(AdminLocation2) => { (admin_info: &str) => ":{admin_info}" },
Expand All @@ -65,6 +70,7 @@ define_messages! {
402(NoSuchServer) => { (server_name: &ServerName) => "{server_name} :No such server" },
403(NoSuchChannel) => { (chname: &ChannelName) => "{chname} :No such channel" },
404(CannotSendToChannel) => { (chan: &ChannelName) => "{chan} :Cannot send to channel" },
406(WasNoSuchNick) => { (nick: &Nickname) => "{nick} :There was no such nickname" },
410(InvalidCapCmd) => { (subcommand: &str) => "{subcommand} :Invalid CAP command" },
412(NoTextToSend) => { () => ":No text to send" },
421(UnknownCommand) => { (command: &str) => "{command} :Unknown command" },
Expand Down
10 changes: 10 additions & 0 deletions sable_network/src/network/network/accessors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use std::collections::VecDeque;

use super::{LookupError, LookupResult, Network};
use crate::network::event::*;
use crate::network::network::HistoricUser;
use crate::network::state_utils;
use crate::prelude::*;

Expand Down Expand Up @@ -103,6 +106,13 @@ impl Network {
})
}

/// Remove a user from nick bindings and add it to historical users for that nick

/// Return a nickname binding for the given nick.
pub fn historic_users_by_nick(&self, nick: &Nickname) -> Option<&VecDeque<HistoricUser>> {
self.historic_nick_users.get(nick)
}

/// Look up a channel by ID
pub fn channel(&self, id: ChannelId) -> LookupResult<wrapper::Channel> {
self.channels.get(&id).ok_or(NoSuchChannel(id)).wrap(self)
Expand Down
6 changes: 5 additions & 1 deletion sable_network/src/network/network/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Defines the [Network] object.

use crate::network::event::*;
use crate::network::network::HistoricUser;
use crate::network::update::*;
use crate::prelude::*;

Expand All @@ -9,7 +10,7 @@ use sable_macros::dispatch_event;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;

use std::collections::HashMap;
use std::collections::{HashMap, VecDeque};

use std::sync::OnceLock;

Expand Down Expand Up @@ -43,6 +44,8 @@ pub struct Network {
#[serde_as(as = "Vec<(_,_)>")]
nick_bindings: HashMap<Nickname, state::NickBinding>,
#[serde_as(as = "Vec<(_,_)>")]
historic_nick_users: HashMap<Nickname, VecDeque<HistoricUser>>,
#[serde_as(as = "Vec<(_,_)>")]
users: HashMap<UserId, state::User>,
#[serde_as(as = "Vec<(_,_)>")]
user_connections: HashMap<UserConnectionId, state::UserConnection>,
Expand Down Expand Up @@ -103,6 +106,7 @@ impl Network {
pub fn new(config: config::NetworkConfig) -> Network {
let net = Network {
nick_bindings: HashMap::new(),
historic_nick_users: HashMap::new(),
users: HashMap::new(),
user_connections: HashMap::new(),

Expand Down
19 changes: 12 additions & 7 deletions sable_network/src/network/network/user_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ impl Network {
updates: &dyn NetworkUpdateReceiver,
) {
if let Some(user) = self.users.remove(&id) {
let mut historic_user = self.translate_historic_user(user.clone());

// First remove the user's memberships and connections
let removed_memberships = self
.memberships
Expand All @@ -28,6 +30,14 @@ impl Network {
let removed_nickname = if let Ok(binding) = self.nick_binding_for_user(user.id) {
let nick = binding.nick();
self.nick_bindings.remove(&nick);
let historic_nick_users =
self.historic_nick_users.entry(nick.clone()).or_insert_with(
|| VecDeque::with_capacity(8), // arbitrary power of two
);
if historic_nick_users.len() == historic_nick_users.capacity() {
historic_nick_users.pop_back();
}
historic_nick_users.push_front(historic_user.clone());
nick
} else {
state_utils::hashed_nick_for(user.id)
Expand All @@ -43,16 +53,11 @@ impl Network {
);
}

historic_user.nickname = removed_nickname;
updates.notify(
update::UserQuit {
// We can't use `translate_historic_user` because we've already removed the nick binding
user: HistoricUser {
account: user
.account
.and_then(|id| self.account(id).ok().map(|acc| acc.name())),
user,
nickname: removed_nickname,
},
user: historic_user,
nickname: removed_nickname,
message,
memberships: removed_memberships,
Expand Down
8 changes: 7 additions & 1 deletion sable_network/src/network/tests/event_application.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::fixtures::*;
use crate::prelude::*;
use serde_json::Value;
use std::str::FromStr;

#[test]
Expand All @@ -10,7 +11,12 @@ fn add_and_remove_user() {
builder.add_user(nick);
let user_id = builder.net.user_by_nick(&nick).unwrap().id();
builder.remove_user(user_id);
let modified_net = builder.json_for_compare();
let mut modified_net = builder.json_for_compare();

if let Value::Object(map) = &mut modified_net {
// Empty this array, because adding and removing a user changes it.
map["historic_nick_users"] = Value::Array(Vec::new());
}

assert_eq!(empty_net, modified_net);
}

0 comments on commit 901b26b

Please sign in to comment.