From a9dfe22d8c085b165653826c37a6c2fa3a58c300 Mon Sep 17 00:00:00 2001 From: homycdev Date: Mon, 29 Apr 2024 21:20:48 +0300 Subject: [PATCH] use struct as argument in trait --- crates/projection-irc/src/commands/mod.rs | 38 +++-- .../src/commands/whois/error.rs | 6 +- .../projection-irc/src/commands/whois/mod.rs | 58 ++++---- .../src/commands/whois/response.rs | 140 +++++++++++++++--- crates/projection-irc/src/lib.rs | 4 +- crates/projection-irc/tests/lib.rs | 50 +------ crates/proto-irc/src/lib.rs | 2 +- 7 files changed, 182 insertions(+), 116 deletions(-) diff --git a/crates/projection-irc/src/commands/mod.rs b/crates/projection-irc/src/commands/mod.rs index 8df7a93..95e32b3 100644 --- a/crates/projection-irc/src/commands/mod.rs +++ b/crates/projection-irc/src/commands/mod.rs @@ -1,17 +1,33 @@ -use lavina_core::prelude::Str; -use lavina_core::repo::Storage; -use proto_irc::response::WriteResponse; +use lavina_core::{prelude::Str, repo::Storage}; use std::future::Future; use tokio::io::AsyncWrite; pub mod whois; -pub trait Handler { - fn handle( - &self, - server_name: Str, - client: Str, - writer: &mut (impl AsyncWrite + Unpin), - storage: Storage, - ) -> impl Future>; +pub struct HandlerArgs { + server_name: Str, + client: Str, + writer: T, + storage: Storage, +} + +impl HandlerArgs +where + T: AsyncWrite + Unpin, +{ + pub fn new(server_name: Str, client: Str, writer: T, storage: Storage) -> HandlerArgs { + HandlerArgs { + server_name, + client, + writer, + storage, + } + } +} + +pub trait Handler +where + T: AsyncWrite + Unpin, +{ + fn handle(&self, arg: HandlerArgs) -> impl Future>; } diff --git a/crates/projection-irc/src/commands/whois/error.rs b/crates/projection-irc/src/commands/whois/error.rs index 2b8f3c4..b7f5053 100644 --- a/crates/projection-irc/src/commands/whois/error.rs +++ b/crates/projection-irc/src/commands/whois/error.rs @@ -3,7 +3,7 @@ use tokio::io::{AsyncWrite, AsyncWriteExt}; use lavina_core::prelude::Str; use proto_irc::response::WriteResponse; -/// ERR_NOSUCHNICK (401) +/// ErrNoSuchNick401 pub struct ErrNoSuchNick401 { client: Str, nick: Str, @@ -15,7 +15,7 @@ impl ErrNoSuchNick401 { } } -/// ERR_NOSUCHSERVER (402) +/// ErrNoSuchServer402 struct ErrNoSuchServer402 { client: Str, /// target parameter in WHOIS @@ -23,7 +23,7 @@ struct ErrNoSuchServer402 { server_name: Str, } -/// ERR_NONICKNAMEGIVEN (431) +/// ErrNoNicknameGiven431 pub struct ErrNoNicknameGiven431 { client: Str, } diff --git a/crates/projection-irc/src/commands/whois/mod.rs b/crates/projection-irc/src/commands/whois/mod.rs index 1ab32e0..ee9fe6c 100644 --- a/crates/projection-irc/src/commands/whois/mod.rs +++ b/crates/projection-irc/src/commands/whois/mod.rs @@ -1,39 +1,34 @@ -use tokio::io::{AsyncWrite, AsyncWriteExt}; -use tracing::instrument::WithSubscriber; +use tokio::io::AsyncWrite; use lavina_core::prelude::Str; -use lavina_core::repo::Storage; use proto_irc::client::command_args::Whois; use proto_irc::response::{IrcResponseMessage, WriteResponse}; use crate::commands::whois::error::{ErrNoNicknameGiven431, ErrNoSuchNick401}; -use crate::commands::whois::response::{RplWhoIsUser311, RPL_ENDOFWHOIS_318}; +use crate::commands::whois::response::{RplEndOfWhois318, RplWhoIsUser311}; use crate::commands::Handler; +use super::HandlerArgs; + pub mod error; pub mod response; -impl Handler for Whois { - async fn handle( - &self, - server_name: Str, - client: Str, - writer: &mut (impl AsyncWrite + Unpin), - mut storage: Storage, - ) -> anyhow::Result<()> { +impl Handler for Whois { + async fn handle(&self, body: HandlerArgs) -> anyhow::Result<()> { match self { - Whois::Nick(nick) => { - handle_nick_target(nick.clone(), None, server_name, client, writer, storage.clone()).await? - } - Whois::TargetNick(target, nick) => { - handle_nick_target(nick.clone(), Some(target.clone()), server_name, client, writer, storage).await? - } + Whois::Nick(nick) => handle_nick_target(nick.clone(), body).await?, + Whois::TargetNick(_, nick) => handle_nick_target(nick.clone(), body).await?, Whois::EmptyArgs => { + let HandlerArgs { + server_name, + mut writer, + .. + } = body; IrcResponseMessage::empty_tags( Some(server_name.clone()), ErrNoNicknameGiven431::new(server_name.clone()), ) - .write_response(writer) + .write_response(&mut writer) .await? } } @@ -41,15 +36,13 @@ impl Handler for Whois { } } -async fn handle_nick_target( - nick: Str, - // todo: implement logic with target - _target: Option, - server_name: Str, - client: Str, - writer: &mut (impl AsyncWrite + Unpin), - mut storage: Storage, -) -> anyhow::Result<()> { +async fn handle_nick_target(nick: Str, body: HandlerArgs) -> anyhow::Result<()> { + let HandlerArgs { + server_name, + mut writer, + client, + mut storage, + } = body; if let Some(user) = storage.retrieve_user_by_name(nick.clone().as_ref()).await? { IrcResponseMessage::empty_tags( Some(server_name.clone()), @@ -58,19 +51,20 @@ async fn handle_nick_target( nick.clone(), Some(Str::from(user.name.clone())), server_name.clone(), + nick.clone(), ), ) - .write_response(writer) + .write_response(&mut writer) .await?; IrcResponseMessage::empty_tags( Some(server_name.clone()), - RPL_ENDOFWHOIS_318::new(client.clone(), user.name.clone().into()), + RplEndOfWhois318::new(client.clone(), user.name.clone().into()), ) - .write_response(writer) + .write_response(&mut writer) .await? } else { - ErrNoSuchNick401::new(client.clone(), nick.clone()).write_response(writer).await? + ErrNoSuchNick401::new(client.clone(), nick.clone()).write_response(&mut writer).await? } Ok(()) } diff --git a/crates/projection-irc/src/commands/whois/response.rs b/crates/projection-irc/src/commands/whois/response.rs index e7f42b8..b707a49 100644 --- a/crates/projection-irc/src/commands/whois/response.rs +++ b/crates/projection-irc/src/commands/whois/response.rs @@ -1,10 +1,25 @@ +use std::{net::IpAddr, time::SystemTime}; + use tokio::io::{AsyncWrite, AsyncWriteExt}; use lavina_core::prelude::Str; -use proto_irc::response::WriteResponse; +use proto_irc::{response::WriteResponse, Chan}; -struct RplWhoisCertfp276; -struct RplWhoisRegNick307; +/// " :has client certificate fingerprint " +/// Clients MUST only be sent this numeric if they are either using the WHOIS command on themselves +/// or they are an operator. +struct RplWhoisCertfp276 { + client: Str, + nick: Str, + fingerprint: Str, +} + +/// " :has identified for this nick" +struct RplWhoisRegNick307 { + client: Str, + nick: Str, +} +/// " * :" pub struct RplWhoIsUser311 { client: Str, /// unique name @@ -13,37 +28,124 @@ pub struct RplWhoIsUser311 { username: Option, /// server name host: Str, + realname: Str, } impl RplWhoIsUser311 { - pub fn new(client: Str, nick: Str, username: Option, host: Str) -> Self { + pub fn new(client: Str, nick: Str, username: Option, host: Str, realname: Str) -> Self { RplWhoIsUser311 { client, nick, username, host, + realname, } } } +/// " :" +struct RplWhoisServer312 { + client: Str, + nick: Str, + server: Str, + /// description of the server + server_info: String, +} -struct _RplWhoisServer312; -struct _RplWhoisOperator313; -struct _RplWhoisIdle317; -struct _RplWhoisChannels319; -struct _RplWhoisSpecial320; -struct _RplWhoisAccount330; -struct _RplWhoisActually338; -struct _RplWhoisHost378; -struct _RplWhoisModes379; -struct _RplWhoisSecure671; -struct _RplAway301; -pub struct RPL_ENDOFWHOIS_318 { +/// " :is an IRC operator" +struct RplWhoisOperator313 { client: Str, nick: Str, } -impl RPL_ENDOFWHOIS_318 { + +/// Sent as a reply to the WHOIS command, this numeric indicates how long the client with the nickname has been idle. +/// is the number of seconds since the client has been active. +/// Servers generally denote specific commands (for instance, perhaps JOIN, PRIVMSG, NOTICE, etc) as updating the ‘idle time’, +/// and calculate this off when the idle time was last updated. +/// is a unix timestamp representing when the user joined the network. The text used in the last param of this message may vary. +struct RplWhoisIdle317 { + client: Str, + nick: Str, + /// number of seconds client been active + secs: f32, + sigon: SystemTime, +} + +struct PrefixedChan { + prefix: Option, + chan: Chan, +} + +struct RplWhoisChannels319 { + client: Str, + nick: Str, + highest_membership: PrefixedChan, + /// The last parameter of this numeric is a list of [prefix] pairs, + /// delimited by a SPACE character (' ', 0x20). + /// RPL_WHOISCHANNELS can be sent multiple times in the same whois reply, + /// if the target is on too many channels to fit in a single message. + chanels: [PrefixedChan; 20], +} + +/// is used for extra human-readable information on the client with nickname . +/// This should only be used for non-essential information, +/// that does not need to be machine-readable or understood by client software. +struct RplWhoisSpecial320 { + client: Str, + nick: Str, +} +/// " :is logged in as" +struct RplWhoisAccount330 { + client: Str, + nick: Str, + account: Str, +} + +/// " :is actually ..." +/// " :Is actually using host" +/// " @ :Is actually using host" +struct RplWhoisActually338 { + client: Str, + nick: Str, + username: Str, + /// CANNOT start with a colon (':', 0x3A) + host: Str, + ip: IpAddr, +} + +/// " :is connecting from *@localhost 127.0.0.1" +struct RplWhoisHost378 { + client: Str, + nick: Str, + host: Str, +} + +struct Mode(Str); +/// " :is using modes +ailosw" +struct RplWhoisModes379 { + client: Str, + nick: Str, + modes: Vec, +} + +/// " :is using a secure connection" +struct RplWhoisSecure671 { + client: Str, + nick: Str, + is_secure: bool, +} +/// " :" +struct RplAway301 { + client: Str, + nick: Str, + default_away_message: Str, +} +pub struct RplEndOfWhois318 { + client: Str, + nick: Str, +} +impl RplEndOfWhois318 { pub fn new(client: Str, nick: Str) -> Self { - RPL_ENDOFWHOIS_318 { client, nick } + RplEndOfWhois318 { client, nick } } } @@ -70,7 +172,7 @@ impl WriteResponse for RplWhoIsUser311 { } } -impl WriteResponse for RPL_ENDOFWHOIS_318 { +impl WriteResponse for RplEndOfWhois318 { async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> { writer.write_all(b"318 ").await?; writer.write_all(self.client.as_bytes()).await?; diff --git a/crates/projection-irc/src/lib.rs b/crates/projection-irc/src/lib.rs index 8ca15b0..1e1d394 100644 --- a/crates/projection-irc/src/lib.rs +++ b/crates/projection-irc/src/lib.rs @@ -729,12 +729,12 @@ async fn handle_incoming_message( } }, ClientMessage::Whois { arg } => { - arg.handle( + arg.handle(commands::HandlerArgs::new( config.server_name.clone(), user.nickname.clone(), writer, storage.clone(), - ) + )) .await? } ClientMessage::Mode { target } => { diff --git a/crates/projection-irc/tests/lib.rs b/crates/projection-irc/tests/lib.rs index 42cfb8c..24bde61 100644 --- a/crates/projection-irc/tests/lib.rs +++ b/crates/projection-irc/tests/lib.rs @@ -8,10 +8,10 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader}; use tokio::net::tcp::{ReadHalf, WriteHalf}; use tokio::net::TcpStream; -use lavina_core::{player::PlayerRegistry, room::RoomRegistry}; use lavina_core::repo::{Storage, StorageConfig}; -use projection_irc::{launch, read_irc_message, RunningServer, ServerConfig}; +use lavina_core::{player::PlayerRegistry, room::RoomRegistry}; use projection_irc::APP_VERSION; +use projection_irc::{launch, read_irc_message, RunningServer, ServerConfig}; struct TestScope<'a> { reader: BufReader>, @@ -194,52 +194,6 @@ async fn scenario_cap_full_negotiation() -> Result<()> { Ok(()) } -// #[tokio::test] -// async fn scenario_whois_command() -> Result<()> { -// let mut server = TestServer::start().await?; -// -// server.storage.create_user("tester").await?; -// server.storage.set_password("tester", "password").await?; -// let mut stream = TcpStream::connect(server.server.addr).await?; -// let mut s = TestScope::new(&mut stream); -// -// -// -// s.send("PASS password").await?; -// s.send("NICK tester").await?; -// s.send("USER UserName 0 * :Real Name").await?; -// -// s.expect(":testserver 001 tester :Welcome to testserver Server").await?; -// s.expect(":testserver 002 tester :Welcome to testserver Server").await?; -// s.expect(":testserver 003 tester :Welcome to testserver Server").await?; -// s.expect( -// format!( -// ":testserver 004 tester testserver {} r CFILPQbcefgijklmnopqrstvz", -// &APP_VERSION -// ) -// .as_str(), -// ) -// .await?; -// s.expect(":testserver 005 tester CHANTYPES=# :are supported by this server").await?; -// s.expect_nothing().await?; -// -// s.send("WHOIS tester").await?; -// s.expect(" ").await?; -// -// s.send("QUIT :Leaving").await?; -// s.expect(":testserver ERROR :Leaving the server").await?; -// s.expect_eof().await?; -// -// stream.shutdown().await?; -// -// // wrap up -// -// server.server.terminate().await?; -// -// Ok(()) -// } - - #[tokio::test] async fn scenario_cap_short_negotiation() -> Result<()> { let mut server = TestServer::start().await?; diff --git a/crates/proto-irc/src/lib.rs b/crates/proto-irc/src/lib.rs index ab82664..43eb6b3 100644 --- a/crates/proto-irc/src/lib.rs +++ b/crates/proto-irc/src/lib.rs @@ -1,11 +1,11 @@ //! Client-to-Server IRC protocol. pub mod client; mod prelude; +pub mod response; pub mod server; #[cfg(test)] mod testkit; pub mod user; -pub mod response; use crate::prelude::Str;