From 6fad5b18521232f8d2f33408efd7489cdcdff4dd Mon Sep 17 00:00:00 2001 From: shadow cat Date: Wed, 18 Feb 2026 21:52:22 -0500 Subject: [PATCH] server side preparation --- src/bin/client/net.rs | 36 ++-- src/bin/client/ui/friends.rs | 12 ++ src/bin/client/ui/mod.rs | 1 + src/bin/server/db/data.rs | 48 ++++++ src/bin/server/db/mod.rs | 15 +- src/bin/server/db/util.rs | 10 +- src/bin/server/db/ver.rs | 38 ----- src/bin/server/handle.rs | 313 +++++++++++++++++++++++++++++++++++ src/bin/server/main.rs | 200 +--------------------- src/bin/server/net.rs | 16 +- src/net/conversion.rs | 54 ------ src/net/data.rs | 156 ++++++++--------- src/net/mod.rs | 7 +- src/net/msg.rs | 132 ++++++--------- src/net/request.rs | 44 +---- 15 files changed, 547 insertions(+), 535 deletions(-) create mode 100644 src/bin/client/ui/friends.rs create mode 100644 src/bin/server/db/data.rs delete mode 100644 src/bin/server/db/ver.rs create mode 100644 src/bin/server/handle.rs delete mode 100644 src/net/conversion.rs diff --git a/src/bin/client/net.rs b/src/bin/client/net.rs index 359a12a..cd77195 100644 --- a/src/bin/client/net.rs +++ b/src/bin/client/net.rs @@ -1,8 +1,8 @@ use crate::ClientEvent; use dashmap::DashMap; use openworm::net::{ - ClientMsg, ClientRequestMsg, RecvHandler, RequestId, RequestMsg, SERVER_NAME, ServerMsg, - ServerRespMsg, SkipServerVerification, recv_uni, send_uni, + ClientMsg, ClientMsgInst, RecvHandler, RequestId, RequestMsg, SERVER_NAME, ServerMsg, + ServerMsgInst, SkipServerVerification, recv_uni, send_uni, }; use quinn::{ ClientConfig, Connection, Endpoint, IdleTimeout, TransportConfig, @@ -10,12 +10,10 @@ use quinn::{ }; use std::{ net::{Ipv6Addr, SocketAddr, SocketAddrV6, ToSocketAddrs}, - str::FromStr, sync::Arc, time::Duration, }; use tokio::sync::{mpsc::UnboundedSender, oneshot}; -use winit::event_loop::EventLoopProxy; pub const CLIENT_SOCKET: SocketAddr = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0)); @@ -30,21 +28,8 @@ pub struct NetHandle { send: UnboundedSender, } -#[derive(Clone)] -pub struct AppHandle { - pub proxy: EventLoopProxy, -} - -impl AppHandle { - pub fn send(&self, event: ClientEvent) { - self.proxy.send_event(event).unwrap_or_else(|_| panic!()); - } -} - type NetResult = Result; -pub trait ClientRequest {} - pub enum NetCtrlMsg { Send(ClientMsg), Request(ClientMsg, oneshot::Sender), @@ -156,9 +141,9 @@ impl NetHandle { let request_id = req_id.next(); match msg { NetCtrlMsg::Send(msg) => { - let msg = ClientRequestMsg { + let msg = ClientMsgInst { id: request_id, - msg: msg.into(), + msg, }; if send_uni(&conn, msg).await.is_err() { println!("disconnected from server"); @@ -166,9 +151,9 @@ impl NetHandle { } } NetCtrlMsg::Request(msg, send) => { - let msg = ClientRequestMsg { + let msg = ClientMsgInst { id: request_id, - msg: msg.into(), + msg, }; recv.requests.insert(request_id, send); if send_uni(&conn, msg).await.is_err() { @@ -206,15 +191,14 @@ struct ServerRecv { msg: F, } -impl RecvHandler for ServerRecv { - async fn msg(&self, resp: ServerRespMsg) { - let msg = resp.msg.into(); +impl RecvHandler for ServerRecv { + async fn msg(&self, resp: ServerMsgInst) { if let Some(id) = resp.id && let Some((_, send)) = self.requests.remove(&id) { - send.send(msg); + let _ = send.send(resp.msg); } else { - self.msg.run(msg).await; + self.msg.run(resp.msg).await; } } } diff --git a/src/bin/client/ui/friends.rs b/src/bin/client/ui/friends.rs new file mode 100644 index 0000000..27a72fe --- /dev/null +++ b/src/bin/client/ui/friends.rs @@ -0,0 +1,12 @@ +// pub fn view(rsc: &mut Rsc, session: &Session) -> StrongWidget { +// let mut view = WidgetSelector::new(View::Info, info(rsc)); +// view.set(View::User, users(rsc, session)); +// let view = view.add(rsc); +// let [info, server] = tabs(rsc, view, [("info", View::Info), ("users", View::User)]); +// +// let side_bar = rect(Color::BLACK.alpha(150)) +// .foreground((info, server).span(Dir::DOWN)) +// .width(260); +// +// (side_bar, view).span(Dir::RIGHT).add_strong(rsc) +// } diff --git a/src/bin/client/ui/mod.rs b/src/bin/client/ui/mod.rs index e360207..efcf4bc 100644 --- a/src/bin/client/ui/mod.rs +++ b/src/bin/client/ui/mod.rs @@ -4,6 +4,7 @@ use iris::prelude::*; mod channel; pub mod color; mod connect; +mod friends; mod main; mod misc; mod server; diff --git a/src/bin/server/db/data.rs b/src/bin/server/db/data.rs new file mode 100644 index 0000000..b8e3be0 --- /dev/null +++ b/src/bin/server/db/data.rs @@ -0,0 +1,48 @@ +use iris::core::util::HashSet; +pub use openworm::net::{AccountToken, ServerPerms}; + +pub type UserId = u64; +pub type MsgId = i128; +pub type ChannelId = u64; +pub type ImageId = u64; + +#[derive(bitcode::Encode, bitcode::Decode)] +pub struct User { + pub username: String, + pub password_hash: String, + pub pfp: Option, + pub bio: String, + pub friends: Friends, + pub server_perms: ServerPerms, +} + +impl User { + pub fn new(username: String, password_hash: String, perms: ServerPerms) -> Self { + Self { + username, + password_hash, + bio: String::new(), + pfp: None, + friends: Default::default(), + server_perms: perms, + } + } +} + +#[derive(Default, bitcode::Encode, bitcode::Decode)] +pub struct Friends { + pub current: HashSet, + pub outgoing: HashSet, + pub incoming: HashSet, +} + +#[derive(bitcode::Encode, bitcode::Decode)] +pub struct Msg { + pub content: String, + pub author: UserId, +} + +pub struct Channel { + pub name: String, + pub desc: String, +} diff --git a/src/bin/server/db/mod.rs b/src/bin/server/db/mod.rs index be16125..d57bdfa 100644 --- a/src/bin/server/db/mod.rs +++ b/src/bin/server/db/mod.rs @@ -1,31 +1,22 @@ +mod data; mod util; -mod ver; use std::path::Path; +pub use data::*; use util::*; -use ver::*; pub const DB_VERSION: u64 = 0; #[derive(Clone)] pub struct Db { db: Database, - pub account_tokens: DbMap, + pub account_tokens: DbMap, pub msgs: DbMap, pub users: DbMap, pub usernames: DbMap, } -pub type UserId = UserIdV0; -pub type MsgId = MsgIdV0; -pub type ChannelId = ChannelIdV0; -pub type ImageId = ImageIdV0; -pub type User = UserV0; -pub type Msg = MsgV0; -pub type ChannelInfo = ChannelV0; -pub type ServerPerms = ServerPermsV0; - impl Db { pub fn open(path: impl AsRef) -> Db { let db = Database::open(path); diff --git a/src/bin/server/db/util.rs b/src/bin/server/db/util.rs index 11ea790..f083c62 100644 --- a/src/bin/server/db/util.rs +++ b/src/bin/server/db/util.rs @@ -17,7 +17,7 @@ impl Clone for DbMap { Self { db: self.db.clone(), keyspace: self.keyspace.clone(), - _pd: self._pd.clone(), + _pd: self._pd, } } } @@ -126,7 +126,7 @@ impl WriteTx { }) } - pub fn get, V: DecodeOwned>(&self, map: &DbMap, k: K) -> Option { + pub fn get, V: DecodeOwned>(&self, map: &DbMap, k: &K) -> Option { let k = Slice::new(k.to_bytes().as_ref()); let v = self.0.get(&map.keyspace, k).unwrap()?; Some(bitcode::decode(&v).unwrap()) @@ -137,7 +137,11 @@ impl WriteTx { self.0.get(&map.keyspace, k).unwrap().is_some() } - pub fn remove, V: DecodeOwned>(&mut self, map: &DbMap, k: K) -> Option { + pub fn remove, V: DecodeOwned>( + &mut self, + map: &DbMap, + k: K, + ) -> Option { let k = Slice::new(k.to_bytes().as_ref()); let v = self.0.take(&map.keyspace, k).unwrap()?; Some(bitcode::decode(&v).unwrap()) diff --git a/src/bin/server/db/ver.rs b/src/bin/server/db/ver.rs deleted file mode 100644 index d19cf86..0000000 --- a/src/bin/server/db/ver.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub type UserIdV0 = u64; -pub type MsgIdV0 = i128; -pub type ChannelIdV0 = u64; -pub type ImageIdV0 = u64; - -#[derive(Clone, Copy, bitcode::Encode, bitcode::Decode)] -pub struct ServerPermsV0(u32); -impl ServerPermsV0 { - pub const NONE: Self = Self(0); - pub const ACCOUNT_TOKENS: Self = Self(1 << 0); - pub const ALL: Self = Self(u32::MAX); -} - -#[derive(bitcode::Encode, bitcode::Decode)] -pub struct UserV0 { - pub username: String, - pub password_hash: String, - pub pfp: Option, - pub bio: String, - pub server_perms: ServerPermsV0, -} - -#[derive(bitcode::Encode, bitcode::Decode)] -pub struct MsgV0 { - pub content: String, - pub author: UserIdV0, -} - -pub struct ChannelV0 { - pub name: String, - pub desc: String, -} - -impl ServerPermsV0 { - pub fn contains(&self, other: Self) -> bool { - (self.0 & other.0) == other.0 - } -} diff --git a/src/bin/server/handle.rs b/src/bin/server/handle.rs new file mode 100644 index 0000000..11f9d00 --- /dev/null +++ b/src/bin/server/handle.rs @@ -0,0 +1,313 @@ +use super::net::ClientSender; +use crate::{ + ClientId, account_token, + db::{Db, ServerPerms, User, UserId}, + net::ClientReplier, +}; +use openworm::net::*; +use scrypt::{ + Scrypt, + password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng}, +}; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::RwLock; + +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum ClientState { + Login, + Authed(UserId), +} + +pub struct ClientHandler { + pub db: Db, + pub send: ClientSender, + pub senders: Arc>>, + pub id: ClientId, + pub state: Arc>, +} + +impl ClientHandler { + pub async fn handle_msg(&self, msg: ClientMsg, replier: &ClientReplier) -> Option<()> { + // TODO: there are some awful edge cases where you delete an account and it's in the middle + // of a non TX transaction; make sure to use TX, maybe need another check_login closure + // fortunately these should be very rare so I'ma ignore for now + macro_rules! reply { + ($msg:expr) => { + replier.send($msg).await; + return None; + }; + } + let check_login = async || { + if let ClientState::Authed(uid) = &*self.state.read().await { + Some(*uid) + } else { + reply!(NotLoggedIn); + } + }; + let check_user = async || { + if let ClientState::Authed(uid) = &*self.state.read().await { + if let Some(user) = self.db.users.get(uid) { + Some((*uid, user)) + } else { + reply!(AccountDeleted); + } + } else { + reply!(NotLoggedIn); + } + }; + let check_server_perms = async |perms| { + let id = check_login().await?; + if self + .db + .users + .get(&id) + .is_some_and(|u| !u.server_perms.contains(perms)) + { + reply!(NoPermission); + } else { + Some(()) + } + }; + let db = &self.db; + match msg { + ClientMsg::GenerateToken(info) => { + let check = if info.perms == ServerPerms::NONE { + ServerPerms::ACCOUNT_TOKENS + } else { + ServerPerms::ALL + }; + check_server_perms(check).await?; + let token = account_token(&self.db, info.perms); + reply!(GenerateTokenResp { token }); + } + ClientMsg::CreateAccount(info) => { + let CreateAccount { + token, + username, + password, + } = &info; + let salt = SaltString::generate(&mut OsRng); + let params = scrypt::Params::new(11, 8, 1, 32).unwrap(); + let hash = Scrypt + .hash_password_customized(password.as_bytes(), None, None, params, &salt) + .unwrap() + .to_string(); + let mut id; + loop { + let mut tx = db.write_tx(); + let Some(perms) = tx.remove(&db.account_tokens, token.to_string()) else { + println!("invalid token: {:?}", self.send.remote()); + reply!(CreateAccountResp::InvalidToken); + }; + if tx.has_key(&db.usernames, username.clone()) { + reply!(CreateAccountResp::UsernameExists); + } + id = rand::random(); + while tx.has_key(&db.users, id) { + id = rand::random(); + } + tx.insert( + &db.users, + &id, + &User::new(username.clone(), hash.clone(), perms), + ); + tx.insert(&db.usernames, username, &id); + if tx.commit() { + break; + } + } + println!("account created: \"{username}\""); + *self.state.write().await = ClientState::Authed(id); + reply!(CreateAccountResp::Ok { id }); + } + ClientMsg::Login(info) => { + let Login { username, password } = &info; + let Some(id) = db.usernames.get(username) else { + reply!(LoginResp::UnknownUsername); + }; + let Some(user) = db.users.get(&id) else { + panic!("invalid state! (should be a user)"); + }; + let hash = PasswordHash::new(&user.password_hash).unwrap(); + if Scrypt.verify_password(password.as_bytes(), &hash).is_err() { + println!("invalid password: \"{username}\""); + reply!(LoginResp::InvalidPassword); + } + *self.state.write().await = ClientState::Authed(id); + reply!(LoginResp::Ok { id }); + } + ClientMsg::RequestUsers(_) => { + check_login().await?; + check_server_perms(ServerPerms::ALL).await?; + let users: Vec<_> = self + .db + .users + .iter() + .map(|(id, u)| ServerUser { + id, + username: u.username, + }) + .collect(); + reply!(RequestUsersResp { users }); + } + ClientMsg::AddFriend(info) => { + let user_id = check_login().await?; + loop { + let mut tx = db.write_tx(); + let Some(mut user) = tx.get(&db.users, &user_id) else { + reply!(AccountDeleted); + }; + let Some(friend_id) = tx.get(&db.usernames, &info.username) else { + reply!(AddFriendResp::UnknownUser); + }; + if user.friends.outgoing.contains(&friend_id) { + reply!(AddFriendResp::AlreadySent); + } + if friend_id == user_id { + reply!(AddFriendResp::CannotAddSelf); + } + let Some(mut other) = tx.get(&db.users, &user_id) else { + println!("WARNING: username without valid user!"); + reply!(AddFriendResp::UnknownUser); + }; + if other.friends.current.contains(&user_id) { + reply!(AddFriendResp::AlreadyFriends); + } + user.friends.outgoing.insert(friend_id); + other.friends.incoming.insert(user_id); + tx.insert(&db.users, &user_id, &user); + tx.insert(&db.users, &friend_id, &other); + if tx.commit() { + break; + } + } + reply!(AddFriendResp::Ok); + } + ClientMsg::RemoveFriend(info) => { + let user_id = check_login().await?; + loop { + let mut tx = db.write_tx(); + let mut user = tx.get(&db.users, &user_id)?; + let friend_id = info.id; + let Some(mut other) = tx.get(&db.users, &user_id) else { + println!("WARNING: username without valid user!"); + return None; + }; + user.friends.current.remove(&friend_id); + other.friends.current.remove(&user_id); + tx.insert(&db.users, &user_id, &user); + tx.insert(&db.users, &friend_id, &other); + if tx.commit() { + break; + } + } + reply!(AddFriendResp::Ok); + } + ClientMsg::RequestFriends(_) => { + let user = check_user().await?.1; + reply!(RequestFriendsResp { + current: user.friends.current, + incoming: user.friends.incoming, + outgoing: user.friends.outgoing, + }); + } + ClientMsg::AnswerFriendRequest(answer) => { + let user_id = check_login().await?; + loop { + let mut tx = db.write_tx(); + let mut user = tx.get(&db.users, &user_id)?; + let other_id = answer.id; + let Some(mut other) = tx.get(&db.users, &user_id) else { + println!("WARNING: username without valid user!"); + return None; + }; + match answer.action { + FriendRequestAction::Accept => { + user.friends.incoming.remove(&other_id); + other.friends.outgoing.remove(&user_id); + user.friends.current.insert(other_id); + other.friends.current.insert(user_id); + } + FriendRequestAction::Deny => { + user.friends.incoming.remove(&other_id); + other.friends.outgoing.remove(&user_id); + } + } + tx.insert(&db.users, &user_id, &user); + tx.insert(&db.users, &other_id, &other); + if tx.commit() { + break; + } + } + None + } + } + } +} + +impl RecvHandler for ClientHandler { + async fn connect(&self) -> () {} + async fn msg(&self, req: ClientMsgInst) { + let replier = self.send.replier(req.id); + self.handle_msg(req.msg, &replier).await; + } + + async fn disconnect(&self, reason: DisconnectReason) -> () { + match reason { + DisconnectReason::Closed | DisconnectReason::Timeout => (), + DisconnectReason::Other(e) => println!("connection issue: {e}"), + } + } +} + +// ClientMsg::SendMsg(msg) => { +// let uid = check_login().await?; +// let msg = Msg { +// author: uid, +// content: msg.content, +// }; +// // TODO: it is technically possible to send 2 messages at the exact same time... +// // should probably append a number if one already exists at that time, +// // but also I can't see this ever happening...? +// // should be an easy fix later (write tx) +// let timestamp = time::OffsetDateTime::now_utc().unix_timestamp_nanos(); +// self.db.msgs.insert(×tamp, &msg); +// let mut handles = Vec::new(); +// let msg = LoadMsg { +// content: msg.content, +// author: uid, +// }; +// for (&id, send) in self.senders.read().await.iter() { +// if id == self.id { +// continue; +// } +// let send = send.clone(); +// let msg = msg.clone(); +// let fut = async move { +// let _ = send +// .send(LoadMsg { +// content: msg.content, +// author: msg.author, +// }) +// .await; +// }; +// handles.push(tokio::spawn(fut)); +// } +// for h in handles { +// h.await.unwrap(); +// } +// None +// } +// ClientMsg::RequestMsgs => { +// check_login().await?; +// let msgs = self +// .db +// .msgs +// .values() +// .map(|msg| LoadMsg { +// content: msg.content, +// author: msg.author, +// }) +// .collect(); +// replier.send(ServerMsg::LoadMsgs(msgs)).await; +// } diff --git a/src/bin/server/main.rs b/src/bin/server/main.rs index a2db2ce..ba1474d 100644 --- a/src/bin/server/main.rs +++ b/src/bin/server/main.rs @@ -1,22 +1,18 @@ mod db; +mod handle; mod net; -use crate::db::{Db, Msg, ServerPerms, User}; +use crate::{ + db::{Db, ServerPerms}, + handle::{ClientHandler, ClientState}, +}; use clap::Parser; use net::{ClientSender, ConAccepter, listen}; use openworm::{ - net::{ - ClientMsg, ClientRequestMsg, CreateAccount, CreateAccountResp, DisconnectReason, LoadMsg, - Login, LoginResp, RecvHandler, RequestUsersResp, ServerError, ServerMsg, ServerUser, - install_crypto_provider, - }, + net::{AccountToken, ClientMsgInst, RecvHandler, install_crypto_provider}, rsc::DataDir, }; use rand::distr::{Alphanumeric, SampleString}; -use scrypt::{ - Scrypt, - password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng}, -}; use std::{ collections::HashMap, sync::{ @@ -62,7 +58,7 @@ pub async fn run_server(port: u16) { endpoint.wait_idle().await; } -pub fn account_token(db: &Db, perms: ServerPerms) -> String { +pub fn account_token(db: &Db, perms: ServerPerms) -> AccountToken { let token = Alphanumeric.sample_string(&mut rand::rng(), 16); db.account_tokens.insert(&token, &perms); token @@ -76,14 +72,8 @@ struct ServerListener { count: AtomicU64, } -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum ClientState { - Login, - Authed(u64), -} - impl ConAccepter for ServerListener { - async fn accept(&self, send: ClientSender) -> impl RecvHandler { + async fn accept(&self, send: ClientSender) -> impl RecvHandler { let id = self.count.fetch_add(1, Ordering::Release); self.senders.write().await.insert(id, send.clone()); ClientHandler { @@ -95,177 +85,3 @@ impl ConAccepter for ServerListener { } } } - -struct ClientHandler { - db: Db, - send: ClientSender, - senders: Arc>>, - id: ClientId, - state: Arc>, -} - -impl RecvHandler for ClientHandler { - async fn connect(&self) -> () {} - async fn msg(&self, req: ClientRequestMsg) { - let msg = ClientMsg::from(req.msg); - let replier = self.send.replier(req.id); - match msg { - ClientMsg::SendMsg(msg) => { - let ClientState::Authed(uid) = &*self.state.read().await else { - let _ = replier.send(ServerError::NotLoggedIn).await; - return; - }; - let msg = Msg { - author: *uid, - content: msg.content, - }; - // TODO: it is technically possible to send 2 messages at the exact same time... - // should probably append a number if one already exists at that time, - // but also I can't see this ever happening...? - // should be an easy fix later (write tx) - let timestamp = time::OffsetDateTime::now_utc().unix_timestamp_nanos(); - self.db.msgs.insert(×tamp, &msg); - let mut handles = Vec::new(); - let msg = LoadMsg { - content: msg.content, - author: *uid, - }; - for (&id, send) in self.senders.read().await.iter() { - if id == self.id { - continue; - } - let send = send.clone(); - let msg = msg.clone(); - let fut = async move { - let _ = send - .send(LoadMsg { - content: msg.content, - author: msg.author, - }) - .await; - }; - handles.push(tokio::spawn(fut)); - } - for h in handles { - h.await.unwrap(); - } - } - ClientMsg::RequestMsgs => { - let ClientState::Authed(_uid) = &*self.state.read().await else { - let _ = replier.send(ServerError::NotLoggedIn).await; - return; - }; - let msgs = self - .db - .msgs - .values() - .map(|msg| LoadMsg { - content: msg.content, - author: msg.author, - }) - .collect(); - let _ = replier.send(ServerMsg::LoadMsgs(msgs)).await; - } - ClientMsg::CreateAccount(info) => { - let CreateAccount { - token, - username, - password, - } = &info; - let salt = SaltString::generate(&mut OsRng); - let params = scrypt::Params::new(11, 8, 1, 32).unwrap(); - let hash = Scrypt - .hash_password_customized(password.as_bytes(), None, None, params, &salt) - .unwrap() - .to_string(); - let mut id; - loop { - let mut tx = self.db.write_tx(); - let Some(perms) = tx.remove(&self.db.account_tokens, token.to_string()) else { - let _ = replier.send(CreateAccountResp::InvalidToken).await; - println!("invalid token: {:?}", self.send.remote()); - return; - }; - if tx.has_key(&self.db.usernames, username.clone()) { - let _ = replier.send(CreateAccountResp::UsernameExists).await; - return; - } - id = rand::random(); - while tx.has_key(&self.db.users, id) { - id = rand::random(); - } - tx.insert( - &self.db.users, - &id, - &User { - username: username.clone(), - password_hash: hash.clone(), - bio: String::new(), - pfp: None, - server_perms: perms, - }, - ); - tx.insert(&self.db.usernames, username, &id); - if tx.commit() { - break; - } - } - println!("account created: \"{username}\""); - *self.state.write().await = ClientState::Authed(id); - let _ = replier.send(CreateAccountResp::Ok { id }).await; - } - ClientMsg::Login(info) => { - let Login { username, password } = &info; - let Some(id) = self.db.usernames.get(username) else { - let _ = replier.send(LoginResp::UnknownUsername).await; - return; - }; - let Some(user) = self.db.users.get(&id) else { - panic!("invalid state! (should be a user)"); - }; - let hash = PasswordHash::new(&user.password_hash).unwrap(); - if Scrypt.verify_password(password.as_bytes(), &hash).is_err() { - println!("invalid password: \"{username}\""); - let _ = replier.send(LoginResp::InvalidPassword).await; - return; - } - *self.state.write().await = ClientState::Authed(id); - let _ = replier.send(LoginResp::Ok { id }).await; - } - ClientMsg::RequestUsers(_) => { - if self - .db - .users - .get(&self.id) - .is_some_and(|u| !u.server_perms.contains(ServerPerms::ALL)) - { - let _ = replier.send(ServerError::NoPermission).await; - return; - } - let users: Vec<_> = self - .db - .users - .iter() - .map(|(id, u)| ServerUser { - id, - username: u.username, - }) - .collect(); - let _ = replier.send(RequestUsersResp { users }).await; - } - } - } - - async fn disconnect(&self, reason: DisconnectReason) -> () { - match reason { - DisconnectReason::Closed | DisconnectReason::Timeout => (), - DisconnectReason::Other(e) => println!("connection issue: {e}"), - } - } -} - -impl ClientState { - pub fn is_authed(&self) -> bool { - matches!(self, Self::Authed(_)) - } -} diff --git a/src/bin/server/net.rs b/src/bin/server/net.rs index 5c04cd5..c7f843b 100644 --- a/src/bin/server/net.rs +++ b/src/bin/server/net.rs @@ -1,5 +1,5 @@ use openworm::net::{ - ClientRequestMsg, RecvHandler, RequestId, SERVER_NAME, SendResult, ServerMsg, ServerRespMsg, + ClientMsgInst, RecvHandler, RequestId, SERVER_NAME, SendResult, ServerMsg, ServerMsgInst, recv_uni, send_uni, }; use quinn::{ @@ -65,9 +65,9 @@ impl ClientSender { } pub async fn send(&self, msg: impl Into) -> SendResult { - let msg = ServerRespMsg { + let msg = ServerMsgInst { id: None, - msg: msg.into().into(), + msg: msg.into(), }; send_uni(&self.conn, msg).await } @@ -79,12 +79,12 @@ pub struct ClientReplier { } impl ClientReplier { - pub async fn send(&self, msg: impl Into) -> SendResult { - let msg = ServerRespMsg { + pub async fn send(&self, msg: impl Into) { + let msg = ServerMsgInst { id: Some(self.req_id), - msg: msg.into().into(), + msg: msg.into(), }; - send_uni(&self.conn, msg).await + let _ = send_uni(&self.conn, msg).await; } } @@ -92,7 +92,7 @@ pub trait ConAccepter: Send + Sync + 'static { fn accept( &self, send: ClientSender, - ) -> impl Future> + Send; + ) -> impl Future> + Send; } pub fn listen( diff --git a/src/net/conversion.rs b/src/net/conversion.rs deleted file mode 100644 index 92e19d5..0000000 --- a/src/net/conversion.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::net::{ - ClientMsg, ServerMsg, - data::{ClientMsgInst, ServerMsgInst}, -}; - -impl From for ClientMsgInst { - fn from(value: ClientMsg) -> Self { - match value { - ClientMsg::CreateAccount(v) => Self::CreateAccountV0(v), - ClientMsg::RequestMsgs => Self::RequestMsgsV0, - ClientMsg::SendMsg(v) => Self::SendMsgV0(v), - ClientMsg::Login(v) => Self::LoginV0(v), - ClientMsg::RequestUsers(v) => Self::RequestUsersV0(v), - } - } -} - -impl From for ClientMsg { - fn from(value: ClientMsgInst) -> Self { - match value { - ClientMsgInst::CreateAccountV0(v) => Self::CreateAccount(v), - ClientMsgInst::RequestMsgsV0 => Self::RequestMsgs, - ClientMsgInst::SendMsgV0(v) => Self::SendMsg(v), - ClientMsgInst::LoginV0(v) => Self::Login(v), - ClientMsgInst::RequestUsersV0(v) => Self::RequestUsers(v), - } - } -} - -impl From for ServerMsgInst { - fn from(value: ServerMsg) -> Self { - match value { - ServerMsg::CreateAccountResp(v) => Self::CreateAccountRespV0(v), - ServerMsg::LoadMsg(v) => Self::LoadMsgV0(v), - ServerMsg::LoadMsgs(v) => Self::LoadMsgsV0(v), - ServerMsg::ServerError(v) => Self::ServerErrorV0(v), - ServerMsg::LoginResp(v) => Self::LoginRespV0(v), - ServerMsg::RequestUsersResp(v) => Self::RequestUsersRespV0(v), - } - } -} - -impl From for ServerMsg { - fn from(value: ServerMsgInst) -> Self { - match value { - ServerMsgInst::CreateAccountRespV0(v) => Self::CreateAccountResp(v), - ServerMsgInst::LoadMsgV0(v) => Self::LoadMsg(v), - ServerMsgInst::LoadMsgsV0(v) => Self::LoadMsgs(v), - ServerMsgInst::ServerErrorV0(v) => Self::ServerError(v), - ServerMsgInst::LoginRespV0(v) => Self::LoginResp(v), - ServerMsgInst::RequestUsersRespV0(v) => Self::RequestUsersResp(v), - } - } -} diff --git a/src/net/data.rs b/src/net/data.rs index 41bea67..7200569 100644 --- a/src/net/data.rs +++ b/src/net/data.rs @@ -1,106 +1,110 @@ -use rand::TryRng; +use iris::core::util::HashSet; -#[repr(u32)] -#[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub enum ClientMsgInst { - CreateAccountV0(CreateAccountV0) = 0, - LoginV0(LoginV0) = 1, - RequestMsgsV0 = 2, - SendMsgV0(SendMsgV0) = 3, - RequestUsersV0(RequestUsersV0) = 4, -} - -#[repr(u32)] -#[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub enum ServerMsgInst { - CreateAccountRespV0(CreateAccountRespV0) = 0, - LoginRespV0(LoginRespV0) = 1, - LoadMsgV0(LoadMsgV0) = 2, - LoadMsgsV0(Vec) = 3, - ServerErrorV0(ServerErrorV0) = 4, - RequestUsersRespV0(RequestUsersRespV0) = 5, -} - -pub type UserIdV0 = u64; +pub type UserId = u64; +pub type AccountToken = String; #[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub struct CreateAccountV0 { +pub struct CreateAccount { pub username: String, pub password: String, pub token: String, } #[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub struct LoginV0 { - pub username: String, - pub password: String, -} - -#[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub enum CreateAccountRespV0 { - Ok { id: UserIdV0 }, +pub enum CreateAccountResp { + Ok { id: UserId }, UsernameExists, InvalidToken, } #[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub enum LoginRespV0 { - Ok { id: UserIdV0 }, +pub struct Login { + pub username: String, + pub password: String, +} + +#[derive(Debug, bitcode::Encode, bitcode::Decode)] +pub enum LoginResp { + Ok { id: UserId }, UnknownUsername, InvalidPassword, } #[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub struct RequestUsersV0; - +pub struct RequestUsers; #[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub struct RequestUsersRespV0 { - pub users: Vec, +pub struct RequestUsersResp { + pub users: Vec, } - #[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub struct ServerUserV0 { - pub id: UserIdV0, +pub struct ServerUser { + pub id: UserId, pub username: String, } #[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub struct LoginKeyV0(Vec); -impl LoginKeyV0 { - pub const BIT_LEN: usize = 1024; - pub const BYTE_LEN: usize = Self::BIT_LEN / 8; - - pub fn new() -> Self { - let mut key = [0u8; Self::BYTE_LEN]; - rand::rngs::SysRng - .try_fill_bytes(&mut key) - .expect("failed to generate random key"); - Self(key.to_vec()) - } - - pub fn bytes(&self) -> &[u8] { - &self.0 - } -} -impl From> for LoginKeyV0 { - fn from(value: Vec) -> Self { - Self(value) - } -} - -#[derive(Debug, Clone, bitcode::Encode, bitcode::Decode)] -pub struct SendMsgV0 { - pub content: String, -} - -#[derive(Debug, Clone, bitcode::Encode, bitcode::Decode)] -pub struct LoadMsgV0 { - pub content: String, - pub author: UserIdV0, +pub struct RequestFriends; +#[derive(Debug, bitcode::Encode, bitcode::Decode)] +pub struct RequestFriendsResp { + pub current: HashSet, + pub incoming: HashSet, + pub outgoing: HashSet, } #[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub enum ServerErrorV0 { - NotLoggedIn, - NoPermission, +pub struct AddFriend { + pub username: String, } +#[derive(Debug, bitcode::Encode, bitcode::Decode)] +pub enum AddFriendResp { + Ok, + UnknownUser, + CannotAddSelf, + AlreadySent, + AlreadyFriends, +} + +#[derive(Debug, bitcode::Encode, bitcode::Decode)] +pub struct RemoveFriend { + pub id: UserId, +} + +#[derive(Debug, bitcode::Encode, bitcode::Decode)] +pub struct AnswerFriendRequest { + pub id: UserId, + pub action: FriendRequestAction, +} +#[derive(Debug, bitcode::Encode, bitcode::Decode)] +pub enum FriendRequestAction { + Accept, + Deny, +} + +#[derive(Debug, bitcode::Encode, bitcode::Decode)] +pub struct GenerateToken { + pub perms: ServerPerms, +} +#[derive(Debug, bitcode::Encode, bitcode::Decode)] +pub struct GenerateTokenResp { + pub token: AccountToken, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, bitcode::Encode, bitcode::Decode)] +pub struct ServerPerms(u32); +impl ServerPerms { + pub const NONE: Self = Self(0); + pub const ACCOUNT_TOKENS: Self = Self(1 << 0); + pub const ALL: Self = Self(u32::MAX); +} +impl ServerPerms { + pub fn contains(&self, other: Self) -> bool { + (self.0 & other.0) == other.0 + } +} + +#[derive(Debug, bitcode::Encode, bitcode::Decode)] +pub struct NotLoggedIn; +#[derive(Debug, bitcode::Encode, bitcode::Decode)] +pub struct NoPermission; +#[derive(Debug, bitcode::Encode, bitcode::Decode)] +pub struct AccountDeleted; diff --git a/src/net/mod.rs b/src/net/mod.rs index 5a54b6b..f8c1568 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,11 +1,10 @@ -mod conversion; -pub mod data; +mod data; mod msg; mod no_cert; mod request; mod transfer; -pub use data::{ClientMsgInst, ServerMsgInst}; +pub use data::*; pub use msg::*; pub use no_cert::*; pub use request::*; @@ -13,8 +12,6 @@ pub use transfer::*; pub const SERVER_NAME: &str = "openworm"; -pub type ServerResp = Result; - pub fn install_crypto_provider() { quinn::rustls::crypto::ring::default_provider() .install_default() diff --git a/src/net/msg.rs b/src/net/msg.rs index 40aee74..3e59f62 100644 --- a/src/net/msg.rs +++ b/src/net/msg.rs @@ -1,83 +1,53 @@ -use super::data; +use super::{RequestMsg, data::*}; -#[derive(Debug)] -pub enum ClientMsg { - CreateAccount(CreateAccount), - Login(Login), - RequestMsgs, - SendMsg(SendMsg), - RequestUsers(RequestUsers), -} - -#[derive(Debug)] -pub enum ServerMsg { - CreateAccountResp(CreateAccountResp), - LoginResp(LoginResp), - LoadMsg(LoadMsg), - LoadMsgs(Vec), - ServerError(ServerError), - RequestUsersResp(RequestUsersResp), -} - -// TODO: a ton of this should really just be macros :sob: - -pub use data::CreateAccountRespV0 as CreateAccountResp; -pub use data::CreateAccountV0 as CreateAccount; -pub use data::LoadMsgV0 as LoadMsg; -pub use data::LoginKeyV0 as LoginKey; -pub use data::LoginRespV0 as LoginResp; -pub use data::LoginV0 as Login; -pub use data::RequestUsersRespV0 as RequestUsersResp; -pub use data::RequestUsersV0 as RequestUsers; -pub use data::SendMsgV0 as SendMsg; -pub use data::ServerErrorV0 as ServerError; -pub use data::ServerUserV0 as ServerUser; -pub use data::UserIdV0 as UserId; - -impl From for ClientMsg { - fn from(value: CreateAccount) -> Self { - Self::CreateAccount(value) - } -} - -impl From for ClientMsg { - fn from(value: Login) -> Self { - Self::Login(value) - } -} - -impl From for ClientMsg { - fn from(value: RequestUsers) -> Self { - Self::RequestUsers(value) - } -} - -impl From for ServerMsg { - fn from(value: ServerError) -> Self { - Self::ServerError(value) - } -} - -impl From for ServerMsg { - fn from(value: LoadMsg) -> Self { - Self::LoadMsg(value) - } -} - -impl From for ServerMsg { - fn from(value: CreateAccountResp) -> Self { - Self::CreateAccountResp(value) - } -} - -impl From for ServerMsg { - fn from(value: LoginResp) -> Self { - Self::LoginResp(value) - } -} - -impl From for ServerMsg { - fn from(value: RequestUsersResp) -> Self { - Self::RequestUsersResp(value) - } +msg_type!(ClientMsg: { + 0: CreateAccount => CreateAccountResp, + 1: Login => LoginResp, + 2: RequestUsers => RequestUsersResp, + 3: RequestFriends => RequestFriendsResp, + 4: AddFriend => AddFriendResp, + 5: RemoveFriend, + 6: AnswerFriendRequest, + 7: GenerateToken => GenerateTokenResp, +}); +msg_type!(ServerMsg: { + 0: NotLoggedIn, + 1: NoPermission, + 2: AccountDeleted, + 3: CreateAccountResp, + 4: LoginResp, + 5: RequestUsersResp, + 6: RequestFriendsResp, + 7: AddFriendResp, + 8: GenerateTokenResp, +}); + +macro_rules! msg_type { + ($msg:ident: {$($num:literal: $name:ident $(=> $resp:ident)?,)*}) => { + #[repr(u32)] + #[derive(Debug, bitcode::Encode, bitcode::Decode)] + pub enum $msg {$( + $name($name) = $num, + )*} + $( + impl From<$name> for $msg { + fn from(value: $name) -> Self { + Self::$name(value) + } + } + $( + impl RequestMsg for $name { + type Result = $resp; + fn result(msg: ServerMsg) -> Option { + if let ServerMsg::$resp(res) = msg { + Some(res) + } else { + None + } + } + } + )? + )* + }; } +use msg_type; diff --git a/src/net/request.rs b/src/net/request.rs index 98bef51..de10846 100644 --- a/src/net/request.rs +++ b/src/net/request.rs @@ -19,54 +19,18 @@ impl RequestId { } #[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub struct ClientRequestMsg { +pub struct ClientMsgInst { pub id: RequestId, - pub msg: ClientMsgInst, + pub msg: ClientMsg, } #[derive(Debug, bitcode::Encode, bitcode::Decode)] -pub struct ServerRespMsg { +pub struct ServerMsgInst { pub id: Option, - pub msg: ServerMsgInst, + pub msg: ServerMsg, } pub trait RequestMsg: Into { type Result; fn result(msg: ServerMsg) -> Option; } - -impl RequestMsg for CreateAccount { - type Result = CreateAccountResp; - - fn result(msg: ServerMsg) -> Option { - if let ServerMsg::CreateAccountResp(res) = msg { - Some(res) - } else { - None - } - } -} - -impl RequestMsg for Login { - type Result = LoginResp; - - fn result(msg: ServerMsg) -> Option { - if let ServerMsg::LoginResp(res) = msg { - Some(res) - } else { - None - } - } -} - -impl RequestMsg for RequestUsers { - type Result = RequestUsersResp; - - fn result(msg: ServerMsg) -> Option { - if let ServerMsg::RequestUsersResp(res) = msg { - Some(res) - } else { - None - } - } -}