Moved Rust source code
This commit is contained in:
9
Rust/Cargo.toml
Normal file
9
Rust/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "chat-reseau"
|
||||
version = "0.2.0"
|
||||
authors = ["Lucien Cartier-Tilet <drakpa@drakpa.fr>"]
|
||||
|
||||
[dependencies]
|
||||
colored = "1.6"
|
||||
chrono = "0.4"
|
||||
term_size = "0.3"
|
||||
288
Rust/src/client.rs
Normal file
288
Rust/src/client.rs
Normal file
@@ -0,0 +1,288 @@
|
||||
extern crate chrono;
|
||||
extern crate colored;
|
||||
extern crate term_size;
|
||||
use std;
|
||||
use std::io::*;
|
||||
use std::net::TcpStream;
|
||||
use std::thread;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use self::colored::*;
|
||||
use self::chrono::Local;
|
||||
|
||||
// TODO: Limit usernames to ascii
|
||||
// TODO: Implement requests 1.x from protocol
|
||||
// TODO: forbid usernames already in use
|
||||
|
||||
fn hash_name(name: &str) -> usize {
|
||||
let mut s = DefaultHasher::new();
|
||||
let name = String::from(name);
|
||||
name.hash(&mut s);
|
||||
s.finish() as usize
|
||||
}
|
||||
|
||||
fn get_entry() -> String {
|
||||
let mut buf = String::new();
|
||||
stdin().read_line(&mut buf).unwrap();
|
||||
buf.replace("\n", "").replace("\r", "")
|
||||
}
|
||||
|
||||
fn get_name(writer: &mut BufWriter<&TcpStream>) {
|
||||
loop {
|
||||
let mut line = &*get_entry();
|
||||
line = line.trim();
|
||||
if line.len() > 20 {
|
||||
println!("Nickname too long, it must be at most 20 characters long");
|
||||
continue;
|
||||
}
|
||||
match line {
|
||||
"" => {
|
||||
continue;
|
||||
}
|
||||
"/quit" => {
|
||||
println!("Disconnecting...");
|
||||
writeln!(writer, "BYE").unwrap();
|
||||
writer.flush().unwrap();
|
||||
return ();
|
||||
}
|
||||
line => {
|
||||
let line_str: String = String::from(line);
|
||||
let spliced: Vec<&str> = line_str.split_whitespace().collect();
|
||||
if spliced.len() > 1 {
|
||||
println!("Cannot use whitespace in username.");
|
||||
continue;
|
||||
}
|
||||
writeln!(writer, "{}", line).unwrap();
|
||||
writer.flush().unwrap();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_server(stream: TcpStream) {
|
||||
let mut writer = BufWriter::new(&stream);
|
||||
|
||||
// entrée du nom d'utilisateur
|
||||
get_name(&mut writer);
|
||||
|
||||
loop {
|
||||
let line = &*get_entry();
|
||||
line.trim();
|
||||
match line {
|
||||
"" => {}
|
||||
"/quit" => {
|
||||
writeln!(writer, "BYE").unwrap();
|
||||
writer.flush().unwrap();
|
||||
return ();
|
||||
}
|
||||
"/clients" => {
|
||||
writeln!(writer, "REQ CLIENTS").unwrap();
|
||||
writer.flush().unwrap();
|
||||
}
|
||||
line => {
|
||||
if line.len() > 2000 {
|
||||
println!(
|
||||
"{}",
|
||||
"Cannot send a message longer than 2000 characters".bright_red()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
writeln!(writer, "MSG {}", line).unwrap();
|
||||
writer.flush().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn exchange_with_server(stream: TcpStream) {
|
||||
let server = stream.peer_addr().unwrap();
|
||||
println!("Connected to {}", server);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let COLORS: Vec<&str> = vec![
|
||||
// "black",
|
||||
"red",
|
||||
"green",
|
||||
"yellow",
|
||||
"blue",
|
||||
"magenta",
|
||||
"cyan",
|
||||
// "white",
|
||||
];
|
||||
|
||||
let stream_cpy = stream.try_clone().unwrap();
|
||||
let mut reader = BufReader::new(&stream_cpy);
|
||||
|
||||
macro_rules! receive {
|
||||
() => ({
|
||||
let mut line = String::new();
|
||||
match reader.read_line(&mut line) {
|
||||
Ok(len) => {
|
||||
if len == 0 {
|
||||
// Reader is at EOF. Could use ErrorKind::UnexpectedEOF,
|
||||
// but still unstable.
|
||||
let ret = std::io::Error::new(
|
||||
std::io::ErrorKind::Other, "test");
|
||||
return
|
||||
Err(ret): std::result::Result<&str,std::io::Error>;
|
||||
}
|
||||
line.pop();
|
||||
}
|
||||
Err(e) => { return Err(e); }
|
||||
};
|
||||
line
|
||||
})
|
||||
}
|
||||
|
||||
thread::spawn(move || {
|
||||
write_to_server(stream.try_clone().unwrap());
|
||||
});
|
||||
|
||||
match (|| loop {
|
||||
let input: String = String::from(receive!());
|
||||
let spliced_input: Vec<&str> = input.split(" ").collect();
|
||||
match spliced_input[0] {
|
||||
"WELCOME" => {
|
||||
println!("{}", ">>> Login Successful <<<".green());
|
||||
println!("Type /clients to get the list of users connected");
|
||||
println!("Type /quit to disconnect and quit");
|
||||
}
|
||||
"FROM" => {
|
||||
let date = Local::now();
|
||||
let name = String::from(spliced_input[1]);
|
||||
let mut first_line = true;
|
||||
|
||||
// Hashing name for color
|
||||
let mut s = DefaultHasher::new();
|
||||
name.hash(&mut s);
|
||||
let name_hash: usize = (s.finish() as usize) % COLORS.len();
|
||||
|
||||
// Formatting name
|
||||
let mut name = String::new();
|
||||
for _i in 0..(20 - spliced_input[1].to_string().len()) {
|
||||
name.push(' ');
|
||||
}
|
||||
name.push('<');
|
||||
name.push_str(spliced_input[1]);
|
||||
name.push('>');
|
||||
|
||||
if let Some((w, _)) = term_size::dimensions() {
|
||||
let mut msg = String::new();
|
||||
let w = w - 34;
|
||||
|
||||
for mut i in 3..spliced_input.len() {
|
||||
if w > msg.len() + spliced_input[i].len() + 1 {
|
||||
msg.push(' ');
|
||||
msg.push_str(spliced_input[i]);
|
||||
} else {
|
||||
if first_line == true {
|
||||
println!(
|
||||
"{} {}:{}",
|
||||
date.format("[%H:%M:%S]").to_string().dimmed(),
|
||||
name.color(COLORS[name_hash]),
|
||||
msg.yellow().dimmed()
|
||||
);
|
||||
first_line = false;
|
||||
} else {
|
||||
println!(
|
||||
"{}{}",
|
||||
" |".green(),
|
||||
msg.yellow().dimmed()
|
||||
);
|
||||
}
|
||||
msg = String::new();
|
||||
#[allow(unused_assignments)]
|
||||
i = i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if first_line == true {
|
||||
println!(
|
||||
"{} {}:{}",
|
||||
date.format("[%H:%M:%S]").to_string().dimmed(),
|
||||
name.color(COLORS[name_hash]),
|
||||
msg.yellow().dimmed()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"{}{}",
|
||||
" |".green(),
|
||||
msg.yellow().dimmed()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let mut msg = String::new();
|
||||
for i in 3..spliced_input.len() {
|
||||
msg.push_str(" ");
|
||||
msg.push_str(spliced_input[i]);
|
||||
}
|
||||
println!(
|
||||
"{} {}{}",
|
||||
date.format("[%H:%M:%S]").to_string().dimmed(),
|
||||
name.color(COLORS[name_hash]),
|
||||
msg.yellow().dimmed()
|
||||
);
|
||||
}
|
||||
}
|
||||
"BYE" => {
|
||||
return Ok("Ok");
|
||||
}
|
||||
"LIST" => {
|
||||
println!("{}", ">>>> LIST OF CLIENTS CONNECTED <<<<".bold().yellow());
|
||||
for i in 2..spliced_input.len() {
|
||||
println!("\t\t{}", spliced_input[i]);
|
||||
}
|
||||
}
|
||||
"JOIN" => {
|
||||
let date = Local::now();
|
||||
let name_hash: usize = hash_name(spliced_input[1].clone()) % COLORS.len();
|
||||
|
||||
println!(
|
||||
"{}{}{}{}",
|
||||
date.format("[%H:%M:%S]").to_string().dimmed(),
|
||||
" ------> ".green(),
|
||||
spliced_input[1].color(COLORS[name_hash]),
|
||||
" has joined".green()
|
||||
)
|
||||
}
|
||||
"LOGOUT" => {
|
||||
let date = Local::now();
|
||||
let name_hash: usize = hash_name(spliced_input[1].clone()) % COLORS.len();
|
||||
|
||||
println!(
|
||||
"{}{}{}{}",
|
||||
date.format("[%H:%M:%S]").to_string().dimmed(),
|
||||
" <------ ".red(),
|
||||
spliced_input[1].color(COLORS[name_hash]),
|
||||
" has left".red()
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
println!("{}", input);
|
||||
}
|
||||
}
|
||||
// println!("{}", input);
|
||||
})()
|
||||
{
|
||||
Ok(_) => {
|
||||
println!("{}", ">>> Logout successful <<<".green());
|
||||
}
|
||||
Err(_) => {
|
||||
println!("{}", "Error: Connection with server lost".red());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client(server_address: String) {
|
||||
println!("Trying to connect to the server...");
|
||||
match TcpStream::connect(server_address) {
|
||||
Ok(stream) => {
|
||||
exchange_with_server(stream);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{} {}", "Connection to server failed:".red(), e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Rust/src/main.rs
Normal file
35
Rust/src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
#![feature(type_ascription)]
|
||||
#![feature(stmt_expr_attributes)]
|
||||
use std::env;
|
||||
|
||||
pub mod client;
|
||||
pub mod server;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() == 2 {
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Server opened //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
println!("Opening server on port {}", args[1]);
|
||||
// serveur(args[1].clone());
|
||||
let mut serv = String::from("0.0.0.0:");
|
||||
serv.push_str(&args[1]);
|
||||
server::serveur(serv);
|
||||
} else if args.len() == 3 {
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Client opened //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
println!("Client connecting on server {}:{}", args[1], args[2]);
|
||||
let mut serv: String = if args[1] == "localhost" {
|
||||
String::from("127.0.0.1")
|
||||
} else {
|
||||
args[1].clone()
|
||||
};
|
||||
serv.push(':');
|
||||
serv.push_str(&args[2]);
|
||||
client::client(serv);
|
||||
} else {
|
||||
println!("Usage: {} [server ip] port", args[0]);
|
||||
}
|
||||
}
|
||||
254
Rust/src/server.rs
Normal file
254
Rust/src/server.rs
Normal file
@@ -0,0 +1,254 @@
|
||||
use std::io::*;
|
||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||
use std::thread;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// TODO: implement requests 1.x from protocol
|
||||
// TODO: forbid usernames already in use
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Evolution implementation protocole //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
|
||||
1.1 [ ]
|
||||
1.2 [ ]
|
||||
1.3 [ ]
|
||||
1.4 [ ]
|
||||
1.5 [ ]
|
||||
1.6 [ ]
|
||||
1.7 [X]
|
||||
1.8 [X]
|
||||
1.9 [ ]
|
||||
2.1 [X]
|
||||
2.2 [X]
|
||||
3.1 [ ] // pas utile avec Rust
|
||||
3.2 [X]
|
||||
4.1.1 [X]
|
||||
4.1.2 [X]
|
||||
4.2.1 [X]
|
||||
4.2.2 [-]
|
||||
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// TYPES //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Map for all connected clients containing their name and stream
|
||||
type UserMapValue = (String, TcpStream);
|
||||
type UserMap = HashMap<SocketAddr, UserMapValue>;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CODE //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fn distribute_message(
|
||||
msg: &str,
|
||||
not_to: &SocketAddr,
|
||||
lock: &mut MutexGuard<UserMap>,
|
||||
everyone: bool,
|
||||
) {
|
||||
let mut name = String::new();
|
||||
for (client, entry) in (*lock).iter() {
|
||||
if client == not_to {
|
||||
name = entry.0.clone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (other_client, entry) in (*lock).iter() {
|
||||
let other_name = &entry.0;
|
||||
let other_stream = &entry.1;
|
||||
if everyone == false && other_client == not_to {
|
||||
continue;
|
||||
}
|
||||
match (|| -> Result<()> {
|
||||
let mut writer = BufWriter::new(other_stream);
|
||||
// test if message begins with "MSG " /////////////////////////
|
||||
if &msg[..4] == "MSG " {
|
||||
try!(writeln!(writer, "FROM {} {}", name, msg));
|
||||
} else {
|
||||
try!(writeln!(writer, "{}", msg));
|
||||
}
|
||||
///////////////////////////////////////////////////////////////
|
||||
try!(writer.flush());
|
||||
return Ok(());
|
||||
})()
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Client {} <{}> disappeared during message distribution: {}",
|
||||
other_client, other_name, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_clients_name(to: &SocketAddr, lock: &mut MutexGuard<UserMap>) {
|
||||
let mut clients = String::new();
|
||||
for (client, entry) in (*lock).iter() {
|
||||
clients.push_str(&format!(
|
||||
"{}{} ",
|
||||
&entry.0.trim(),
|
||||
if client == to { "(you)" } else { "" }
|
||||
));
|
||||
}
|
||||
let clients = clients.trim();
|
||||
for (client, entry) in (*lock).iter() {
|
||||
if client == to {
|
||||
let stream = &entry.1;
|
||||
let mut writer = BufWriter::new(stream);
|
||||
writeln!(writer, "LIST CLIENTS {}", clients).unwrap();
|
||||
writer.flush().unwrap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn disconnect_user(name: &str, client: &SocketAddr, lock: &mut MutexGuard<UserMap>) {
|
||||
(*lock).remove(&client);
|
||||
distribute_message(&format!("LOGOUT {}", name), client, lock, true);
|
||||
}
|
||||
|
||||
fn handle_client(stream: TcpStream, clients: Arc<Mutex<UserMap>>) {
|
||||
// Get client IP and port
|
||||
let client = stream.peer_addr().unwrap();
|
||||
println!("New connection from {}", client);
|
||||
|
||||
// Buffered reading and writing
|
||||
let mut reader = BufReader::new(&stream);
|
||||
let mut writer = BufWriter::new(&stream);
|
||||
|
||||
// Write an entire line to the client
|
||||
// Can fail on IO errors, du to try! macro
|
||||
macro_rules! send {
|
||||
($line:expr) => ({
|
||||
try!(writeln!(writer, "{}", $line));
|
||||
try!(writer.flush());
|
||||
})
|
||||
}
|
||||
|
||||
// Read an entire line from the client
|
||||
// Can fail on IO errors or when EOF is reached
|
||||
macro_rules! receive {
|
||||
() => ({
|
||||
let mut line = String::new();
|
||||
match reader.read_line(&mut line) {
|
||||
Ok(len) => {
|
||||
if len == 0 {
|
||||
// Reader is at EOF.
|
||||
return Err(Error::new(ErrorKind::Other, "unexpected EOF"));
|
||||
}
|
||||
line.pop();
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
line
|
||||
})
|
||||
}
|
||||
|
||||
// Initialization: Ask user for his name
|
||||
let name = match (|| {
|
||||
send!("Welcome!");
|
||||
send!("Please enter your name:");
|
||||
let name = receive!();
|
||||
println!("Client {} identified as {}", client, name);
|
||||
Ok(name)
|
||||
})()
|
||||
{
|
||||
Ok(name) => name,
|
||||
Err(e) => {
|
||||
println!("Client {} disappeared during initialization: {}", client, e);
|
||||
return ();
|
||||
}
|
||||
};
|
||||
|
||||
// Add user to global map. Lock will be released at the end of the scope
|
||||
{
|
||||
let mut lock = clients.lock().unwrap();
|
||||
(*lock).insert(client, (name.clone(), stream.try_clone().unwrap()));
|
||||
distribute_message(&format!("JOIN {}", name), &client, &mut lock, false);
|
||||
}
|
||||
|
||||
writeln!(writer, "WELCOME").unwrap();
|
||||
writer.flush().unwrap();
|
||||
|
||||
// Chat loop: Receive messages from users
|
||||
match (|| loop {
|
||||
match receive!().as_str() {
|
||||
"BYE" => {
|
||||
send!("BYE");
|
||||
return Ok(());
|
||||
}
|
||||
"PING" => {
|
||||
send!("PONG");
|
||||
}
|
||||
"REQ CLIENTS" => {
|
||||
let mut lock = clients.lock().unwrap();
|
||||
send_clients_name(&client, &mut lock);
|
||||
}
|
||||
input => {
|
||||
println!("{} <{}>: {}", client, name, input);
|
||||
{
|
||||
let mut lock = clients.lock().unwrap();
|
||||
distribute_message(&format!("{}", input), &client, &mut lock, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
||||
{
|
||||
Ok(_) => {
|
||||
println!("Client {} <{}> left", client, name);
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Client {} <{}> disappeared during chat: {}",
|
||||
client, name, e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove user from global map
|
||||
{
|
||||
let mut lock = clients.lock().unwrap();
|
||||
disconnect_user(&name, &client, &mut lock);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serveur(addr: String) {
|
||||
// Manage UserMap in a mutex
|
||||
let clients = Arc::new(Mutex::new(HashMap::new()));
|
||||
let serv_addr = addr.clone();
|
||||
|
||||
// Start a TCP Listener
|
||||
let listener = match TcpListener::bind(serv_addr.as_str()) {
|
||||
Ok(listener) => listener,
|
||||
Err(e) => panic!("Could not read start TCP listener: {}", e),
|
||||
};
|
||||
|
||||
println!("Successfully started the server on {}", serv_addr);
|
||||
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(stream) => {
|
||||
let clients = clients.clone();
|
||||
thread::spawn(move || {
|
||||
//connection succeeded
|
||||
handle_client(stream, clients)
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
writeln!(stderr(), "Connection failed: {}", e).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// close the socket server
|
||||
drop(listener);
|
||||
}
|
||||
Reference in New Issue
Block a user