forked from lavina/lavina
1
0
Fork 0

Compare commits

..

2 Commits

Author SHA1 Message Date
homycdev ad00fe759a cargo fmt 2024-05-05 15:57:32 +03:00
homycdev 66a3bb7a68 added tests, refactoring made 2024-05-05 15:57:14 +03:00
15 changed files with 216 additions and 158 deletions

View File

@ -8,6 +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,
//! so that they don't overload the room actor.
use std::collections::{HashMap, HashSet};
use std::ops::Deref;
use std::sync::Arc;
use chrono::{DateTime, Utc};
@ -124,6 +125,15 @@ impl PlayerConnection {
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?)
}
/// Handler in [Player::check_user_existance].
#[tracing::instrument(skip(self), name = "PlayerConnection::send_dialog_message")]
pub async fn check_user_existance(&self, recipient: PlayerId) -> Result<GetInfoResult> {
let (promise, deferred) = oneshot();
let cmd = ClientCommand::GetInfo { recipient, promise };
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?)
}
}
/// Handle to a player actor.
@ -200,6 +210,15 @@ pub enum ClientCommand {
body: Str,
promise: Promise<()>,
},
GetInfo {
recipient: PlayerId,
promise: Promise<GetInfoResult>,
},
}
pub enum GetInfoResult {
UserExists,
UserDoesntExist,
}
pub enum JoinResult {
@ -500,6 +519,10 @@ impl Player {
self.send_dialog_message(connection_id, recipient, body).await;
let _ = promise.send(());
}
ClientCommand::GetInfo { recipient, promise } => {
let result = self.check_user_existance(recipient).await;
let _ = promise.send(result);
}
}
}
@ -596,6 +619,15 @@ impl Player {
self.broadcast_update(update, connection_id).await;
}
#[tracing::instrument(skip(self), name = "Player::check_user_existance")]
async fn check_user_existance(&self, recipient: PlayerId) -> GetInfoResult {
if self.storage.check_user_existance(recipient.as_inner().as_ref()).await.unwrap() {
GetInfoResult::UserExists
} else {
GetInfoResult::UserDoesntExist
}
}
/// Broadcasts an update to all connections except the one with the given id.
///
/// This is called after handling a client command.

View File

@ -7,7 +7,7 @@ use anyhow::anyhow;
use chrono::{DateTime, Utc};
use serde::Deserialize;
use sqlx::sqlite::SqliteConnectOptions;
use sqlx::{ConnectOptions, Connection, FromRow, Sqlite, SqliteConnection, Transaction};
use sqlx::{ConnectOptions, Connection, Execute, FromRow, Sqlite, SqliteConnection, Transaction};
use tokio::sync::Mutex;
use crate::prelude::*;
@ -56,6 +56,17 @@ impl Storage {
Ok(res)
}
#[tracing::instrument(skip(self), name = "Storage::check_user_existance")]
pub async fn check_user_existance(&self, username: &str) -> Result<bool> {
let mut executor = self.conn.lock().await;
let result: Option<(String,)> = sqlx::query_as("select name from users where name = ?;")
.bind(username)
.fetch_optional(&mut *executor)
.await?;
Ok(result.is_some())
}
#[tracing::instrument(skip(self), name = "Storage::retrieve_room_by_name")]
pub async fn retrieve_room_by_name(&self, name: &str) -> Result<Option<StoredRoom>> {
let mut executor = self.conn.lock().await;

View File

@ -1,33 +0,0 @@
use lavina_core::{prelude::Str, repo::Storage};
use std::future::Future;
use tokio::io::AsyncWrite;
pub mod whois;
pub struct HandlerArgs<T: AsyncWrite + Unpin> {
server_name: Str,
client: Str,
writer: T,
storage: Storage,
}
impl<T> HandlerArgs<T>
where
T: AsyncWrite + Unpin,
{
pub fn new(server_name: Str, client: Str, writer: T, storage: Storage) -> HandlerArgs<T> {
HandlerArgs {
server_name,
client,
writer,
storage,
}
}
}
pub trait Handler<T>
where
T: AsyncWrite + Unpin,
{
fn handle(&self, arg: HandlerArgs<T>) -> impl Future<Output = anyhow::Result<()>>;
}

View File

@ -1,70 +0,0 @@
use tokio::io::AsyncWrite;
use lavina_core::prelude::Str;
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::{RplEndOfWhois318, RplWhoIsUser311};
use crate::commands::Handler;
use super::HandlerArgs;
pub mod error;
pub mod response;
impl<T: AsyncWrite + Unpin> Handler<T> for Whois {
async fn handle(&self, body: HandlerArgs<T>) -> anyhow::Result<()> {
match self {
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(&mut writer)
.await?
}
}
Ok(())
}
}
async fn handle_nick_target(nick: Str, body: HandlerArgs<impl AsyncWrite + Unpin>) -> 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()),
RplWhoIsUser311::new(
client.clone(),
nick.clone(),
Some(Str::from(user.name.clone())),
server_name.clone(),
nick.clone(),
),
)
.write_response(&mut writer)
.await?;
IrcResponseMessage::empty_tags(
Some(server_name.clone()),
RplEndOfWhois318::new(client.clone(), user.name.clone().into()),
)
.write_response(&mut writer)
.await?
} else {
ErrNoSuchNick401::new(client.clone(), nick.clone()).write_response(&mut writer).await?
}
Ok(())
}

View File

@ -0,0 +1,18 @@
use lavina_core::{player::PlayerConnection, prelude::Str, LavinaCore};
use std::future::Future;
use tokio::io::AsyncWrite;
pub struct IrcConnection<'a, T: AsyncWrite + Unpin> {
pub server_name: Str,
/// client is nick of requester
pub client: Str,
pub writer: &'a mut T,
pub player_connection: &'a mut PlayerConnection,
}
pub trait Handler<T>
where
T: AsyncWrite + Unpin,
{
fn handle(&self, arg: IrcConnection<T>) -> impl Future<Output = anyhow::Result<()>>;
}

View File

@ -29,11 +29,12 @@ use proto_irc::user::PrefixedNick;
use proto_irc::{Chan, Recipient, Tag};
use sasl::AuthBody;
mod cap;
mod commands;
use handler::Handler;
mod whois;
use crate::cap::Capabilities;
use commands::Handler;
use proto_irc::response::IrcResponseMessage;
mod handler;
pub const APP_VERSION: &str = concat!("lavina", "_", env!("CARGO_PKG_VERSION"));
@ -80,7 +81,7 @@ async fn handle_socket(
match registered_user {
Ok(user) => {
log::debug!("User registered");
handle_registered_socket(config, core.players, core.rooms, &mut reader, &mut writer, user).await?;
handle_registered_socket(config, core.players, core.rooms, &mut reader, &mut writer, user, &mut storage).await?;
}
Err(err) => {
log::debug!("Registration failed: {err}");
@ -811,13 +812,15 @@ async fn handle_incoming_message(
}
},
ClientMessage::Whois { arg } => {
arg.handle(commands::HandlerArgs::new(
config.server_name.clone(),
user.nickname.clone(),
arg.handle(handler::IrcConnection {
server_name: config.server_name.clone(),
client: user.nickname.clone(),
writer,
storage.clone(),
))
.await?
player_connection: user_handle,
})
.await?;
writer.flush().await?;
}
ClientMessage::Mode { target } => {
match target {

View File

@ -0,0 +1,67 @@
use lavina_core::{
player::{GetInfoResult, PlayerId},
prelude::Str,
};
use proto_irc::{
client::command_args::Whois,
commands::whois::{
error::{ErrNoNicknameGiven431, ErrNoSuchNick401},
response::RplEndOfWhois318,
},
response::{IrcResponseMessage, WriteResponse},
};
use tokio::io::AsyncWrite;
use crate::handler::{Handler, IrcConnection};
impl<T: AsyncWrite + Unpin> Handler<T> for Whois {
async fn handle(&self, body: IrcConnection<'_, T>) -> anyhow::Result<()> {
match self {
Whois::Nick(nick) => handle_nick_target(nick.clone(), body).await?,
Whois::TargetNick(_, nick) => handle_nick_target(nick.clone(), body).await?,
Whois::EmptyArgs => {
let IrcConnection {
server_name,
mut writer,
..
} = body;
IrcResponseMessage::empty_tags(
Some(server_name.clone()),
ErrNoNicknameGiven431::new(server_name.clone()),
)
.write_response(&mut writer)
.await?
}
}
Ok(())
}
}
async fn handle_nick_target(nick: Str, body: IrcConnection<'_, impl AsyncWrite + Unpin>) -> anyhow::Result<()> {
let IrcConnection {
server_name,
mut writer,
client,
player_connection,
} = body;
if let GetInfoResult::UserDoesntExist =
player_connection.check_user_existance(PlayerId::from(nick.clone())?).await?
{
IrcResponseMessage::empty_tags(
Some(server_name.clone()),
ErrNoSuchNick401::new(client.clone(), nick.clone()),
)
.write_response(&mut writer)
.await?
}
IrcResponseMessage::empty_tags(
Some(server_name.clone()),
RplEndOfWhois318::new(client.clone(), nick.clone()),
)
.write_response(&mut writer)
.await?;
Ok(())
}

View File

@ -383,7 +383,14 @@ async fn scenario_two_users() -> Result<()> {
// The second user should receive the PART message
s2.expect(":tester1 PART #test").await?;
s1.send("WHOIS tester2").await?;
s1.expect(":testserver 318 tester1 tester2 :End of /WHOIS list").await?;
stream1.shutdown().await?;
s2.send("WHOIS tester3").await?;
s2.expect(":testserver 401 tester2 tester3 :No such nick/channel").await?;
s2.expect(":testserver 318 tester2 tester3 :End of /WHOIS list").await?;
stream2.shutdown().await?;
server.shutdown().await?;

View File

@ -55,13 +55,19 @@ impl<'a> TestScope<'a> {
Ok(event)
}
async fn expect_starttls_required(&mut self) -> Result<()> {
assert_matches!(self.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"features"));
assert_matches!(self.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
assert_matches!(self.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"required"));
assert_matches!(self.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
assert_matches!(self.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"features"));
Ok(())
async fn read<T: FromXml>(&mut self) -> Result<T> {
self.buffer.clear();
let (ns, event) = self.reader.read_resolved_event_into_async(&mut self.buffer).await?;
let mut parser: Continuation<_, std::result::Result<T, anyhow::Error>> = T::parse().consume(ns, &event);
loop {
match parser {
Continuation::Final(res) => return Ok(res?),
Continuation::Continue(next) => {
let (ns, event) = self.reader.read_resolved_event_into_async(&mut self.buffer).await?;
parser = next.consume(ns, &event);
}
}
}
}
}
@ -92,24 +98,6 @@ impl<'a> TestScopeTls<'a> {
Ok(())
}
async fn expect_auth_mechanisms(&mut self) -> Result<()> {
assert_matches!(self.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"features"));
assert_matches!(self.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"mechanisms"));
assert_matches!(self.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"mechanism"));
assert_matches!(self.next_xml_event().await?, Event::Text(b) => assert_eq!(&*b, b"PLAIN"));
assert_matches!(self.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"mechanism"));
assert_matches!(self.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"mechanisms"));
assert_matches!(self.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"features"));
Ok(())
}
async fn expect_bind_feature(&mut self) -> Result<()> {
assert_matches!(self.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"features"));
assert_matches!(self.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"bind"));
assert_matches!(self.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"features"));
Ok(())
}
async fn next_xml_event(&mut self) -> Result<Event<'_>> {
self.buffer.clear();
let event = self.reader.read_event_into_async(&mut self.buffer);
@ -188,7 +176,11 @@ async fn scenario_basic() -> Result<()> {
s.send(r#"<stream:stream xmlns:stream="http://etherx.jabber.org/streams" to="127.0.0.1" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns="jabber:client" version="1.0">"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Decl(_) => {});
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"stream"));
s.expect_starttls_required().await?;
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"features"));
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
assert_matches!(s.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"required"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"features"));
s.send(r#"<starttls/>"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"proceed"));
let buffer = s.buffer;
@ -210,7 +202,14 @@ async fn scenario_basic() -> Result<()> {
s.send(r#"<stream:stream xmlns:stream="http://etherx.jabber.org/streams" to="127.0.0.1" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns="jabber:client" version="1.0">"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Decl(_) => {});
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"stream"));
s.expect_auth_mechanisms().await?;
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"features"));
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"mechanisms"));
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"mechanism"));
assert_matches!(s.next_xml_event().await?, Event::Text(b) => assert_eq!(&*b, b"PLAIN"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"mechanism"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"mechanisms"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"features"));
// base64-encoded b"\x00tester\x00password"
s.send(r#"<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AHRlc3RlcgBwYXNzd29yZA==</auth>"#)
@ -220,7 +219,9 @@ async fn scenario_basic() -> Result<()> {
s.send(r#"<stream:stream xmlns:stream="http://etherx.jabber.org/streams" to="127.0.0.1" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns="jabber:client" version="1.0">"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Decl(_) => {});
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"stream"));
s.expect_bind_feature().await?;
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"features"));
assert_matches!(s.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"bind"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"features"));
s.send(r#"<iq id="bind_1" type="set"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>kek</resource></bind></iq>"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"iq"));
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"bind"));
@ -256,7 +257,11 @@ async fn scenario_wrong_password() -> Result<()> {
s.send(r#"<stream:stream xmlns:stream="http://etherx.jabber.org/streams" to="127.0.0.1" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns="jabber:client" version="1.0">"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Decl(_) => {});
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"stream"));
s.expect_starttls_required().await?;
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"features"));
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
assert_matches!(s.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"required"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"features"));
s.send(r#"<starttls/>"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"proceed"));
let buffer = s.buffer;
@ -278,7 +283,15 @@ async fn scenario_wrong_password() -> Result<()> {
s.send(r#"<stream:stream xmlns:stream="http://etherx.jabber.org/streams" to="127.0.0.1" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns="jabber:client" version="1.0">"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Decl(_) => {});
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"stream"));
s.expect_auth_mechanisms().await?;
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"features"));
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"mechanisms"));
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"mechanism"));
assert_matches!(s.next_xml_event().await?, Event::Text(b) => assert_eq!(&*b, b"PLAIN"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"mechanism"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"mechanisms"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"features"));
// base64-encoded b"\x00tester\x00password2"
s.send(r#"<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AHRlc3RlcgBwYXNzd29yZDI=</auth>"#)
.await?;
@ -310,7 +323,11 @@ async fn scenario_basic_without_headers() -> Result<()> {
s.send(r#"<stream:stream xmlns:stream="http://etherx.jabber.org/streams" to="127.0.0.1" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns="jabber:client" version="1.0">"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Decl(_) => {});
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"stream"));
s.expect_starttls_required().await?;
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"features"));
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
assert_matches!(s.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"required"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"features"));
s.send(r#"<starttls/>"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"proceed"));
let buffer = s.buffer;
@ -358,7 +375,11 @@ async fn terminate_socket() -> Result<()> {
s.send(r#"<stream:stream xmlns:stream="http://etherx.jabber.org/streams" to="127.0.0.1" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns="jabber:client" version="1.0">"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Decl(_) => {});
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"stream"));
s.expect_starttls_required().await?;
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"features"));
assert_matches!(s.next_xml_event().await?, Event::Start(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
assert_matches!(s.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"required"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"starttls"));
assert_matches!(s.next_xml_event().await?, Event::End(b) => assert_eq!(b.local_name().into_inner(), b"features"));
s.send(r#"<starttls/>"#).await?;
assert_matches!(s.next_xml_event().await?, Event::Empty(b) => assert_eq!(b.local_name().into_inner(), b"proceed"));

View File

@ -0,0 +1 @@
pub mod whois;

View File

@ -1,7 +1,6 @@
use tokio::io::{AsyncWrite, AsyncWriteExt};
use lavina_core::prelude::Str;
use proto_irc::response::WriteResponse;
use crate::{prelude::Str, response::WriteResponse};
/// ErrNoSuchNick401
pub struct ErrNoSuchNick401 {
@ -34,7 +33,7 @@ impl ErrNoNicknameGiven431 {
}
impl WriteResponse for ErrNoSuchNick401 {
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
async fn write_response(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
writer.write_all(b"401 ").await?;
writer.write_all(self.client.as_bytes()).await?;
writer.write_all(b" ").await?;
@ -46,7 +45,7 @@ impl WriteResponse for ErrNoSuchNick401 {
}
impl WriteResponse for ErrNoNicknameGiven431 {
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
async fn write_response(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
writer.write_all(b"431").await?;
writer.write_all(self.client.as_bytes()).await?;
writer.write_all(b" :").await?;
@ -56,7 +55,7 @@ impl WriteResponse for ErrNoNicknameGiven431 {
}
impl WriteResponse for ErrNoSuchServer402 {
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
async fn write_response(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
writer.write_all(b"402 ").await?;
writer.write_all(self.client.as_bytes()).await?;
writer.write_all(b" ").await?;

View File

@ -0,0 +1,2 @@
pub mod error;
pub mod response;

View File

@ -2,8 +2,7 @@ use std::{net::IpAddr, time::SystemTime};
use tokio::io::{AsyncWrite, AsyncWriteExt};
use lavina_core::prelude::Str;
use proto_irc::{response::WriteResponse, Chan};
use crate::{prelude::Str, response::WriteResponse, Chan};
/// "<client> <nick> :has client certificate fingerprint <fingerprint>"
/// Clients MUST only be sent this numeric if they are either using the WHOIS command on themselves
@ -150,12 +149,12 @@ impl RplEndOfWhois318 {
}
impl WriteResponse for RplWhoIsUser311 {
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
async fn write_response(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
writer.write_all(b"311 ").await?;
writer.write_all(self.client.as_bytes()).await?;
writer.write_all(b" ").await?;
writer.write_all(self.nick.as_bytes()).await?;
if let Some(username) = self.username {
if let Some(username) = &self.username {
writer.write_all(b" ").await?;
writer.write_all(username.as_bytes()).await?;
}
@ -173,7 +172,7 @@ impl WriteResponse for RplWhoIsUser311 {
}
impl WriteResponse for RplEndOfWhois318 {
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
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?;
writer.write_all(b" ").await?;

View File

@ -1,5 +1,6 @@
//! Client-to-Server IRC protocol.
pub mod client;
pub mod commands;
mod prelude;
pub mod response;
pub mod server;

View File

@ -6,7 +6,7 @@ use crate::prelude::Str;
use crate::Tag;
pub trait WriteResponse {
fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> impl Future<Output = std::io::Result<()>>;
fn write_response(&self, writer: &mut (impl AsyncWrite + Unpin)) -> impl Future<Output = std::io::Result<()>>;
}
/// Server-to-client enum agnostic message
@ -34,7 +34,7 @@ impl<T> IrcResponseMessage<T> {
}
impl<T: WriteResponse> WriteResponse for IrcResponseMessage<T> {
async fn write_response(self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
async fn write_response(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
if let Some(sender) = &self.sender {
writer.write_all(b":").await?;
writer.write_all(sender.as_bytes()).await?;