diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Cargo.toml | 15 | ||||
| -rwxr-xr-x | client.py | 56 | ||||
| -rw-r--r-- | src/doublemap.rs | 17 | ||||
| -rw-r--r-- | src/main.rs | 187 | ||||
| -rw-r--r-- | src/server/gameserver.rs | 1 | ||||
| -rw-r--r-- | src/server/mod.rs | 22 | ||||
| -rw-r--r-- | src/server/streamconnection.rs | 68 | ||||
| -rw-r--r-- | src/server/tcpserver.rs | 89 | ||||
| -rw-r--r-- | src/server/unixserver.rs | 109 |
10 files changed, 566 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2ceed55 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "asciifarm" +version = "0.1.0" +authors = ["troido <troido@protonmail.com>"] +edition = "2018" + +[dependencies] +mio-uds = "0.6" +mio = "0.6" +slab = "0.4.2" +nix = "0.13" +libc = "0.2" +users = "0.8" +json = "0.11.13" + diff --git a/client.py b/client.py new file mode 100755 index 0000000..f1f6867 --- /dev/null +++ b/client.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +import socket +import sys +import threading +import json +import getpass + + +def send(sock, msg): + length = len(msg) + header = length.to_bytes(4, byteorder="big") + totalmsg = header + msg + sock.sendall(totalmsg) + +def receive(sock): + header = recvall(sock, 4) #sock.recv(4) + length = int.from_bytes(header, byteorder="big") + return recvall(sock, length) + +def recvall(sock, length): + chunks = [] + bytes_recd = 0 + while bytes_recd < length: + chunk = sock.recv(min(length - bytes_recd, 4096)) + if chunk == b'': + break + #raise RuntimeError("socket connection broken") + chunks.append(chunk) + bytes_recd = bytes_recd + len(chunk) + return b''.join(chunks) + + + +sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +sock.connect("\0rustifarm")#("localhost", 1234)) + +def listen(): + while True: + d = receive(sock) + if len(d) == 0: + print("Connection closed by server", file=sys.stdout) + return + print(str(d, "utf-8")) + +threading.Thread(target=listen, daemon=True).start() + +if len(sys.argv) >= 2: + name = sys.argv[1] +else: + name = "~" + getpass.getuser() + +send(sock, bytes(json.dumps(["name", name]), "utf-8")) + +for line in sys.stdin: + send(sock, bytes(json.dumps(["chat", line.strip()]), "utf-8")) diff --git a/src/doublemap.rs b/src/doublemap.rs new file mode 100644 index 0000000..add0803 --- /dev/null +++ b/src/doublemap.rs @@ -0,0 +1,17 @@ + +use std::Collections::HashMap; + +struct DoubleMap<K, V> { + keytoval: HashMap<K, V>, + valtokey: HashMap<V, K> +} + +impl DoubleMap<K, V> { + + pub fn new() -> DoubleMap<K, V> { + DoubleMap { + keytoval: HashMap::new(), + valtokey: HashMap::new() + + +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..37a298f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,187 @@ + + +use std::thread::sleep; +use std::time::Duration; +use std::path::Path; +use std::collections::HashMap; +use std::io; + +use json; +use json::JsonValue; + +pub mod server; +use self::server::Server; +// use self::server::tcpserver::TcpServer; +use self::server::unixserver::UnixServer; + + +enum Message { + Name(String), + Chat(String), + Input, + Invalid(String) +} + +struct GameServer<T: Server> { + players: HashMap<usize, String>, + server: T +} + +impl<T: Server> GameServer<T> { + pub fn new(server: T) -> GameServer<T> { + GameServer { + players: HashMap::new(), + server + } + } + + fn send_error(&mut self, id: usize, err_text: &str) -> Result<(), io::Error>{ + self.server.send(id, err_text) + } + + pub fn broadcast_message(&mut self, text: &str){ + for (id, _name) in &self.players { + let _ = self.server.send(*id, text); + } + } + + pub fn handle_message(&mut self, id: usize, msg: Message) { + match msg { + Message::Name(name) => { + let (firstchar, username) = name.split_at(1); + if firstchar == "~"{ + if Some(username.to_string()) != self.server.get_name(id) { + let _ = self.send_error(id, &format!("A tilde name must match your username")); + } + } else { + if let Some(oldname) = self.players.get(&id) { + self.broadcast_message(&format!("{} is now known as {}", oldname, name)); + } else { + self.broadcast_message(&format!("{} connected", name)); + } + self.players.insert(id, name); + } + } + Message::Chat(text) => { + if let Some(name) = self.players.get(&id) { + self.broadcast_message(&format!("{}: {}", name, text)); + } else { + let _ = self.send_error(id, &format!("Set a name before you send other messages")); + } + } + Message::Input => { () } + Message::Invalid(text) => { + let _ = self.send_error(id, &format!("Invalid: {}", text)); + } + } + } + + pub fn remove_connection(&mut self, id: usize) { + if let Some(name) = self.players.remove(&id){ + self.broadcast_message(&format!("{} disconnected", name)); + } + } +} + +fn main() { + +// let addr = "127.0.0.1:1234".parse().unwrap(); + + let addr = Path::new("\0rustifarm"); + + let mut socketserver = UnixServer::new(&addr).expect("binding server failed"); + + let mut gameserver = GameServer::new(socketserver); + + println!("listening on {:?}", addr); + +// let mut players: HashMap<usize, String> = HashMap::new(); + + loop { + let _joined = server.accept_pending_connections(); +// for id in joined { +// // let name = server.get_name(id).expect("Unable to get name"); +// gameserver.broadcast_message(&format!("{} connected", id)); +// } + let (messages, left) = server.recv_pending_messages(); + for (id, message) in messages { + +// let name = server.get_name(id).expect("Unable to get name"); + gameserver.handle_message(parse_message(&message[..])); +// match parse_message(&message[..]) { +// Message::Name(name) => { +// let (firstchar, username) = name.split_at(1); +// if firstchar == "~"{ +// if Some(username.to_string()) != server.get_name(id) { +// let _ = server.send(id, &format!("A tilde name must match your username")); +// } +// } else { +// if let Some(oldname) = players.get(&id) { +// server.broadcast(&format!("{} is now known as {}", oldname, name)); +// } else { +// server.broadcast(&format!("{} connected", name)); +// } +// players.insert(id, name); +// } +// } +// Message::Chat(text) => { +// if let Some(name) = players.get(&id) { +// server.broadcast(&format!("{}: {}", name, text)); +// } else { +// let _ = server.send(id, &format!("Set a name before you send other messages")); +// } +// } +// Message::Input => { () } +// Message::Invalid(text) => { +// let _ = server.send(id, &format!("Invalid: {}", text)); +// } +// }; + println!("{}: {}", id, message); + } + for id in left { + gameserver.remove_connection(id); +// if let Some(name) = players.remove(&id){ +// server.broadcast(&format!("{} disconnected", name)); +// } + } + sleep(Duration::from_millis(100)); + } +} + + +fn parse_message(msg: &str) -> Message { + if let Ok(data) = json::parse(msg) { + if let JsonValue::Array(arr) = data { + if arr.len() < 2 { + return Message::Invalid("array not long enough".to_string()); + } + if let Some(msgtype) = arr[0].as_str() { + match msgtype { + "name" => { + if let Some(name) = arr[1].as_str(){ + Message::Name(name.to_string()) + } else { + Message::Invalid("name is not a string".to_string()) + } + } + "chat" => { + if let Some(text) = arr[1].as_str(){ + Message::Chat(text.to_string()) + } else { + Message::Invalid("chat text is not a string".to_string()) + } + + } + "input" => { + Message::Input + } + _ => { + Message::Invalid(format!("unknown messsage type {:?}", msgtype).to_string()) + } + } + } else { Message::Invalid(format!("first array value not string: {:?}", arr[0].dump()).to_string()) } + } else { Message::Invalid("not json array".to_string()) } + } else { Message::Invalid("not json message".to_string()) } +} + + diff --git a/src/server/gameserver.rs b/src/server/gameserver.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/server/gameserver.rs @@ -0,0 +1 @@ + diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..131eb19 --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,22 @@ +use std::io; + +pub mod tcpserver; +pub mod unixserver; + +mod streamconnection; + + +pub trait Server { + + fn accept_pending_connections(&mut self) -> Vec<usize>; + + fn recv_pending_messages(&mut self) -> (Vec<(usize, String)>, Vec<usize>); + + fn send(&mut self, id: usize, text: &str) -> Result<(), io::Error>; + + fn broadcast(&mut self, text: &str); + + fn get_name(&self, _id: usize) -> Option<String> { + None + } +} diff --git a/src/server/streamconnection.rs b/src/server/streamconnection.rs new file mode 100644 index 0000000..2bb111a --- /dev/null +++ b/src/server/streamconnection.rs @@ -0,0 +1,68 @@ + + +use std::io; +use std::io::{Read, Write}; +use std::mem::transmute; + + +pub struct StreamConnection<T: Read+Write> { + pub stream: T, + buffer: Vec<u8> +} + +impl <T: Read+Write> StreamConnection<T> { + + pub fn new(stream: T) -> StreamConnection<T> { + StreamConnection { + stream, + buffer: Vec::new() + } + } + + pub fn read(&mut self) -> Result<(Vec<String>, bool), io::Error> { + let mut buf = [0; 2048]; + let mut closed = false; + loop { + match self.stream.read(&mut buf) { + Err(e) => { + if e.kind() == io::ErrorKind::WouldBlock { + break; + } else { + return Err(e); + } + } + Ok(0) => { + closed = true; + break; + } + Ok(i) => { + self.buffer.extend_from_slice(&buf[..i]); +// messages.push(String::from_utf8_lossy(&buf[..i]).to_string()); + } + } + } + let mut messages = Vec::new(); + while self.buffer.len() >= 4 { + let mut header: [u8; 4] = [0;4]; + header.copy_from_slice(&self.buffer[..4]); + let mlen: usize = u32::from_be(unsafe { transmute(header) }) as usize; + if self.buffer.len() - 4 < mlen { + break; + } + let rest = self.buffer.split_off(4+mlen); + let message = String::from_utf8_lossy(&self.buffer[4..]).to_string(); + messages.push(message); + self.buffer = rest; + } + return Ok((messages, closed)); + } + + pub fn send(&mut self, text: &str) -> Result<(), io::Error> { + let bytes: &[u8] = text.as_bytes(); + let len: u32 = bytes.len() as u32; + let header: [u8; 4] = unsafe { transmute(len.to_be()) }; + self.stream.write_all(&header)?; + self.stream.write_all(bytes) + } + +} diff --git a/src/server/tcpserver.rs b/src/server/tcpserver.rs new file mode 100644 index 0000000..b411634 --- /dev/null +++ b/src/server/tcpserver.rs @@ -0,0 +1,89 @@ + + +use std::io; +use std::net::SocketAddr; +use mio::net::{TcpListener, TcpStream}; +use slab::Slab; + +use self::super::streamconnection::StreamConnection; + + +pub struct TcpServer { + listener: TcpListener, + connections: Slab<StreamConnection<TcpStream>> +} + +impl TcpServer { + + pub fn new(addr: &SocketAddr) -> Result<TcpServer, io::Error> { + let listener = TcpListener::bind(addr)?; + Ok( TcpServer { + listener, + connections: Slab::new() + }) + } +} + +impl super::Server for TcpServer { + + fn accept_pending_connections(&mut self) -> Vec<usize> { + let mut new_connections = Vec::new(); + loop { + match self.listener.accept() { + Err(_e) => { + break; + } + Ok((stream, _address)) => { + let con = StreamConnection::new(stream); + let id = self.connections.insert(con); + new_connections.push(id); + } + } + } + new_connections + } + + + fn recv_pending_messages(&mut self) -> (Vec<(usize, String)>, Vec<usize>){ + // let mut buf = [0; 2048]; + let mut messages: Vec<(usize, String)> = Vec::new(); + let mut to_remove = Vec::new(); + for (key, connection) in self.connections.iter_mut(){ + match connection.read() { + Err(_e) => { + to_remove.push(key); + } + Ok((con_messages, closed)) => { + for message in con_messages { + messages.push((key, message)); + } + if closed { + to_remove.push(key); + } + } + } + } + for key in to_remove.iter() { + self.connections.remove(*key); + } + (messages, to_remove) + } + + fn broadcast(&mut self, text: &str) { + for (_id, conn) in self.connections.iter_mut() { + conn.send(text).expect("BroadCast Failed"); + } + } + + fn send(&mut self, id: usize, text: &str) -> Result<(), io::Error> { + match self.connections.get_mut(id){ + Some(conn) => { + conn.send(text) + } + None => Err(io::Error::new(io::ErrorKind::Other, "index is empty")) + } + } + + +} + diff --git a/src/server/unixserver.rs b/src/server/unixserver.rs new file mode 100644 index 0000000..dc9feab --- /dev/null +++ b/src/server/unixserver.rs @@ -0,0 +1,109 @@ + + +use std::io; +use std::path::Path; +use std::os::unix::io::AsRawFd; +use mio_uds::{UnixListener, UnixStream}; +use slab::Slab; +use nix::sys::socket::getsockopt; +use nix::sys::socket::sockopt; +use users; + +use self::super::streamconnection::StreamConnection; + + +pub struct UnixServer { + listener: UnixListener, + connections: Slab<StreamConnection<UnixStream>> +} + +impl UnixServer { + + pub fn new(addr: &Path) -> Result<UnixServer, io::Error> { + let listener = UnixListener::bind(addr)?; + Ok( UnixServer { + listener, + connections: Slab::new() + }) + } + + +} + +impl super::Server for UnixServer { + + fn accept_pending_connections(&mut self) -> Vec<usize> { + let mut new_connections = Vec::new(); + loop { + match self.listener.accept() { + Ok(Some((stream, _address))) => { + let con = StreamConnection::new(stream); + let id = self.connections.insert(con); + new_connections.push(id); + } + Ok(None) => { + break; + } + Err(_e) => { + break; + } + } + } + new_connections + } + + + fn recv_pending_messages(&mut self) -> (Vec<(usize, String)>, Vec<usize>){ + // let mut buf = [0; 2048]; + let mut messages: Vec<(usize, String)> = Vec::new(); + let mut to_remove = Vec::new(); + for (key, connection) in self.connections.iter_mut(){ + match connection.read() { + Err(_e) => { + to_remove.push(key); + } + Ok((con_messages, closed)) => { + for message in con_messages { + messages.push((key, message)); + } + if closed { + to_remove.push(key); + } + } + } + } + for key in to_remove.iter() { + self.connections.remove(*key); + } + (messages, to_remove) + } + + fn broadcast(&mut self, text: &str) { + for (_id, conn) in self.connections.iter_mut() { + let _ = conn.send(text); + } + } + + fn send(&mut self, id: usize, text: &str) -> Result<(), io::Error> { + match self.connections.get_mut(id){ + Some(conn) => { + conn.send(text) + } + None => Err(io::Error::new(io::ErrorKind::Other, "index is empty")) + } + } + + + fn get_name(&self, id: usize) -> Option<String> { + let connection = self.connections.get(id)?; + let fd = connection.stream.as_raw_fd(); + if let Ok(peercred) = getsockopt(fd, sockopt::PeerCredentials) { + let uid = peercred.uid(); + let user = users::get_user_by_uid(uid)?; + let name = user.name(); + Some(name.to_string_lossy().to_string()) + } else { None } + } + +} + |
