🏡 index : ~doyle/titanirc.git

author Jordan Doyle <jordan@doyle.la> 2021-01-31 22:18:16.0 +00:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-02-01 1:41:16.0 +00:00:00
commit
c7813bb5ae43dea7ab73cd4abfa1efce20f77ecb [patch]
tree
3044d4c2a6d0a95768d9218ee7aefc99f555e867
parent
afca2b1e4a1872dac57d2c7b3dde451e2f65d3c0
download
c7813bb5ae43dea7ab73cd4abfa1efce20f77ecb.tar.gz

Zero-copy encoding to the wire



Diff

 Cargo.lock                                    |  19 +-----
 titanirc-codec/src/wire.rs                    |   7 +-
 titanirc-server/Cargo.toml                    |   4 +-
 titanirc-server/src/entities/user/commands.rs |  63 +++++++++------
 titanirc-server/src/entities/user/mod.rs      |  26 +++---
 titanirc-types/src/lib.rs                     |  50 ++++++------
 titanirc-types/src/primitives.rs              | 105 +++++++++++++++----------
 titanirc-types/src/replies.rs                 | 112 +++++++++++++--------------
 8 files changed, 211 insertions(+), 175 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index a06c6d5..e5d7673 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,7 +3,7 @@
[[package]]
name = "actix"
version = "0.11.0-beta.1"
source = "git+https://github.com/actix/actix?rev=fdaa5d50e25ffc892f5c1c6fcc51097796debecf#fdaa5d50e25ffc892f5c1c6fcc51097796debecf"
source = "git+https://github.com/JordanForks/actix#6c04d4eab0b9bf883e85a9b5659ecdc00a609bc4"
dependencies = [
 "actix-macros 0.1.3",
 "actix-rt",
@@ -937,30 +937,17 @@ dependencies = [
]

[[package]]
name = "tokio-stream"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76066865172052eb8796c686f0b441a93df8b08d40a950b062ffb9a426f00edd"
dependencies = [
 "futures-core",
 "pin-project-lite",
 "tokio",
]

[[package]]
name = "tokio-util"
version = "0.6.2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feb971a26599ffd28066d387f109746df178eff14d5ea1e235015c5601967a4b"
checksum = "ebb7cb2f00c5ae8df755b252306272cd1790d39728363936e01827e11f0b017b"
dependencies = [
 "async-stream",
 "bytes",
 "futures-core",
 "futures-sink",
 "log",
 "pin-project-lite",
 "tokio",
 "tokio-stream",
]

[[package]]
diff --git a/titanirc-codec/src/wire.rs b/titanirc-codec/src/wire.rs
index 01e6da7..0db4539 100644
--- a/titanirc-codec/src/wire.rs
+++ b/titanirc-codec/src/wire.rs
@@ -1,4 +1,4 @@
use bytes::{BytesMut};
use bytes::BytesMut;
use titanirc_types::Command;
use tokio_util::codec::Decoder as FrameDecoder;

@@ -7,7 +7,8 @@ pub const MAX_LENGTH: usize = 1024;
pub struct Decoder;

impl FrameDecoder for Decoder {
    type Item = Command;
    /// Returns `'static` since we just return `BytesCow::Owned(bytes::Bytes)` and doesn't use the lifetime.
    type Item = Command<'static>;
    type Error = std::io::Error;

    fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
@@ -49,7 +50,7 @@ impl FrameDecoder for Decoder {

pub struct Encoder;

impl tokio_util::codec::Encoder<titanirc_types::ServerMessage> for Encoder {
impl tokio_util::codec::Encoder<titanirc_types::ServerMessage<'_>> for Encoder {
    type Error = std::io::Error;

    fn encode(
diff --git a/titanirc-server/Cargo.toml b/titanirc-server/Cargo.toml
index 5dcc9ad..bf7b7f8 100644
--- a/titanirc-server/Cargo.toml
+++ b/titanirc-server/Cargo.toml
@@ -10,8 +10,8 @@ edition = "2018"
titanirc-codec = { path = "../titanirc-codec" }
titanirc-types = { path = "../titanirc-types" }

actix = { git = "https://github.com/actix/actix", rev = "fdaa5d50e25ffc892f5c1c6fcc51097796debecf" } # tokio 1.0 support not yet released
actix-rt = "2.0.0-beta.1"
actix = { git = "https://github.com/JordanForks/actix" }
actix-rt = "=2.0.0-beta.2"
tokio = { version = "1.1", features = ["net", "signal"] }
tokio-util = "0.6"
async-stream = "0.3"
diff --git a/titanirc-server/src/entities/user/commands.rs b/titanirc-server/src/entities/user/commands.rs
index 3bab702..d8c437e 100644
--- a/titanirc-server/src/entities/user/commands.rs
+++ b/titanirc-server/src/entities/user/commands.rs
@@ -1,4 +1,4 @@
use std::{ops::Deref, time::Instant};
use std::time::Instant;

use actix::{Actor, AsyncContext, StreamHandler, WrapFuture};
use titanirc_types::{
@@ -9,8 +9,8 @@ pub trait CommandHandler<T>: Actor {
    fn handle_cmd(&mut self, command: T, ctx: &mut Self::Context);
}

impl StreamHandler<Result<Command, std::io::Error>> for super::User {
    fn handle(&mut self, cmd: Result<Command, std::io::Error>, ctx: &mut Self::Context) {
impl StreamHandler<Result<Command<'static>, std::io::Error>> for super::User {
    fn handle(&mut self, cmd: Result<Command<'static>, std::io::Error>, ctx: &mut Self::Context) {
        self.last_active = Instant::now();

        match cmd {
@@ -30,8 +30,12 @@ impl StreamHandler<Result<Command, std::io::Error>> for super::User {
// TODO: all the 'raw' writes using byte strings below probably need to
//  be wrapped in something a bit more friendly.

impl CommandHandler<NickCommand> for super::User {
    fn handle_cmd(&mut self, NickCommand { nick }: NickCommand, _ctx: &mut Self::Context) {
impl CommandHandler<NickCommand<'static>> for super::User {
    fn handle_cmd(
        &mut self,
        NickCommand { nick, .. }: NickCommand<'static>,
        _ctx: &mut Self::Context,
    ) {
        self.writer.write(titanirc_types::Reply::RplWelcome.into());
        self.writer.write(titanirc_types::Reply::RplYourHost.into());
        self.writer.write(titanirc_types::Reply::RplCreated.into());
@@ -44,8 +48,12 @@ impl CommandHandler<NickCommand> for super::User {
    }
}

impl CommandHandler<JoinCommand> for super::User {
    fn handle_cmd(&mut self, JoinCommand { channel }: JoinCommand, ctx: &mut Self::Context) {
impl CommandHandler<JoinCommand<'static>> for super::User {
    fn handle_cmd(
        &mut self,
        JoinCommand { channel, .. }: JoinCommand<'static>,
        ctx: &mut Self::Context,
    ) {
        if let Some(ref nick) = self.nick {
            let server_addr = self.server.clone();
            let ctx_addr = ctx.address();
@@ -71,34 +79,42 @@ impl CommandHandler<JoinCommand> for super::User {
    }
}

impl CommandHandler<ModeCommand> for super::User {
    fn handle_cmd(&mut self, ModeCommand { mode, .. }: ModeCommand, _ctx: &mut Self::Context) {
impl CommandHandler<ModeCommand<'static>> for super::User {
    fn handle_cmd(
        &mut self,
        ModeCommand { mode, .. }: ModeCommand<'static>,
        _ctx: &mut Self::Context,
    ) {
        self.writer
            .write(titanirc_types::Reply::RplUmodeIs(mode).into())
    }
}

impl CommandHandler<MotdCommand> for super::User {
    fn handle_cmd(&mut self, _command: MotdCommand, _ctx: &mut Self::Context) {
impl CommandHandler<MotdCommand<'static>> for super::User {
    fn handle_cmd(&mut self, _command: MotdCommand<'static>, _ctx: &mut Self::Context) {
        static SERVER_NAME: bytes::Bytes = bytes::Bytes::from_static(b"my.test.server");
        static MOTD1: bytes::Bytes = bytes::Bytes::from_static(b"Hello, welcome to this server!");
        static MOTD2: bytes::Bytes = bytes::Bytes::from_static(b"it's very cool!");

        self.writer.write(
            titanirc_types::Reply::RplMotdStart(titanirc_types::ServerName(SERVER_NAME.clone()))
                .into(),
            titanirc_types::Reply::RplMotdStart(titanirc_types::ServerName(
                SERVER_NAME.clone().into(),
            ))
            .into(),
        );
        self.writer.write(
            titanirc_types::Reply::RplMotd(titanirc_types::FreeText(MOTD1.clone().into())).into(),
        );
        self.writer.write(
            titanirc_types::Reply::RplMotd(titanirc_types::FreeText(MOTD2.clone().into())).into(),
        );
        self.writer
            .write(titanirc_types::Reply::RplMotd(titanirc_types::FreeText(MOTD1.clone())).into());
        self.writer
            .write(titanirc_types::Reply::RplMotd(titanirc_types::FreeText(MOTD2.clone())).into());
        self.writer
            .write(titanirc_types::Reply::RplEndOfMotd.into());
    }
}

impl CommandHandler<VersionCommand> for super::User {
    fn handle_cmd(&mut self, _command: VersionCommand, _ctx: &mut Self::Context) {
impl CommandHandler<VersionCommand<'static>> for super::User {
    fn handle_cmd(&mut self, _command: VersionCommand<'static>, _ctx: &mut Self::Context) {
        static SERVER_NAME: bytes::Bytes = bytes::Bytes::from_static(b"my.test.server");
        static INFO: bytes::Bytes =
            bytes::Bytes::from_static(b"https://github.com/MITBorg/titanirc");
@@ -107,21 +123,22 @@ impl CommandHandler<VersionCommand> for super::User {
            titanirc_types::Reply::RplVersion(
                clap::crate_version!().to_string(),
                "release".to_string(),
                titanirc_types::ServerName(SERVER_NAME.clone()),
                titanirc_types::FreeText(INFO.clone()),
                titanirc_types::ServerName(SERVER_NAME.clone().into()),
                titanirc_types::FreeText(INFO.clone().into()),
            )
            .into(),
        )
    }
}

impl CommandHandler<PrivmsgCommand> for super::User {
impl CommandHandler<PrivmsgCommand<'static>> for super::User {
    fn handle_cmd(
        &mut self,
        PrivmsgCommand {
            receiver,
            free_text,
        }: PrivmsgCommand,
            ..
        }: PrivmsgCommand<'static>,
        ctx: &mut Self::Context,
    ) {
        if let Some(nick) = &self.nick {
diff --git a/titanirc-server/src/entities/user/mod.rs b/titanirc-server/src/entities/user/mod.rs
index 0912d23..0bce0fb 100644
--- a/titanirc-server/src/entities/user/mod.rs
+++ b/titanirc-server/src/entities/user/mod.rs
@@ -3,7 +3,7 @@ pub mod events;

use crate::{entities::channel::events::JoinBroadcast, server::Server};

use std::{collections::HashMap, sync::Arc};
use std::sync::Arc;

use actix::{
    io::{FramedWrite, WriteHandler},
@@ -11,13 +11,17 @@ use actix::{
};
use std::time::{Duration, Instant};
use titanirc_types::{
    Channel, FreeText, JoinCommand, PrivmsgCommand, Receiver, ServerMessage, Source,
    Channel, FreeText, JoinCommand, Nick, PrivmsgCommand, Receiver, ServerMessage, Source,
};
use tokio::{io::WriteHalf, net::TcpStream};

pub struct User {
    pub server: Addr<Server>,
    pub writer: FramedWrite<ServerMessage, WriteHalf<TcpStream>, titanirc_codec::Encoder>,
    pub writer: FramedWrite<
        WriteHalf<TcpStream>,
        titanirc_codec::Encoder,
        <titanirc_codec::Encoder as tokio_util::codec::Encoder<ServerMessage<'static>>>::Error,
    >,
    pub last_active: Instant,
    pub nick: Option<String>,
}
@@ -27,7 +31,7 @@ pub struct User {
impl User {
    pub fn new(
        server: Addr<Server>,
        writer: FramedWrite<ServerMessage, WriteHalf<TcpStream>, titanirc_codec::Encoder>,
        writer: FramedWrite<WriteHalf<TcpStream>, titanirc_codec::Encoder>,
    ) -> Self {
        Self {
            server,
@@ -70,9 +74,10 @@ impl actix::Handler<Arc<JoinBroadcast>> for User {

    fn handle(&mut self, msg: Arc<JoinBroadcast>, _ctx: &mut Self::Context) -> Self::Result {
        self.writer.write(ServerMessage::Command(
            Source::User(bytes::Bytes::from(msg.nick.as_bytes().to_owned()).into()),
            Source::User(Nick(msg.nick.as_bytes().into())),
            JoinCommand {
                channel: Channel::from(bytes::Bytes::from(msg.channel_name.as_bytes().to_owned())),
                _phantom: std::marker::PhantomData,
                channel: Channel(msg.channel_name.as_bytes().into()),
            }
            .into(),
        ));
@@ -88,12 +93,11 @@ impl actix::Handler<Arc<crate::entities::common_events::Message>> for User {
        _ctx: &mut Self::Context,
    ) -> Self::Result {
        self.writer.write(ServerMessage::Command(
            Source::User(bytes::Bytes::from(msg.from.as_bytes().to_owned()).into()),
            Source::User(Nick(msg.from.as_bytes().into())),
            PrivmsgCommand {
                free_text: FreeText(bytes::Bytes::from(msg.message.as_bytes().to_owned())),
                receiver: {
                    Receiver::Channel(bytes::Bytes::from(msg.to.as_bytes().to_owned()).into())
                },
                _phantom: std::marker::PhantomData,
                free_text: FreeText(msg.message.as_bytes().into()),
                receiver: Receiver::Channel(Channel(msg.to.as_bytes().into())),
            }
            .into(),
        ));
diff --git a/titanirc-types/src/lib.rs b/titanirc-types/src/lib.rs
index 582f5dc..044d762 100644
--- a/titanirc-types/src/lib.rs
+++ b/titanirc-types/src/lib.rs
@@ -24,19 +24,19 @@ macro_rules! define_commands {
    (
        $(
            $name:ident$((
                $($param:ty),*
                $($param:ident$(<$($gen:tt),+>)?),*
            ))?
        ),* $(,)?
    ) => {
        paste::paste! {
            #[derive(Debug)]
            pub enum Command {
                $([<$name:camel>]([<$name:camel Command>])),*
            pub enum Command<'a> {
                $([<$name:camel>]([<$name:camel Command>]<'a>)),*
            }

            $(const [<$name _BYTES>]: &[u8] = stringify!($name).as_bytes();)*

            impl Command {
            impl Command<'_> {
                pub fn parse(input: Bytes) -> Result<Option<Self>, nom::Err<NomError<BytesWrapper>>> {
                    let input = BytesWrapper::from(input);

@@ -56,7 +56,7 @@ macro_rules! define_commands {
                }
            }

            impl std::fmt::Display for Command {
            impl std::fmt::Display for Command<'_> {
                fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                    match self {
                        $(Self::[<$name:camel>](cmd) => cmd.fmt(fmt)),*
@@ -66,11 +66,12 @@ macro_rules! define_commands {

            $(
                #[derive(Debug)]
                pub struct [<$name:camel Command>] {
                    $($(pub [<$param:snake>]: $param),*),*
                pub struct [<$name:camel Command>]<'a> {
                    pub _phantom: std::marker::PhantomData<&'a ()>,
                    $($(pub [<$param:snake>]: $param$(<$($gen),+>)?),*),*
                }

                impl [<$name:camel Command>] {
                impl [<$name:camel Command>]<'_> {
                    #[allow(unused_variables)]
                    pub fn parse(rest: BytesWrapper) -> Result<Self, nom::Err<nom::error::Error<BytesWrapper>>> {
                        $(
@@ -81,12 +82,13 @@ macro_rules! define_commands {
                        )*

                        Ok(Self {
                            _phantom: std::marker::PhantomData,
                            $($([<$param:snake>]),*),*
                        })
                    }
                }

                impl std::fmt::Display for [<$name:camel Command>] {
                impl std::fmt::Display for [<$name:camel Command>]<'_> {
                    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                        fmt.write_str(stringify!($name))?;

@@ -101,8 +103,8 @@ macro_rules! define_commands {
                    }
                }

                impl Into<Command> for [<$name:camel Command>] {
                    fn into(self) -> Command {
                impl<'a> Into<Command<'a>> for [<$name:camel Command>]<'a> {
                    fn into(self) -> Command<'a> {
                        Command::[<$name:camel>](self)
                    }
                }
@@ -112,24 +114,24 @@ macro_rules! define_commands {
}

define_commands! {
    USER(Username, HostName, ServerName, RealName),
    NICK(Nick),
    USER(Username<'a>, HostName<'a>, ServerName<'a>, RealName<'a>),
    NICK(Nick<'a>),

    MOTD,
    VERSION,
    HELP,
    USERS,
    TIME,
    PONG(ServerName),
    PING(ServerName),
    PONG(ServerName<'a>),
    PING(ServerName<'a>),
    LIST,
    MODE(Nick, Mode),
    WHOIS(Nick),
    USERHOST(Nick),
    USERIP(Nick),
    JOIN(Channel),
    MODE(Nick<'a>, Mode<'a>),
    WHOIS(Nick<'a>),
    USERHOST(Nick<'a>),
    USERIP(Nick<'a>),
    JOIN(Channel<'a>),

    PRIVMSG(Receiver, FreeText),
    PRIVMSG(Receiver<'a>, FreeText<'a>),
}

#[cfg(test)]
@@ -149,7 +151,8 @@ mod tests {
            Ok(Some(Command::Privmsg(super::PrivmsgCommand {
                receiver: super::Receiver::User(super::Nick(nick)),
                free_text: super::primitives::FreeText(msg),
            }))) if nick == "foo" && msg == "baz"
                _phantom: std::marker::PhantomData,
            }))) if &*nick == b"foo" && &*msg == b"baz"
        ))
    }

@@ -160,7 +163,8 @@ mod tests {
            Ok(Some(Command::Privmsg(super::PrivmsgCommand {
                receiver: super::Receiver::User(super::Nick(nick)),
                free_text: super::primitives::FreeText(msg),
            }))) if nick == "foo" && msg == "baz"
                _phantom: std::marker::PhantomData,
            }))) if &*nick == b"foo" && &*msg == b"baz"
        ))
    }
}
diff --git a/titanirc-types/src/primitives.rs b/titanirc-types/src/primitives.rs
index c5ea3fc..e4f9341 100644
--- a/titanirc-types/src/primitives.rs
+++ b/titanirc-types/src/primitives.rs
@@ -2,7 +2,7 @@ use bytes::Bytes;
use derive_more::{Deref, From};
use nom::{
    bytes::complete::{tag, take_till},
    combinator::{iterator},
    combinator::iterator,
    sequence::terminated,
    IResult,
};
@@ -18,6 +18,29 @@ pub trait PrimitiveParser {
        Self: Sized;
}

#[derive(Debug, From)]
pub enum BytesCow<'a> {
    Owned(Bytes),
    Borrowed(&'a [u8]),
}

impl From<BytesWrapper> for BytesCow<'_> {
    fn from(other: BytesWrapper) -> Self {
        Self::Owned(other.into())
    }
}

impl std::ops::Deref for BytesCow<'_> {
    type Target = [u8];

    fn deref(&self) -> &Self::Target {
        match self {
            Self::Owned(b) => &*b,
            Self::Borrowed(b) => *b,
        }
    }
}

macro_rules! noop_validator {
    ($name:ty) => {
        impl ValidatingParser for $name {
@@ -137,37 +160,37 @@ impl ValidatingParser for Special {
}

#[derive(Debug, Deref, From)]
pub struct Username(pub Bytes);
space_terminated_primitive!(Username);
noop_validator!(Username);
pub struct Username<'a>(pub BytesCow<'a>);
space_terminated_primitive!(Username<'_>);
noop_validator!(Username<'_>);

#[derive(Debug, Deref, From)]
pub struct Mode(pub Bytes);
space_terminated_primitive!(Mode);
noop_validator!(Mode);
pub struct Mode<'a>(pub BytesCow<'a>);
space_terminated_primitive!(Mode<'_>);
noop_validator!(Mode<'_>);

#[derive(Debug, Deref, From)]
pub struct HostName(pub Bytes);
space_terminated_primitive!(HostName);
noop_validator!(HostName);
pub struct HostName<'a>(pub BytesCow<'a>);
space_terminated_primitive!(HostName<'_>);
noop_validator!(HostName<'_>);

#[derive(Debug, Deref, From)]
pub struct ServerName(pub Bytes);
space_terminated_primitive!(ServerName);
noop_validator!(ServerName);
pub struct ServerName<'a>(pub BytesCow<'a>);
space_terminated_primitive!(ServerName<'_>);
noop_validator!(ServerName<'_>);

#[derive(Debug, Deref, From)]
pub struct RealName(pub Bytes);
space_terminated_primitive!(RealName);
noop_validator!(RealName);
pub struct RealName<'a>(pub BytesCow<'a>);
space_terminated_primitive!(RealName<'_>);
noop_validator!(RealName<'_>);

#[derive(Debug, Deref, From)]
pub struct Nick(pub Bytes);
space_terminated_primitive!(Nick);
pub struct Nick<'a>(pub BytesCow<'a>);
space_terminated_primitive!(Nick<'_>);

// TODO: i feel like this would be better suited as a nom chomper to stop
// iterating over the string twice unnecessarily
impl ValidatingParser for Nick {
impl ValidatingParser for Nick<'_> {
    fn validate(bytes: &[u8]) -> bool {
        if bytes.is_empty() {
            return false;
@@ -184,20 +207,20 @@ impl ValidatingParser for Nick {
}

#[derive(Debug, Deref, From)]
pub struct Channel(pub Bytes);
space_terminated_primitive!(Channel);
noop_validator!(Channel);
pub struct Channel<'a>(pub BytesCow<'a>);
space_terminated_primitive!(Channel<'_>);
noop_validator!(Channel<'_>);

#[derive(Debug, Deref, From)]
pub struct FreeText(pub Bytes);
free_text_primitive!(FreeText);
noop_validator!(FreeText);
pub struct FreeText<'a>(pub BytesCow<'a>);
free_text_primitive!(FreeText<'_>);
noop_validator!(FreeText<'_>);

#[derive(Debug, Deref, From)]
pub struct Nicks(pub Vec<Nick>);
space_delimited_display!(Nicks);
pub struct Nicks<'a>(pub Vec<Nick<'a>>);
space_delimited_display!(Nicks<'_>);

impl PrimitiveParser for Nicks {
impl PrimitiveParser for Nicks<'_> {
    fn parse(bytes: BytesWrapper) -> IResult<BytesWrapper, Self> {
        let mut it = iterator(
            bytes,
@@ -212,9 +235,9 @@ impl PrimitiveParser for Nicks {
}

#[derive(Debug)]
pub struct RightsPrefixedNick(pub Rights, pub Nick);
pub struct RightsPrefixedNick<'a>(pub Rights, pub Nick<'a>);

impl std::fmt::Display for RightsPrefixedNick {
impl std::fmt::Display for RightsPrefixedNick<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)?;
        self.1.fmt(f)
@@ -222,13 +245,13 @@ impl std::fmt::Display for RightsPrefixedNick {
}

#[derive(Debug, Deref, From)]
pub struct RightsPrefixedNicks(pub Vec<RightsPrefixedNick>);
space_delimited_display!(RightsPrefixedNicks);
pub struct RightsPrefixedNicks<'a>(pub Vec<RightsPrefixedNick<'a>>);
space_delimited_display!(RightsPrefixedNicks<'_>);

#[derive(Debug)]
pub struct RightsPrefixedChannel(pub Rights, pub Nick);
pub struct RightsPrefixedChannel<'a>(pub Rights, pub Nick<'a>);

impl std::fmt::Display for RightsPrefixedChannel {
impl std::fmt::Display for RightsPrefixedChannel<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)?;
        self.1.fmt(f)
@@ -236,8 +259,8 @@ impl std::fmt::Display for RightsPrefixedChannel {
}

#[derive(Debug, Deref, From)]
pub struct RightsPrefixedChannels(pub Vec<RightsPrefixedChannel>);
space_delimited_display!(RightsPrefixedChannels);
pub struct RightsPrefixedChannels<'a>(pub Vec<RightsPrefixedChannel<'a>>);
space_delimited_display!(RightsPrefixedChannels<'_>);

#[derive(Debug)]
pub enum Rights {
@@ -255,12 +278,12 @@ impl std::fmt::Display for Rights {
}

#[derive(Debug, From)]
pub enum Receiver {
    User(Nick),
    Channel(Channel),
pub enum Receiver<'a> {
    User(Nick<'a>),
    Channel(Channel<'a>),
}

impl std::ops::Deref for Receiver {
impl std::ops::Deref for Receiver<'_> {
    type Target = str;

    fn deref(&self) -> &Self::Target {
@@ -272,7 +295,7 @@ impl std::ops::Deref for Receiver {
    }
}

impl PrimitiveParser for Receiver {
impl PrimitiveParser for Receiver<'_> {
    fn parse(bytes: BytesWrapper) -> IResult<BytesWrapper, Self> {
        if bytes.get(0) == Some(&b'#') {
            let (rest, channel) = Channel::parse(bytes)?;
diff --git a/titanirc-types/src/replies.rs b/titanirc-types/src/replies.rs
index 0e8809b..e680b67 100644
--- a/titanirc-types/src/replies.rs
+++ b/titanirc-types/src/replies.rs
@@ -4,20 +4,20 @@ use crate::{primitives::*, Command};
use std::fmt::Write;

#[derive(Debug)]
pub enum Source {
    User(Nick), // change Nick to whatever type nick!user@netmask is..
pub enum Source<'a> {
    User(Nick<'a>), // change Nick to whatever type nick!user@netmask is..
    Server,
}

#[derive(Debug, derive_more::From)]
pub enum ServerMessage {
    Reply(Reply),
    Command(Source, Command), // change Nick to whatever type nick!user@netmask is..
pub enum ServerMessage<'a> {
    Reply(Reply<'a>),
    Command(Source<'a>, Command<'a>), // change Nick to whatever type nick!user@netmask is..
    Ping,
    Pong,
}

impl ServerMessage {
impl ServerMessage<'_> {
    pub fn write(self, server_name: &str, client_username: &str, dst: &mut bytes::BytesMut) {
        match self {
            Self::Reply(reply) => write!(
@@ -45,18 +45,18 @@ impl ServerMessage {
macro_rules! define_replies {
    (
        $(
            $name:ident$(($($arg:ty),*))? = $num:expr $(=> $msg:expr)?
            $name:ident$(($($arg:ident$(<$($gen:tt),+>)?),*))? = $num:expr $(=> $msg:expr)?
        ),* $(,)?
    ) => {
        #[derive(Debug)]
        #[allow(clippy::pub_enum_variant_names)]
        pub enum Reply {
        pub enum Reply<'a> {
            $(
                $name$(($($arg),*))*,
                $name$(($($arg$(<$($gen),+>)?),*))*,
            )*
        }

        impl std::fmt::Display for Reply {
        impl std::fmt::Display for Reply<'_> {
            fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                paste::paste! {
                    match self {
@@ -66,7 +66,7 @@ macro_rules! define_replies {
            }
        }

        impl Reply {
        impl Reply<'_> {
            #[must_use]
            pub fn code(&self) -> &'static str {
                paste::paste! {
@@ -106,14 +106,14 @@ define_replies! {
    RplMyInfo = 004 => ":my.test.server 0.0.1 DOQRSZaghilopsuwz CFILMPQSbcefgijklmnopqrstuvz bkloveqjfI",
    RplISupport = 005 => "D :are supported by this server",

    RplUmodeIs(Mode) = 221 => "{}",
    RplUmodeIs(Mode<'a>) = 221 => "{}",

    ErrNoSuchNick(Nick) = 401 => "{} :No such nick/channel",
    ErrNoSuchServer(ServerName) = 402 => "{} :No such server",
    ErrNoSuchChannel(Channel) = 403 => "{} :No such channel",
    ErrCannotSendToChan(Channel) = 404 => "{} :Cannot send to channel",
    ErrTooManyChannels(Channel) = 405 => "{} :You have joined too many channels",
    ErrWasNoSuchNick(Nick) = 406 => "{} :There was no such nickname",
    ErrNoSuchNick(Nick<'a>) = 401 => "{} :No such nick/channel",
    ErrNoSuchServer(ServerName<'a>) = 402 => "{} :No such server",
    ErrNoSuchChannel(Channel<'a>) = 403 => "{} :No such channel",
    ErrCannotSendToChan(Channel<'a>) = 404 => "{} :Cannot send to channel",
    ErrTooManyChannels(Channel<'a>) = 405 => "{} :You have joined too many channels",
    ErrWasNoSuchNick(Nick<'a>) = 406 => "{} :There was no such nickname",
    ErrTooManyTargets(Target) = 407 => "{} :Duplicate recipients. No message delivered",
    ErrNoOrigin = 409 => ":No origin specified",
    ErrNoRecipient(CommandName) = 411 => ":No recipient given ({})",
@@ -122,16 +122,16 @@ define_replies! {
    ErrWildTopLevel(Mask) = 414 => "{} :Wildcard in toplevel domain",
    ErrUnknownCommand(CommandName) = 421 => "{} :Unknown command",
    ErrNoMotd = 422 => ":MOTD File is missing",
    ErrNoAdminInfo(ServerName) = 423 => "{} :No administrative info available",
    ErrNoAdminInfo(ServerName<'a>) = 423 => "{} :No administrative info available",
    ErrFileError(FileOp, File) = 424 => ":File error doing {} on {}",
    ErrNoNickGiven = 431 => ":No nickname given",
    ErrErroneusNick(Nick) = 432 => "{} :Erroneus nickname",
    ErrNickInUse(Nick) = 433 => "{} :Nick is already in use",
    ErrNickCollision(Nick) = 436 => "{} :Nick collision KILL",
    ErrUserNotInChannel(Nick, Channel) = 441 => "{} {} :They aren't on that channel",
    ErrNotOnChannel(Channel) = 442 => "{} :You're not on that channel",
    ErrUserOnChannel(Username, Channel) = 443 => "{} {} :is already on channel",
    ErrNoLogin(Username) = 444 => "{} :User not logged in",
    ErrErroneusNick(Nick<'a>) = 432 => "{} :Erroneus nickname",
    ErrNickInUse(Nick<'a>) = 433 => "{} :Nick is already in use",
    ErrNickCollision(Nick<'a>) = 436 => "{} :Nick collision KILL",
    ErrUserNotInChannel(Nick<'a>, Channel<'a>) = 441 => "{} {} :They aren't on that channel",
    ErrNotOnChannel(Channel<'a>) = 442 => "{} :You're not on that channel",
    ErrUserOnChannel(Username<'a>, Channel<'a>) = 443 => "{} {} :is already on channel",
    ErrNoLogin(Username<'a>) = 444 => "{} :User not logged in",
    ErrSummonDisabled = 445 => ":SUMMON has been disabled",
    ErrUsersDisabled = 446 => ":USERS has been disabled",
    ErrNotRegistered = 451 => ":You have not registered",
@@ -140,52 +140,52 @@ define_replies! {
    ErrNoPermForHost = 463 => ":Your host isn't among the privileged",
    ErrPasswdMismatch = 464 => ":Password incorrect",
    ErrYoureBannedCreep = 465 => ":You are banned from this server",
    ErrKeySet(Channel) = 467 => "{} :Channel key already set",
    ErrChannelIsFull(Channel) = 471 => "{} :Cannot join channel (+l)",
    ErrKeySet(Channel<'a>) = 467 => "{} :Channel key already set",
    ErrChannelIsFull(Channel<'a>) = 471 => "{} :Cannot join channel (+l)",
    ErrUnknownMode(Char) = 472 => "{} :is unknown mode char to me",
    ErrInviteOnlyChan(Channel) = 473 => "{} :Cannot join channel (+i)",
    ErrBannedFromChan(Channel) = 474 => "{} :Cannot join channel (+b)",
    ErrBadChannelKey(Channel) = 475 => "{} :Cannot join channel (+k)",
    ErrInviteOnlyChan(Channel<'a>) = 473 => "{} :Cannot join channel (+i)",
    ErrBannedFromChan(Channel<'a>) = 474 => "{} :Cannot join channel (+b)",
    ErrBadChannelKey(Channel<'a>) = 475 => "{} :Cannot join channel (+k)",
    ErrNoPrivileges = 481 => ":Permission Denied- You're not an IRC operator",
    ErrChanOPrivsNeeded(Channel) = 482 => "{} :You're not channel operator",
    ErrChanOPrivsNeeded(Channel<'a>) = 482 => "{} :You're not channel operator",
    ErrCantKillServer = 483 => ":You cant kill a server!",
    ErrNoOperHost = 491 => ":No O-lines for your host",
    ErrUmodeUnknownFlag = 501 => ":Unknown MODE flag",
    ErrUsersDontMatch = 502 => ":Cant change mode for other users",
    RplNone = 300,
    RplUserHost(UserHost) = 302 => "{}",
    RplIson(Nicks) = 303 => "{}",
    RplAway(Nick, FreeText) = 301 => "{} :{}",
    RplIson(Nicks<'a>) = 303 => "{}",
    RplAway(Nick<'a>, FreeText<'a>) = 301 => "{} :{}",
    RplUnaway = 305 => ":You are no longer marked as being away",
    RplNowAway = 306 => ":You have been marked as being away",
    RplWhoisUser(Nick, Username, HostName, RealName) = 311 => "{} {} {} * :{}",
    RplWhoisServer(Nick, ServerName, ServerInfo) = 312 => "{} {} :{}",
    RplWhoisOperator(Nick) = 313 => "{} :is an IRC operator",
    RplWhoisIdle(Nick, Integer) = 317 => "{} {} :seconds idle",
    RplEndOfWhois(Nick) = 318 => "{} :End of /WHOIS list",
    RplWhoisChannels(Nick, RightsPrefixedChannels) = 319 => "{} :{}", // todo
    RplWhoWasUser(Nick, Username, HostName, RealName) = 314 => "{} {} {} * :{}",
    RplEndOfWhoWas(Nick) = 369 => "{} :End of WHOWAS",
    RplWhoisUser(Nick<'a>, Username<'a>, HostName<'a>, RealName<'a>) = 311 => "{} {} {} * :{}",
    RplWhoisServer(Nick<'a>, ServerName<'a>, ServerInfo) = 312 => "{} {} :{}",
    RplWhoisOperator(Nick<'a>) = 313 => "{} :is an IRC operator",
    RplWhoisIdle(Nick<'a>, Integer) = 317 => "{} {} :seconds idle",
    RplEndOfWhois(Nick<'a>) = 318 => "{} :End of /WHOIS list",
    RplWhoisChannels(Nick<'a>, RightsPrefixedChannels<'a>) = 319 => "{} :{}", // todo
    RplWhoWasUser(Nick<'a>, Username<'a>, HostName<'a>, RealName<'a>) = 314 => "{} {} {} * :{}",
    RplEndOfWhoWas(Nick<'a>) = 369 => "{} :End of WHOWAS",
    RplListStart = 321 => "Channel :Users  RealName",
    RplList(Channel, AmtVisible, FreeText) = 322 => "{} {} :{}",
    RplList(Channel<'a>, AmtVisible, FreeText<'a>) = 322 => "{} {} :{}",
    RplListEnd = 323 => ":End of /LIST",
    RplChannelModeIs(Channel, Mode, ModeParams) = 324 => "{} {} {}",
    RplNoTopic(Channel) = 331 => "{} :No topic is set",
    RplTopic(Channel, FreeText) = 332 => "{} :{}",
    RplInviting(Channel, Nick) = 341 => "{} {}",
    RplVersion(Version, Debuglevel, ServerName, FreeText) = 351 => "{}.{} {} :{}",
    RplWhoReply(Channel, Username, HostName, ServerName, Nick, HG, Hopcount, RealName) = 352 => "{} {} {} {} {} {}[*][@|+] :{} {}",
    RplChannelModeIs(Channel<'a>, Mode<'a>, ModeParams) = 324 => "{} {} {}",
    RplNoTopic(Channel<'a>) = 331 => "{} :No topic is set",
    RplTopic(Channel<'a>, FreeText<'a>) = 332 => "{} :{}",
    RplInviting(Channel<'a>, Nick<'a>) = 341 => "{} {}",
    RplVersion(Version, Debuglevel, ServerName<'a>, FreeText<'a>) = 351 => "{}.{} {} :{}",
    RplWhoReply(Channel<'a>, Username<'a>, HostName<'a>, ServerName<'a>, Nick<'a>, HG, Hopcount, RealName<'a>) = 352 => "{} {} {} {} {} {}[*][@|+] :{} {}",
    RplEndOfWho(Target) = 315 => "{} :End of /WHO list",
    RplNamReply(Channel, RightsPrefixedNicks) = 353 => "{} :{}",
    RplEndOfNames(Channel) = 366 => "{} :End of /NAMES list",
    RplLinks(Mask, ServerName, Hopcount, ServerInfo) = 364 => "{} {} :{} {}",
    RplNamReply(Channel<'a>, RightsPrefixedNicks<'a>) = 353 => "{} :{}",
    RplEndOfNames(Channel<'a>) = 366 => "{} :End of /NAMES list",
    RplLinks(Mask, ServerName<'a>, Hopcount, ServerInfo) = 364 => "{} {} :{} {}",
    RplEndOfLinks(Mask) = 365 => "{} :End of /LINKS list",
    RplBanList(Channel, Banid) = 367 => "{} {}",
    RPLEndOfBanList(Channel) = 368 => "{} :End of channel ban list",
    RplBanList(Channel<'a>, Banid) = 367 => "{} {}",
    RPLEndOfBanList(Channel<'a>) = 368 => "{} :End of channel ban list",
    RplInfo(String) = 371 => ":{}",
    RplEndOfInfo = 374 => ":End of /INFO list",
    RplMotdStart(ServerName) = 375 => ":- {} Message of the day -",
    RplMotd(FreeText) = 372 => ":- {}",
    RplMotdStart(ServerName<'a>) = 375 => ":- {} Message of the day -",
    RplMotd(FreeText<'a>) = 372 => ":- {}",
    RplEndOfMotd = 376 => ":End of /MOTD command",
    RplYoureOper = 381 => ":You are now an IRC operator",
    RplRehashing(ConfigFile) = 382 => "{} :Rehashing",