split irc proto in server in client modules

This commit is contained in:
Nikita Vilunov 2023-02-09 20:26:05 +01:00
parent dc7b748a99
commit e5d0722fe0
4 changed files with 313 additions and 276 deletions

View File

@ -42,7 +42,7 @@ async fn handle_socket(
},
Err(err) => log::warn!("Failed to read from socket: {err}"),
}
let parsed = parse_client_message(&buffer[..]);
let parsed = client::client_message(&buffer[..]);
match parsed {
Ok((rest, msg)) => {
log::info!("Incoming IRC message: {msg:?}");

134
src/protos/irc/client.rs Normal file
View File

@ -0,0 +1,134 @@
use super::*;
/// Client-to-server command.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ClientMessage {
/// CAP. Capability-related commands.
Capability {
subcommand: CapabilitySubcommand,
},
/// PING
Ping {
token: ByteVec,
},
Pong {
token: ByteVec,
},
}
pub fn client_message(input: &[u8]) -> IResult<&[u8], ClientMessage> {
alt((
client_message_capability,
client_message_ping,
client_message_pong,
))(input)
}
fn client_message_capability(input: &[u8]) -> IResult<&[u8], ClientMessage> {
let (input, _) = tag("CAP ")(input)?;
let (input, subcommand) = capability_subcommand(input)?;
Ok((input, ClientMessage::Capability { subcommand }))
}
fn client_message_ping(input: &[u8]) -> IResult<&[u8], ClientMessage> {
let (input, _) = tag("PING ")(input)?;
let (input, token) = token(input)?;
Ok((
input,
ClientMessage::Ping {
token: token.to_owned(),
},
))
}
fn client_message_pong(input: &[u8]) -> IResult<&[u8], ClientMessage> {
let (input, _) = tag("PONG ")(input)?;
let (input, token) = token(input)?;
Ok((
input,
ClientMessage::Pong {
token: token.to_owned(),
},
))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CapabilitySubcommand {
/// CAP LS {code}
List { code: [u8; 3] },
/// CAP END
End,
}
fn capability_subcommand(input: &[u8]) -> IResult<&[u8], CapabilitySubcommand> {
alt((capability_subcommand_ls, capability_subcommand_end))(input)
}
fn capability_subcommand_ls(input: &[u8]) -> IResult<&[u8], CapabilitySubcommand> {
let (input, _) = tag("LS ")(input)?;
let (input, code) = take(3usize)(input)?;
Ok((
input,
CapabilitySubcommand::List {
code: code.try_into().unwrap(),
},
))
}
fn capability_subcommand_end(input: &[u8]) -> IResult<&[u8], CapabilitySubcommand> {
let (input, _) = tag("END")(input)?;
Ok((input, CapabilitySubcommand::End))
}
#[cfg(test)]
mod test {
use assert_matches::*;
use super::*;
#[test]
fn test_client_message_cap_ls() {
let input = b"CAP LS 302";
let expected = ClientMessage::Capability {
subcommand: CapabilitySubcommand::List { code: *b"302" },
};
let result = client_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
}
#[test]
fn test_client_message_cap_end() {
let input = b"CAP END";
let expected = ClientMessage::Capability {
subcommand: CapabilitySubcommand::End,
};
let result = client_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
}
#[test]
fn test_client_message_ping() {
let input = b"PING 1337";
let expected = ClientMessage::Ping {
token: b"1337".to_vec(),
};
let result = client_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
}
#[test]
fn test_client_message_pong() {
let input = b"PONG 1337";
let expected = ClientMessage::Pong {
token: b"1337".to_vec(),
};
let result = client_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
}
}

View File

@ -1,4 +1,6 @@
//! Client-to-Server IRC protocol.
pub mod client;
pub mod server;
use std::io::Write;
@ -10,131 +12,6 @@ use nom::{
type ByteVec = Vec<u8>;
/// Server-to-client message.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ServerMessage {
/// Optional tags section, prefixed with `@`
tags: Vec<Tag>,
/// Optional server name, prefixed with `:`.
sender: Option<String>,
body: ServerMessageBody,
}
impl ServerMessage {
pub fn write(&self, writer: &mut impl Write) -> std::io::Result<()> {
self.body.write(writer)?;
writer.write(b"\n")?;
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,
},
}
impl ServerMessageBody {
pub fn write(&self, writer: &mut impl Write) -> std::io::Result<()> {
match self {
ServerMessageBody::Notice {
first_target,
rest_targets,
text,
} => {
writer.write(b"NOTICE ")?;
writer.write(&first_target)?;
writer.write(b" :")?;
writer.write(&text)?;
}
ServerMessageBody::Ping { token } => {
writer.write(b"PING ")?;
writer.write(&token)?;
}
ServerMessageBody::Pong { from, token } => {
writer.write(b"PONG ")?;
writer.write(&from)?;
writer.write(b" :")?;
writer.write(&token)?;
}
}
Ok(())
}
}
fn server_message_body(input: &[u8]) -> IResult<&[u8], ServerMessageBody> {
alt((
sserver_message_body_notice,
server_message_body_ping,
server_message_body_pong,
))(input)
}
fn sserver_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(),
},
))
}
/// Single message tag value.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Tag {
@ -142,23 +19,6 @@ pub struct Tag {
value: Option<u8>,
}
/// Client-to-server command.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ClientMessage {
/// CAP. Capability-related commands.
Capability {
subcommand: CapabilitySubcommand,
},
/// PING
Ping {
token: ByteVec,
},
Pong {
from: ByteVec,
token: ByteVec,
},
}
fn receiver(input: &[u8]) -> IResult<&[u8], &[u8]> {
take_while(|i| i != b'\n' && i != b'\r' && i != b' ')(input)
}
@ -166,136 +26,3 @@ fn receiver(input: &[u8]) -> IResult<&[u8], &[u8]> {
fn token(input: &[u8]) -> IResult<&[u8], &[u8]> {
take_while(|i| i != b'\n' && i != b'\r')(input)
}
pub fn parse_client_message(input: &[u8]) -> IResult<&[u8], ClientMessage> {
alt((command_capability, command_ping))(input)
}
fn command_capability(input: &[u8]) -> IResult<&[u8], ClientMessage> {
let (input, _) = tag("CAP ")(input)?;
let (input, subcommand) = capability_subcommand(input)?;
Ok((input, ClientMessage::Capability { subcommand }))
}
fn command_ping(input: &[u8]) -> IResult<&[u8], ClientMessage> {
let (input, _) = tag("PING ")(input)?;
let (input, token) = token(input)?;
Ok((
input,
ClientMessage::Ping {
token: token.to_owned(),
},
))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CapabilitySubcommand {
/// CAP LS {code}
List { code: [u8; 3] },
/// CAP END
End,
}
fn capability_subcommand(input: &[u8]) -> IResult<&[u8], CapabilitySubcommand> {
alt((capability_subcommand_ls, capability_subcommand_end))(input)
}
fn capability_subcommand_ls(input: &[u8]) -> IResult<&[u8], CapabilitySubcommand> {
let (input, _) = tag("LS ")(input)?;
let (input, code) = take(3usize)(input)?;
Ok((
input,
CapabilitySubcommand::List {
code: code.try_into().unwrap(),
},
))
}
fn capability_subcommand_end(input: &[u8]) -> IResult<&[u8], CapabilitySubcommand> {
let (input, _) = tag("END")(input)?;
Ok((input, CapabilitySubcommand::End))
}
#[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);
}
#[test]
fn test_client_message_cap_ls() {
let input = b"CAP LS 302";
let expected = ClientMessage::Capability {
subcommand: CapabilitySubcommand::List { code: *b"302" },
};
let result = parse_client_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
}
#[test]
fn test_client_message_cap_end() {
let input = b"CAP END";
let expected = ClientMessage::Capability {
subcommand: CapabilitySubcommand::End,
};
let result = parse_client_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
}
#[test]
fn test_client_message_ping() {
let input = b"PING 1337";
let expected = ClientMessage::Ping {
token: b"1337".to_vec(),
};
let result = parse_client_message(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
}
}

176
src/protos/irc/server.rs Normal file
View File

@ -0,0 +1,176 @@
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<String>,
pub body: ServerMessageBody,
}
impl ServerMessage {
pub fn write(&self, writer: &mut impl Write) -> std::io::Result<()> {
self.body.write(writer)?;
writer.write(b"\n")?;
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,
},
}
impl ServerMessageBody {
pub fn write(&self, writer: &mut impl Write) -> std::io::Result<()> {
match self {
ServerMessageBody::Notice {
first_target,
rest_targets,
text,
} => {
writer.write(b"NOTICE ")?;
writer.write(&first_target)?;
writer.write(b" :")?;
writer.write(&text)?;
}
ServerMessageBody::Ping { token } => {
writer.write(b"PING ")?;
writer.write(&token)?;
}
ServerMessageBody::Pong { from, token } => {
writer.write(b"PONG ")?;
writer.write(&from)?;
writer.write(b" :")?;
writer.write(&token)?;
}
}
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);
}
}