forked from lavina/lavina
1
0
Fork 0
lavina/crates/proto-irc/src/server.rs

530 lines
17 KiB
Rust
Raw Normal View History

use std::sync::Arc;
use nonempty::NonEmpty;
use tokio::io::AsyncWrite;
use tokio::io::AsyncWriteExt;
use super::*;
use crate::user::PrefixedNick;
/// Server-to-client message.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ServerMessage {
/// Optional tags section, prefixed with `@`
pub tags: Vec<Tag>,
/// Optional server name, prefixed with `:`.
2023-04-13 22:38:26 +00:00
pub sender: Option<Str>,
pub body: ServerMessageBody,
}
impl ServerMessage {
pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
2023-02-10 18:47:58 +00:00
match &self.sender {
Some(ref sender) => {
writer.write_all(b":").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(sender.as_bytes()).await?;
writer.write_all(b" ").await?;
2023-02-10 18:47:58 +00:00
}
None => {}
}
self.body.write_async(writer).await?;
2023-07-22 19:11:01 +00:00
writer.write_all(b"\r\n").await?;
Ok(())
}
}
2023-04-13 19:15:48 +00:00
pub fn server_message(input: &str) -> IResult<&str, ServerMessage> {
let (input, command) = server_message_body(input)?;
2023-07-23 13:21:26 +00:00
let (input, _) = tag("\r\n")(input)?;
let message = ServerMessage {
tags: vec![],
sender: None,
body: command,
};
Ok((input, message))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ServerMessageBody {
Notice {
2023-04-13 22:38:26 +00:00
first_target: Str,
rest_targets: Vec<Str>,
text: Str,
},
Ping {
2023-04-13 22:38:26 +00:00
token: Str,
},
Pong {
2023-04-13 22:38:26 +00:00
from: Str,
token: Str,
},
2023-02-13 17:08:37 +00:00
PrivateMessage {
target: Recipient,
2023-04-13 22:38:26 +00:00
body: Str,
2023-02-13 17:08:37 +00:00
},
2023-02-13 18:32:52 +00:00
Join(Chan),
2023-02-15 17:54:48 +00:00
Part(Chan),
2023-02-16 23:38:34 +00:00
Error {
2023-04-13 22:38:26 +00:00
reason: Str,
2023-02-16 23:38:34 +00:00
},
Cap {
target: Str,
subcmd: CapSubBody,
},
Authenticate(Str),
2023-02-10 18:47:58 +00:00
N001Welcome {
2023-04-13 22:38:26 +00:00
client: Str,
text: Str,
2023-02-10 18:47:58 +00:00
},
N002YourHost {
2023-04-13 22:38:26 +00:00
client: Str,
text: Str,
2023-02-10 18:47:58 +00:00
},
N003Created {
2023-04-13 22:38:26 +00:00
client: Str,
text: Str,
2023-02-10 18:47:58 +00:00
},
N004MyInfo {
2023-04-13 22:38:26 +00:00
client: Str,
hostname: Str,
softname: Str,
2023-02-10 18:47:58 +00:00
// TODO user modes, channel modes, channel modes with a parameter
},
N005ISupport {
2023-04-13 22:38:26 +00:00
client: Str,
params: Str, // TODO make this a datatype
2023-02-10 18:47:58 +00:00
},
2023-02-16 19:37:17 +00:00
/// Reply to a client's [Mode](crate::protos::irc::client::ClientMessage::Mode) request.
N221UserModeIs {
2023-04-13 22:38:26 +00:00
client: Str,
modes: Str,
2023-02-16 19:37:17 +00:00
},
/// Final reply to a client's [Who](crate::protos::irc::client::ClientMessage::Who) request.
N315EndOfWho {
2023-04-13 22:38:26 +00:00
client: Str,
mask: Recipient,
/// Usually `b"End of WHO list"`
2023-04-13 22:38:26 +00:00
msg: Str,
},
2023-02-13 18:32:52 +00:00
N332Topic {
2023-04-13 22:38:26 +00:00
client: Str,
2023-02-13 18:32:52 +00:00
chat: Chan,
2023-04-13 22:38:26 +00:00
topic: Str,
2023-02-13 18:32:52 +00:00
},
/// A reply to a client's [Who](crate::protos::irc::client::ClientMessage::Who) request.
N352WhoReply {
2023-04-13 22:38:26 +00:00
client: Str,
// chan = *
2023-04-13 22:38:26 +00:00
username: Str,
/// User's hostname
2023-04-13 22:38:26 +00:00
host: Str,
/// Hostname of the server the user is connected to
2023-04-13 22:38:26 +00:00
server: Str,
nickname: Str,
/// Flags
flags: AwayStatus,
hops: u8,
2023-04-13 22:38:26 +00:00
realname: Str,
},
2023-02-13 18:32:52 +00:00
N353NamesReply {
2023-04-13 22:38:26 +00:00
client: Str,
2023-02-13 18:32:52 +00:00
chan: Chan,
members: NonEmpty<PrefixedNick>,
2023-02-13 18:32:52 +00:00
},
N366NamesReplyEnd {
2023-04-13 22:38:26 +00:00
client: Str,
2023-02-13 18:32:52 +00:00
chan: Chan,
},
2023-02-16 21:49:17 +00:00
N474BannedFromChan {
2023-04-13 22:38:26 +00:00
client: Str,
2023-02-16 21:49:17 +00:00
chan: Chan,
2023-04-13 22:38:26 +00:00
message: Str,
2023-02-16 21:49:17 +00:00
},
N502UsersDontMatch {
client: Str,
message: Str,
},
N900LoggedIn {
nick: Str,
address: Str,
account: Str,
message: Str,
},
N903SaslSuccess {
nick: Str,
message: Str,
},
N904SaslFail {
nick: Str,
text: Str,
2024-03-20 18:59:15 +00:00
},
}
impl ServerMessageBody {
pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
match self {
ServerMessageBody::Notice {
first_target,
rest_targets,
text,
} => {
writer.write_all(b"NOTICE ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(first_target.as_bytes()).await?;
writer.write_all(b" :").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(text.as_bytes()).await?;
}
ServerMessageBody::Ping { token } => {
writer.write_all(b"PING ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(token.as_bytes()).await?;
}
ServerMessageBody::Pong { from, token } => {
writer.write_all(b"PONG ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(from.as_bytes()).await?;
writer.write_all(b" :").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(token.as_bytes()).await?;
}
2023-02-13 17:08:37 +00:00
ServerMessageBody::PrivateMessage { target, body } => {
writer.write_all(b"PRIVMSG ").await?;
target.write_async(writer).await?;
writer.write_all(b" :").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(body.as_bytes()).await?;
2023-02-13 17:08:37 +00:00
}
2023-02-13 18:32:52 +00:00
ServerMessageBody::Join(chan) => {
writer.write_all(b"JOIN ").await?;
chan.write_async(writer).await?;
}
2023-02-15 17:54:48 +00:00
ServerMessageBody::Part(chan) => {
writer.write_all(b"PART ").await?;
chan.write_async(writer).await?;
}
2023-02-16 23:38:34 +00:00
ServerMessageBody::Error { reason } => {
writer.write_all(b"ERROR :").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(reason.as_bytes()).await?;
2023-02-16 23:38:34 +00:00
}
ServerMessageBody::Cap { target, subcmd } => {
writer.write_all(b"CAP ").await?;
writer.write_all(target.as_bytes()).await?;
match subcmd {
CapSubBody::Ls(caps) => {
writer.write_all(b" LS :").await?;
writer.write_all(caps.as_bytes()).await?;
}
CapSubBody::Ack(caps) => {
writer.write_all(b" ACK :").await?;
writer.write_all(caps.as_bytes()).await?;
}
}
}
ServerMessageBody::Authenticate(body) => {
writer.write_all(b"AUTHENTICATE ").await?;
writer.write_all(body.as_bytes()).await?;
}
2023-02-10 18:47:58 +00:00
ServerMessageBody::N001Welcome { client, text } => {
writer.write_all(b"001 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
writer.write_all(b" :").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(text.as_bytes()).await?;
2023-02-10 18:47:58 +00:00
}
ServerMessageBody::N002YourHost { client, text } => {
writer.write_all(b"002 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
writer.write_all(b" :").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(text.as_bytes()).await?;
2023-02-10 18:47:58 +00:00
}
ServerMessageBody::N003Created { client, text } => {
writer.write_all(b"003 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
writer.write_all(b" :").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(text.as_bytes()).await?;
2023-02-10 18:47:58 +00:00
}
ServerMessageBody::N004MyInfo {
client,
hostname,
softname,
} => {
writer.write_all(b"004 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
writer.write_all(b" ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(hostname.as_bytes()).await?;
writer.write_all(b" ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(softname.as_bytes()).await?;
2023-02-16 21:49:17 +00:00
writer.write_all(b" r CFILPQbcefgijklmnopqrstvz").await?;
2023-02-10 18:47:58 +00:00
// TODO remove hardcoded modes
}
ServerMessageBody::N005ISupport { client, params } => {
writer.write_all(b"005 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
writer.write_all(b" ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(params.as_bytes()).await?;
writer.write_all(b" :are supported by this server").await?;
2023-02-10 18:47:58 +00:00
}
2023-02-16 19:37:17 +00:00
ServerMessageBody::N221UserModeIs { client, modes } => {
writer.write_all(b"221 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
2023-02-16 19:37:17 +00:00
writer.write_all(b" ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(modes.as_bytes()).await?;
2023-02-16 21:49:17 +00:00
}
ServerMessageBody::N315EndOfWho { client, mask, msg } => {
writer.write_all(b"315 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
writer.write_all(b" ").await?;
mask.write_async(writer).await?;
writer.write_all(b" :").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(msg.as_bytes()).await?;
}
2024-03-20 18:59:15 +00:00
ServerMessageBody::N332Topic { client, chat, topic } => {
2023-02-13 18:32:52 +00:00
writer.write_all(b"332 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
2023-02-13 18:32:52 +00:00
writer.write_all(b" ").await?;
chat.write_async(writer).await?;
writer.write_all(b" :").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(topic.as_bytes()).await?;
2023-02-13 18:32:52 +00:00
}
ServerMessageBody::N352WhoReply {
client,
username,
host,
server,
flags,
2023-02-16 18:33:36 +00:00
nickname,
hops,
realname,
} => {
writer.write_all(b"352 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
writer.write_all(b" * ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(username.as_bytes()).await?;
writer.write_all(b" ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(host.as_bytes()).await?;
writer.write_all(b" ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(server.as_bytes()).await?;
writer.write_all(b" ").await?;
match flags {
AwayStatus::Here => writer.write_all(b"H").await?,
AwayStatus::Gone => writer.write_all(b"G").await?,
}
2023-02-16 18:33:36 +00:00
writer.write_all(b" ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(nickname.as_bytes()).await?;
writer.write_all(b" :").await?;
writer.write_all(hops.to_string().as_bytes()).await?;
writer.write_all(b" ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(realname.as_bytes()).await?;
}
2024-03-20 18:59:15 +00:00
ServerMessageBody::N353NamesReply { client, chan, members } => {
2023-02-13 18:32:52 +00:00
writer.write_all(b"353 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
2023-02-13 18:32:52 +00:00
writer.write_all(b" = ").await?;
chan.write_async(writer).await?;
writer.write_all(b" :").await?;
{
let member = &members.head;
2024-03-20 18:59:15 +00:00
writer.write_all(member.prefix.to_string().as_bytes()).await?;
writer.write_all(member.nick.as_bytes()).await?;
}
for member in &members.tail {
writer.write_all(b" ").await?;
writer.write_all(member.prefix.to_string().as_bytes()).await?;
writer.write_all(member.nick.as_bytes()).await?;
}
2023-02-13 18:32:52 +00:00
}
ServerMessageBody::N366NamesReplyEnd { client, chan } => {
writer.write_all(b"366 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
2023-02-13 18:32:52 +00:00
writer.write_all(b" ").await?;
chan.write_async(writer).await?;
writer.write_all(b" :End of /NAMES list").await?;
}
2024-03-20 18:59:15 +00:00
ServerMessageBody::N474BannedFromChan { client, chan, message } => {
2023-02-16 21:49:17 +00:00
writer.write_all(b"474 ").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(client.as_bytes()).await?;
2023-02-16 21:49:17 +00:00
writer.write_all(b" ").await?;
chan.write_async(writer).await?;
writer.write_all(b" :").await?;
writer.write_all(message.as_bytes()).await?;
}
ServerMessageBody::N502UsersDontMatch { client, message } => {
writer.write_all(b"502 ").await?;
writer.write_all(client.as_bytes()).await?;
writer.write_all(b" ").await?;
writer.write_all(b" :").await?;
2023-04-13 19:15:48 +00:00
writer.write_all(message.as_bytes()).await?;
2023-02-16 21:49:17 +00:00
}
2024-03-20 18:59:15 +00:00
ServerMessageBody::N900LoggedIn {
nick,
address,
account,
message,
} => {
writer.write_all(b"900 ").await?;
writer.write_all(nick.as_bytes()).await?;
writer.write_all(b" ").await?;
writer.write_all(address.as_bytes()).await?;
writer.write_all(b" ").await?;
writer.write_all(account.as_bytes()).await?;
writer.write_all(b" :").await?;
writer.write_all(message.as_bytes()).await?;
}
ServerMessageBody::N903SaslSuccess { nick, message } => {
writer.write_all(b"903 ").await?;
writer.write_all(nick.as_bytes()).await?;
writer.write_all(b" :").await?;
writer.write_all(message.as_bytes()).await?;
}
ServerMessageBody::N904SaslFail { nick, text } => {
writer.write_all(b"904").await?;
writer.write_all(b" ").await?;
writer.write_all(nick.as_bytes()).await?;
writer.write_all(b" :").await?;
writer.write_all(text.as_bytes()).await?;
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CapSubBody {
Ls(Str),
Ack(Str),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AwayStatus {
Here,
Gone,
}
2023-04-13 19:15:48 +00:00
fn server_message_body(input: &str) -> IResult<&str, ServerMessageBody> {
alt((
server_message_body_notice,
server_message_body_ping,
server_message_body_pong,
2024-03-20 18:59:15 +00:00
server_message_body_cap,
))(input)
}
2023-04-13 19:15:48 +00:00
fn server_message_body_notice(input: &str) -> IResult<&str, ServerMessageBody> {
let (input, _) = tag("NOTICE ")(input)?;
let (input, first_target) = receiver(input)?;
let (input, _) = tag(" :")(input)?;
let (input, text) = token(input)?;
2023-04-13 19:15:48 +00:00
let first_target = first_target.into();
let text = text.into();
Ok((
input,
ServerMessageBody::Notice {
first_target,
rest_targets: vec![],
text,
},
))
}
2023-04-13 19:15:48 +00:00
fn server_message_body_ping(input: &str) -> IResult<&str, ServerMessageBody> {
let (input, _) = tag("PING ")(input)?;
let (input, token) = token(input)?;
Ok((input, ServerMessageBody::Ping { token: token.into() }))
}
2023-04-13 19:15:48 +00:00
fn server_message_body_pong(input: &str) -> IResult<&str, ServerMessageBody> {
let (input, _) = tag("PONG ")(input)?;
let (input, from) = receiver(input)?;
let (input, _) = tag(" :")(input)?;
let (input, token) = token(input)?;
Ok((
input,
ServerMessageBody::Pong {
2023-04-13 19:15:48 +00:00
from: from.into(),
token: token.into(),
},
))
}
fn server_message_body_cap(input: &str) -> IResult<&str, ServerMessageBody> {
let (input, _) = tag("CAP ")(input)?;
let (input, from) = receiver(input)?;
let (input, _) = tag(" LS :")(input)?;
let (input, token) = token(input)?;
Ok((
input,
ServerMessageBody::Cap {
target: from.into(),
subcmd: CapSubBody::Ls(token.into()),
},
))
}
#[cfg(test)]
mod test {
use assert_matches::*;
use super::*;
use crate::testkit::*;
#[test]
fn test_server_message_notice() {
2023-07-23 13:21:26 +00:00
let input = "NOTICE * :*** Looking up your hostname...\r\n";
let expected = ServerMessage {
tags: vec![],
sender: None,
body: ServerMessageBody::Notice {
2023-04-13 19:15:48 +00:00
first_target: "*".into(),
rest_targets: vec![],
2023-04-13 19:15:48 +00:00
text: "*** Looking up your hostname...".into(),
},
};
let result = server_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
let mut bytes = vec![];
sync_future(expected.write_async(&mut bytes)).unwrap().unwrap();
2023-04-13 19:15:48 +00:00
assert_eq!(bytes, input.as_bytes());
}
#[test]
fn test_server_message_pong() {
2023-07-23 13:21:26 +00:00
let input = "PONG server.example :LAG004911\r\n";
let expected = ServerMessage {
tags: vec![],
sender: None,
body: ServerMessageBody::Pong {
2023-04-13 19:15:48 +00:00
from: "server.example".into(),
token: "LAG004911".into(),
},
};
let result = server_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
let mut bytes = vec![];
sync_future(expected.write_async(&mut bytes)).unwrap().unwrap();
assert_eq!(bytes, input.as_bytes());
}
#[test]
fn test_server_message_cap_ls() {
let input = "CAP * LS :sasl\r\n";
let expected = ServerMessage {
tags: vec![],
sender: None,
body: ServerMessageBody::Cap {
target: "*".into(),
subcmd: CapSubBody::Ls("sasl".into()),
},
};
let result = server_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
let mut bytes = vec![];
sync_future(expected.write_async(&mut bytes)).unwrap().unwrap();
2023-04-13 19:15:48 +00:00
assert_eq!(bytes, input.as_bytes());
}
}