server side preparation
This commit is contained in:
@@ -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<NetCtrlMsg>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppHandle {
|
||||
pub proxy: EventLoopProxy<ClientEvent>,
|
||||
}
|
||||
|
||||
impl AppHandle {
|
||||
pub fn send(&self, event: ClientEvent) {
|
||||
self.proxy.send_event(event).unwrap_or_else(|_| panic!());
|
||||
}
|
||||
}
|
||||
|
||||
type NetResult<T> = Result<T, String>;
|
||||
|
||||
pub trait ClientRequest {}
|
||||
|
||||
pub enum NetCtrlMsg {
|
||||
Send(ClientMsg),
|
||||
Request(ClientMsg, oneshot::Sender<ServerMsg>),
|
||||
@@ -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<F: MsgHandler> {
|
||||
msg: F,
|
||||
}
|
||||
|
||||
impl<F: MsgHandler> RecvHandler<ServerRespMsg> for ServerRecv<F> {
|
||||
async fn msg(&self, resp: ServerRespMsg) {
|
||||
let msg = resp.msg.into();
|
||||
impl<F: MsgHandler> RecvHandler<ServerMsgInst> for ServerRecv<F> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
src/bin/client/ui/friends.rs
Normal file
12
src/bin/client/ui/friends.rs
Normal file
@@ -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)
|
||||
// }
|
||||
@@ -4,6 +4,7 @@ use iris::prelude::*;
|
||||
mod channel;
|
||||
pub mod color;
|
||||
mod connect;
|
||||
mod friends;
|
||||
mod main;
|
||||
mod misc;
|
||||
mod server;
|
||||
|
||||
48
src/bin/server/db/data.rs
Normal file
48
src/bin/server/db/data.rs
Normal file
@@ -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<ImageId>,
|
||||
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<UserId>,
|
||||
pub outgoing: HashSet<UserId>,
|
||||
pub incoming: HashSet<UserId>,
|
||||
}
|
||||
|
||||
#[derive(bitcode::Encode, bitcode::Decode)]
|
||||
pub struct Msg {
|
||||
pub content: String,
|
||||
pub author: UserId,
|
||||
}
|
||||
|
||||
pub struct Channel {
|
||||
pub name: String,
|
||||
pub desc: String,
|
||||
}
|
||||
@@ -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<String, ServerPerms>,
|
||||
pub account_tokens: DbMap<AccountToken, ServerPerms>,
|
||||
pub msgs: DbMap<MsgId, Msg>,
|
||||
pub users: DbMap<UserId, User>,
|
||||
pub usernames: DbMap<String, UserId>,
|
||||
}
|
||||
|
||||
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<Path>) -> Db {
|
||||
let db = Database::open(path);
|
||||
|
||||
@@ -17,7 +17,7 @@ impl<K, V> Clone for DbMap<K, V> {
|
||||
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<K: Key<Output = K>, V: DecodeOwned>(&self, map: &DbMap<K, V>, k: K) -> Option<V> {
|
||||
pub fn get<K: Key<Output = K>, V: DecodeOwned>(&self, map: &DbMap<K, V>, k: &K) -> Option<V> {
|
||||
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<K: Key<Output = K>, V: DecodeOwned>(&mut self, map: &DbMap<K, V>, k: K) -> Option<V> {
|
||||
pub fn remove<K: Key<Output = K>, V: DecodeOwned>(
|
||||
&mut self,
|
||||
map: &DbMap<K, V>,
|
||||
k: K,
|
||||
) -> Option<V> {
|
||||
let k = Slice::new(k.to_bytes().as_ref());
|
||||
let v = self.0.take(&map.keyspace, k).unwrap()?;
|
||||
Some(bitcode::decode(&v).unwrap())
|
||||
|
||||
@@ -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<ImageIdV0>,
|
||||
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
|
||||
}
|
||||
}
|
||||
313
src/bin/server/handle.rs
Normal file
313
src/bin/server/handle.rs
Normal file
@@ -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<RwLock<HashMap<ClientId, ClientSender>>>,
|
||||
pub id: ClientId,
|
||||
pub state: Arc<RwLock<ClientState>>,
|
||||
}
|
||||
|
||||
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<ClientMsgInst> 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;
|
||||
// }
|
||||
@@ -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<ClientRequestMsg> {
|
||||
async fn accept(&self, send: ClientSender) -> impl RecvHandler<ClientMsgInst> {
|
||||
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<RwLock<HashMap<ClientId, ClientSender>>>,
|
||||
id: ClientId,
|
||||
state: Arc<RwLock<ClientState>>,
|
||||
}
|
||||
|
||||
impl RecvHandler<ClientRequestMsg> 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(_))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ServerMsg>) -> 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<ServerMsg>) -> SendResult {
|
||||
let msg = ServerRespMsg {
|
||||
pub async fn send(&self, msg: impl Into<ServerMsg>) {
|
||||
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<Output = impl RecvHandler<ClientRequestMsg>> + Send;
|
||||
) -> impl Future<Output = impl RecvHandler<ClientMsgInst>> + Send;
|
||||
}
|
||||
|
||||
pub fn listen(
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
use crate::net::{
|
||||
ClientMsg, ServerMsg,
|
||||
data::{ClientMsgInst, ServerMsgInst},
|
||||
};
|
||||
|
||||
impl From<ClientMsg> 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<ClientMsgInst> 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<ServerMsg> 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<ServerMsgInst> 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
156
src/net/data.rs
156
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<LoadMsgV0>) = 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<ServerUserV0>,
|
||||
pub struct RequestUsersResp {
|
||||
pub users: Vec<ServerUser>,
|
||||
}
|
||||
|
||||
#[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<u8>);
|
||||
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<Vec<u8>> for LoginKeyV0 {
|
||||
fn from(value: Vec<u8>) -> 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<UserId>,
|
||||
pub incoming: HashSet<UserId>,
|
||||
pub outgoing: HashSet<UserId>,
|
||||
}
|
||||
|
||||
#[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;
|
||||
|
||||
@@ -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<T> = Result<T, String>;
|
||||
|
||||
pub fn install_crypto_provider() {
|
||||
quinn::rustls::crypto::ring::default_provider()
|
||||
.install_default()
|
||||
|
||||
132
src/net/msg.rs
132
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<LoadMsg>),
|
||||
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<CreateAccount> for ClientMsg {
|
||||
fn from(value: CreateAccount) -> Self {
|
||||
Self::CreateAccount(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Login> for ClientMsg {
|
||||
fn from(value: Login) -> Self {
|
||||
Self::Login(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequestUsers> for ClientMsg {
|
||||
fn from(value: RequestUsers) -> Self {
|
||||
Self::RequestUsers(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServerError> for ServerMsg {
|
||||
fn from(value: ServerError) -> Self {
|
||||
Self::ServerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadMsg> for ServerMsg {
|
||||
fn from(value: LoadMsg) -> Self {
|
||||
Self::LoadMsg(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CreateAccountResp> for ServerMsg {
|
||||
fn from(value: CreateAccountResp) -> Self {
|
||||
Self::CreateAccountResp(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoginResp> for ServerMsg {
|
||||
fn from(value: LoginResp) -> Self {
|
||||
Self::LoginResp(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequestUsersResp> 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<Self::Result> {
|
||||
if let ServerMsg::$resp(res) = msg {
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
)?
|
||||
)*
|
||||
};
|
||||
}
|
||||
use msg_type;
|
||||
|
||||
@@ -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<RequestId>,
|
||||
pub msg: ServerMsgInst,
|
||||
pub msg: ServerMsg,
|
||||
}
|
||||
|
||||
pub trait RequestMsg: Into<ClientMsg> {
|
||||
type Result;
|
||||
fn result(msg: ServerMsg) -> Option<Self::Result>;
|
||||
}
|
||||
|
||||
impl RequestMsg for CreateAccount {
|
||||
type Result = CreateAccountResp;
|
||||
|
||||
fn result(msg: ServerMsg) -> Option<Self::Result> {
|
||||
if let ServerMsg::CreateAccountResp(res) = msg {
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestMsg for Login {
|
||||
type Result = LoginResp;
|
||||
|
||||
fn result(msg: ServerMsg) -> Option<Self::Result> {
|
||||
if let ServerMsg::LoginResp(res) = msg {
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestMsg for RequestUsers {
|
||||
type Result = RequestUsersResp;
|
||||
|
||||
fn result(msg: ServerMsg) -> Option<Self::Result> {
|
||||
if let ServerMsg::RequestUsersResp(res) = msg {
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user