Skip to content

Commit

Permalink
Add welcome reply on user registration
Browse files Browse the repository at this point in the history
Also add shortcodes for: RPL_YOURHOST, RPL_MYINFO and RPL_CREATED.
These replies are not yet implemented, however.
Rework formatting code for IRC reply messages and splitting long lists
across multiple lines.
Needs more testing.

Signed-off-by: Joanna Doyle <jjadoyle@gmail.com>
  • Loading branch information
aoeixsz4 committed Oct 17, 2020
1 parent b83a4d2 commit ec4e1d3
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 47 deletions.
77 changes: 40 additions & 37 deletions src/irc.rs
Expand Up @@ -14,9 +14,6 @@
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
macro_rules! gef {
($e:expr) => (Err(GenError::from($e)));
}
pub mod chan;
pub mod error;
pub mod reply;
Expand All @@ -34,6 +31,11 @@ use std::clone::Clone;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, Weak};


macro_rules! gef {
($e:expr) => (Err(GenError::from($e)));
}

#[derive(Debug)]
pub enum NamedEntity {
User(Weak<User>),
Expand Down Expand Up @@ -61,6 +63,7 @@ pub struct User {
username: String,
real_name: Mutex<String>,
host: Host,
server: String,
channel_list: Mutex<HashMap<String, Weak<Channel>>>,
flags: Mutex<UserFlags>,
irc: Arc<Core>,
Expand All @@ -75,6 +78,7 @@ impl Clone for User {
username: self.username.clone(),
real_name: Mutex::new(self.real_name.lock().unwrap().clone()),
host: self.host.clone(),
server: self.server.clone(),
channel_list: Mutex::new(self.channel_list.lock().unwrap().clone()),
flags: Mutex::new(self.flags.lock().unwrap().clone()),
irc: Arc::clone(&self.irc),
Expand All @@ -98,6 +102,7 @@ impl User {
username: String,
real_name: String,
host: client::Host,
server: String,
client: &Arc<Client>,
) -> Arc<Self> {
Arc::new(User {
Expand All @@ -107,6 +112,7 @@ impl User {
username,
real_name: Mutex::new(real_name),
host,
server,
channel_list: Mutex::new(HashMap::new()),
client: Arc::downgrade(client),
flags: Mutex::new(UserFlags { registered: true }), /*channel_list: Mutex::new(Vec::new())*/
Expand Down Expand Up @@ -198,6 +204,10 @@ impl User {
)
}

pub fn get_server(&self) -> String {
self.server.clone()
}

pub async fn send_msg(
self: &Arc<Self>,
src: &User,
Expand Down Expand Up @@ -230,40 +240,21 @@ impl User {
/* passing to an async fn and awaiting on it is gonna
* cause lifetime problems with a &str... */
let host = self.irc.get_host();
let line = format!(":{} {}", host, reply);
if line.len() > rfc::MAX_MSG_SIZE - 2 {
match reply {
/* not all can be recursed */
ircReply::NameReply(chan, mut nick_vec) => {
/* "353 {} :{}<CR><LF>" */
let overhead = rfc::MAX_MSG_PARAMS - (10 + chan.len() + host.len());
let mut vec_len = nick_vec.len();
let mut i = 0;
let mut sum = 0;

/* count how many strings we can fit */
while i < vec_len {
if sum + nick_vec[i].len() >= overhead {
let temp = nick_vec.split_off(i);
let line = format!(":{} {}", host, ircReply::NameReply(chan.clone(), nick_vec));
let my_client = self.fetch_client()?;
my_client.send_line(&line).await?;
nick_vec = temp;
i = 0;
sum = 0;
vec_len = nick_vec.len();
}
}

Ok(ircReply::None)
}
_ => Ok(ircReply::None),
let mut line = reply.format(&self.get_server(), &self.get_nick());
let my_client = self.fetch_client()?;
/* break up long messages if neccessary,
* reply::split essentially returns line, None when
* line is not larger than MAX_MSG_SIZE */
loop {
let (trim, rest_opt) = reply::split(&line);
my_client.send_line(&trim).await?;
if let Some(rest) = rest_opt {
line = rest;
} else {
break;
}
} else {
let my_client = self.fetch_client()?;
my_client.send_line(&line).await?;
Ok(ircReply::None)
}
Ok(ircReply::None)
}

pub async fn send_line(self: &Arc<Self>, line: &str) -> Result<ircReply, GenError> { /* GDB++ */
Expand Down Expand Up @@ -483,6 +474,7 @@ impl Core {
let host = client.get_host();
let id = client.get_id();
let irc = client.get_irc();
let server = irc.hostname.clone();
trace!(
"register user {}!{}@{}, Real name: {} -- client id {}",
&nick, &username, &host_str, &real_name, id
Expand All @@ -494,6 +486,7 @@ impl Core {
username,
real_name,
host.clone(),
server,
client,
);
self.insert_name(&nick, NamedEntity::User(Arc::downgrade(&user)))?;
Expand Down Expand Up @@ -737,8 +730,13 @@ pub async fn user(irc: &Core, client: &Arc<Client>, params: ParsedMsg) -> Result

if let Some(new_client_type) = result {
client.set_client_type(new_client_type);
/* really we want a way to return multiple replies in a queue */
Ok(ircReply::Welcome(client.get_user().get_nick(),
client.get_user().get_username(),
client.get_host_string()))
} else {
Ok(ircReply::None)
}
Ok(ircReply::None)
}

pub async fn nick(irc: &Core, client: &Arc<Client>, params: ParsedMsg) -> Result<ircReply, GenError> {
Expand Down Expand Up @@ -803,6 +801,11 @@ pub async fn nick(irc: &Core, client: &Arc<Client>, params: ParsedMsg) -> Result

if let Some(new_client_type) = result {
client.set_client_type(new_client_type);
/* really we want a way to return multiple replies in a queue */
Ok(ircReply::Welcome(client.get_user().get_nick(),
client.get_user().get_username(),
client.get_host_string()))
} else {
Ok(ircReply::None)
}
Ok(ircReply::None)
}
131 changes: 121 additions & 10 deletions src/irc/reply.rs
Expand Up @@ -43,25 +43,136 @@
*/

use std::fmt;
use std::str;
use crate::irc::rfc_defs as rfc;

pub enum Reply {
None,
Welcome(String, String, String),
YourHost(String, String),
Created(String),
MyInfo(String, String, String, String),
Topic(String, String),
NameReply(String, Vec<String>),
EndofNames(String),
ListReply(String, String),
EndofList,
}

type Code = u16;
type CodeStr = String;

impl Reply {
/* map enums to numberic reply codes */
fn numeric(&self) -> Code {
match self {
Reply::Welcome(_n, _u, _h) => 001,
Reply::YourHost(_s,_v) => 002,
Reply::Created(_t) => 003,
Reply::MyInfo(_s, _v, _uM, _cM) => 004,
Reply::None => 300,
Reply::ListReply(_ch, _top) => 322,
Reply::EndofList => 323,
Reply::Topic(_ch, _top) => 332,
Reply::NameReply(_ch, _ns) => 353,
Reply::EndofNames(_ch) => 366
}
}

/* convert reply codes to strings */
fn reply_code(&self) -> CodeStr {
self.numeric().to_string()
}

/* the body is everything in the reply after :<server> <Code> <recipient> */
fn body(&self) -> Option<String> {
match self {
Reply::None => None,
Reply::Welcome(nick, user, host) => Some(format!(":Welcome to Rusty IRC Network {}!{}@{}", nick, user, host)),
Reply::YourHost(serv, ver) => Some(format!(":Your host is {}, running version {}", serv, ver)),
Reply::Created(time) => Some(format!(":This server was created {}", time)),
Reply::MyInfo(serv, ver, umodes, chanmodes) => Some(format!(":{} {} {} {}", serv, ver, umodes, chanmodes)),
Reply::ListReply(chan, topic) => Some(format!(":{} {}", chan, topic)),
Reply::EndofList => Some(format!(":End of /LIST")),
Reply::Topic(chan, topic_msg) => Some(format!("{} :{}", chan, topic_msg)),
Reply::NameReply(chan, nicks) => Some(format!("{} :{}", chan, nicks.join(" "))),
Reply::EndofNames(chan) => Some(format!("{} :End of /NAMES list", chan)),
}
}

/* format a full IRC string for sending to the client
- NB this isn't currently checked for exceeding RFC message length */
pub fn format(&self, server: &str, recipient: &str) -> String {
if let Some(reply_body) = self.body() {
format!(":{} {} {} {}", server, self.reply_code(), recipient, reply_body)
} else {
format!(":{} {} {}", server, self.reply_code(), recipient)
}
}
}

/* `:asdf.cool.net 001 luser :Welcome my lovely!` */
pub fn split(message: &str) -> (String, Option<String>) {
let msg_bytes = message.as_bytes();
if msg_bytes.len() <= rfc::MAX_MSG_SIZE - 2
|| msg_bytes[0] != b':' {
return (message.to_string(), None);
}

let message_trimmed = &message[1..];
let substrings: Vec<&str> = message_trimmed.splitn(2, " :").collect();
if substrings.len() != 2 {
panic!("message {} contains no ` :` token!", message);
}
let prefix = substrings[0];
let prefix_bytes = substrings[0].as_bytes();
let reply_bulk = substrings[1].as_bytes();
let overhead = prefix_bytes.len() + 5;
let room = rfc::MAX_MSG_SIZE - overhead;
if reply_bulk.len() <= room {
panic!("body {} is already short enough, algorithm is broken", str::from_utf8(reply_bulk).unwrap());
}

if let Some(space_index) = rfind_space_index(reply_bulk, room) {
let chunk = str::from_utf8(&reply_bulk[..space_index]).unwrap();
let remainder = str::from_utf8(&reply_bulk[space_index+1..]).unwrap();
(
format!(":{} :{}", prefix, chunk),
Some(format!(":{} :{}", prefix, remainder))
)
} else {
/* if there was no space we could use to split at, just cut arbitrarily at the max */
let chunk = str::from_utf8(&reply_bulk[..room]).unwrap();
let remainder = str::from_utf8(&reply_bulk[room..]).unwrap();
(
format!(":{} :{}", prefix, chunk),
Some(format!(":{} :{}", prefix, remainder))
)
}
}

fn rfind_space_index (bytes: &[u8], mut index: usize) -> Option<usize> {
while index > 0 {
if bytes[index] == b' ' {
return Some(index)
}
}
None
}

impl fmt::Display for Reply {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Reply::None => write!(f, "300"),
Reply::Welcome(nick, user, host) => write!(f, "001 :Welcome to Rusty IRC Network {}!{}@{}", nick, user, host),
Reply::YourHost(serv, ver) => write!(f, "002 :Your host is {}, running version {}", serv, ver),
Reply::Created(time) => write!(f, "003 :This server was created {}", time),
Reply::MyInfo(serv, ver, umodes, chanmodes) => write!(f, "004 :{} {} {} {}", serv, ver, umodes, chanmodes),
Reply::ListReply(chan, topic) => write!(f, "322 :{} {}", chan, topic),
Reply::EndofList => write!(f, "323 :End of /LIST"),
Reply::Topic(chan, topic_msg) => write!(f, "332 {} :{}", chan, topic_msg),
Reply::NameReply(chan, nicks) => write!(f, "353 {} :{}", chan, nicks.join(" ")),
Reply::EndofNames(chan) => write!(f, "366 {} :End of /NAMES list", chan),
}
}
}

pub enum Reply {
None,
Topic(String, String),
NameReply(String, Vec<String>),
ListReply(String, String),
EndofList,
EndofNames(String),
}
}

0 comments on commit ec4e1d3

Please sign in to comment.