From 1cc7dd8920f4e6e3b1fd800b820fcfd3a3675511 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 23 Jun 2022 12:19:28 +0200 Subject: [PATCH] Remove other packages from workspace --- Cargo.lock | 197 +------------------- Cargo.toml | 2 - cove-core/Cargo.toml | 19 -- cove-core/src/conn.rs | 182 ------------------- cove-core/src/id.rs | 33 ---- cove-core/src/lib.rs | 13 -- cove-core/src/macros.rs | 106 ----------- cove-core/src/message.rs | 29 --- cove-core/src/packets.rs | 144 --------------- cove-core/src/replies.rs | 70 -------- cove-core/src/session.rs | 10 -- cove-server/Cargo.toml | 17 -- cove-server/src/main.rs | 378 --------------------------------------- cove-server/src/util.rs | 60 ------- 14 files changed, 5 insertions(+), 1255 deletions(-) delete mode 100644 cove-core/Cargo.toml delete mode 100644 cove-core/src/conn.rs delete mode 100644 cove-core/src/id.rs delete mode 100644 cove-core/src/lib.rs delete mode 100644 cove-core/src/macros.rs delete mode 100644 cove-core/src/message.rs delete mode 100644 cove-core/src/packets.rs delete mode 100644 cove-core/src/replies.rs delete mode 100644 cove-core/src/session.rs delete mode 100644 cove-server/Cargo.toml delete mode 100644 cove-server/src/main.rs delete mode 100644 cove-server/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index 03951c1..e53640c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,17 +39,6 @@ dependencies = [ "syn", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -68,15 +57,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - [[package]] name = "block-buffer" version = "0.10.2" @@ -146,40 +126,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" -[[package]] -name = "cove-core" -version = "0.1.0" -dependencies = [ - "futures", - "hex", - "log", - "rand", - "serde", - "serde_json", - "sha2", - "thiserror", - "tokio", - "tokio-stream", - "tokio-tungstenite 0.16.1", -] - -[[package]] -name = "cove-server" -version = "0.1.0" -dependencies = [ - "anyhow", - "cove-core", - "env_logger", - "futures", - "log", - "rand", - "serde_json", - "thiserror", - "tokio", - "tokio-stream", - "tokio-tungstenite 0.16.1", -] - [[package]] name = "cove-tui" version = "0.1.0" @@ -198,7 +144,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "tokio-tungstenite 0.17.1", + "tokio-tungstenite", "toss", ] @@ -246,22 +192,13 @@ dependencies = [ "typenum", ] -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - [[package]] name = "digest" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.2", + "block-buffer", "crypto-common", ] @@ -301,19 +238,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "env_logger" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -488,15 +412,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] - [[package]] name = "http" version = "0.2.8" @@ -514,12 +429,6 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "idna" version = "0.2.3" @@ -655,12 +564,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - [[package]] name = "openssl-probe" version = "0.1.5" @@ -964,19 +867,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha-1" version = "0.10.0" @@ -985,18 +875,7 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", -] - -[[package]] -name = "sha2" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.3", + "digest", ] [[package]] @@ -1082,15 +961,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.31" @@ -1179,33 +1049,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "tokio-stream" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e80b39df6afcc12cdf752398ade96a6b9e99c903dfdc36e53ad10b9c366bca72" -dependencies = [ - "futures-util", - "log", - "rustls", - "rustls-native-certs", - "tokio", - "tokio-rustls", - "tungstenite 0.16.0", - "webpki", -] - [[package]] name = "tokio-tungstenite" version = "0.17.1" @@ -1218,7 +1061,7 @@ dependencies = [ "rustls-native-certs", "tokio", "tokio-rustls", - "tungstenite 0.17.2", + "tungstenite", "webpki", ] @@ -1233,27 +1076,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "tungstenite" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" -dependencies = [ - "base64", - "byteorder", - "bytes", - "http", - "httparse", - "log", - "rand", - "rustls", - "sha-1 0.9.8", - "thiserror", - "url", - "utf-8", - "webpki", -] - [[package]] name = "tungstenite" version = "0.17.2" @@ -1268,7 +1090,7 @@ dependencies = [ "log", "rand", "rustls", - "sha-1 0.10.0", + "sha-1", "thiserror", "url", "utf-8", @@ -1472,15 +1294,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 7189df0..2c937e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,5 @@ [workspace] members = [ - "cove-core", - "cove-server", "cove-tui", ] diff --git a/cove-core/Cargo.toml b/cove-core/Cargo.toml deleted file mode 100644 index 356ff5d..0000000 --- a/cove-core/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "cove-core" -version = "0.1.0" -edition = "2021" - -[dependencies] -futures = "0.3.21" -hex = { version = "0.4.3", features = ["serde"] } -log = "0.4.14" -rand = "0.8.4" -serde = { version = "1.0.136", features = ["derive"] } -serde_json = "1.0.78" -sha2 = "0.10.1" -thiserror = "1.0.30" -tokio = { version = "1.16.1", features = ["full"] } -tokio-stream = "0.1.8" -tokio-tungstenite = { version = "0.16.1", features = [ - "rustls-tls-native-roots", -] } diff --git a/cove-core/src/conn.rs b/cove-core/src/conn.rs deleted file mode 100644 index 32eb544..0000000 --- a/cove-core/src/conn.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::sync::Arc; -use std::time::Duration; -use std::{fmt, io, result}; - -use futures::stream::{SplitSink, SplitStream}; -use futures::StreamExt; -use log::debug; -use rand::Rng; -use tokio::net::TcpStream; -use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; -use tokio::sync::Mutex; -use tokio_stream::wrappers::UnboundedReceiverStream; -use tokio_tungstenite::tungstenite::{self, Message}; -use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; - -use crate::packets::Packet; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("IO error: {0}")] - Io(#[from] io::Error), - #[error("WS error: {0}")] - Ws(#[from] tungstenite::Error), - #[error("MPSC error: {0}")] - Mpsc(#[from] mpsc::error::SendError), - #[error("Serde error: {0}")] - Serde(#[from] serde_json::Error), - #[error("client did not pong")] - NoPong, - #[error("illegal binary packet")] - IllegalBinaryPacket, -} - -pub type Result = result::Result; - -type WsStream = WebSocketStream>; - -#[derive(Clone)] -pub struct ConnTx { - tx: UnboundedSender, -} - -impl fmt::Debug for ConnTx { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ConnTx").finish_non_exhaustive() - } -} - -impl ConnTx { - pub fn send(&self, packet: &Packet) -> Result<()> { - let str = serde_json::to_string(packet).expect("unserializable packet"); - debug!("↑ {}", str.trim()); // TODO Format somewhat nicer? - self.tx.send(Message::Text(str))?; - Ok(()) - } -} - -pub struct ConnRx { - ws_rx: SplitStream, - last_ping_payload: Arc>>, -} - -impl fmt::Debug for ConnRx { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ConnRx").finish_non_exhaustive() - } -} - -impl ConnRx { - pub async fn recv(&mut self) -> Result> { - loop { - let msg = match self.ws_rx.next().await { - None => return Ok(None), - Some(msg) => msg?, - }; - - let str = match msg { - Message::Text(str) => str, - Message::Pong(payload) => { - *self.last_ping_payload.lock().await = payload; - continue; - } - Message::Ping(_) => { - // Tungstenite automatically replies to pings - continue; - } - Message::Binary(_) => return Err(Error::IllegalBinaryPacket), - Message::Close(_) => return Ok(None), - }; - - let packet = serde_json::from_str(&str)?; - - debug!("↓ {}", str.trim()); // TODO Format somewhat nicer? - - return Ok(Some(packet)); - } - } -} - -pub struct ConnMaintenance { - // Shoveling packets into the WS connection - rx: UnboundedReceiver, - ws_tx: SplitSink, - // Pinging and ponging - tx: UnboundedSender, - ping_delay: Duration, - last_ping_payload: Arc>>, -} - -impl fmt::Debug for ConnMaintenance { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ConnMaintenance").finish_non_exhaustive() - } -} - -impl ConnMaintenance { - pub async fn perform(self) -> Result<()> { - let result = tokio::try_join!( - Self::shovel(self.rx, self.ws_tx), - Self::ping_pong(self.tx, self.ping_delay, self.last_ping_payload) - ); - result.map(|_| ()) - } - - async fn shovel( - rx: UnboundedReceiver, - ws_tx: SplitSink, - ) -> Result<()> { - UnboundedReceiverStream::new(rx) - .map(Ok) - .forward(ws_tx) - .await?; - Ok(()) - } - - async fn ping_pong( - tx: UnboundedSender, - ping_delay: Duration, - last_ping_payload: Arc>>, - ) -> Result<()> { - let mut payload = [0u8; 8]; - - rand::thread_rng().fill(&mut payload); - tx.send(Message::Ping(payload.to_vec()))?; - tokio::time::sleep(ping_delay).await; - - loop { - { - let last_payload = last_ping_payload.lock().await; - if (&payload as &[u8]) != (&last_payload as &[u8]) { - return Err(Error::NoPong); - } - }; - - rand::thread_rng().fill(&mut payload); - tx.send(Message::Ping(payload.to_vec()))?; - - tokio::time::sleep(ping_delay).await; - } - } -} - -pub fn new(stream: WsStream, ping_delay: Duration) -> (ConnTx, ConnRx, ConnMaintenance) { - let (ws_tx, ws_rx) = stream.split(); - let (tx, rx) = mpsc::unbounded_channel(); - let last_ping_payload = Arc::new(Mutex::new(vec![])); - - let conn_tx = ConnTx { tx: tx.clone() }; - let conn_rx = ConnRx { - ws_rx, - last_ping_payload: last_ping_payload.clone(), - }; - let conn_maintenance = ConnMaintenance { - ws_tx, - rx, - tx, - ping_delay, - last_ping_payload, - }; - - (conn_tx, conn_rx, conn_maintenance) -} diff --git a/cove-core/src/id.rs b/cove-core/src/id.rs deleted file mode 100644 index 2986927..0000000 --- a/cove-core/src/id.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::fmt; - -use hex::ToHex; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; - -use crate::macros::id_alias; - -// TODO Use base64 representation instead - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] -struct Id(#[serde(with = "hex")] [u8; 32]); - -impl Id { - fn of(str: &str) -> Self { - let mut hasher = Sha256::new(); - hasher.update(str); - Self(hasher.finalize().into()) - } -} - -// TODO Impl better fmt::Debug for Id and aliases - -impl fmt::Display for Id { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0.encode_hex::()) - } -} - -// Prevent misuse of one id as another by only making the aliases public. -id_alias!(MessageId); -id_alias!(SessionId); -id_alias!(Identity); diff --git a/cove-core/src/lib.rs b/cove-core/src/lib.rs deleted file mode 100644 index 08ad754..0000000 --- a/cove-core/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![warn(clippy::use_self)] - -pub mod conn; -mod id; -mod macros; -mod message; -pub mod packets; -pub mod replies; -mod session; - -pub use self::id::*; -pub use self::message::*; -pub use self::session::*; diff --git a/cove-core/src/macros.rs b/cove-core/src/macros.rs deleted file mode 100644 index faf94d8..0000000 --- a/cove-core/src/macros.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Use `pub(crate) use ` to make a macro importable from elsewhere. -// See https://stackoverflow.com/a/31749071 - -macro_rules! id_alias { - ($name:ident) => { - #[derive( - Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, - )] - pub struct $name(Id); - - impl $name { - pub fn of(str: &str) -> Self { - Self(Id::of(str)) - } - } - - impl fmt::Display for $name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } - } - }; -} - -pub(crate) use id_alias; - -macro_rules! packets { - ( - $( cmd $cmdName:ident($cmd:ident, $rpl:ident), )* // Commands with reply - $( ntf $ntfName:ident($ntf:ident), )* // Notifications - ) => { - #[derive(Debug, Deserialize, Serialize)] - #[serde(tag = "name", content = "data")] - pub enum Cmd { - $( $cmdName($cmd), )* - } - - $( - impl std::convert::TryFrom for $cmd { - type Error = (); - fn try_from(cmd: Cmd) -> Result { - match cmd { - Cmd::$cmdName(val) => Ok(val), - _ => Err(()), - } - } - } - - impl From<$cmd> for Cmd { - fn from(cmd: $cmd) -> Self { - Self::$cmdName(cmd) - } - } - )* - - #[derive(Debug, Deserialize, Serialize)] - #[serde(tag = "name", content = "data")] - pub enum Rpl { - $( $cmdName($rpl), )* - } - - $( - impl std::convert::TryFrom for $rpl { - type Error = (); - fn try_from(rpl: Rpl) -> Result { - match rpl { - Rpl::$cmdName(val) => Ok(val), - _ => Err(()), - } - } - } - - impl From<$rpl> for Rpl { - fn from(rpl: $rpl) -> Self { - Self::$cmdName(rpl) - } - } - )* - - #[derive(Debug, Deserialize, Serialize)] - #[serde(tag = "name", content = "data")] - pub enum Ntf { - $( $ntfName($ntf), )* - } - - $( - impl std::convert::TryFrom for $ntf { - type Error = (); - fn try_from(ntf: Ntf) -> Result { - match ntf { - Ntf::$ntfName(val) => Ok(val), - _ => Err(()), - } - } - } - - impl From<$ntf> for Ntf { - fn from(ntf: $ntf) -> Self { - Self::$ntfName(ntf) - } - } - )* - }; -} - -pub(crate) use packets; diff --git a/cove-core/src/message.rs b/cove-core/src/message.rs deleted file mode 100644 index b3ff412..0000000 --- a/cove-core/src/message.rs +++ /dev/null @@ -1,29 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{Identity, MessageId}; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Message { - pub time: u128, - pub pred: MessageId, - pub parent: Option, - pub identity: Identity, - pub nick: String, - pub content: String, -} - -impl Message { - pub fn id(&self) -> MessageId { - let time = self.time; - let pred = self.pred; - let parent = match self.parent { - Some(id) => format!("{id}"), - None => "none".to_string(), - }; - let identity = self.identity; - let nick = MessageId::of(&self.nick); - let content = MessageId::of(&self.content); - let str = format!("message {time} {pred} {parent} {identity} {nick} {content}"); - MessageId::of(&str) - } -} diff --git a/cove-core/src/packets.rs b/cove-core/src/packets.rs deleted file mode 100644 index 588b3b8..0000000 --- a/cove-core/src/packets.rs +++ /dev/null @@ -1,144 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::macros::packets; -use crate::{Message, MessageId, Session}; - -#[derive(Debug, Deserialize, Serialize)] -pub struct RoomCmd { - pub name: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub enum RoomRpl { - Success, - InvalidRoom { reason: String }, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct IdentifyCmd { - pub nick: String, - pub identity: String, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(tag = "type")] -pub enum IdentifyRpl { - Success { - you: Session, - others: Vec, - last_message: MessageId, - }, - InvalidNick { - reason: String, - }, - InvalidIdentity { - reason: String, - }, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct NickCmd { - pub nick: String, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(tag = "type")] -pub enum NickRpl { - Success { you: Session }, - InvalidNick { reason: String }, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct SendCmd { - pub parent: Option, - pub content: String, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(tag = "type")] -pub enum SendRpl { - Success { message: Message }, - InvalidContent { reason: String }, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct WhoCmd {} - -#[derive(Debug, Deserialize, Serialize)] -pub struct WhoRpl { - pub you: Session, - pub others: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct JoinNtf { - pub who: Session, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct NickNtf { - pub who: Session, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct PartNtf { - pub who: Session, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct SendNtf { - pub message: Message, -} - -// Create a Cmd enum for all commands, a Rpl enum for all replies and a Ntf enum -// for all notifications, as well as TryFrom impls for the individual structs. -packets! { - cmd Room(RoomCmd, RoomRpl), - cmd Identify(IdentifyCmd, IdentifyRpl), - cmd Nick(NickCmd, NickRpl), - cmd Send(SendCmd, SendRpl), - cmd Who(WhoCmd, WhoRpl), - ntf Join(JoinNtf), - ntf Nick(NickNtf), - ntf Part(PartNtf), - ntf Send(SendNtf), -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(tag = "type")] -pub enum Packet { - Cmd { - id: u64, - #[serde(flatten)] - cmd: Cmd, - }, - Rpl { - id: u64, - #[serde(flatten)] - rpl: Rpl, - }, - Ntf { - #[serde(flatten)] - ntf: Ntf, - }, -} - -impl Packet { - pub fn cmd>(id: u64, cmd: C) -> Self { - Self::Cmd { - id, - cmd: cmd.into(), - } - } - - pub fn rpl>(id: u64, rpl: R) -> Self { - Self::Rpl { - id, - rpl: rpl.into(), - } - } - - pub fn ntf>(ntf: N) -> Self { - Self::Ntf { ntf: ntf.into() } - } -} diff --git a/cove-core/src/replies.rs b/cove-core/src/replies.rs deleted file mode 100644 index 31a15f9..0000000 --- a/cove-core/src/replies.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::collections::HashMap; -use std::hash::Hash; -use std::result; -use std::time::Duration; - -use tokio::sync::oneshot::{self, Receiver, Sender}; -use tokio::time; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("timed out")] - TimedOut, - #[error("canceled")] - Canceled, -} - -pub type Result = result::Result; - -pub struct PendingReply { - timeout: Duration, - result: Receiver, -} - -impl PendingReply { - pub async fn get(self) -> Result { - let result = time::timeout(self.timeout, self.result).await; - match result { - Err(_) => Err(Error::TimedOut), - Ok(Err(_)) => Err(Error::Canceled), - Ok(Ok(value)) => Ok(value), - } - } -} - -pub struct Replies { - timeout: Duration, - pending: HashMap>, -} - -impl Replies { - pub fn new(timeout: Duration) -> Self { - Self { - timeout, - pending: HashMap::new(), - } - } - - pub fn wait_for(&mut self, id: I) -> PendingReply { - let (tx, rx) = oneshot::channel(); - self.pending.insert(id, tx); - PendingReply { - timeout: self.timeout, - result: rx, - } - } - - pub fn complete(&mut self, id: &I, result: R) { - if let Some(tx) = self.pending.remove(id) { - let _ = tx.send(result); - } - } - - pub fn cancel(&mut self, id: &I) { - self.pending.remove(id); - } - - pub fn purge(&mut self) { - self.pending.retain(|_, tx| !tx.is_closed()); - } -} diff --git a/cove-core/src/session.rs b/cove-core/src/session.rs deleted file mode 100644 index d595484..0000000 --- a/cove-core/src/session.rs +++ /dev/null @@ -1,10 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{Identity, SessionId}; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Session { - pub id: SessionId, - pub nick: String, - pub identity: Identity, -} diff --git a/cove-server/Cargo.toml b/cove-server/Cargo.toml deleted file mode 100644 index b37faa2..0000000 --- a/cove-server/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "cove-server" -version = "0.1.0" -edition = "2021" - -[dependencies] -anyhow = "1.0.53" -cove-core = { path = "../cove-core" } -env_logger = "0.9.0" -futures = "0.3.21" -log = "0.4.14" -rand = "0.8.4" -serde_json = "1.0.78" -thiserror = "1.0.30" -tokio = { version = "1.16.1", features = ["full"] } -tokio-stream = "0.1.8" -tokio-tungstenite = "0.16.1" diff --git a/cove-server/src/main.rs b/cove-server/src/main.rs deleted file mode 100644 index 62fc52a..0000000 --- a/cove-server/src/main.rs +++ /dev/null @@ -1,378 +0,0 @@ -// TODO Logging - -#![warn(clippy::use_self)] - -mod util; - -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Duration; - -use anyhow::{anyhow, bail}; -use cove_core::conn::{self, ConnMaintenance, ConnRx, ConnTx}; -use cove_core::packets::{ - Cmd, IdentifyCmd, IdentifyRpl, JoinNtf, NickCmd, NickNtf, NickRpl, Packet, PartNtf, RoomCmd, - RoomRpl, SendCmd, SendNtf, SendRpl, WhoCmd, WhoRpl, -}; -use cove_core::{Identity, Message, MessageId, Session, SessionId}; -use log::{info, warn}; -use rand::Rng; -use tokio::net::{TcpListener, TcpStream}; -use tokio::sync::Mutex; -use tokio_tungstenite::MaybeTlsStream; - -#[derive(Debug, Clone)] -struct Client { - session: Session, - send: ConnTx, -} - -#[derive(Debug)] -struct Room { - name: String, - clients: HashMap, - last_message: MessageId, - last_timestamp: u128, -} - -impl Room { - fn new(name: String) -> Self { - Self { - name, - clients: HashMap::new(), - last_message: MessageId::of(&format!("{}", rand::thread_rng().gen::())), - last_timestamp: util::timestamp(), - } - } - - fn client(&self, id: SessionId) -> &Client { - self.clients.get(&id).expect("invalid session id") - } - - fn client_mut(&mut self, id: SessionId) -> &mut Client { - self.clients.get_mut(&id).expect("invalid session id") - } - - fn notify_all(&self, packet: &Packet) { - for client in self.clients.values() { - let _ = client.send.send(packet); - } - } - - fn notify_except(&self, id: SessionId, packet: &Packet) { - for client in self.clients.values() { - if client.session.id != id { - let _ = client.send.send(packet); - } - } - } - - fn join(&mut self, client: Client) { - if self.clients.contains_key(&client.session.id) { - // Session ids are generated randomly and a collision should be very - // unlikely. - panic!("duplicated session id"); - } - - self.notify_all(&Packet::ntf(JoinNtf { - who: client.session.clone(), - })); - - self.clients.insert(client.session.id, client); - } - - fn part(&mut self, id: SessionId) { - let client = self.clients.remove(&id).expect("invalid session id"); - - self.notify_all(&Packet::ntf(PartNtf { - who: client.session, - })); - } - - fn nick(&mut self, id: SessionId, nick: String) { - let who = { - let client = self.client_mut(id); - client.session.nick = nick; - client.session.clone() - }; - - self.notify_except(id, &Packet::ntf(NickNtf { who })) - } - - fn send(&mut self, id: SessionId, parent: Option, content: String) -> Message { - let client = &self.clients[&id]; - - self.last_timestamp = util::timestamp_after(self.last_timestamp); - - let message = Message { - time: self.last_timestamp, - pred: self.last_message, - parent, - identity: client.session.identity, - nick: client.session.nick.clone(), - content, - }; - - self.last_message = message.id(); - info!( - "&{} now at {} ({})", - self.name, self.last_message, self.last_timestamp - ); - - self.notify_except( - id, - &Packet::ntf(SendNtf { - message: message.clone(), - }), - ); - - message - } - - fn who(&self, id: SessionId) -> (Session, Vec) { - let session = self.client(id).session.clone(); - let others = self - .clients - .values() - .filter(|client| client.session.id != id) - .map(|client| client.session.clone()) - .collect(); - (session, others) - } -} - -#[derive(Debug)] -struct ServerSession { - tx: ConnTx, - rx: ConnRx, - room: Arc>, - session: Session, -} - -impl ServerSession { - async fn handle_nick(&mut self, id: u64, cmd: NickCmd) -> anyhow::Result<()> { - if let Some(reason) = util::check_nick(&cmd.nick) { - self.tx - .send(&Packet::rpl(id, NickRpl::InvalidNick { reason }))?; - return Ok(()); - } - - self.session.nick = cmd.nick.clone(); - self.tx.send(&Packet::rpl( - id, - NickRpl::Success { - you: self.session.clone(), - }, - ))?; - self.room.lock().await.nick(self.session.id, cmd.nick); - - Ok(()) - } - - async fn handle_send(&mut self, id: u64, cmd: SendCmd) -> anyhow::Result<()> { - if let Some(reason) = util::check_content(&cmd.content) { - self.tx - .send(&Packet::rpl(id, SendRpl::InvalidContent { reason }))?; - return Ok(()); - } - - let message = self - .room - .lock() - .await - .send(self.session.id, cmd.parent, cmd.content); - - self.tx - .send(&Packet::rpl(id, SendRpl::Success { message }))?; - - Ok(()) - } - - async fn handle_who(&mut self, id: u64, _cmd: WhoCmd) -> anyhow::Result<()> { - let (you, others) = self.room.lock().await.who(self.session.id); - self.tx.send(&Packet::rpl(id, WhoRpl { you, others }))?; - Ok(()) - } - - async fn handle_packet(&mut self, packet: Packet) -> anyhow::Result<()> { - match packet { - Packet::Cmd { id, cmd } => match cmd { - Cmd::Room(_) => Err(anyhow!("unexpected Room cmd")), - Cmd::Identify(_) => Err(anyhow!("unexpected Identify cmd")), - Cmd::Nick(cmd) => self.handle_nick(id, cmd).await, - Cmd::Send(cmd) => self.handle_send(id, cmd).await, - Cmd::Who(cmd) => self.handle_who(id, cmd).await, - }, - Packet::Rpl { .. } => Err(anyhow!("unexpected rpl")), - Packet::Ntf { .. } => Err(anyhow!("unexpected ntf")), - } - } - - async fn run(&mut self) -> anyhow::Result<()> { - while let Some(packet) = self.rx.recv().await? { - self.handle_packet(packet).await?; - } - Ok(()) - } -} - -#[derive(Debug, Clone)] -struct Server { - rooms: Arc>>>>, -} - -impl Server { - fn new() -> Self { - Self { - rooms: Arc::new(Mutex::new(HashMap::new())), - } - } - - async fn room(&self, name: String) -> Arc> { - self.rooms - .lock() - .await - .entry(name.clone()) - .or_insert_with(|| Arc::new(Mutex::new(Room::new(name)))) - .clone() - } - - async fn negotiate_room(tx: &ConnTx, rx: &mut ConnRx) -> anyhow::Result { - loop { - match rx.recv().await? { - Some(Packet::Cmd { - id, - cmd: Cmd::Room(RoomCmd { name }), - }) => { - if let Some(reason) = util::check_room(&name) { - tx.send(&Packet::rpl(id, RoomRpl::InvalidRoom { reason }))?; - continue; - } - tx.send(&Packet::rpl(id, RoomRpl::Success))?; - return Ok(name); - } - Some(_) => bail!("invalid packet during room negotiation"), - None => bail!("connection closed during room negotiation"), - } - } - } - - async fn negotiate_identity(tx: &ConnTx, rx: &mut ConnRx) -> anyhow::Result<(u64, Session)> { - loop { - match rx.recv().await? { - Some(Packet::Cmd { - id, - cmd: Cmd::Identify(IdentifyCmd { nick, identity }), - }) => { - if let Some(reason) = util::check_identity(&identity) { - tx.send(&Packet::rpl(id, IdentifyRpl::InvalidNick { reason }))?; - continue; - } - if let Some(reason) = util::check_nick(&nick) { - tx.send(&Packet::rpl(id, IdentifyRpl::InvalidNick { reason }))?; - continue; - } - let session = Session { - id: SessionId::of(&format!("{}", rand::thread_rng().gen::())), - nick, - identity: Identity::of(&identity), - }; - return Ok((id, session)); - } - Some(_) => bail!("invalid packet during room negotiation"), - None => bail!("connection closed during room negotiation"), - } - } - } - - fn welcome(id: u64, you: Session, room: &Room, tx: &ConnTx) -> anyhow::Result<()> { - let others = room - .clients - .values() - .map(|client| client.session.clone()) - .collect::>(); - let last_message = room.last_message; - - tx.send(&Packet::rpl( - id, - IdentifyRpl::Success { - you, - others, - last_message, - }, - ))?; - - Ok(()) - } - - async fn greet(&self, tx: ConnTx, mut rx: ConnRx) -> anyhow::Result { - let room = Self::negotiate_room(&tx, &mut rx).await?; - let (id, session) = Self::negotiate_identity(&tx, &mut rx).await?; - - let room = self.room(room).await; - { - let mut room = room.lock().await; - // Reply to successful identify command in the same lock as joining - // the room so the client doesn' miss any messages. - Self::welcome(id, session.clone(), &*room, &tx)?; - // Join room only after welcome so current session is not yet - // present in room during welcome. - room.join(Client { - session: session.clone(), - send: tx.clone(), - }); - } - - Ok(ServerSession { - tx, - rx, - room, - session, - }) - } - - async fn greet_and_run(&self, tx: ConnTx, rx: ConnRx) -> anyhow::Result<()> { - let mut session = self.greet(tx, rx).await?; - let result = session.run().await; - session.room.lock().await.part(session.session.id); - result - } - - /// Wrapper for [`ConnMaintenance::perform`] so it returns an - /// [`anyhow::Result`]. - async fn maintain(maintenance: ConnMaintenance) -> anyhow::Result<()> { - maintenance.perform().await?; - Ok(()) - } - - async fn handle_conn(&self, stream: TcpStream) -> anyhow::Result<()> { - let stream = MaybeTlsStream::Plain(stream); - let stream = tokio_tungstenite::accept_async(stream).await?; - let (tx, rx, maintenance) = conn::new(stream, Duration::from_secs(10)); - tokio::try_join!(self.greet_and_run(tx, rx), Self::maintain(maintenance))?; - Ok(()) - } - - async fn on_conn(self, stream: TcpStream) -> anyhow::Result<()> { - let peer_addr = stream.peer_addr()?; - info!("<{peer_addr}> Connected"); - - if let Err(e) = self.handle_conn(stream).await { - warn!("<{peer_addr}> Err: {e}"); - } - - info!("<{peer_addr}> Disconnected"); - Ok(()) - } -} - -#[tokio::main] -async fn main() { - env_logger::init(); - - let server = Server::new(); - let listener = TcpListener::bind(("::0", 40080)).await.unwrap(); - while let Ok((stream, _)) = listener.accept().await { - tokio::spawn(server.clone().on_conn(stream)); - } -} diff --git a/cove-server/src/util.rs b/cove-server/src/util.rs deleted file mode 100644 index 2c71fcf..0000000 --- a/cove-server/src/util.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::cmp; -use std::time::{SystemTime, UNIX_EPOCH}; - -pub fn timestamp() -> u128 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("executed after 1970") - .as_millis() -} - -pub fn timestamp_after(previous: u128) -> u128 { - cmp::max(timestamp(), previous + 1) -} - -pub fn check_room(room: &str) -> Option { - if room.is_empty() { - return Some("is empty".to_string()); - } - if !room.is_ascii() { - return Some("contains non-ascii characters".to_string()); - } - if room.len() > 1024 { - return Some("contains more than 1024 characters".to_string()); - } - if !room - .chars() - .all(|c| c == '-' || c == '.' || ('a'..='z').contains(&c)) - { - return Some("must only contain a-z, '-' and '_'".to_string()); - } - None -} - -pub fn check_nick(nick: &str) -> Option { - if nick.is_empty() { - return Some("is empty".to_string()); - } - if nick.trim().is_empty() { - return Some("contains only whitespace".to_string()); - } - let nick = nick.trim(); - if nick.chars().count() > 1024 { - return Some("contains more than 1024 characters".to_string()); - } - None -} - -pub fn check_identity(identity: &str) -> Option { - if identity.chars().count() > 32 * 1024 { - return Some("contains more than 32768 characters".to_string()); - } - None -} - -pub fn check_content(content: &str) -> Option { - if content.chars().count() > 128 * 1024 { - return Some("contains more than 131072 characters".to_string()); - } - None -}