├── .gitignore ├── client ├── .gitignore ├── src │ ├── main.rs │ ├── settings.rs │ ├── state.rs │ ├── connection.rs │ ├── service.rs │ └── types.rs ├── README.md ├── Cargo.toml └── Cargo.lock ├── server ├── .gitignore ├── src │ ├── managers │ │ ├── mod.rs │ │ ├── manager.rs │ │ ├── stream_manager.rs │ │ └── data_manager.rs │ ├── main.rs │ ├── settings.rs │ ├── service.rs │ ├── state.rs │ ├── reader.rs │ ├── messages_pool.rs │ └── types.rs ├── Dockerfile ├── Cargo.toml ├── README.md └── Cargo.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /server/src/managers/mod.rs: -------------------------------------------------------------------------------- 1 | mod manager; 2 | mod stream_manager; 3 | mod data_manager; 4 | 5 | pub use manager::Manager; -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.65.0 as build 2 | ENV PKG_CONFIG_ALLOW_CROSS=1 3 | 4 | WORKDIR /usr/src/tchat-server 5 | COPY . . 6 | 7 | RUN cargo install --path . 8 | 9 | FROM gcr.io/distroless/cc-debian10 10 | 11 | COPY --from=build /usr/local/cargo/bin/tchat-server /usr/local/bin/tchat-server 12 | 13 | CMD ["tchat-server", "--port", "8080"] -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tchat-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.1.1", features = ["derive"] } 10 | uuid = { version = "1.3.0", features = ["v4"] } 11 | anyhow = "1.0" 12 | parking_lot = "0.12.1" -------------------------------------------------------------------------------- /client/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use service::Service; 4 | 5 | use crate::{ 6 | settings::Settings, 7 | state::State 8 | }; 9 | 10 | mod settings; 11 | mod types; 12 | mod connection; 13 | mod state; 14 | mod service; 15 | 16 | fn main() -> io::Result<()> { 17 | let settings = Settings::new(); 18 | let state = State::new()?; 19 | 20 | Service::run(settings, state)?; 21 | Ok(()) 22 | } -------------------------------------------------------------------------------- /server/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use service::Service; 4 | use settings::Settings; 5 | use state::State; 6 | 7 | mod settings; 8 | mod state; 9 | mod service; 10 | mod managers; 11 | mod messages_pool; 12 | mod reader; 13 | mod types; 14 | 15 | fn main() -> Result<()> { 16 | let settings = Settings::new(); 17 | let state = State::new(settings); 18 | 19 | Service::run(state)?; 20 | 21 | Ok(()) 22 | } -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Terminal Chat client 2 | ![demo](https://drop.davy.page/hRMXlkDN/Mar-01-2023%2013-44-04.gif) 3 | 4 | ## How to use the terminal chat client? 5 | 0. Install the app via cargo package manager 6 | ```cargo install tchat``` 7 | 1. Learn options 8 | ```tchat -h``` 9 | 2. Connect to a server 10 | ```tchat -a
``` 11 | Example server: ```tchat -a 31.172.76.176:9005``` 12 | 13 | You can use main terminal chat server just to test how it works :). It's address is **31.172.76.176:9005**. -------------------------------------------------------------------------------- /client/src/settings.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(Parser)] 4 | pub struct Args { 5 | #[arg(short, long, help = "Server address")] 6 | pub address: String, 7 | 8 | #[arg(short, long, help = "Server secret key")] 9 | pub key: Option, 10 | } 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct Settings { 14 | pub server_address: String, 15 | pub server_key: Option, 16 | } 17 | 18 | impl Settings { 19 | pub fn new() -> Settings { 20 | let args = Args::parse(); 21 | 22 | Settings { 23 | server_address: args.address, 24 | server_key: args.key 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # Terminal Chat server 2 | 3 | ## How to run the terminal chat server? 4 | The easiest way to do it is to build a docker image and then run it. There's already a [ready-to-use Dockerfile](https://github.com/IDSaves/terminal-chat/blob/master/server/Dockerfile) so you just go with a `docker build -t .` inside a server's directory. After you built a docker image just type in `docker run -p :8080`. 5 | 6 | If you don't wanna use docker you can install the server's package directly on your computer by typing `cargo install`. Of course you will need to install Rust before you do it :). 7 | -------------------------------------------------------------------------------- /client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tchat" 3 | version = "0.1.2" 4 | authors = ["Ivan Davydov davydoff33@yandex.ru"] 5 | license = "MIT" 6 | description = "Client for tchat-server." 7 | readme = "README.md" 8 | homepage = "https://github.com/IDSaves/terminal-chat" 9 | repository = "https://github.com/IDSaves/terminal-chat" 10 | keywords = ["cli", "chat-client", "terminal", "chat"] 11 | categories = ["command-line-utilities", "chat", "terminal"] 12 | edition = "2021" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | clap = { version = "4.1.1", features = ["derive"] } 18 | termion = "2.0.1" 19 | parking_lot = "0.12.1" -------------------------------------------------------------------------------- /server/src/settings.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(Parser)] 4 | pub struct Args { 5 | #[arg(short, long, help = "Port that the server will serve")] 6 | pub port: u16, 7 | 8 | #[arg(short, long, help = "Maximum amount of chat users")] 9 | pub max_users: Option, 10 | 11 | #[arg(short, long, help = "The key that users need to know to participate the chat")] 12 | pub key: Option, 13 | } 14 | 15 | #[derive(Debug, Clone)] 16 | pub struct Settings { 17 | pub port: u16, 18 | pub max_users: u16, 19 | pub key: Option, 20 | } 21 | 22 | impl Settings { 23 | pub fn new() -> Settings { 24 | let args = Args::parse(); 25 | 26 | Settings { 27 | port: args.port, 28 | max_users: args.max_users.unwrap_or(10), 29 | key: args.key 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/src/service.rs: -------------------------------------------------------------------------------- 1 | use std::{net::TcpListener, thread, sync::Arc}; 2 | use anyhow::Result; 3 | use parking_lot::Mutex; 4 | 5 | use crate::{state::State, managers::Manager, messages_pool::MessagesPool}; 6 | 7 | pub struct Service; 8 | 9 | impl Service { 10 | pub fn run(state: State) -> Result<()> { 11 | let listener = TcpListener::bind(format!("0.0.0.0:{}", state.get().settings.port))?; 12 | 13 | println!("Running!"); 14 | 15 | let messages_pool = Arc::new(Mutex::new(MessagesPool::new())); 16 | 17 | for con in listener.incoming() { 18 | let cloned_state = state.clone(); 19 | let cloned_messages_pool = messages_pool.clone(); 20 | thread::spawn(move || -> Result<()> { 21 | Manager::new(con?, cloned_state, cloned_messages_pool)?; 22 | 23 | Ok(()) 24 | }); 25 | } 26 | 27 | Ok(()) 28 | } 29 | } -------------------------------------------------------------------------------- /server/src/state.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::Arc, 3 | collections::HashMap 4 | }; 5 | use parking_lot::{Mutex, MutexGuard}; 6 | 7 | use crate::settings::Settings; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct UserData { 11 | pub address: String, 12 | } 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct StateData { 16 | pub settings: Settings, 17 | pub users: HashMap, 18 | } 19 | 20 | pub struct State(Arc>); 21 | 22 | impl State { 23 | pub fn new(settings: Settings) -> State { 24 | State( 25 | Arc::new(Mutex::new(StateData { 26 | settings, 27 | users: HashMap::new() 28 | })) 29 | ) 30 | } 31 | 32 | pub fn get(&self) -> MutexGuard { 33 | self.0.lock() 34 | } 35 | } 36 | 37 | impl Clone for State { 38 | fn clone(&self) -> Self { 39 | State(Arc::clone(&self.0)) 40 | } 41 | 42 | fn clone_from(&mut self, source: &Self) { 43 | *self = source.clone(); 44 | } 45 | } -------------------------------------------------------------------------------- /server/src/reader.rs: -------------------------------------------------------------------------------- 1 | use std::{io::{BufReader, self, BufRead, Error, ErrorKind}, self, net::TcpStream}; 2 | 3 | use crate::types::SignalHeader; 4 | 5 | pub trait StreamReader { 6 | fn read_signal(&mut self) -> io::Result; 7 | } 8 | 9 | impl StreamReader for BufReader { 10 | fn read_signal(&mut self) -> io::Result { 11 | let mut res_line = String::new(); 12 | let mut headers_read = false; 13 | loop { 14 | let mut buf_line = String::new(); 15 | match self.read_line(&mut buf_line) { 16 | Err(_) => return Err(Error::new(ErrorKind::ConnectionAborted, "boom boom")), 17 | Ok(0) => return Err(Error::new(ErrorKind::BrokenPipe, "boom boom")), 18 | Ok(m) => m, 19 | }; 20 | res_line.push_str(&buf_line); 21 | 22 | if res_line.ends_with("\r\n\r\n"){ 23 | if !res_line.contains(&SignalHeader::WithMessage.to_string()) || headers_read { 24 | break; 25 | } 26 | headers_read = true; 27 | } 28 | } 29 | 30 | Ok(res_line) 31 | } 32 | } -------------------------------------------------------------------------------- /server/src/managers/manager.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::TcpStream, 3 | io::BufReader, 4 | sync::Arc 5 | }; 6 | use parking_lot::Mutex; 7 | use anyhow::Result; 8 | 9 | use crate::{state::State, messages_pool::MessagesPool}; 10 | use super::stream_manager::StreamManager; 11 | 12 | pub struct Manager { 13 | pub stream: TcpStream, 14 | pub reader: BufReader, 15 | pub state: State, 16 | pub messages_pool: Arc>, 17 | pub last_read_message_id: String, 18 | pub connected_user_username: Option, 19 | pub connected_peer_addr: String 20 | } 21 | 22 | impl Manager { 23 | pub fn new(stream: TcpStream, state: State, messages_pool: Arc>) -> Result<()> { 24 | let mut manager = Manager { 25 | stream: stream.try_clone()?, 26 | reader: BufReader::new(stream.try_clone()?), 27 | state, 28 | messages_pool, 29 | last_read_message_id: String::new(), 30 | connected_user_username: None, 31 | connected_peer_addr: stream.try_clone()?.peer_addr()?.to_string() 32 | }; 33 | 34 | manager.process_connection()?; 35 | Ok(()) 36 | } 37 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terminal Chat 2 | Chat service written in Rust. Includes server and client applications. 3 | ![demo](https://drop.davy.page/hRMXlkDN/Mar-01-2023%2013-44-04.gif) 4 | 5 | [**Chat client on CRATES.IO**](https://crates.io/crates/tchat) 6 | 7 | ## How to run the terminal chat server? 8 | The easiest way to do it is to build a docker image and then run it. There's already a [ready-to-use Dockerfile](https://github.com/IDSaves/terminal-chat/blob/master/server/Dockerfile) so you just go with a `docker build -t .` inside a server's directory. After you built a docker image just type in `docker run -p :8080`. 9 | 10 | If you don't wanna use docker you can install the server's package directly on your computer by typing `cargo install`. Of course you will need to install Rust before you do it :). 11 | ## How to use the terminal chat client? 12 | 0. Install the app via cargo package manager 13 | ```cargo install tchat``` 14 | 1. Learn options 15 | ```tchat -h``` 16 | 2. Connect to a server 17 | ```tchat -a
``` 18 | Example server: ```tchat -a 31.172.76.176:9005``` 19 | 20 | You can use main terminal chat server just to test how it works :). It's address is **31.172.76.176:9005**. -------------------------------------------------------------------------------- /client/src/state.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::{ 3 | mpsc::{ 4 | Sender, 5 | Receiver, 6 | self 7 | }, 8 | Arc 9 | }, 10 | io::{ 11 | self, 12 | Write 13 | } 14 | }; 15 | 16 | use parking_lot::Mutex; 17 | 18 | pub struct State { 19 | pub username: String, 20 | pub chat_reload_receiver: Option>, 21 | pub chat_reload_sender: Sender<()>, 22 | pub user_input: Arc>, 23 | pub messages: Arc>> 24 | } 25 | 26 | impl State { 27 | pub fn new() -> io::Result { 28 | let (sx, rx) = mpsc::channel::<()>(); 29 | let user_input = Arc::new(Mutex::new(String::new())); 30 | let messages = Arc::new(Mutex::new(Vec::::new())); 31 | 32 | let mut instance = State { 33 | username: String::new(), 34 | chat_reload_receiver: Some(rx), 35 | chat_reload_sender: sx, 36 | user_input, 37 | messages, 38 | }; 39 | 40 | instance.read_username()?; 41 | 42 | Ok(instance) 43 | } 44 | 45 | fn read_username(&mut self) -> io::Result<()> { 46 | println!("{}", termion::clear::All); 47 | print!("Username: "); 48 | std::io::stdout().flush()?; 49 | 50 | let mut username = String::new(); 51 | io::stdin().read_line(&mut username)?; 52 | 53 | self.username = username.trim().to_owned(); 54 | println!("{}", termion::clear::All); 55 | 56 | Ok(()) 57 | } 58 | } -------------------------------------------------------------------------------- /client/src/connection.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::TcpStream, 3 | io::{ 4 | self, 5 | Write, 6 | Error, 7 | ErrorKind, BufRead, BufReader 8 | }, 9 | }; 10 | 11 | use crate::types::{ 12 | SignalType, 13 | SignalHeader, 14 | SignalData, 15 | AuthStatus 16 | }; 17 | 18 | pub struct Connection { 19 | pub stream: TcpStream, 20 | reader: io::BufReader 21 | } 22 | 23 | impl Connection { 24 | pub fn new(address: &str, username: &str) -> io::Result { 25 | let signal = SignalData::new( 26 | vec![ 27 | SignalHeader::SignalType(SignalType::Connection), 28 | SignalHeader::Username(username.to_owned()) 29 | ], 30 | None 31 | ); 32 | let mut connection = TcpStream::connect(address)?; 33 | connection.write_all(signal.to_string().as_bytes())?; 34 | let reader = BufReader::new(connection.try_clone()?); 35 | 36 | let mut instance = Connection { 37 | stream: connection, 38 | reader 39 | }; 40 | 41 | let data_from_socket = instance.read_signal()?; 42 | if data_from_socket.contains(&AuthStatus::DENIED.to_string()) { 43 | return Err(Error::new(ErrorKind::ConnectionAborted, "Access denied")); 44 | } 45 | 46 | return Ok(instance) 47 | } 48 | 49 | pub fn read_signal(&mut self) -> io::Result { 50 | let mut res_line = String::new(); 51 | let mut headers_read = false; 52 | loop { 53 | let mut buf_line = String::new(); 54 | match self.reader.read_line(&mut buf_line) { 55 | Err(e) => panic!("Got an error: {}", e), 56 | Ok(0) => return Err(Error::new(ErrorKind::BrokenPipe, "Connection closed")), 57 | Ok(_) => (), 58 | }; 59 | res_line.push_str(&buf_line); 60 | 61 | if res_line.ends_with("\r\n\r\n"){ 62 | if !res_line.contains(&SignalHeader::WithMessage.to_string()) || headers_read { 63 | break; 64 | } 65 | headers_read = true; 66 | } 67 | } 68 | 69 | Ok(res_line) 70 | } 71 | } 72 | 73 | impl Clone for Connection { 74 | fn clone(&self) -> Self { 75 | Connection { 76 | stream: self.stream.try_clone().unwrap(), 77 | reader: BufReader::new(self.stream.try_clone().unwrap()) 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /server/src/managers/stream_manager.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{ 3 | Write, BufReader 4 | }, 5 | thread, 6 | sync::mpsc::{ 7 | self, 8 | Sender 9 | } 10 | }; 11 | use anyhow::Result; 12 | 13 | use crate::{managers::data_manager::DataManager, reader::StreamReader}; 14 | 15 | use super::manager::Manager; 16 | 17 | pub trait StreamManager { 18 | fn process_connection(&mut self) -> Result<()>; 19 | fn process_disconnection(&mut self) -> Result<()>; 20 | fn send_data(&mut self, data: &str) -> Result<()>; 21 | fn process_signals(&mut self, sender: Sender<()>) -> Result<()>; 22 | } 23 | 24 | impl StreamManager for Manager { 25 | fn process_connection(&mut self) -> Result<()> { 26 | println!("Connection established - {}", self.connected_peer_addr); 27 | 28 | let auth_data = match BufReader::new( 29 | self.stream.try_clone()? 30 | ).read_signal() { 31 | Ok(v) => v, 32 | Err(_) => { 33 | self.process_disconnection()?; 34 | return Ok(()) 35 | } 36 | }; 37 | 38 | if self.auth(auth_data.clone()).is_err() { 39 | self.deny_auth()?; 40 | self.process_disconnection()?; 41 | return Ok(()) 42 | } 43 | 44 | let (channel_sender, channel_receiver) = mpsc::channel::<()>(); 45 | self.process_signals(channel_sender)?; 46 | 47 | self.process_messages_pool(channel_receiver)?; 48 | 49 | self.process_disconnection()?; 50 | Ok(()) 51 | } 52 | 53 | fn process_disconnection(&mut self) -> Result<()> { 54 | if self.connected_user_username.is_some() { 55 | self.remove_user(self.connected_user_username.clone().unwrap())?; 56 | } 57 | println!("Connection closed - {}", self.connected_peer_addr); 58 | Ok(()) 59 | } 60 | 61 | fn send_data(&mut self, data: &str) -> Result<()> { 62 | self.stream.write(data.as_bytes())?; 63 | Ok(()) 64 | } 65 | 66 | fn process_signals(&mut self, sender: Sender<()>) -> Result<()> { 67 | let cloned_stream = self.stream.try_clone()?; 68 | let cloned_messages_pool = self.messages_pool.clone(); 69 | 70 | thread::spawn(move || -> Result<()> { 71 | let mut reader = BufReader::new(cloned_stream.try_clone()?); 72 | loop { 73 | let data_from_socket = match reader.read_signal() { 74 | Ok(s) => s, 75 | Err(_) => { 76 | break; 77 | } 78 | }; 79 | 80 | match Self::process_incoming_message(cloned_messages_pool.clone(), data_from_socket) { 81 | Ok(_) => (), 82 | Err(_) => println!("invalid message") 83 | }; 84 | } 85 | 86 | sender.send(())?; 87 | 88 | Ok(()) 89 | }); 90 | 91 | Ok(()) 92 | } 93 | } -------------------------------------------------------------------------------- /server/src/messages_pool.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::{HashMap, VecDeque}, iter}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct PoolMessage { 5 | pub id: String, 6 | pub username: String, 7 | pub message: String, 8 | pub from_server: bool, 9 | } 10 | 11 | impl PoolMessage { 12 | fn new() -> PoolMessage { 13 | PoolMessage { 14 | id: String::new(), 15 | username: String::new(), 16 | message: String::new(), 17 | from_server: false, 18 | } 19 | } 20 | } 21 | 22 | pub struct MessagesPool { 23 | pool: VecDeque, 24 | indexes: HashMap, 25 | length: u16, 26 | } 27 | 28 | impl MessagesPool { 29 | pub fn new() -> MessagesPool { 30 | let arr: VecDeque = iter::repeat_with(|| PoolMessage::new()) 31 | .take(256) 32 | .collect(); 33 | MessagesPool { 34 | pool: arr, 35 | indexes: HashMap::new(), 36 | length: 0 37 | } 38 | } 39 | 40 | pub fn push(&mut self, v: PoolMessage) { 41 | if self.length == 256 { 42 | self.pool.pop_front(); 43 | self.pool.push_back(v); 44 | 45 | let mut new_indexes: HashMap = HashMap::new(); 46 | for (index, message) in self.pool.iter().enumerate() { 47 | new_indexes.insert(message.id.clone(), index as u8); 48 | } 49 | self.indexes = new_indexes; 50 | } 51 | else { 52 | let index = self.length as u8; 53 | self.pool[index as usize] = v.clone(); 54 | self.length += 1; 55 | self.indexes.insert(v.id.clone(), index); 56 | } 57 | } 58 | 59 | fn read_from(&self, id: &str) -> (Vec, Option) { 60 | let found_index = self.indexes.get(id); 61 | match found_index { 62 | Some(v) => { 63 | let index: u16 = v.to_owned() as u16 + 1; 64 | let sliced_pool = &Vec::from(self.pool.clone())[index.into()..self.length.into()]; 65 | let sliced_pool_last = { 66 | if sliced_pool.len() == 0 { 67 | None 68 | } 69 | else { 70 | Some(sliced_pool.last().unwrap().clone().id) 71 | } 72 | }; 73 | return (sliced_pool.clone().into(), sliced_pool_last) 74 | }, 75 | None => { 76 | let last_el = self.last(); 77 | let index = match last_el { 78 | Some(v) => Some(v.id.clone()), 79 | None => None 80 | }; 81 | let sliced_pool = &Vec::from(self.pool.clone())[..self.length.into()]; 82 | return (sliced_pool.into(), index) 83 | } 84 | } 85 | } 86 | 87 | pub fn has_new(&self, id: &str) -> Option<(Vec, Option)> { 88 | let last_el = self.last(); 89 | match last_el { 90 | Some(_) => Some(self.read_from(id)), 91 | None => None, 92 | } 93 | } 94 | 95 | fn last(&self) -> Option { 96 | let last_index = { 97 | if self.length > 0 { 98 | self.length - 1 99 | } else { 100 | self.length 101 | } 102 | }; 103 | 104 | let last_el = &self.pool[last_index.into()]; 105 | if last_el.id == "".to_owned() { 106 | None 107 | } else { 108 | Some(last_el.to_owned()) 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /server/src/managers/data_manager.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::sync::mpsc::Receiver; 3 | use std::thread; 4 | use std::time::Duration; 5 | use std::str::FromStr; 6 | use anyhow::Result; 7 | use parking_lot::Mutex; 8 | use uuid::Uuid; 9 | 10 | use crate::messages_pool::{PoolMessage, MessagesPool}; 11 | use crate::state::UserData; 12 | use crate::types::{ 13 | AuthStatus, 14 | SignalData, 15 | SignalHeader, 16 | AuthConnectionError, 17 | IncomingMessageError, 18 | SignalType 19 | }; 20 | 21 | use super::manager::Manager; 22 | use super::stream_manager::StreamManager; 23 | 24 | pub trait DataManager { 25 | fn deny_auth(&mut self) -> Result<()>; 26 | fn auth(&mut self, signal: String) -> Result<()>; 27 | fn remove_user(&mut self, username: String) -> Result<()>; 28 | fn process_messages_pool(&mut self, receiver: Receiver<()>) -> Result<()>; 29 | fn process_incoming_message(messages_pool: Arc>, signal: String) -> Result<()>; 30 | } 31 | 32 | impl DataManager for Manager { 33 | fn deny_auth(&mut self) -> Result<()> { 34 | let response = SignalData::new( 35 | vec![SignalHeader::AuthStatus(AuthStatus::DENIED)], 36 | None 37 | ); 38 | 39 | self.send_data(&response.to_string())?; 40 | Ok(()) 41 | } 42 | 43 | fn auth(&mut self, signal: String) -> Result<()> { 44 | let data = SignalData::from_str(&signal)?; 45 | 46 | match data.signal_type.unwrap() { 47 | SignalType::Connection => { 48 | if let None = data.username { 49 | return Err(AuthConnectionError.into()); 50 | } 51 | let mut state = self.state.get(); 52 | if state.users.contains_key(&data.username.clone().unwrap()) { 53 | return Err(AuthConnectionError.into()) 54 | } 55 | state.users.insert(data.username.clone().unwrap().to_owned(), UserData { 56 | address: self.stream.peer_addr()?.to_string(), 57 | }); 58 | self.messages_pool.lock().push(PoolMessage { 59 | id: Uuid::new_v4().to_string(), 60 | username: String::new(), 61 | message: format!("{} joined the chat!", data.username.clone().unwrap()), 62 | from_server: true 63 | }); 64 | } 65 | _ => return Err(AuthConnectionError.into()), 66 | } 67 | 68 | self.connected_user_username = Some(data.username.unwrap()); 69 | 70 | let response = SignalData::new( 71 | vec![SignalHeader::AuthStatus(AuthStatus::ACCEPTED)], 72 | None 73 | ); 74 | 75 | self.send_data(&response.to_string())?; 76 | Ok(()) 77 | } 78 | 79 | fn remove_user(&mut self, username: String) -> Result<()> { 80 | let mut state = self.state.get(); 81 | 82 | if state.users.contains_key(&username) { 83 | state.users.remove(&username); 84 | self.messages_pool.lock().push(PoolMessage { 85 | id: Uuid::new_v4().to_string(), 86 | username: String::new(), 87 | message: format!("{username} left the chat!"), 88 | from_server: true 89 | }); 90 | } 91 | Ok(()) 92 | } 93 | 94 | fn process_messages_pool(&mut self, receiver: Receiver<()>) -> Result<()> { 95 | loop { 96 | if let Ok(()) = receiver.try_recv() { 97 | break; 98 | }; 99 | 100 | let lock_ref = self.messages_pool.clone(); 101 | let pool_lock = lock_ref.lock(); 102 | 103 | let messages = pool_lock.has_new(&self.last_read_message_id); 104 | if let Some(v) = messages { 105 | if let Some(last) = v.1 { 106 | self.last_read_message_id = last; 107 | } 108 | for message in v.0 { 109 | let mut syg_vec = vec![ 110 | SignalHeader::SignalType(SignalType::NewMessage), 111 | SignalHeader::Username(message.username.clone()), 112 | SignalHeader::WithMessage 113 | ]; 114 | if message.from_server { 115 | syg_vec.push(SignalHeader::ServerMessage); 116 | } 117 | let response = SignalData::new(syg_vec, Some(&message.message)); 118 | self.send_data(&response.to_string())?; 119 | } 120 | } 121 | thread::sleep(Duration::from_millis(10)); 122 | } 123 | 124 | Ok(()) 125 | } 126 | 127 | fn process_incoming_message(messages_pool: Arc>, signal: String) -> Result<()> { 128 | let data = SignalData::from_str(&signal)?; 129 | 130 | if !data.with_message || data.username.is_none() { 131 | return Err(IncomingMessageError.into()) 132 | } 133 | 134 | messages_pool.lock().push(PoolMessage { 135 | id: Uuid::new_v4().to_string(), 136 | username: data.username.clone().unwrap(), 137 | message: data.message.clone().unwrap().trim().to_owned(), 138 | from_server: false 139 | }); 140 | 141 | Ok(()) 142 | } 143 | } -------------------------------------------------------------------------------- /client/src/service.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | thread, 3 | io::{ 4 | self, 5 | Write 6 | }, 7 | str::FromStr 8 | }; 9 | use termion::{ 10 | raw::IntoRawMode, 11 | input::TermRead 12 | }; 13 | use crate::{ 14 | settings::Settings, 15 | state::State, 16 | connection::Connection, 17 | types::{ 18 | SignalType, 19 | SignalData, 20 | SignalHeader 21 | } 22 | }; 23 | 24 | pub struct Service { 25 | pub connection: Connection, 26 | pub settings: Settings, 27 | pub state: State, 28 | } 29 | 30 | impl Service { 31 | pub fn run(settings: Settings, state: State) -> io::Result<()> { 32 | let connection = Connection::new( 33 | &settings.server_address.to_owned(), 34 | &state.username 35 | )?; 36 | 37 | let mut instance = Service { 38 | connection, 39 | settings, 40 | state 41 | }.enable_print(); 42 | 43 | instance.proccess_incoming_messages(); 44 | instance.read_inputs(); 45 | 46 | Ok(()) 47 | } 48 | 49 | pub fn proccess_incoming_messages(&self) { 50 | let messages = self.state.messages.clone(); 51 | let tx = self.state.chat_reload_sender.clone(); 52 | let mut connection = self.connection.clone(); 53 | thread::spawn(move || -> io::Result<()> { 54 | loop { 55 | let data_from_socket = match connection.read_signal() { 56 | Ok(v) => v, 57 | Err(_) => break 58 | }; 59 | let signal = SignalData::from_str(&data_from_socket); 60 | let mut messages = messages.lock(); 61 | if let Ok(s) = signal { 62 | if let Some(SignalType::NewMessage) = s.signal_type { 63 | if s.server_message { 64 | messages.push( 65 | format!( 66 | "{}{}{}{}", 67 | termion::style::Faint, 68 | termion::style::Bold, 69 | s.message.unwrap(), 70 | termion::style::Reset, 71 | ) 72 | ); 73 | } 74 | else { 75 | messages.push( 76 | format!( 77 | "<{}> {}", 78 | s.username.unwrap(), 79 | s.message.unwrap() 80 | ) 81 | ); 82 | } 83 | } 84 | } 85 | match tx.send(()) { 86 | Ok(_) => {}, 87 | Err(_) => break 88 | }; 89 | } 90 | 91 | Ok(()) 92 | }); 93 | } 94 | 95 | pub fn enable_print(self) -> Service { 96 | let rx = self.state.chat_reload_receiver.unwrap(); 97 | let messages = self.state.messages.clone(); 98 | let user_input = self.state.user_input.clone(); 99 | let username = self.state.username.clone(); 100 | 101 | thread::spawn(move || -> io::Result<()> { 102 | loop { 103 | match rx.recv() { 104 | Ok(()) => {}, 105 | Err(_) => break 106 | }; 107 | print!("{}", termion::clear::All); 108 | for (index, m) in messages.lock().iter().enumerate() { 109 | if index == 0 { 110 | print!("\r\n{m}\r\n"); 111 | } 112 | else { 113 | print!("{m}\r\n"); 114 | } 115 | } 116 | let input = user_input.lock().clone(); 117 | print!( 118 | "{}{}{} >{} {}", 119 | termion::color::Bg(termion::color::White), 120 | termion::color::Fg(termion::color::Black), 121 | username, 122 | termion::style::Reset, 123 | input 124 | ); 125 | 126 | std::io::stdout().flush()?; 127 | } 128 | Ok(()) 129 | }); 130 | 131 | Service { 132 | connection: self.connection, 133 | settings: self.settings, 134 | state: State { 135 | username: self.state.username.clone(), 136 | chat_reload_receiver: None, 137 | chat_reload_sender: self.state.chat_reload_sender.clone(), 138 | user_input: self.state.user_input.clone(), 139 | messages: self.state.messages.clone(), 140 | } 141 | } 142 | } 143 | 144 | pub fn read_inputs(&mut self) { 145 | let stdout = io::stdout().into_raw_mode().unwrap(); // НЕЛЬЗЯ УБИРАТЬ 146 | let mut stdin = io::stdin().keys(); 147 | 148 | loop { 149 | let input = stdin.next(); 150 | 151 | if let Some(Ok(key)) = input { 152 | match key { 153 | termion::event::Key::Ctrl('c') => break, 154 | termion::event::Key::Char('\n') => { 155 | let ms = self.state.user_input.lock().clone().trim().to_owned(); 156 | if ms == "" { 157 | match self.state.chat_reload_sender.send(()) { 158 | Ok(_) => {}, 159 | Err(_) => break, 160 | }; 161 | continue; 162 | } 163 | self.state.user_input.lock().clear(); 164 | let signal = SignalData::new( 165 | vec![ 166 | SignalHeader::SignalType(SignalType::NewMessage), 167 | SignalHeader::WithMessage, 168 | SignalHeader::Username(self.state.username.to_owned()) 169 | ], 170 | Some(&ms) 171 | ); 172 | 173 | self.connection.stream.write_all(signal.to_string().as_bytes()).unwrap(); 174 | }, 175 | termion::event::Key::Backspace => { 176 | self.state.user_input.lock().pop(); 177 | match self.state.chat_reload_sender.send(()) { 178 | Ok(_) => {}, 179 | Err(_) => break, 180 | }; 181 | } 182 | termion::event::Key::Char(k) => { 183 | println!("{k}"); 184 | self.state.user_input.lock().push_str(&k.to_string()); 185 | match self.state.chat_reload_sender.send(()) { 186 | Ok(_) => {}, 187 | Err(_) => break, 188 | }; 189 | }, 190 | _ => { 191 | continue; 192 | } 193 | } 194 | } 195 | } 196 | } 197 | } -------------------------------------------------------------------------------- /server/src/types.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | str::FromStr, 3 | fmt, 4 | error::Error 5 | }; 6 | 7 | /* 8 | Сигнал может содержать следующие хедеры 9 | USER: USERNAME 10 | SERVER: AUTH_STATUS 11 | USER+SERVER: WITH_MESSAGE 12 | USER+SERVER: SIGNAL_TYPE 13 | SERVER: SERVER_MESSAGE 14 | */ 15 | 16 | #[derive(Debug)] 17 | pub struct ParseSignalDataError; 18 | impl Error for ParseSignalDataError {} 19 | impl fmt::Display for ParseSignalDataError { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | write!(f, "invalid signal data") 22 | } 23 | } 24 | 25 | #[derive(Debug)] 26 | pub struct AuthConnectionError; 27 | impl Error for AuthConnectionError {} 28 | impl fmt::Display for AuthConnectionError { 29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 30 | write!(f, "auth connection error") 31 | } 32 | } 33 | 34 | #[derive(Debug)] 35 | pub struct IncomingMessageError; 36 | impl Error for IncomingMessageError {} 37 | impl fmt::Display for IncomingMessageError { 38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 39 | write!(f, "incoming message error") 40 | } 41 | } 42 | 43 | 44 | #[derive(Debug, Clone, Copy)] 45 | pub enum SignalType { 46 | Connection, 47 | NewMessage, 48 | } 49 | 50 | impl FromStr for SignalType { 51 | type Err = ParseSignalDataError; 52 | 53 | fn from_str(s: &str) -> Result { 54 | match s { 55 | "CONNECTION" => Ok(SignalType::Connection), 56 | "NEW_MESSAGE" => Ok(SignalType::NewMessage), 57 | _ => Err(ParseSignalDataError) 58 | } 59 | } 60 | } 61 | 62 | impl ToString for SignalType { 63 | fn to_string(&self) -> String { 64 | match self { 65 | SignalType::Connection => "CONNECTION".to_owned(), 66 | SignalType::NewMessage => "NEW_MESSAGE".to_owned(), 67 | } 68 | } 69 | } 70 | 71 | 72 | #[derive(Debug, Clone, Copy)] 73 | pub enum AuthStatus { 74 | ACCEPTED, 75 | DENIED 76 | } 77 | 78 | impl FromStr for AuthStatus { 79 | type Err = ParseSignalDataError; 80 | 81 | fn from_str(s: &str) -> Result { 82 | match s { 83 | "ACCEPTED" => Ok(AuthStatus::ACCEPTED), 84 | "DENIED" => Ok(AuthStatus::DENIED), 85 | _ => Err(ParseSignalDataError) 86 | } 87 | } 88 | } 89 | 90 | impl ToString for AuthStatus { 91 | fn to_string(&self) -> String { 92 | match self { 93 | AuthStatus::ACCEPTED => "ACCEPTED".to_owned(), 94 | AuthStatus::DENIED => "DENIED".to_owned() 95 | } 96 | } 97 | } 98 | 99 | 100 | pub enum SignalHeader { 101 | Username(String), 102 | AuthStatus(AuthStatus), 103 | SignalType(SignalType), 104 | WithMessage, 105 | ServerMessage 106 | } 107 | 108 | impl FromStr for SignalHeader { 109 | type Err = ParseSignalDataError; 110 | 111 | fn from_str(s: &str) -> Result { 112 | let (header, value) = s.split_once(':').unwrap_or((s, s)); 113 | 114 | match header { 115 | "USERNAME" => Ok(SignalHeader::Username(value.trim().to_owned())), 116 | "AUTH_STATUS" => { 117 | match AuthStatus::from_str(value.trim()) { 118 | Ok(v) => return Ok(SignalHeader::AuthStatus(v)), 119 | Err(_) => Err(ParseSignalDataError) 120 | } 121 | }, 122 | "SIGNAL_TYPE" => { 123 | match SignalType::from_str(value.trim()) { 124 | Ok(v) => return Ok(SignalHeader::SignalType(v)), 125 | Err(_) => Err(ParseSignalDataError) 126 | } 127 | } 128 | "WITH_MESSAGE" => Ok(SignalHeader::WithMessage), 129 | "SERVER_MESSAGE" => Ok(SignalHeader::ServerMessage), 130 | _ => Err(ParseSignalDataError) 131 | } 132 | } 133 | } 134 | 135 | impl ToString for SignalHeader { 136 | fn to_string(&self) -> String { 137 | match self { 138 | SignalHeader::Username(v) => format!("USERNAME: {v}\r\n"), 139 | SignalHeader::AuthStatus(v) => format!("AUTH_STATUS: {}\r\n", v.to_string()), 140 | SignalHeader::SignalType(v) => format!("SIGNAL_TYPE: {}\r\n", v.to_string()), 141 | SignalHeader::WithMessage => "WITH_MESSAGE\r\n".to_owned(), 142 | SignalHeader::ServerMessage => "SERVER_MESSAGE\r\n".to_owned() 143 | } 144 | } 145 | } 146 | 147 | #[derive(Debug, Clone)] 148 | pub struct SignalData { 149 | pub username: Option, 150 | pub password: Option, 151 | pub key: Option, 152 | pub auth_status: Option, 153 | pub signal_type: Option, 154 | pub with_message: bool, 155 | pub message: Option, 156 | pub server_message: bool 157 | } 158 | 159 | impl SignalData { 160 | pub fn new(headers: Vec, message: Option<&str>) -> SignalData { 161 | let mut data = SignalData { 162 | username: None, 163 | password: None, 164 | key: None, 165 | auth_status: None, 166 | signal_type: None, 167 | with_message: false, 168 | message: None, 169 | server_message: false 170 | }; 171 | 172 | for header in headers { 173 | match header { 174 | SignalHeader::Username(v) => { 175 | data.username = Some(v); 176 | }, 177 | SignalHeader::AuthStatus(v) => { 178 | data.auth_status = Some(v); 179 | }, 180 | SignalHeader::SignalType(v) => { 181 | data.signal_type = Some(v); 182 | }, 183 | SignalHeader::WithMessage => { 184 | data.with_message = true; 185 | data.message = Some(message.unwrap_or("").to_owned()); 186 | }, 187 | SignalHeader::ServerMessage => { 188 | data.server_message = true; 189 | } 190 | } 191 | } 192 | 193 | data 194 | } 195 | } 196 | 197 | impl FromStr for SignalData { 198 | type Err = ParseSignalDataError; 199 | 200 | fn from_str(s: &str) -> Result { 201 | let mut data = SignalData { 202 | username: None, 203 | password: None, 204 | key: None, 205 | auth_status: None, 206 | signal_type: None, 207 | with_message: false, 208 | message: None, 209 | server_message: false, 210 | }; 211 | let splitted = s.split("\r\n"); 212 | for string in splitted { 213 | let header = match SignalHeader::from_str(string) { 214 | Ok(v) => v, 215 | Err(_) => continue 216 | }; 217 | 218 | match header { 219 | SignalHeader::Username(v) => { 220 | data.username = Some(v); 221 | }, 222 | SignalHeader::AuthStatus(v) => { 223 | data.auth_status = Some(v); 224 | }, 225 | SignalHeader::SignalType(v) => { 226 | data.signal_type = Some(v); 227 | } 228 | SignalHeader::WithMessage => { 229 | data.with_message = true; 230 | }, 231 | SignalHeader::ServerMessage => { 232 | data.server_message = true; 233 | } 234 | } 235 | } 236 | 237 | if data.with_message { 238 | let splitted = s.split_once("\r\n\r\n"); 239 | if let Some(v) = splitted { 240 | if v.1.ends_with("\r\n\r\n") { 241 | let string = v.1.to_owned(); 242 | data.message = Some(string[..string.len() - 4].to_owned()); 243 | } 244 | else { 245 | data.message = Some(v.1.to_owned()); 246 | } 247 | } 248 | else { 249 | return Err(ParseSignalDataError); 250 | } 251 | } 252 | 253 | if let None = data.signal_type { 254 | return Err(ParseSignalDataError) 255 | } 256 | 257 | Ok(data) 258 | } 259 | } 260 | 261 | impl ToString for SignalData { 262 | fn to_string(&self) -> String { 263 | let mut res_str = String::new(); 264 | 265 | if let Some(v) = &self.username { 266 | res_str.push_str(&SignalHeader::Username(v.to_owned()).to_string()); 267 | } 268 | if let Some(v) = &self.auth_status { 269 | res_str.push_str(&SignalHeader::AuthStatus(v.clone()).to_string()); 270 | } 271 | if let Some(v) = &self.signal_type { 272 | res_str.push_str(&SignalHeader::SignalType(v.clone()).to_string()); 273 | } 274 | if self.server_message { 275 | res_str.push_str(&SignalHeader::ServerMessage.to_string()); 276 | } 277 | if self.with_message { 278 | if let Some(v) = &self.message { 279 | res_str.push_str(&SignalHeader::WithMessage.to_string()); 280 | res_str.push_str("\r\n"); 281 | res_str.push_str(&v); 282 | } 283 | } 284 | res_str.push_str("\r\n\r\n"); 285 | 286 | res_str 287 | } 288 | } -------------------------------------------------------------------------------- /client/src/types.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | str::FromStr, 3 | fmt, 4 | error::Error 5 | }; 6 | 7 | /* 8 | Сигнал может содержать следующие хедеры 9 | USER: USERNAME 10 | USER: PASSWORD 11 | USER: KEY 12 | SERVER: AUTH_STATUS 13 | USER+SERVER: WITH_MESSAGE 14 | USER+SERVER: SIGNAL_TYPE 15 | */ 16 | 17 | #[derive(Debug, Clone, PartialEq, Eq)] 18 | pub struct ParseSignalDataError; 19 | impl Error for ParseSignalDataError {} 20 | impl fmt::Display for ParseSignalDataError { 21 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 22 | write!(f, "invalid signal data") 23 | } 24 | } 25 | 26 | #[derive(Debug, Clone, Copy)] 27 | pub enum SignalType { 28 | Connection, 29 | NewMessage, 30 | } 31 | 32 | impl FromStr for SignalType { 33 | type Err = ParseSignalDataError; 34 | 35 | fn from_str(s: &str) -> Result { 36 | match s { 37 | "CONNECTION" => Ok(SignalType::Connection), 38 | "NEW_MESSAGE" => Ok(SignalType::NewMessage), 39 | _ => Err(ParseSignalDataError) 40 | } 41 | } 42 | } 43 | 44 | impl ToString for SignalType { 45 | fn to_string(&self) -> String { 46 | match self { 47 | SignalType::Connection => "CONNECTION".to_owned(), 48 | SignalType::NewMessage => "NEW_MESSAGE".to_owned(), 49 | } 50 | } 51 | } 52 | 53 | 54 | #[derive(Debug, Clone, Copy)] 55 | pub enum AuthStatus { 56 | ACCEPTED, 57 | DENIED 58 | } 59 | 60 | impl FromStr for AuthStatus { 61 | type Err = ParseSignalDataError; 62 | 63 | fn from_str(s: &str) -> Result { 64 | match s { 65 | "ACCEPTED" => Ok(AuthStatus::ACCEPTED), 66 | "DENIED" => Ok(AuthStatus::DENIED), 67 | _ => Err(ParseSignalDataError) 68 | } 69 | } 70 | } 71 | 72 | impl ToString for AuthStatus { 73 | fn to_string(&self) -> String { 74 | match self { 75 | AuthStatus::ACCEPTED => "ACCEPTED".to_owned(), 76 | AuthStatus::DENIED => "DENIED".to_owned() 77 | } 78 | } 79 | } 80 | 81 | 82 | pub enum SignalHeader { 83 | Username(String), 84 | Password(String), 85 | Key(String), 86 | AuthStatus(AuthStatus), 87 | SignalType(SignalType), 88 | WithMessage, 89 | ServerMessage 90 | } 91 | 92 | impl FromStr for SignalHeader { 93 | type Err = ParseSignalDataError; 94 | 95 | fn from_str(s: &str) -> Result { 96 | let (header, value) = s.split_once(':').unwrap_or((s, s)); 97 | 98 | match header { 99 | "USERNAME" => Ok(SignalHeader::Username(value.trim().to_owned())), 100 | "PASSWORD" => Ok(SignalHeader::Password(value.trim().to_owned())), 101 | "KEY" => Ok(SignalHeader::Key(value.trim().to_owned())), 102 | "AUTH_STATUS" => { 103 | match AuthStatus::from_str(value.trim()) { 104 | Ok(v) => return Ok(SignalHeader::AuthStatus(v)), 105 | Err(_) => Err(ParseSignalDataError) 106 | } 107 | }, 108 | "SIGNAL_TYPE" => { 109 | match SignalType::from_str(value.trim()) { 110 | Ok(v) => return Ok(SignalHeader::SignalType(v)), 111 | Err(_) => Err(ParseSignalDataError) 112 | } 113 | } 114 | "WITH_MESSAGE" => Ok(SignalHeader::WithMessage), 115 | "SERVER_MESSAGE" => Ok(SignalHeader::ServerMessage), 116 | _ => Err(ParseSignalDataError) 117 | } 118 | } 119 | } 120 | 121 | impl ToString for SignalHeader { 122 | fn to_string(&self) -> String { 123 | match self { 124 | SignalHeader::Username(v) => format!("USERNAME: {v}\r\n"), 125 | SignalHeader::Password(v) => format!("PASSWORD: {v}\r\n"), 126 | SignalHeader::Key(v) => format!("KEY: {v}\r\n"), 127 | SignalHeader::AuthStatus(v) => format!("AUTH_STATUS: {}\r\n", v.to_string()), 128 | SignalHeader::SignalType(v) => format!("SIGNAL_TYPE: {}\r\n", v.to_string()), 129 | SignalHeader::WithMessage => "WITH_MESSAGE\r\n".to_owned(), 130 | SignalHeader::ServerMessage => "SERVER_MESSAGE\r\n".to_owned() 131 | } 132 | } 133 | } 134 | 135 | #[derive(Debug, Clone)] 136 | pub struct SignalData { 137 | pub username: Option, 138 | pub password: Option, 139 | pub key: Option, 140 | pub auth_status: Option, 141 | pub signal_type: Option, 142 | pub with_message: bool, 143 | pub message: Option, 144 | pub server_message: bool 145 | } 146 | 147 | impl SignalData { 148 | pub fn new(headers: Vec, message: Option<&str>) -> SignalData { 149 | let mut data = SignalData { 150 | username: None, 151 | password: None, 152 | key: None, 153 | auth_status: None, 154 | signal_type: None, 155 | with_message: false, 156 | message: None, 157 | server_message: false 158 | }; 159 | 160 | for header in headers { 161 | match header { 162 | SignalHeader::Username(v) => { 163 | data.username = Some(v); 164 | }, 165 | SignalHeader::Password(v) => { 166 | data.password = Some(v); 167 | }, 168 | SignalHeader::Key(v) => { 169 | data.key = Some(v); 170 | }, 171 | SignalHeader::AuthStatus(v) => { 172 | data.auth_status = Some(v); 173 | }, 174 | SignalHeader::SignalType(v) => { 175 | data.signal_type = Some(v); 176 | }, 177 | SignalHeader::WithMessage => { 178 | data.with_message = true; 179 | data.message = Some(message.unwrap_or("").to_owned()); 180 | }, 181 | SignalHeader::ServerMessage => { 182 | data.server_message = true; 183 | } 184 | } 185 | } 186 | 187 | data 188 | } 189 | } 190 | 191 | impl FromStr for SignalData { 192 | type Err = ParseSignalDataError; 193 | 194 | fn from_str(s: &str) -> Result { 195 | let mut data = SignalData { 196 | username: None, 197 | password: None, 198 | key: None, 199 | auth_status: None, 200 | signal_type: None, 201 | with_message: false, 202 | message: None, 203 | server_message: false, 204 | }; 205 | let splitted = s.split("\r\n"); 206 | for string in splitted { 207 | let header = match SignalHeader::from_str(string) { 208 | Ok(v) => v, 209 | Err(_) => continue 210 | }; 211 | 212 | match header { 213 | SignalHeader::Username(v) => { 214 | data.username = Some(v); 215 | }, 216 | SignalHeader::Password(v) => { 217 | data.password = Some(v); 218 | }, 219 | SignalHeader::Key(v) => { 220 | data.key = Some(v); 221 | }, 222 | SignalHeader::AuthStatus(v) => { 223 | data.auth_status = Some(v); 224 | }, 225 | SignalHeader::SignalType(v) => { 226 | data.signal_type = Some(v); 227 | } 228 | SignalHeader::WithMessage => { 229 | data.with_message = true; 230 | }, 231 | SignalHeader::ServerMessage => { 232 | data.server_message = true; 233 | } 234 | } 235 | } 236 | 237 | if data.with_message { 238 | let splitted = s.split_once("\r\n\r\n"); 239 | if let Some(v) = splitted { 240 | if v.1.ends_with("\r\n\r\n") { 241 | let string = v.1.to_owned(); 242 | data.message = Some(string[..string.len() - 4].to_owned()); 243 | } 244 | else { 245 | data.message = Some(v.1.to_owned()); 246 | } 247 | } 248 | else { 249 | return Err(ParseSignalDataError); 250 | } 251 | } 252 | 253 | if let None = data.signal_type { 254 | return Err(ParseSignalDataError) 255 | } 256 | 257 | Ok(data) 258 | } 259 | } 260 | 261 | impl ToString for SignalData { 262 | fn to_string(&self) -> String { 263 | let mut res_str = String::new(); 264 | 265 | if let Some(v) = &self.username { 266 | res_str.push_str(&SignalHeader::Username(v.to_owned()).to_string()); 267 | } 268 | if let Some(v) = &self.password { 269 | res_str.push_str(&SignalHeader::Password(v.to_owned()).to_string()); 270 | } 271 | if let Some(v) = &self.key { 272 | res_str.push_str(&SignalHeader::Key(v.to_owned()).to_string()); 273 | } 274 | if let Some(v) = &self.auth_status { 275 | res_str.push_str(&SignalHeader::AuthStatus(v.clone()).to_string()); 276 | } 277 | if let Some(v) = &self.signal_type { 278 | res_str.push_str(&SignalHeader::SignalType(v.clone()).to_string()); 279 | } 280 | if self.server_message { 281 | res_str.push_str(&SignalHeader::ServerMessage.to_string()); 282 | } 283 | if self.with_message { 284 | if let Some(v) = &self.message { 285 | res_str.push_str(&SignalHeader::WithMessage.to_string()); 286 | res_str.push_str("\r\n"); 287 | res_str.push_str(&v); 288 | } 289 | } 290 | res_str.push_str("\r\n\r\n"); 291 | 292 | res_str 293 | } 294 | } -------------------------------------------------------------------------------- /client/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "cc" 19 | version = "1.0.79" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 22 | 23 | [[package]] 24 | name = "cfg-if" 25 | version = "1.0.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 28 | 29 | [[package]] 30 | name = "clap" 31 | version = "4.1.4" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" 34 | dependencies = [ 35 | "bitflags", 36 | "clap_derive", 37 | "clap_lex", 38 | "is-terminal", 39 | "once_cell", 40 | "strsim", 41 | "termcolor", 42 | ] 43 | 44 | [[package]] 45 | name = "clap_derive" 46 | version = "4.1.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" 49 | dependencies = [ 50 | "heck", 51 | "proc-macro-error", 52 | "proc-macro2", 53 | "quote", 54 | "syn", 55 | ] 56 | 57 | [[package]] 58 | name = "clap_lex" 59 | version = "0.3.1" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" 62 | dependencies = [ 63 | "os_str_bytes", 64 | ] 65 | 66 | [[package]] 67 | name = "errno" 68 | version = "0.2.8" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 71 | dependencies = [ 72 | "errno-dragonfly", 73 | "libc", 74 | "winapi", 75 | ] 76 | 77 | [[package]] 78 | name = "errno-dragonfly" 79 | version = "0.1.2" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 82 | dependencies = [ 83 | "cc", 84 | "libc", 85 | ] 86 | 87 | [[package]] 88 | name = "heck" 89 | version = "0.4.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 92 | 93 | [[package]] 94 | name = "hermit-abi" 95 | version = "0.2.6" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 98 | dependencies = [ 99 | "libc", 100 | ] 101 | 102 | [[package]] 103 | name = "io-lifetimes" 104 | version = "1.0.4" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" 107 | dependencies = [ 108 | "libc", 109 | "windows-sys", 110 | ] 111 | 112 | [[package]] 113 | name = "is-terminal" 114 | version = "0.4.2" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" 117 | dependencies = [ 118 | "hermit-abi", 119 | "io-lifetimes", 120 | "rustix", 121 | "windows-sys", 122 | ] 123 | 124 | [[package]] 125 | name = "libc" 126 | version = "0.2.139" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 129 | 130 | [[package]] 131 | name = "linux-raw-sys" 132 | version = "0.1.4" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 135 | 136 | [[package]] 137 | name = "lock_api" 138 | version = "0.4.9" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 141 | dependencies = [ 142 | "autocfg", 143 | "scopeguard", 144 | ] 145 | 146 | [[package]] 147 | name = "numtoa" 148 | version = "0.1.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 151 | 152 | [[package]] 153 | name = "once_cell" 154 | version = "1.17.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 157 | 158 | [[package]] 159 | name = "os_str_bytes" 160 | version = "6.4.1" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 163 | 164 | [[package]] 165 | name = "parking_lot" 166 | version = "0.12.1" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 169 | dependencies = [ 170 | "lock_api", 171 | "parking_lot_core", 172 | ] 173 | 174 | [[package]] 175 | name = "parking_lot_core" 176 | version = "0.9.6" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" 179 | dependencies = [ 180 | "cfg-if", 181 | "libc", 182 | "redox_syscall", 183 | "smallvec", 184 | "windows-sys", 185 | ] 186 | 187 | [[package]] 188 | name = "proc-macro-error" 189 | version = "1.0.4" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 192 | dependencies = [ 193 | "proc-macro-error-attr", 194 | "proc-macro2", 195 | "quote", 196 | "syn", 197 | "version_check", 198 | ] 199 | 200 | [[package]] 201 | name = "proc-macro-error-attr" 202 | version = "1.0.4" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 205 | dependencies = [ 206 | "proc-macro2", 207 | "quote", 208 | "version_check", 209 | ] 210 | 211 | [[package]] 212 | name = "proc-macro2" 213 | version = "1.0.50" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" 216 | dependencies = [ 217 | "unicode-ident", 218 | ] 219 | 220 | [[package]] 221 | name = "quote" 222 | version = "1.0.23" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 225 | dependencies = [ 226 | "proc-macro2", 227 | ] 228 | 229 | [[package]] 230 | name = "redox_syscall" 231 | version = "0.2.16" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 234 | dependencies = [ 235 | "bitflags", 236 | ] 237 | 238 | [[package]] 239 | name = "redox_termios" 240 | version = "0.1.2" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" 243 | dependencies = [ 244 | "redox_syscall", 245 | ] 246 | 247 | [[package]] 248 | name = "rustix" 249 | version = "0.36.7" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" 252 | dependencies = [ 253 | "bitflags", 254 | "errno", 255 | "io-lifetimes", 256 | "libc", 257 | "linux-raw-sys", 258 | "windows-sys", 259 | ] 260 | 261 | [[package]] 262 | name = "scopeguard" 263 | version = "1.1.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 266 | 267 | [[package]] 268 | name = "smallvec" 269 | version = "1.10.0" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 272 | 273 | [[package]] 274 | name = "strsim" 275 | version = "0.10.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 278 | 279 | [[package]] 280 | name = "syn" 281 | version = "1.0.107" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 284 | dependencies = [ 285 | "proc-macro2", 286 | "quote", 287 | "unicode-ident", 288 | ] 289 | 290 | [[package]] 291 | name = "tchat" 292 | version = "0.1.2" 293 | dependencies = [ 294 | "clap", 295 | "parking_lot", 296 | "termion", 297 | ] 298 | 299 | [[package]] 300 | name = "termcolor" 301 | version = "1.2.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 304 | dependencies = [ 305 | "winapi-util", 306 | ] 307 | 308 | [[package]] 309 | name = "termion" 310 | version = "2.0.1" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "659c1f379f3408c7e5e84c7d0da6d93404e3800b6b9d063ba24436419302ec90" 313 | dependencies = [ 314 | "libc", 315 | "numtoa", 316 | "redox_syscall", 317 | "redox_termios", 318 | ] 319 | 320 | [[package]] 321 | name = "unicode-ident" 322 | version = "1.0.6" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 325 | 326 | [[package]] 327 | name = "version_check" 328 | version = "0.9.4" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 331 | 332 | [[package]] 333 | name = "winapi" 334 | version = "0.3.9" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 337 | dependencies = [ 338 | "winapi-i686-pc-windows-gnu", 339 | "winapi-x86_64-pc-windows-gnu", 340 | ] 341 | 342 | [[package]] 343 | name = "winapi-i686-pc-windows-gnu" 344 | version = "0.4.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 347 | 348 | [[package]] 349 | name = "winapi-util" 350 | version = "0.1.5" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 353 | dependencies = [ 354 | "winapi", 355 | ] 356 | 357 | [[package]] 358 | name = "winapi-x86_64-pc-windows-gnu" 359 | version = "0.4.0" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 362 | 363 | [[package]] 364 | name = "windows-sys" 365 | version = "0.42.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 368 | dependencies = [ 369 | "windows_aarch64_gnullvm", 370 | "windows_aarch64_msvc", 371 | "windows_i686_gnu", 372 | "windows_i686_msvc", 373 | "windows_x86_64_gnu", 374 | "windows_x86_64_gnullvm", 375 | "windows_x86_64_msvc", 376 | ] 377 | 378 | [[package]] 379 | name = "windows_aarch64_gnullvm" 380 | version = "0.42.1" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 383 | 384 | [[package]] 385 | name = "windows_aarch64_msvc" 386 | version = "0.42.1" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 389 | 390 | [[package]] 391 | name = "windows_i686_gnu" 392 | version = "0.42.1" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 395 | 396 | [[package]] 397 | name = "windows_i686_msvc" 398 | version = "0.42.1" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 401 | 402 | [[package]] 403 | name = "windows_x86_64_gnu" 404 | version = "0.42.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 407 | 408 | [[package]] 409 | name = "windows_x86_64_gnullvm" 410 | version = "0.42.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 413 | 414 | [[package]] 415 | name = "windows_x86_64_msvc" 416 | version = "0.42.1" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 419 | -------------------------------------------------------------------------------- /server/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.68" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.1.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "cc" 25 | version = "1.0.79" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 28 | 29 | [[package]] 30 | name = "cfg-if" 31 | version = "1.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 34 | 35 | [[package]] 36 | name = "clap" 37 | version = "4.1.4" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" 40 | dependencies = [ 41 | "bitflags", 42 | "clap_derive", 43 | "clap_lex", 44 | "is-terminal", 45 | "once_cell", 46 | "strsim", 47 | "termcolor", 48 | ] 49 | 50 | [[package]] 51 | name = "clap_derive" 52 | version = "4.1.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" 55 | dependencies = [ 56 | "heck", 57 | "proc-macro-error", 58 | "proc-macro2", 59 | "quote", 60 | "syn", 61 | ] 62 | 63 | [[package]] 64 | name = "clap_lex" 65 | version = "0.3.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" 68 | dependencies = [ 69 | "os_str_bytes", 70 | ] 71 | 72 | [[package]] 73 | name = "errno" 74 | version = "0.2.8" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 77 | dependencies = [ 78 | "errno-dragonfly", 79 | "libc", 80 | "winapi", 81 | ] 82 | 83 | [[package]] 84 | name = "errno-dragonfly" 85 | version = "0.1.2" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 88 | dependencies = [ 89 | "cc", 90 | "libc", 91 | ] 92 | 93 | [[package]] 94 | name = "getrandom" 95 | version = "0.2.8" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 98 | dependencies = [ 99 | "cfg-if", 100 | "libc", 101 | "wasi", 102 | ] 103 | 104 | [[package]] 105 | name = "heck" 106 | version = "0.4.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 109 | 110 | [[package]] 111 | name = "hermit-abi" 112 | version = "0.2.6" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 115 | dependencies = [ 116 | "libc", 117 | ] 118 | 119 | [[package]] 120 | name = "io-lifetimes" 121 | version = "1.0.4" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" 124 | dependencies = [ 125 | "libc", 126 | "windows-sys 0.42.0", 127 | ] 128 | 129 | [[package]] 130 | name = "is-terminal" 131 | version = "0.4.2" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" 134 | dependencies = [ 135 | "hermit-abi", 136 | "io-lifetimes", 137 | "rustix", 138 | "windows-sys 0.42.0", 139 | ] 140 | 141 | [[package]] 142 | name = "libc" 143 | version = "0.2.139" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 146 | 147 | [[package]] 148 | name = "linux-raw-sys" 149 | version = "0.1.4" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 152 | 153 | [[package]] 154 | name = "lock_api" 155 | version = "0.4.9" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 158 | dependencies = [ 159 | "autocfg", 160 | "scopeguard", 161 | ] 162 | 163 | [[package]] 164 | name = "once_cell" 165 | version = "1.17.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 168 | 169 | [[package]] 170 | name = "os_str_bytes" 171 | version = "6.4.1" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 174 | 175 | [[package]] 176 | name = "parking_lot" 177 | version = "0.12.1" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 180 | dependencies = [ 181 | "lock_api", 182 | "parking_lot_core", 183 | ] 184 | 185 | [[package]] 186 | name = "parking_lot_core" 187 | version = "0.9.7" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 190 | dependencies = [ 191 | "cfg-if", 192 | "libc", 193 | "redox_syscall", 194 | "smallvec", 195 | "windows-sys 0.45.0", 196 | ] 197 | 198 | [[package]] 199 | name = "proc-macro-error" 200 | version = "1.0.4" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 203 | dependencies = [ 204 | "proc-macro-error-attr", 205 | "proc-macro2", 206 | "quote", 207 | "syn", 208 | "version_check", 209 | ] 210 | 211 | [[package]] 212 | name = "proc-macro-error-attr" 213 | version = "1.0.4" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 216 | dependencies = [ 217 | "proc-macro2", 218 | "quote", 219 | "version_check", 220 | ] 221 | 222 | [[package]] 223 | name = "proc-macro2" 224 | version = "1.0.50" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" 227 | dependencies = [ 228 | "unicode-ident", 229 | ] 230 | 231 | [[package]] 232 | name = "quote" 233 | version = "1.0.23" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 236 | dependencies = [ 237 | "proc-macro2", 238 | ] 239 | 240 | [[package]] 241 | name = "redox_syscall" 242 | version = "0.2.16" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 245 | dependencies = [ 246 | "bitflags", 247 | ] 248 | 249 | [[package]] 250 | name = "rustix" 251 | version = "0.36.7" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" 254 | dependencies = [ 255 | "bitflags", 256 | "errno", 257 | "io-lifetimes", 258 | "libc", 259 | "linux-raw-sys", 260 | "windows-sys 0.42.0", 261 | ] 262 | 263 | [[package]] 264 | name = "scopeguard" 265 | version = "1.1.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 268 | 269 | [[package]] 270 | name = "smallvec" 271 | version = "1.10.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 274 | 275 | [[package]] 276 | name = "strsim" 277 | version = "0.10.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 280 | 281 | [[package]] 282 | name = "syn" 283 | version = "1.0.107" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 286 | dependencies = [ 287 | "proc-macro2", 288 | "quote", 289 | "unicode-ident", 290 | ] 291 | 292 | [[package]] 293 | name = "tchat-server" 294 | version = "0.1.0" 295 | dependencies = [ 296 | "anyhow", 297 | "clap", 298 | "parking_lot", 299 | "uuid", 300 | ] 301 | 302 | [[package]] 303 | name = "termcolor" 304 | version = "1.2.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 307 | dependencies = [ 308 | "winapi-util", 309 | ] 310 | 311 | [[package]] 312 | name = "unicode-ident" 313 | version = "1.0.6" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 316 | 317 | [[package]] 318 | name = "uuid" 319 | version = "1.3.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" 322 | dependencies = [ 323 | "getrandom", 324 | ] 325 | 326 | [[package]] 327 | name = "version_check" 328 | version = "0.9.4" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 331 | 332 | [[package]] 333 | name = "wasi" 334 | version = "0.11.0+wasi-snapshot-preview1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 337 | 338 | [[package]] 339 | name = "winapi" 340 | version = "0.3.9" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 343 | dependencies = [ 344 | "winapi-i686-pc-windows-gnu", 345 | "winapi-x86_64-pc-windows-gnu", 346 | ] 347 | 348 | [[package]] 349 | name = "winapi-i686-pc-windows-gnu" 350 | version = "0.4.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 353 | 354 | [[package]] 355 | name = "winapi-util" 356 | version = "0.1.5" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 359 | dependencies = [ 360 | "winapi", 361 | ] 362 | 363 | [[package]] 364 | name = "winapi-x86_64-pc-windows-gnu" 365 | version = "0.4.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 368 | 369 | [[package]] 370 | name = "windows-sys" 371 | version = "0.42.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 374 | dependencies = [ 375 | "windows_aarch64_gnullvm", 376 | "windows_aarch64_msvc", 377 | "windows_i686_gnu", 378 | "windows_i686_msvc", 379 | "windows_x86_64_gnu", 380 | "windows_x86_64_gnullvm", 381 | "windows_x86_64_msvc", 382 | ] 383 | 384 | [[package]] 385 | name = "windows-sys" 386 | version = "0.45.0" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 389 | dependencies = [ 390 | "windows-targets", 391 | ] 392 | 393 | [[package]] 394 | name = "windows-targets" 395 | version = "0.42.1" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" 398 | dependencies = [ 399 | "windows_aarch64_gnullvm", 400 | "windows_aarch64_msvc", 401 | "windows_i686_gnu", 402 | "windows_i686_msvc", 403 | "windows_x86_64_gnu", 404 | "windows_x86_64_gnullvm", 405 | "windows_x86_64_msvc", 406 | ] 407 | 408 | [[package]] 409 | name = "windows_aarch64_gnullvm" 410 | version = "0.42.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 413 | 414 | [[package]] 415 | name = "windows_aarch64_msvc" 416 | version = "0.42.1" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 419 | 420 | [[package]] 421 | name = "windows_i686_gnu" 422 | version = "0.42.1" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 425 | 426 | [[package]] 427 | name = "windows_i686_msvc" 428 | version = "0.42.1" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 431 | 432 | [[package]] 433 | name = "windows_x86_64_gnu" 434 | version = "0.42.1" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 437 | 438 | [[package]] 439 | name = "windows_x86_64_gnullvm" 440 | version = "0.42.1" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 443 | 444 | [[package]] 445 | name = "windows_x86_64_msvc" 446 | version = "0.42.1" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 449 | --------------------------------------------------------------------------------