From e5d0722fe02e75ecc142e0974933604633b7ce08 Mon Sep 17 00:00:00 2001 From: Nikita Vilunov Date: Thu, 9 Feb 2023 20:26:05 +0100 Subject: [PATCH] split irc proto in server in client modules --- src/projections/irc.rs | 2 +- src/protos/irc/client.rs | 134 +++++++++++++++++++ src/protos/irc/mod.rs | 277 +-------------------------------------- src/protos/irc/server.rs | 176 +++++++++++++++++++++++++ 4 files changed, 313 insertions(+), 276 deletions(-) create mode 100644 src/protos/irc/client.rs create mode 100644 src/protos/irc/server.rs diff --git a/src/projections/irc.rs b/src/projections/irc.rs index 6603624..d423b62 100644 --- a/src/projections/irc.rs +++ b/src/projections/irc.rs @@ -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:?}"); diff --git a/src/protos/irc/client.rs b/src/protos/irc/client.rs new file mode 100644 index 0000000..8a936a4 --- /dev/null +++ b/src/protos/irc/client.rs @@ -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)); + } +} diff --git a/src/protos/irc/mod.rs b/src/protos/irc/mod.rs index be8a263..af328e8 100644 --- a/src/protos/irc/mod.rs +++ b/src/protos/irc/mod.rs @@ -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; -/// Server-to-client message. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ServerMessage { - /// Optional tags section, prefixed with `@` - tags: Vec, - /// Optional server name, prefixed with `:`. - sender: Option, - 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, - 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, } -/// 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)); - } -} diff --git a/src/protos/irc/server.rs b/src/protos/irc/server.rs new file mode 100644 index 0000000..64396d2 --- /dev/null +++ b/src/protos/irc/server.rs @@ -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, + /// Optional server name, prefixed with `:`. + pub sender: Option, + 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, + 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); + } +}