forked from lavina/lavina
1
0
Fork 0
lavina/src/protos/irc/server.rs

251 lines
7.3 KiB
Rust

use tokio::io::AsyncWrite;
use tokio::io::AsyncWriteExt;
use super::*;
/// 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 `:`.
pub sender: Option<ByteVec>,
pub body: ServerMessageBody,
}
impl ServerMessage {
pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
match &self.sender {
Some(ref sender) => {
writer.write_all(b":").await?;
writer.write_all(sender.as_slice()).await?;
writer.write_all(b" ").await?;
}
None => {}
}
self.body.write_async(writer).await?;
writer.write_all(b"\n").await?;
Ok(())
}
}
pub fn server_message(input: &[u8]) -> IResult<&[u8], ServerMessage> {
let (input, command) = server_message_body(input)?;
let (input, _) = tag(b"\n")(input)?;
let message = ServerMessage {
tags: vec![],
sender: None,
body: command,
};
Ok((input, message))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ServerMessageBody {
Notice {
first_target: ByteVec,
rest_targets: Vec<ByteVec>,
text: ByteVec,
},
Ping {
token: ByteVec,
},
Pong {
from: ByteVec,
token: ByteVec,
},
N001Welcome {
client: ByteVec,
text: ByteVec,
},
N002YourHost {
client: ByteVec,
text: ByteVec,
},
N003Created {
client: ByteVec,
text: ByteVec,
},
N004MyInfo {
client: ByteVec,
hostname: ByteVec,
softname: ByteVec,
// TODO user modes, channel modes, channel modes with a parameter
},
N005ISupport {
client: ByteVec,
params: ByteVec, // TODO make this a datatype
},
}
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?;
writer.write_all(&first_target).await?;
writer.write_all(b" :").await?;
writer.write_all(&text).await?;
}
ServerMessageBody::Ping { token } => {
writer.write_all(b"PING ").await?;
writer.write_all(&token).await?;
}
ServerMessageBody::Pong { from, token } => {
writer.write_all(b"PONG ").await?;
writer.write_all(&from).await?;
writer.write_all(b" :").await?;
writer.write_all(&token).await?;
}
ServerMessageBody::N001Welcome { client, text } => {
writer.write_all(b"001 ").await?;
writer.write_all(&client).await?;
writer.write_all(b" :").await?;
writer.write_all(text).await?;
}
ServerMessageBody::N002YourHost { client, text } => {
writer.write_all(b"002 ").await?;
writer.write_all(&client).await?;
writer.write_all(b" :").await?;
writer.write_all(text).await?;
}
ServerMessageBody::N003Created { client, text } => {
writer.write_all(b"003 ").await?;
writer.write_all(&client).await?;
writer.write_all(b" :").await?;
writer.write_all(text).await?;
}
ServerMessageBody::N004MyInfo {
client,
hostname,
softname,
} => {
writer.write_all(b"004 ").await?;
writer.write_all(&client).await?;
writer.write_all(b" ").await?;
writer.write_all(&hostname).await?;
writer.write_all(b" ").await?;
writer.write_all(&softname).await?;
writer
.write_all(b" DGMQRSZagiloswz CFILPQbcefgijklmnopqrstvz bkloveqjfI")
.await?;
// TODO remove hardcoded modes
}
ServerMessageBody::N005ISupport { client, params } => {
writer.write_all(b"005 ").await?;
writer.write_all(&client).await?;
writer.write_all(b" ").await?;
writer.write_all(&params).await?;
writer.write_all(b" :are supported by this server").await?;
}
}
Ok(())
}
}
fn server_message_body(input: &[u8]) -> IResult<&[u8], ServerMessageBody> {
alt((
server_message_body_notice,
server_message_body_ping,
server_message_body_pong,
))(input)
}
fn server_message_body_notice(input: &[u8]) -> IResult<&[u8], ServerMessageBody> {
let (input, _) = tag("NOTICE ")(input)?;
let (input, first_target) = receiver(input)?;
let (input, _) = tag(" :")(input)?;
let (input, text) = token(input)?;
let first_target = first_target.to_owned();
let text = text.to_owned();
Ok((
input,
ServerMessageBody::Notice {
first_target,
rest_targets: vec![],
text,
},
))
}
fn server_message_body_ping(input: &[u8]) -> IResult<&[u8], ServerMessageBody> {
let (input, _) = tag("PING ")(input)?;
let (input, token) = token(input)?;
Ok((
input,
ServerMessageBody::Ping {
token: token.to_owned(),
},
))
}
fn server_message_body_pong(input: &[u8]) -> IResult<&[u8], ServerMessageBody> {
let (input, _) = tag("PONG ")(input)?;
let (input, from) = receiver(input)?;
let (input, _) = tag(" :")(input)?;
let (input, token) = token(input)?;
Ok((
input,
ServerMessageBody::Pong {
from: from.to_owned(),
token: token.to_owned(),
},
))
}
#[cfg(test)]
mod test {
use assert_matches::*;
use super::*;
#[test]
fn test_server_message_notice() {
let input = b"NOTICE * :*** Looking up your hostname...\n";
let expected = ServerMessage {
tags: vec![],
sender: None,
body: ServerMessageBody::Notice {
first_target: b"*".to_vec(),
rest_targets: vec![],
text: b"*** Looking up your hostname...".to_vec(),
},
};
let result = server_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
let mut bytes = vec![];
expected.write(&mut bytes).unwrap();
assert_eq!(bytes, input);
}
#[test]
fn test_server_message_pong() {
let input = b"PONG server.example :LAG004911\n";
let expected = ServerMessage {
tags: vec![],
sender: None,
body: ServerMessageBody::Pong {
from: b"server.example".to_vec(),
token: b"LAG004911".to_vec(),
},
};
let result = server_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
let mut bytes = vec![];
expected.write(&mut bytes).unwrap();
assert_eq!(bytes, input);
}
}