Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement WHOWAS #105

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
Loading