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, /// Optional server name, prefixed with `:`. pub sender: Option, 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, text: ByteVec, }, Ping { token: ByteVec, }, Pong { from: ByteVec, token: ByteVec, }, PrivateMessage { target: Recipient, body: ByteVec, }, Join(Chan), Part(Chan), 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 }, N332Topic { client: ByteVec, chat: Chan, topic: ByteVec, }, N353NamesReply { client: ByteVec, chan: Chan, members: ByteVec, // TODO make this a non-empty list with prefixes }, N366NamesReplyEnd { client: ByteVec, chan: Chan, }, } 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::PrivateMessage { target, body } => { writer.write_all(b"PRIVMSG ").await?; target.write_async(writer).await?; writer.write_all(b" :").await?; writer.write_all(&body).await?; } ServerMessageBody::Join(chan) => { writer.write_all(b"JOIN ").await?; chan.write_async(writer).await?; } ServerMessageBody::Part(chan) => { writer.write_all(b"PART ").await?; chan.write_async(writer).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(¶ms).await?; writer.write_all(b" :are supported by this server").await?; } ServerMessageBody::N332Topic { client, chat, topic, } => { writer.write_all(b"332 ").await?; writer.write_all(&client).await?; writer.write_all(b" ").await?; chat.write_async(writer).await?; writer.write_all(b" :").await?; writer.write_all(&topic).await?; } ServerMessageBody::N353NamesReply { client, chan, members, } => { writer.write_all(b"353 ").await?; writer.write_all(&client).await?; writer.write_all(b" = ").await?; chan.write_async(writer).await?; writer.write_all(b" :").await?; writer.write_all(&members).await?; } ServerMessageBody::N366NamesReplyEnd { client, chan } => { writer.write_all(b"366 ").await?; writer.write_all(&client).await?; writer.write_all(b" ").await?; chan.write_async(writer).await?; writer.write_all(b" :End of /NAMES list").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::*; use crate::util::testkit::*; #[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![]; sync_future(expected.write_async(&mut bytes)) .unwrap() .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![]; sync_future(expected.write_async(&mut bytes)) .unwrap() .unwrap(); assert_eq!(bytes, input); } }