forked from lavina/lavina
165 lines
4.4 KiB
Rust
165 lines
4.4 KiB
Rust
//! Client-to-Server IRC protocol.
|
|
pub mod client;
|
|
mod prelude;
|
|
pub mod server;
|
|
#[cfg(test)]
|
|
mod testkit;
|
|
pub mod user;
|
|
|
|
use crate::prelude::Str;
|
|
|
|
use nom::{
|
|
branch::alt,
|
|
bytes::complete::{tag, take, take_while},
|
|
IResult,
|
|
};
|
|
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
|
|
|
/// Single message tag value.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct Tag {
|
|
pub key: Str,
|
|
pub value: Option<Str>,
|
|
}
|
|
|
|
impl Tag {
|
|
pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
|
|
writer.write_all(self.key.as_bytes()).await?;
|
|
if let Some(value) = &self.value {
|
|
writer.write_all(b"=").await?;
|
|
writer.write_all(value.as_bytes()).await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn receiver(input: &str) -> IResult<&str, &str> {
|
|
take_while(|i| i != '\n' && i != '\r' && i != ' ')(input)
|
|
}
|
|
|
|
fn token(input: &str) -> IResult<&str, &str> {
|
|
take_while(|i| i != '\n' && i != '\r')(input)
|
|
}
|
|
|
|
fn params(input: &str) -> IResult<&str, &str> {
|
|
take_while(|i| i != '\n' && i != '\r' && i != ':')(input)
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum Chan {
|
|
/// `#<name>` — network-global channel, available from any server in the network.
|
|
Global(Str),
|
|
/// `&<name>` — server-local channel, available only to connections to the same server. Rarely used in practice.
|
|
Local(Str),
|
|
}
|
|
impl Chan {
|
|
pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
|
|
match self {
|
|
Chan::Global(name) => {
|
|
writer.write_all(b"#").await?;
|
|
writer.write_all(name.as_bytes()).await?;
|
|
}
|
|
Chan::Local(name) => {
|
|
writer.write_all(b"&").await?;
|
|
writer.write_all(name.as_bytes()).await?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn chan(input: &str) -> IResult<&str, Chan> {
|
|
fn chan_global(input: &str) -> IResult<&str, Chan> {
|
|
let (input, _) = tag("#")(input)?;
|
|
let (input, name) = receiver(input)?;
|
|
Ok((input, Chan::Global(name.into())))
|
|
}
|
|
|
|
fn chan_local(input: &str) -> IResult<&str, Chan> {
|
|
let (input, _) = tag("&")(input)?;
|
|
let (input, name) = receiver(input)?;
|
|
Ok((input, Chan::Local(name.into())))
|
|
}
|
|
|
|
alt((chan_global, chan_local))(input)
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum Recipient {
|
|
Nick(Str),
|
|
Chan(Chan),
|
|
}
|
|
impl Recipient {
|
|
pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
|
|
match self {
|
|
Recipient::Nick(nick) => writer.write_all(nick.as_bytes()).await?,
|
|
Recipient::Chan(chan) => chan.write_async(writer).await?,
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn recipient(input: &str) -> IResult<&str, Recipient> {
|
|
fn recipient_chan(input: &str) -> IResult<&str, Recipient> {
|
|
let (input, chan) = chan(input)?;
|
|
Ok((input, Recipient::Chan(chan)))
|
|
}
|
|
|
|
fn recipient_nick(input: &str) -> IResult<&str, Recipient> {
|
|
let (input, nick) = receiver(input)?;
|
|
Ok((input, Recipient::Nick(nick.into())))
|
|
}
|
|
|
|
alt((recipient_chan, recipient_nick))(input)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use assert_matches::*;
|
|
|
|
use super::*;
|
|
use crate::testkit::*;
|
|
|
|
#[test]
|
|
fn test_chan_global() {
|
|
let input = "#testchan";
|
|
let expected = Chan::Global("testchan".into());
|
|
|
|
let result = chan(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.as_slice(), input.as_bytes());
|
|
}
|
|
|
|
#[test]
|
|
fn test_chan_local() {
|
|
let input = "&localchan";
|
|
let expected = Chan::Local("localchan".into());
|
|
|
|
let result = chan(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.as_slice(), input.as_bytes());
|
|
}
|
|
|
|
#[test]
|
|
fn test_recipient_user() {
|
|
let input = "User";
|
|
let expected = Recipient::Nick("User".into());
|
|
|
|
let result = recipient(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.as_slice(), input.as_bytes());
|
|
}
|
|
}
|