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(-)
@@ -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]]
@@ -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;
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(
@@ -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" }
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"
@@ -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 {
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 {
@@ -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(),
));
@@ -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"
))
}
}
@@ -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<'_>);
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)?;
@@ -4,20 +4,20 @@ use crate::{primitives::*, Command};
use std::fmt::Write;
#[derive(Debug)]
pub enum Source {
User(Nick), pub enum Source<'a> {
User(Nick<'a>), Server,
}
#[derive(Debug, derive_more::From)]
pub enum ServerMessage {
Reply(Reply),
Command(Source, Command), pub enum ServerMessage<'a> {
Reply(Reply<'a>),
Command(Source<'a>, Command<'a>), 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 => "{} :{}", 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 => "{} :{}", 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",