use crossbeam_channel::{unbounded, Receiver, Sender};
use crossbeam_utils::thread;
use std::collections::HashMap;
use std::io::prelude::*;
use std::io::ErrorKind;
use std::net::{Shutdown, TcpStream};
use std::time::Duration;
#[macro_use]
extern crate maplit;
pub mod packets;
mod util;
use util::q;
pub type Icbmsg = Vec<String>;
#[derive(Debug)]
pub struct Config {
pub serverip: String,
pub nickname: String,
pub port: u16,
pub group: String,
}
#[derive(Debug, PartialEq)]
pub enum Command {
Bye,
Open(String),
Personal(String, String),
Beep(String),
Name(String),
}
#[derive(Debug)]
pub struct Client {
pub nickname: String,
pub cmd_s: Sender<Command>,
pub msg_r: Receiver<Icbmsg>,
}
#[derive(Debug)]
pub struct Server {
hostname: String,
port: u16,
sock: Option<TcpStream>,
cmd_r: Receiver<Command>,
msg_s: Sender<Icbmsg>,
nickname: String,
group: String,
}
impl Server {
fn new(
hostname: &str,
port: u16,
nickname: &str,
cmd_r: Receiver<Command>,
msg_s: Sender<Icbmsg>,
group: &str,
) -> Server {
Server {
hostname: hostname.to_string(),
port,
cmd_r,
msg_s,
nickname: nickname.to_string(),
sock: None,
group: group.to_string(),
}
}
fn read(&mut self, expected: Option<char>) -> Result<HashMap<&str, String>, std::io::Error> {
let mut buffer = [0; 512];
let nbytes = self.sock.as_ref().unwrap().peek(&mut buffer)?;
if nbytes == 0 {
return Ok(hashmap! {"type" => packets::T_INVALID.to_string()});
}
let mut packet_len = 0;
for (i, byte) in buffer.iter().enumerate() {
if *byte != 0 {
q("Non-zero byte found with position and value", &(i, byte))?;
packet_len = *byte as usize;
break;
}
}
if packet_len == 0 {
return Ok(hashmap! {"type" => packets::T_INVALID.to_string()});
}
let mut message = vec![0; packet_len + 1];
self.sock.as_ref().unwrap().read_exact(&mut message)?;
message.remove(0);
q("received message", &message)?;
let packet_type_byte = message[0] as char;
match expected {
Some(t) if (packet_type_byte == t) => {
q("OK! Received packet of expected type", &t)?;
}
Some(t) => {
q(
"FAIL! Mismatch between expectation and result",
&(t, packet_type_byte),
)?;
return Err(std::io::Error::new(
ErrorKind::NotFound,
"Packet type not found",
));
}
_ => {
q("OK! Nothing was expected, just carry on", &())?;
}
}
q("Looking for a packet of type", &packet_type_byte)?;
for packet in &packets::PACKETS {
if packet.packet_type == packet_type_byte {
let data = (packet.parse)(message, packet_len);
q("data", &data)?;
return Ok(data);
}
}
Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"Invalid data received from peer of type {}",
packet_type_byte
),
))
}
pub fn run(&mut self) {
self.sock
.as_ref()
.unwrap()
.set_nonblocking(true)
.expect("set_nonblocking on socket failed");
thread::scope(|s| {
s.spawn(|_| loop {
if let Ok(m) = self.cmd_r.try_recv() {
match m {
Command::Bye => {
q("Terminating connection to remote host", &()).unwrap();
self.sock
.as_ref()
.unwrap()
.shutdown(Shutdown::Both)
.unwrap();
break;
}
Command::Open(msg) => {
q("Sending message to channel", &msg).unwrap();
let packet = (packets::OPEN.create)(vec![msg.as_str()]);
self.sock
.as_ref()
.unwrap()
.write_all(&packet)
.unwrap();
}
Command::Personal(recipient, msg) => {
let packet = (packets::COMMAND.create)(vec![
packets::CMD_MSG,
format!("{} {}", recipient, msg).as_str(),
]);
self.sock
.as_ref()
.unwrap()
.write_all(&packet)
.unwrap();
}
Command::Beep(recipient) => {
let packet = (packets::COMMAND.create)(vec![
packets::CMD_BEEP,
recipient.as_str(),
]);
self.sock
.as_ref()
.unwrap()
.write_all(&packet)
.unwrap();
}
Command::Name(newname) => {
let packet = (packets::COMMAND.create)(vec![
packets::CMD_NAME,
newname.as_str(),
]);
self.sock
.as_ref()
.unwrap()
.write_all(&packet)
.unwrap();
self.nickname = newname;
}
}
}
if let Ok(v) = self.read(None) {
if [packets::T_OPEN, packets::T_PERSONAL]
.contains(&v["type"].chars().next().unwrap())
{
let msg = vec![
v["type"].clone(),
v["nickname"].clone(),
v["message"].clone(),
];
self.msg_s.send(msg).unwrap();
} else if v["type"].chars().next().unwrap() == packets::T_STATUS {
let msg = vec![
v["type"].clone(),
v["category"].clone(),
v["message"].clone(),
];
self.msg_s.send(msg).unwrap();
} else if v["type"].chars().next().unwrap() == packets::T_BEEP {
let msg = vec![v["type"].clone(), v["nickname"].clone()];
self.msg_s.send(msg).unwrap();
}
}
std::thread::sleep(Duration::from_millis(1));
});
})
.unwrap();
}
fn login(&mut self) -> std::io::Result<()> {
let login_packet = (packets::LOGIN.create)(vec![
self.nickname.as_str(),
self.nickname.as_str(),
self.group.as_str(),
"login",
]);
self.sock
.as_ref()
.unwrap()
.write_all(&login_packet)?;
if self.read(Some(packets::T_LOGIN)).is_err() {
panic!("Login failed.");
}
Ok(())
}
pub fn connect(&mut self) -> std::io::Result<()> {
match TcpStream::connect(format!("{}:{}", &self.hostname, &self.port)) {
Ok(t) => self.sock = Some(t),
Err(e) => panic!(
"Could not connect to {}:{} - {}",
&self.hostname, &self.port, e
),
}
if let Ok(v) = self.read(Some(packets::T_PROTOCOL)) {
q("protocol packet data", &v)?;
q(
"connected to",
&(v.get("hostid").unwrap(), v.get("clientid").unwrap()),
)?;
let msg = vec![
v["type"].clone(),
v["hostid"].clone(),
v["clientid"].clone(),
];
self.msg_s.send(msg).unwrap();
} else {
panic!("Expected a protocol packet, which didn't arrive.")
}
Ok(())
}
}
pub fn init(config: Config) -> Result<(Client, Server), std::io::Error> {
let (msg_s, msg_r) = unbounded();
let (cmd_s, cmd_r) = unbounded();
let mut server = Server::new(
&config.serverip,
config.port,
&config.nickname,
cmd_r,
msg_s,
&config.group,
);
server.connect()?;
server.login()?;
let client = Client {
nickname: config.nickname,
cmd_s,
msg_r,
};
Ok((client, server))
}