From 9bb27a5fd0ef62874762df6d0eb180de6f351a24 Mon Sep 17 00:00:00 2001 From: Till Hoeppner Date: Tue, 21 Apr 2015 20:54:48 +0200 Subject: Ditch convenience api, switch to byte-indexing --- src/client.rs | 140 +++++++++ src/command.rs | 20 +- src/ident.rs | 2 +- src/lib.rs | 7 +- src/message.rs | 25 +- src/reply.rs | 915 +++++++++++++++++++++++++++++---------------------------- src/server.rs | 161 ---------- 7 files changed, 633 insertions(+), 637 deletions(-) create mode 100644 src/client.rs delete mode 100644 src/server.rs diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..a738262 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,140 @@ +use std::io::{ + self, + Write, + Read, + BufRead, + BufReader, +}; + +use std::net::TcpStream; +use std::borrow::Cow; + +use message::Message; +use command::Command; +use ::{ DEBUG, Result, IrscError }; + +#[cfg(feature = "ssl")] +use openssl::ssl::{ SslContext, SslMethod, SslStream }; + +/// Yes, I don't like the name either, but it's private, so... +enum StreamKind { + Plain(TcpStream), + #[cfg(feature = "ssl")] + Ssl(SslStream) +} + +impl Write for StreamKind { + fn write(&mut self, buf: &[u8]) -> io::Result { + match *self { + StreamKind::Plain(ref mut s) => s.write(buf), + #[cfg(feature = "ssl")] + StreamKind::Ssl(ref mut s) => s.write(buf) + } + } + + fn flush(&mut self) -> io::Result<()> { + match *self { + StreamKind::Plain(ref mut s) => s.flush(), + #[cfg(feature = "ssl")] + StreamKind::Ssl(ref mut s) => s.flush() + } + } +} + +impl Read for StreamKind { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match *self { + StreamKind::Plain(ref mut s) => s.read(buf), + #[cfg(feature = "ssl")] + StreamKind::Ssl(ref mut s) => s.read(buf) + } + } +} + +pub struct Client { + stream: Option +} + +impl Client { + pub fn new() -> Client { + Client { + stream: None + } + } + + fn handle_event(&mut self, msg: &Message) { + let _ = match Command::from_message(msg) { + Some(Command::PING(s1, s2)) => self.send(Command::PONG(s1, s2)), + _ => Ok(()) + }; + } + + pub fn connect(&mut self, host: String, port: u16) -> Result<()> { + let s = &mut self.stream; + match *s { Some(_) => return Err(IrscError::AlreadyConnected), _ => () }; + *s = match TcpStream::connect((host.as_ref(), port)) { + Ok(tcp) => Some(StreamKind::Plain(tcp)), + Err(e) => return Err(IrscError::Io(e)) + }; + + Ok(()) + } + + #[cfg(feature = "ssl")] + pub fn connect_ssl(&mut self, host: String, port: u16) -> Result<()> { + let mut s = self.stream.lock(); + match *s { Some(_) => return Err(IrscError::AlreadyConnected), _ => () }; + let tcp_stream = match TcpStream::connect((host.as_ref(), port)) { + Ok(tcp) => Some(tcp), + Err(e) => return Err(IrscError::Io(e)) + }; + + let ssl = SslContext::new(SslMethod::Tlsv1); + let ssl_stream = SslStream::new(&ssl, tcp_stream); + *s = ssl_stream; + + Ok(()) + } + + #[inline] + fn send_raw(&mut self, s: &str) -> Result<()> { + info!(">> {}", s); + if DEBUG && s.len() > 512 { + panic!("Message too long, kittens will die if this runs in release mode. Msg: {}", s) + } + + self.stream.as_mut() + .ok_or(IrscError::NotConnected) + .and_then(|mut stream| stream.write_all(s.as_bytes()) + .and_then(|_| stream.flush()) + .map_err(IrscError::Io)) + } + + pub fn send_message(&mut self, msg: Message) -> Result<()> { + self.send_raw(&msg.to_string()) + } + + pub fn send(&mut self, cmd: Command) -> Result<()> { + self.send_message(cmd.to_message()) + } + + pub fn listen(&mut self, events: F) -> Result<()> + where F: Fn(&mut Client, &Message) { + let reader = BufReader::new(match self.stream { + Some(StreamKind::Plain(ref s)) => (*s).try_clone().unwrap(), + #[cfg(feature = "ssl")] + Some(StreamKind::Ssl(ref s)) => (*s).try_clone().unwrap(), + None => return Err(IrscError::NotConnected) + }); + + for line in reader.lines() { + let line = line.unwrap().parse(); + + if let Ok(msg) = line { + self.handle_event(&msg); + events(self, &msg); + } + } + Ok(()) + } +} diff --git a/src/command.rs b/src/command.rs index 2abf5c8..bfe7825 100644 --- a/src/command.rs +++ b/src/command.rs @@ -458,7 +458,7 @@ pub enum Command<'a> { /// MODE !12345ircd O ; Command to ask who the channel /// creator for "!12345ircd" is /// ``` - MODE(CS<'a>, CS<'a> /* *( ( "-" / "+" ) * * ) */), + MODE(CS<'a>, Vec<(CS<'a>, CS<'a>)>), /// ```text``` /// 3.2.4 Topic message @@ -1608,11 +1608,13 @@ impl<'a> Command<'a> { Some(PART(ch.split(",").map(Borrowed).collect(), reason.first().map(|&m| m).map(Borrowed))) } else { None }, - /*"NOTICE" => msg.content().get(0).and_then(|c| msg.content().get(1).map(|t| - Command::NOTICE(t, c))), - "PING" => msg.content().get(0).map(|s1| - Command::PING(&s1, msg.content().get(1).map(|&s| s))),*/ - _ => unimplemented!() + "PRIVMSG" => if let [target, msg, ..] = msg.elements().as_ref() { + Some(PRIVMSG(Borrowed(target), Borrowed(msg))) + } else { None }, + "NOTICE" => if let [target, msg, ..] = msg.elements().as_ref() { + Some(NOTICE(Borrowed(target), Borrowed(msg))) + } else { None }, + _ => None } } @@ -1640,6 +1642,12 @@ impl<'a> Command<'a> { &PART(ref ch, ref reason) => Message::format(None, Borrowed("PART"), vec![Owned(ch.connect(","))], reason.clone(), MsgType::Irc), + &PRIVMSG(ref target, ref msg) => + Message::format(None, Borrowed("PRIVMSG"), + vec![target.clone()], Some(msg.clone()), MsgType::Irc), + &NOTICE(ref target, ref text) => + Message::format(None, Borrowed("NOTICE"), + vec![target.clone()], Some(text.clone()), MsgType::Irc), /*&Command::PING(ref server1, ref server2) => { let mut c = Vec::new(); c.push(server1.clone()); diff --git a/src/ident.rs b/src/ident.rs index 0008d53..6dd7de8 100644 --- a/src/ident.rs +++ b/src/ident.rs @@ -1,7 +1,7 @@ use regex::Regex; use std::borrow::ToOwned; -static PATTERN: Regex = regex!(":(.*)!(.*)@(.*)"); +static PATTERN: Regex = regex!("(.*)!(.*)@(.*)"); #[derive(Debug, Clone)] pub struct Ident { diff --git a/src/lib.rs b/src/lib.rs index aed8bc4..2ba5045 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ extern crate regex; extern crate log; extern crate eventual; -pub mod server; +pub mod client; pub mod color; pub mod ident; pub mod callback; @@ -17,6 +17,11 @@ pub mod reply; use std::io; use std::result; +pub use ident::Ident; +pub use message::{ Message, MsgType }; +pub use command::Command; +pub use reply::Reply; + #[derive(Debug)] pub enum IrscError { Io(io::Error), diff --git a/src/message.rs b/src/message.rs index b37ba6b..dd88b1f 100644 --- a/src/message.rs +++ b/src/message.rs @@ -16,6 +16,7 @@ pub enum MsgType { Ctcp } +/// Byte indices, be careful. #[derive(Debug, PartialEq, Eq, Clone)] pub struct Message { pub source: String, @@ -83,7 +84,7 @@ impl Message { } pub fn range(&self, r: &Range) -> &str { - self.source.slice_chars(r.start as usize, r.end as usize) + &self.source[r.start as usize..r.end as usize] } pub fn prefix(&self) -> Option<&str> { self.prefix.as_ref().map(|r| self.range(r)) } @@ -96,11 +97,12 @@ impl Message { impl FromStr for Message { type Err = IrscError; fn from_str(i: &str) -> Result { - info!("Attempting to parse message: {}", i); + info!("<< {}", i); let len = i.len(); + // remember, bytes, not chars let mut s = 0; - let prefix = if len >= 1 && i[s..].chars().next() == Some(':') { + let prefix = if len >= 1 && i[s..].as_bytes()[0] == ':' as u8 { i[s..].find(' ').map(|i| { let n = 1u16..(s + i) as u16; s += i + 1; n }) } else { None }; @@ -110,12 +112,10 @@ impl FromStr for Message { p }); - // TODO: Parse last non-suffix argument as suffix if no suffix - // with colon is available. let mut content = Vec::with_capacity(15); let mut suffix = None; while s < len - 3 { - if i[s..].chars().next() == Some(':') { + if i[s..].as_bytes()[0] == ':' as u8 { suffix = Some(s as u16 + 1 as u16..i.len() as u16); break } @@ -129,8 +129,9 @@ impl FromStr for Message { s += 1; } - let msg_type = if suffix.as_ref() - .and_then(|s| i[s.start as usize..].chars().next()) == Some('\u{1}') { MsgType::Ctcp } else { MsgType::Irc }; + let msg_type = if suffix.as_ref().map(|s| i[s.start as usize..].as_bytes()[0] == 1 + && i[(s.end - 3) as usize..].as_bytes()[0] == 1) + == Some(true) { MsgType::Ctcp } else { MsgType::Irc }; command.map(move |c| Ok(Message::new( @@ -141,8 +142,8 @@ impl FromStr for Message { // strip \{1} if CTCP message // strip \r\n for each line, relying on their existence match msg_type { - MsgType::Irc => suffix.map(|s| s.start..s.end - 2), - MsgType::Ctcp => suffix.map(|s| s.start + 1..s.end - 3) + MsgType::Irc => suffix.map(|s| s.start..s.end - 1), + MsgType::Ctcp => suffix.map(|s| s.start + 1..s.end - 2) }, msg_type )) @@ -200,7 +201,7 @@ mod test { Some(1..2), 3..10, vec![11..14], - Some(17..57), + Some(17..58), MsgType::Ctcp ); @@ -217,7 +218,7 @@ mod test { Some(1..6), 7..13, vec![14..18], - Some(20..51), + Some(20..52), MsgType::Irc ); assert_eq!(a.parse::().unwrap(), a2.clone()); diff --git a/src/reply.rs b/src/reply.rs index 005ec80..d1aad9d 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -1,107 +1,113 @@ -use ::{ Result, IrscError }; use std::str::FromStr; -use std::borrow::ToOwned; +use std::borrow::{ Cow, ToOwned }; +use std::borrow::Cow::*; + +use ::{ Result, IrscError }; +use ::message::{ MsgType, Message }; + +pub type CS<'a> = Cow<'a, str>; + #[allow(non_camel_case_types)] #[derive(Debug, Hash, PartialEq, Eq)] -pub enum Reply { +pub enum Reply<'a> { /// 001 RPL_WELCOME /// "Welcome to the Internet Relay Network /// !@" - RPL_WELCOME = 001, + RPL_WELCOME(CS<'a>), /// 002 RPL_YOURHOST /// "Your host is , running version " - RPL_YOURHOST = 002, + RPL_YOURHOST(CS<'a>), /// 003 RPL_CREATED /// "This server was created " - RPL_CREATED = 003, + RPL_CREATED(CS<'a>), /// 004 RPL_MYINFO /// " /// " - /// + /// /// - The server sends Replies 001 to 004 to a user upon /// successful registration. - /// - RPL_MYINFO = 004, + /// + RPL_MYINFO(CS<'a>), /// 005 RPL_BOUNCE /// "Try server , port " - /// + /// /// - Sent by the server to a user to suggest an alternative /// server. This is often used when the connection is /// refused because the server is already full. - /// - RPL_BOUNCE = 005, + /// + RPL_BOUNCE(CS<'a>), /// 302 RPL_USERHOST /// ":*1 *( " " )" - /// + /// /// - Reply format used by USERHOST to list replies to /// the query list. The reply string is composed as /// follows: - /// + /// /// reply = nickname [ "*" ] "=" ( "+" / "-" ) hostname - /// + /// /// The '*' indicates whether the client has registered /// as an Operator. The '-' or '+' characters represent /// whether the client has set an AWAY message or not /// respectively. - /// - RPL_USERHOST = 302, + /// + RPL_USERHOST(CS<'a>), /// 303 RPL_ISON /// ":*1 *( " " )" - /// + /// /// - Reply format used by ISON to list replies to the /// query list. - /// - RPL_ISON = 303, + /// + RPL_ISON(CS<'a>), /// 301 RPL_AWAY /// " :" - RPL_AWAY = 301, + RPL_AWAY(CS<'a>), /// 305 RPL_UNAWAY /// ":You are no longer marked as being away" - RPL_UNAWAY = 305, + RPL_UNAWAY(CS<'a>), /// 306 RPL_NOWAWAY /// ":You have been marked as being away" - /// + /// /// - These replies are used with the AWAY command (if /// allowed). RPL_AWAY is sent to any client sending a /// PRIVMSG to a client which is away. RPL_AWAY is only /// sent by the server to which the client is connected. /// Replies RPL_UNAWAY and RPL_NOWAWAY are sent when the /// client removes and sets an AWAY message. - /// - RPL_NOWAWAY = 306, + /// + RPL_NOWAWAY(CS<'a>), /// 311 RPL_WHOISUSER /// " * :" - RPL_WHOISUSER = 311, + RPL_WHOISUSER(CS<'a>), /// 312 RPL_WHOISSERVER /// " :" - RPL_WHOISSERVER = 312, + RPL_WHOISSERVER(CS<'a>), /// 313 RPL_WHOISOPERATOR /// " :is an IRC operator" - RPL_WHOISOPERATOR = 313, + RPL_WHOISOPERATOR(CS<'a>), /// 317 RPL_WHOISIDLE /// " :seconds idle" - RPL_WHOISIDLE = 317, + RPL_WHOISIDLE(CS<'a>), /// 318 RPL_ENDOFWHOIS /// " :End of WHOIS list" - RPL_ENDOFWHOIS = 318, + RPL_ENDOFWHOIS(CS<'a>), /// 319 RPL_WHOISCHANNELS /// " :*( ( "@" / "+" ) " " )" - /// + /// /// - Replies 311 - 313, 317 - 319 are all replies /// generated in response to a WHOIS message. Given that /// there are enough parameters present, the answering @@ -116,141 +122,141 @@ pub enum Reply { /// has been granted permission to speak on a moderated /// channel. The RPL_ENDOFWHOIS reply is used to mark /// the end of processing a WHOIS message. - /// - RPL_WHOISCHANNELS = 319, + /// + RPL_WHOISCHANNELS(CS<'a>), /// 314 RPL_WHOWASUSER /// " * :" - RPL_WHOWASUSER = 314, + RPL_WHOWASUSER(CS<'a>), /// 369 RPL_ENDOFWHOWAS /// " :End of WHOWAS" - /// + /// /// - When replying to a WHOWAS message, a server MUST use /// the replies RPL_WHOWASUSER, RPL_WHOISSERVER or /// ERR_WASNOSUCHNICK for each nickname in the presented /// list. At the end of all reply batches, there MUST /// be RPL_ENDOFWHOWAS (even if there was only one reply /// and it was an error). - /// - RPL_ENDOFWHOWAS = 369, + /// + RPL_ENDOFWHOWAS(CS<'a>), /// 321 RPL_LISTSTART /// Obsolete. Not used. - /// - RPL_LISTSTART = 321, + /// + RPL_LISTSTART, /// 322 RPL_LIST /// " <# visible> :" - RPL_LIST = 322, + RPL_LIST(CS<'a>), /// 323 RPL_LISTEND /// ":End of LIST" - /// + /// /// - Replies RPL_LIST, RPL_LISTEND mark the actual replies /// with data and end of the server's response to a LIST /// command. If there are no channels available to return, /// only the end reply MUST be sent. - /// - RPL_LISTEND = 323, + /// + RPL_LISTEND(CS<'a>), /// 325 RPL_UNIQOPIS /// " " - /// - RPL_UNIQOPIS = 325, + /// + RPL_UNIQOPIS(CS<'a>), /// 324 RPL_CHANNELMODEIS /// " " - /// - RPL_CHANNELMODEIS = 324, + /// + RPL_CHANNELMODEIS(CS<'a>), /// 331 RPL_NOTOPIC /// " :No topic is set" - RPL_NOTOPIC = 331, + RPL_NOTOPIC(CS<'a>), /// 332 RPL_TOPIC /// " :" - /// + /// /// - When sending a TOPIC message to determine the /// channel topic, one of two replies is sent. If /// the topic is set, RPL_TOPIC is sent back else /// RPL_NOTOPIC. - /// - RPL_TOPIC = 332, + /// + RPL_TOPIC(CS<'a>), /// 341 RPL_INVITING /// " " - /// + /// /// - Returned by the server to indicate that the /// attempted INVITE message was successful and is /// being passed onto the end client. - /// - RPL_INVITING = 341, + /// + RPL_INVITING(CS<'a>), /// 342 RPL_SUMMONING /// " :Summoning user to IRC" - /// + /// /// - Returned by a server answering a SUMMON message to /// indicate that it is summoning that user. - /// - RPL_SUMMONING = 342, + /// + RPL_SUMMONING(CS<'a>), /// 346 RPL_INVITELIST /// " " - RPL_INVITELIST = 346, + RPL_INVITELIST(CS<'a>), /// 347 RPL_ENDOFINVITELIST /// " :End of channel invite list" - /// + /// /// - When listing the 'invitations masks' for a given channel, /// a server is required to send the list back using the /// RPL_INVITELIST and RPL_ENDOFINVITELIST messages. A /// separate RPL_INVITELIST is sent for each active mask. /// After the masks have been listed (or if none present) a /// RPL_ENDOFINVITELIST MUST be sent. - /// - RPL_ENDOFINVITELIST = 347, + /// + RPL_ENDOFINVITELIST(CS<'a>), /// 348 RPL_EXCEPTLIST /// " " - RPL_EXCEPTLIST = 348, + RPL_EXCEPTLIST(CS<'a>), /// 349 RPL_ENDOFEXCEPTLIST /// " :End of channel exception list" - /// + /// /// - When listing the 'exception masks' for a given channel, /// a server is required to send the list back using the /// RPL_EXCEPTLIST and RPL_ENDOFEXCEPTLIST messages. A /// separate RPL_EXCEPTLIST is sent for each active mask. /// After the masks have been listed (or if none present) /// a RPL_ENDOFEXCEPTLIST MUST be sent. - /// - RPL_ENDOFEXCEPTLIST = 349, + /// + RPL_ENDOFEXCEPTLIST(CS<'a>), /// 351 RPL_VERSION /// ". :" - /// + /// /// - Reply by the server showing its version details. /// The is the version of the software being /// used (including any patchlevel revisions) and the /// is used to indicate if the server is /// running in "debug mode". - /// + /// /// The "comments" field may contain any comments about /// the version or further version details. - /// - RPL_VERSION = 351, + /// + RPL_VERSION(CS<'a>), /// 352 RPL_WHOREPLY /// " /// ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] /// : " - /// - RPL_WHOREPLY = 352, + /// + RPL_WHOREPLY(CS<'a>), /// 315 RPL_ENDOFWHO /// " :End of WHO list" - /// + /// /// - The RPL_WHOREPLY and RPL_ENDOFWHO pair are used /// to answer a WHO message. The RPL_WHOREPLY is only /// sent if there is an appropriate match to the WHO @@ -258,20 +264,20 @@ pub enum Reply { /// with a WHO message, a RPL_ENDOFWHO MUST be sent /// after processing each list item with being /// the item. - /// - RPL_ENDOFWHO = 315, + /// + RPL_ENDOFWHO(CS<'a>), /// 353 RPL_NAMREPLY /// "( "=" / "*" / "@" ) /// :[ "@" / "+" ] *( " " [ "@" / "+" ] ) /// - "@" is used for secret channels, "*" for private /// channels, and "=" for others (public channels). - /// - RPL_NAMREPLY = 353, + /// + RPL_NAMREPLY(CS<'a>), /// 366 RPL_ENDOFNAMES /// " :End of NAMES list" - /// + /// /// - To reply to a NAMES message, a reply pair consisting /// of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the /// server back to the client. If there is no channel @@ -281,188 +287,188 @@ pub enum Reply { /// channels and contents are sent back in a series of /// RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark /// the end. - /// - RPL_ENDOFNAMES = 366, + /// + RPL_ENDOFNAMES(CS<'a>), /// 364 RPL_LINKS /// " : " - RPL_LINKS = 364, + RPL_LINKS(CS<'a>), /// 365 RPL_ENDOFLINKS /// " :End of LINKS list" - /// + /// /// - In replying to the LINKS message, a server MUST send /// replies back using the RPL_LINKS numeric and mark the /// end of the list using an RPL_ENDOFLINKS reply. - /// - RPL_ENDOFLINKS = 365, + /// + RPL_ENDOFLINKS(CS<'a>), /// 367 RPL_BANLIST /// " " - RPL_BANLIST = 367, + RPL_BANLIST(CS<'a>), /// 368 RPL_ENDOFBANLIST /// " :End of channel ban list" - /// + /// /// - When listing the active 'bans' for a given channel, /// a server is required to send the list back using the /// RPL_BANLIST and RPL_ENDOFBANLIST messages. A separate /// RPL_BANLIST is sent for each active banmask. After the /// banmasks have been listed (or if none present) a /// RPL_ENDOFBANLIST MUST be sent. - /// - RPL_ENDOFBANLIST = 368, + /// + RPL_ENDOFBANLIST(CS<'a>), /// 371 RPL_INFO /// ":" - RPL_INFO = 371, + RPL_INFO(CS<'a>), /// 374 RPL_ENDOFINFO /// ":End of INFO list" - /// + /// /// - A server responding to an INFO message is required to /// send all its 'info' in a series of RPL_INFO messages /// with a RPL_ENDOFINFO reply to indicate the end of the /// replies. - /// - RPL_ENDOFINFO = 374, + /// + RPL_ENDOFINFO(CS<'a>), /// 375 RPL_MOTDSTART /// ":- Message of the day - " - RPL_MOTDSTART = 375, + RPL_MOTDSTART(CS<'a>), /// 372 RPL_MOTD /// ":- " - RPL_MOTD = 372, + RPL_MOTD(CS<'a>), /// 376 RPL_ENDOFMOTD /// ":End of MOTD command" - /// + /// /// - When responding to the MOTD message and the MOTD file /// is found, the file is displayed line by line, with /// each line no longer than 80 characters, using /// RPL_MOTD format replies. These MUST be surrounded /// by a RPL_MOTDSTART (before the RPL_MOTDs) and an /// RPL_ENDOFMOTD (after). - /// - RPL_ENDOFMOTD = 376, + /// + RPL_ENDOFMOTD(CS<'a>), /// 381 RPL_YOUREOPER /// ":You are now an IRC operator" - /// + /// /// - RPL_YOUREOPER is sent back to a client which has /// just successfully issued an OPER message and gained /// operator status. - /// - RPL_YOUREOPER = 381, + /// + RPL_YOUREOPER(CS<'a>), /// 382 RPL_REHASHING /// " :Rehashing" - /// + /// /// - If the REHASH option is used and an operator sends /// a REHASH message, an RPL_REHASHING is sent back to /// the operator. - /// - RPL_REHASHING = 382, + /// + RPL_REHASHING(CS<'a>), /// 383 RPL_YOURESERVICE /// "You are service " - /// + /// /// - Sent by the server to a service upon successful /// registration. - /// - RPL_YOURESERVICE = 383, + /// + RPL_YOURESERVICE(CS<'a>), /// 391 RPL_TIME /// " :" - /// + /// /// - When replying to the TIME message, a server MUST send /// the reply using the RPL_TIME format above. The string /// showing the time need only contain the correct day and /// time there. There is no further requirement for the /// time string. - /// - RPL_TIME = 391, + /// + RPL_TIME(CS<'a>), /// 392 RPL_USERSSTART /// ":UserID Terminal Host" - RPL_USERSSTART = 392, + RPL_USERSSTART(CS<'a>), /// 393 RPL_USERS /// ": " - RPL_USERS = 393, + RPL_USERS(CS<'a>), /// 394 RPL_ENDOFUSERS /// ":End of users" - RPL_ENDOFUSERS = 394, + RPL_ENDOFUSERS(CS<'a>), /// 395 RPL_NOUSERS /// ":Nobody logged in" - /// + /// /// - If the USERS message is handled by a server, the /// replies RPL_USERSTART, RPL_USERS, RPL_ENDOFUSERS and /// RPL_NOUSERS are used. RPL_USERSSTART MUST be sent /// first, following by either a sequence of RPL_USERS /// or a single RPL_NOUSER. Following this is /// RPL_ENDOFUSERS. - /// - RPL_NOUSERS = 395, + /// + RPL_NOUSERS(CS<'a>), /// 200 RPL_TRACELINK /// "Link /// V /// /// " - RPL_TRACELINK = 200, + RPL_TRACELINK(CS<'a>), /// 201 RPL_TRACECONNECTING /// "Try. " - RPL_TRACECONNECTING = 201, + RPL_TRACECONNECTING(CS<'a>), /// 202 RPL_TRACEHANDSHAKE /// "H.S. " - RPL_TRACEHANDSHAKE = 202, + RPL_TRACEHANDSHAKE(CS<'a>), /// 203 RPL_TRACEUNKNOWN /// "???? []" - RPL_TRACEUNKNOWN = 203, + RPL_TRACEUNKNOWN(CS<'a>), /// 204 RPL_TRACEOPERATOR /// "Oper " - RPL_TRACEOPERATOR = 204, + RPL_TRACEOPERATOR(CS<'a>), /// 205 RPL_TRACEUSER /// "User " - RPL_TRACEUSER = 205, + RPL_TRACEUSER(CS<'a>), /// 206 RPL_TRACESERVER /// "Serv S C /// @ V" - RPL_TRACESERVER = 206, + RPL_TRACESERVER(CS<'a>), /// 207 RPL_TRACESERVICE /// "Service " - RPL_TRACESERVICE = 207, + RPL_TRACESERVICE(CS<'a>), /// 208 RPL_TRACENEWTYPE /// " 0 " - RPL_TRACENEWTYPE = 208, + RPL_TRACENEWTYPE(CS<'a>), /// 209 RPL_TRACECLASS /// "Class " - RPL_TRACECLASS = 209, + RPL_TRACECLASS(CS<'a>), /// 210 RPL_TRACERECONNECT /// Unused. - RPL_TRACERECONNECT = 210, + RPL_TRACERECONNECT(CS<'a>), /// 261 RPL_TRACELOG /// "File " - RPL_TRACELOG = 261, + RPL_TRACELOG(CS<'a>), /// 262 RPL_TRACEEND /// " :End of TRACE" - /// + /// /// - The RPL_TRACE* are all returned by the server in /// response to the TRACE message. How many are /// returned is dependent on the TRACE message and @@ -479,19 +485,19 @@ pub enum Reply { /// response to a TRACE command traversing the IRC /// network should reflect the actual connectivity of /// the servers themselves along that path. - /// + /// /// RPL_TRACENEWTYPE is to be used for any connection /// which does not fit in the other categories but is /// being displayed anyway. /// RPL_TRACEEND is sent to indicate the end of the list. - /// - RPL_TRACEEND = 262, + /// + RPL_TRACEEND(CS<'a>), /// 211 RPL_STATSLINKINFO /// " /// ///