pub use rpl::Reply;
use std::fmt;
use std::cell::RefCell;
pub const MESSAGE_LENGTH: usize = 512;
pub const PARAMS_LENGTH: usize = 15;
pub mod rpl {
pub type Reply = &'static str;
pub const WELCOME: Reply = "001"; pub const YOURHOST: Reply = "002"; pub const CREATED: Reply = "003"; pub const MYINFO: Reply = "004"; pub const ISUPPORT: Reply = "005";
pub const UMODEIS: Reply = "221"; pub const LUSERCLIENT: Reply = "251"; pub const LUSEROP: Reply = "252"; pub const LUSERUNKNOWN: Reply = "253"; pub const LUSERCHANNELS: Reply = "254"; pub const LUSERME: Reply = "255"; pub const ADMINME: Reply = "256"; pub const ADMINLOC1: Reply = "257"; pub const ADMINLOC2: Reply = "258"; pub const ADMINMAIL: Reply = "259";
pub const WHOISUSER: Reply = "311"; pub const WHOISSERVER: Reply = "312"; pub const WHOISOPERATOR: Reply = "313"; pub const ENDOFWHO: Reply = "315"; pub const WHOISIDLE: Reply = "317"; pub const ENDOFWHOIS: Reply = "318"; pub const WHOISCHANNELS: Reply = "319"; pub const LIST: Reply = "322"; pub const LISTEND: Reply = "323"; pub const CHANNELMODEIS: Reply = "324"; pub const NOTOPIC: Reply = "331"; pub const TOPIC: Reply = "332"; pub const INVITING: Reply = "341"; pub const INVITELIST: Reply = "346"; pub const ENDOFINVITELIST: Reply = "347"; pub const EXCEPTLIST: Reply = "348"; pub const ENDOFEXCEPTLIST: Reply = "349"; pub const VERSION: Reply = "351"; pub const WHOREPLY: Reply = "352"; pub const NAMREPLY: Reply = "353"; pub const ENDOFNAMES: Reply = "366"; pub const BANLIST: Reply = "367"; pub const ENDOFBANLIST: Reply = "368"; pub const INFO: Reply = "371"; pub const MOTD: Reply = "372"; pub const ENDOFINFO: Reply = "374"; pub const MOTDSTART: Reply = "375"; pub const ENDOFMOTD: Reply = "376"; pub const YOUREOPER: Reply = "381"; pub const TIME: Reply = "391";
pub const ERR_NOSUCHNICK: Reply = "401"; pub const ERR_NOSUCHCHANNEL: Reply = "403"; pub const ERR_CANNOTSENDTOCHAN: Reply = "404"; pub const ERR_INVALIDCAPCMD: Reply = "410"; pub const ERR_NORECIPIENT: Reply = "411"; pub const ERR_NOTEXTTOSEND: Reply = "412"; pub const ERR_INPUTTOOLONG: Reply = "417"; pub const ERR_UNKNOWNCOMMAND: Reply = "421"; pub const ERR_NOMOTD: Reply = "422"; pub const ERR_NONICKNAMEGIVEN: Reply = "431"; pub const ERR_ERRONEUSNICKNAME: Reply = "432"; pub const ERR_NICKNAMEINUSE: Reply = "433"; pub const ERR_USERNOTINCHANNEL: Reply = "441"; pub const ERR_NOTONCHANNEL: Reply = "442"; pub const ERR_USERONCHANNEL: Reply = "443"; pub const ERR_NOTREGISTERED: Reply = "451"; pub const ERR_NEEDMOREPARAMS: Reply = "461"; pub const ERR_ALREADYREGISTRED: Reply = "462"; pub const ERR_PASSWDMISMATCH: Reply = "464"; pub const ERR_YOUREBANNEDCREEP: Reply = "465"; pub const ERR_KEYSET: Reply = "467"; pub const ERR_CHANNELISFULL: Reply = "471"; pub const ERR_UNKNOWNMODE: Reply = "472"; pub const ERR_INVITEONLYCHAN: Reply = "473"; pub const ERR_BANNEDFROMCHAN: Reply = "474"; pub const ERR_BADCHANKEY: Reply = "475"; pub const ERR_CHANOPRIVSNEEDED: Reply = "482";
pub const ERR_UMODEUNKNOWNFLAG: Reply = "501"; pub const ERR_USERSDONTMATCH: Reply = "502";
pub const LOGGEDIN: Reply = "900"; pub const LOGGEDOUT: Reply = "901"; pub const ERR_NICKLOCKED: Reply = "902"; pub const SASLSUCCESS: Reply = "903"; pub const ERR_SASLFAIL: Reply = "904"; pub const ERR_SASLTOOLONG: Reply = "905"; pub const ERR_SASLABORTED: Reply = "906"; pub const ERR_SASLALREADY: Reply = "907"; pub const SASLMECHS: Reply = "908"; }
fn parse_word(s: &str) -> (&str, &str) {
let mut split = s.splitn(2, ' ')
.map(str::trim)
.filter(|s| !s.is_empty());
(split.next().unwrap_or(""), split.next().unwrap_or(""))
}
fn parse_tags(buf: &str) -> (&str, &str) {
if buf.starts_with('@') {
let (tags, rest) = parse_word(buf);
(&tags[1..], rest)
} else {
("", buf)
}
}
fn parse_prefix(buf: &str) -> (Option<&str>, &str) {
if buf.starts_with(':') {
let (prefix, rest) = parse_word(buf);
(Some(&prefix[1..]), rest)
} else {
(None, buf.trim_start())
}
}
fn parse_command(buf: &str) -> (Result<Command, &str>, &str) {
let (command_string, rest) = parse_word(buf);
(Command::parse(command_string).ok_or(command_string), rest)
}
pub struct Tag<'a> {
pub key: &'a str,
pub value: Option<&'a str>,
pub is_client: bool,
}
impl<'a> Tag<'a> {
pub fn parse(buf: &'a str) -> Self {
let mut split = buf.splitn(2, '=');
let key = split.next().unwrap();
let value = match split.next() {
Some("") | None => None,
Some(other) => if other.ends_with('\\') {
Some(&other[..other.len() - 1])
} else {
Some(other)
}
};
let is_client = key.starts_with('+');
Self {
key: if is_client {&key[1..]} else {key},
value,
is_client,
}
}
}
pub fn tags(s: &str) -> impl Iterator<Item=Tag<'_>> {
s.split(';').map(|item| Tag::parse(item))
}
macro_rules! commands {
( $( $cmd:ident $cmd_str:literal $n:literal )* ) => {
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Command {
$( $cmd, )*
Reply(Reply),
}
impl Command {
pub fn parse(s: &str) -> Option<Self> {
$( if s.eq_ignore_ascii_case($cmd_str) {
Some(Command::$cmd)
} else )* {
None
}
}
pub fn required_params(&self) -> usize {
match self {
$(
Command::$cmd => $n,
)*
Command::Reply(_) => 0,
}
}
pub fn as_str(&self) -> &'static str {
match self {
$(
Command::$cmd => $cmd_str,
)*
Command::Reply(s) => s,
}
}
}
impl From<&'static str> for Command {
fn from(reply: &'static str) -> Self {
Command::Reply(reply)
}
}
impl fmt::Display for Command {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
}
}
commands! {
Admin "ADMIN" 0
Authenticate "AUTHENTICATE" 1
Cap "CAP" 1
Info "INFO" 0
Invite "INVITE" 2
Join "JOIN" 1
Kick "KICK" 2
List "LIST" 0
Lusers "LUSERS" 0
Mode "MODE" 1
Motd "MOTD" 0
Names "NAMES" 0
Nick "NICK" 1
Notice "NOTICE" 2
Oper "OPER" 2
Part "PART" 1
Pass "PASS" 1
Ping "PING" 1
Pong "PONG" 1
PrivMsg "PRIVMSG" 2
Quit "QUIT" 0
TagMsg "TAGMSG" 1
Time "TIME" 0
Topic "TOPIC" 1
User "USER" 4
Version "VERSION" 0
Who "WHO" 0
Whois "WHOIS" 1
}
pub fn assert_msg(msg: &Message<'_>, prefix: Option<&str>, command: Result<Command, &str>,
params: &[&str])
{
assert_eq!(msg.prefix, prefix, "prefix of {:?}", msg);
assert_eq!(msg.command, command, "command of {:?}", msg);
assert_eq!(msg.num_params, params.len(), "number of parameters of {:?}", msg);
for (i, (actual, expected)) in msg.params.iter().zip(params.iter()).enumerate() {
if expected.is_empty() {
continue;
}
assert_eq!(actual, expected, "parameter #{} of {:?}", i, msg);
}
}
#[derive(Clone, Debug)]
pub struct Message<'a> {
pub tags: &'a str,
pub prefix: Option<&'a str>,
pub command: Result<Command, &'a str>,
pub num_params: usize,
pub params: [&'a str; PARAMS_LENGTH],
}
impl<'a> Message<'a> {
pub fn parse(s: &'a str) -> Option<Message<'a>>
{
let mut buf = s.trim();
if buf.is_empty() || buf.contains('\0') {
return None;
}
let (tags, rest) = parse_tags(buf);
buf = rest;
let (prefix, rest) = parse_prefix(buf);
buf = rest;
let (command, rest) = parse_command(buf);
buf = rest;
if let Err("") = command {
return None;
}
let mut params = [""; PARAMS_LENGTH];
let mut num_params = 0;
while num_params < PARAMS_LENGTH {
if buf.is_empty() {
break;
}
if buf.starts_with(':') {
params[num_params] = &buf[1..];
buf = "";
} else {
let (word, rest) = parse_word(buf);
params[num_params] = word;
buf = rest;
}
num_params += 1;
}
Some(Message { tags, prefix, command, num_params, params })
}
pub fn has_enough_params(&self) -> bool {
match self.command {
Ok(cmd) => cmd.required_params() <= self.num_params,
Err(_) => false,
}
}
}
pub struct MessageBuffer<'a> {
buf: &'a mut String,
}
impl<'a> MessageBuffer<'a> {
fn with_prefix<C>(buf: &'a mut String, prefix: &str, command: C) -> Self
where C: Into<Command>
{
if !prefix.is_empty() {
buf.push(':');
buf.push_str(prefix);
buf.push(' ');
}
buf.push_str(command.into().as_str());
MessageBuffer { buf }
}
pub fn param(self, param: &str) -> Self
{
let param = param.trim();
if param.is_empty() {
return self;
}
self.buf.push(' ');
self.buf.push_str(param);
self
}
pub fn trailing_param(self, param: &str)
{
self.buf.push(' ');
self.buf.push(':');
self.buf.push_str(param);
}
pub fn raw_param(&mut self) -> &mut String {
self.buf.push(' ');
self.buf
}
pub fn raw_trailing_param(&mut self) -> &mut String {
self.buf.push(' ');
self.buf.push(':');
self.buf
}
}
impl Drop for MessageBuffer<'_> {
fn drop(&mut self) {
self.buf.push('\r');
self.buf.push('\n');
}
}
pub struct TagBuffer<'a> {
buf: &'a mut String,
tag_start: usize,
}
impl<'a> TagBuffer<'a> {
fn new(buf: &'a mut String) -> Self {
buf.reserve(MESSAGE_LENGTH);
let tag_start = buf.len();
buf.push('@');
TagBuffer {
buf,
tag_start,
}
}
fn is_empty(&self) -> bool {
self.buf.len() == self.tag_start + 1
}
pub fn tag(self, key: &str, value: Option<&str>) -> Self {
if !self.is_empty() {
self.buf.push(';');
}
self.buf.push_str(key);
if let Some(value) = value {
self.buf.push('=');
self.buf.push_str(value);
}
self
}
fn raw_tag(self, s: &str) -> Self {
if !self.is_empty() {
self.buf.push(';');
}
self.buf.push_str(s);
self
}
pub fn save_tags_len(self, out: &mut usize) -> Self {
if self.buf.ends_with('@') {
*out = 0;
} else {
*out = self.buf.len() + 1 - self.tag_start;
}
self
}
pub fn prefixed_command<C>(self, prefix: &str, cmd: C) -> MessageBuffer<'a>
where C: Into<Command>
{
if self.is_empty() {
self.buf.pop();
} else {
self.buf.push(' ');
}
MessageBuffer::with_prefix(self.buf, prefix, cmd)
}
}
#[derive(Debug)]
pub struct Buffer {
buf: String,
}
impl Default for Buffer {
fn default() -> Self {
Self::new()
}
}
impl Buffer {
pub fn new() -> Self {
Self {
buf: String::new(),
}
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn message<C>(&mut self, prefix: &str, command: C) -> MessageBuffer<'_>
where C: Into<Command>
{
self.buf.reserve(MESSAGE_LENGTH);
MessageBuffer::with_prefix(&mut self.buf, prefix, command)
}
pub fn tagged_message(&mut self, client_tags: &str) -> TagBuffer<'_> {
client_tags.split(';')
.filter(|s| s.starts_with('+'))
.fold(TagBuffer::new(&mut self.buf), |buf, tag| buf.raw_tag(tag))
}
pub fn build(self) -> String {
self.buf
}
}
thread_local! {
static DOMAIN: RefCell<String> = RefCell::new(String::new());
static NICKNAME: RefCell<String> = RefCell::new(String::new());
}
pub struct ReplyBuffer {
buf: Buffer,
}
impl ReplyBuffer {
pub fn new(domain: &str, nickname: &str) -> Self {
DOMAIN.with(|s| {
let mut s = s.borrow_mut();
s.clear();
s.push_str(domain);
});
NICKNAME.with(|n| {
let mut n = n.borrow_mut();
n.clear();
n.push_str(nickname);
});
Self {
buf: Buffer::new(),
}
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn reply<C>(&mut self, r: C) -> MessageBuffer<'_>
where C: Into<Command>
{
let msg = DOMAIN.with(move |s| self.buf.message(&s.borrow(), r));
NICKNAME.with(|s| msg.param(&s.borrow()))
}
pub fn message<C>(&mut self, prefix: &str, command: C) -> MessageBuffer<'_>
where C: Into<Command>
{
self.buf.message(prefix, command)
}
pub fn build(self) -> String {
self.buf.build()
}
}