work (new network + db initial working state)

This commit is contained in:
2026-02-15 16:59:47 -05:00
parent a8b55f669f
commit 8be13e14bc
14 changed files with 418 additions and 232 deletions

View File

@@ -1,8 +1,8 @@
use crate::ClientEvent;
use dashmap::DashMap;
use openworm::net::{
AccountCreated, ClientMsg, ClientMsgInst, CreateAccount, RecvHandler, RequestId, SERVER_NAME,
ServerMsg, ServerRespMsg, SkipServerVerification, recv_uni, send_uni,
AccountCreated, ClientMsg, ClientMsgInst, ClientRequestMsg, CreateAccount, RecvHandler,
RequestId, SERVER_NAME, ServerMsg, ServerRespMsg, SkipServerVerification, recv_uni, send_uni,
};
use quinn::{
ClientConfig, Connection, Endpoint, IdleTimeout, TransportConfig,
@@ -60,11 +60,11 @@ impl NetHandle {
pub async fn request<R: RequestMsg>(&self, msg: R) -> Result<R::Result, ()> {
let (send, recv) = oneshot::channel();
self.send_(NetCtrlMsg::Request(msg.into(), send));
let Ok(recv) = recv.await else { todo!() };
let Ok(recv) = recv.await else { return Err(()) };
if let Some(res) = R::result(recv) {
Ok(res)
} else {
todo!()
Err(())
}
}
@@ -167,17 +167,24 @@ pub async fn connect(msg: impl MsgHandler, info: ConnectInfo) -> Result<NetHandl
tokio::spawn(recv_uni(conn_, recv.clone()));
tokio::spawn(async move {
while let Some(msg) = ui_recv.recv().await {
let request_id = req_id.next();
match msg {
NetCtrlMsg::Send(msg) => {
let msg = ClientMsgInst::from(msg);
let msg = ClientRequestMsg {
id: request_id,
msg: msg.into(),
};
if send_uni(&conn, msg).await.is_err() {
println!("disconnected from server");
break;
}
}
NetCtrlMsg::Request(msg, send) => {
let msg = ClientMsgInst::from(msg);
recv.requests.insert(req_id.next(), send);
let msg = ClientRequestMsg {
id: request_id,
msg: msg.into(),
};
recv.requests.insert(request_id, send);
if send_uni(&conn, msg).await.is_err() {
println!("disconnected from server");
break;
@@ -215,7 +222,7 @@ struct ServerRecv<F: MsgHandler> {
impl<F: MsgHandler> RecvHandler<ServerRespMsg> for ServerRecv<F> {
async fn msg(&self, resp: ServerRespMsg) {
let msg = resp.msg.into();
if let Some(id) = resp.request_id
if let Some(id) = resp.id
&& let Some((_, send)) = self.requests.remove(&id)
{
send.send(msg);

View File

@@ -40,7 +40,7 @@ pub fn start(rsc: &mut Rsc) -> WeakWidget {
pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
let url = field("", "server", rsc);
let token = field("", "account token", rsc);
let token = field("", "account creation token", rsc);
let username = field("", "username", rsc);
let password = field("", "password", rsc);
@@ -81,6 +81,7 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
else {
return fail("failed to create account");
};
println!("account created!!!!");
});
});

View File

@@ -1,3 +0,0 @@
pub struct DBMsg {
}

View File

@@ -1,163 +0,0 @@
use std::{marker::PhantomData, path::Path};
use bitcode::{Decode, DecodeOwned, Encode};
use fjall::{Database, Keyspace, KeyspaceCreateOptions, UserValue};
pub struct DbU64(u64);
pub const DB_VERSION: DbU64 = DbU64(0);
impl Into<UserValue> for DbU64 {
fn into(self) -> UserValue {
UserValue::new(&self.0.to_be_bytes())
}
}
#[derive(Encode, Decode)]
pub struct User {
pub username: String,
pub password_hash: String,
}
#[derive(Clone, Encode, Decode)]
pub struct Msg {
pub user: u64,
pub content: String,
}
#[derive(Clone)]
pub struct Db {
pub db: fjall::Database,
pub msgs: DbMap<u64, Msg>,
pub users: DbMap<u64, User>,
pub usernames: DbMap<String, u64>,
}
pub struct DbMap<K, V> {
keyspace: Keyspace,
_pd: PhantomData<(K, V)>,
}
pub trait Key {
type Output<'a>: AsRef<[u8]>
where
Self: 'a;
fn bytes(&self) -> Self::Output<'_>;
}
impl Key for String {
type Output<'a> = &'a Self;
fn bytes(&self) -> Self::Output<'_> {
self
}
}
impl Key for str {
type Output<'a> = &'a Self;
fn bytes(&self) -> Self::Output<'_> {
self
}
}
impl Key for u64 {
type Output<'a> = [u8; 8];
fn bytes(&self) -> Self::Output<'_> {
self.to_be_bytes()
}
}
impl<K: Key, V: Encode + DecodeOwned> DbMap<K, V> {
pub fn insert(&self, k: &K, v: &V) {
self.keyspace.insert_(k, v);
}
pub fn get(&self, k: &K) -> Option<V> {
self.keyspace.get_(k)
}
pub fn init_unique(&self, k: &K) -> bool {
self.keyspace
.compare_and_swap(k.bytes(), None as Option<&[u8]>, Some(&[0]))
.unwrap()
.is_ok()
}
pub fn iter_all(&self) -> impl Iterator<Item = V> {
self.keyspace.iter_all()
}
}
pub fn open_db(path: impl AsRef<Path>) -> Db {
let config = fjall::Config::new(path.as_ref());
let db = Database::open(config).expect("failed to open database");
let info = open_ks("info", &db);
if !info.contains_key("version").unwrap() {
println!("no previous db found, creating new");
info.insert("version", DB_VERSION);
} else {
let version: u64 = info.get("version").expect("failed to read db version");
println!("found existing db version {version}");
if version != DB_VERSION {
panic!("non matching db version! (auto update in the future)");
}
}
Db {
msgs: open_ks("msg", &db),
users: open_ks("user", &db),
usernames: open_ks("username", &db),
db,
}
}
trait DbUtil {
fn insert_<V: Encode>(&self, k: &(impl Key + ?Sized), v: V);
fn get_<V: DecodeOwned>(&self, k: &(impl Key + ?Sized)) -> Option<V>;
fn iter_all<V: DecodeOwned>(&self) -> impl Iterator<Item = V>;
}
impl DbUtil for Tree {
fn insert_<V: Encode>(&self, k: &(impl Key + ?Sized), v: &V) {
let bytes = bitcode::encode(v);
self.insert(k.bytes(), bytes).unwrap();
}
fn get_<V: Decode<()>>(&self, k: &(impl Key + ?Sized)) -> Option<V> {
let bytes = self.get(k.bytes()).unwrap()?;
Some(
bincode::decode_from_slice(&bytes, BINCODE_CONFIG)
.unwrap()
.0,
)
}
fn iter_all<V: Decode<()>>(&self) -> impl Iterator<Item = V> {
self.iter().map(|r| {
bincode::decode_from_slice(&r.unwrap().1, BINCODE_CONFIG)
.unwrap()
.0
})
}
}
pub fn open_ks<K, V>(name: &str, db: &Database) -> DbMap<K, V> {
DbMap {
keyspace: db.keyspace(name, KeyspaceCreateOptions::default).unwrap(),
_pd: PhantomData,
}
}
impl<K, V> Clone for DbMap<K, V> {
fn clone(&self) -> Self {
Self {
keyspace: self.keyspace.clone(),
_pd: self._pd,
}
}
}
impl Db {
pub fn flush(&self) {
// test to see if it gets dropped and just works
// self.db.persist(fjall::PersistMode::SyncAll).unwrap();
}
}

55
src/bin/server/db/mod.rs Normal file
View File

@@ -0,0 +1,55 @@
mod util;
mod ver;
use std::path::Path;
use util::*;
use ver::*;
pub const DB_VERSION: u64 = 0;
#[derive(Clone)]
pub struct Db {
db: Database,
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;
impl Db {
pub fn open(path: impl AsRef<Path>) -> Db {
let db = Database::open(path);
let info: DbMap<String, u64> = DbMap::open("info", &db);
if let Some(version) = info.get("version") {
println!("found existing db version {version}");
if version != DB_VERSION {
panic!("non matching db version! (auto update in the future)");
}
} else {
println!("no previous db found, creating new");
info.insert("version", &DB_VERSION);
}
Db {
msgs: DbMap::open("msg", &db),
users: DbMap::open("user", &db),
usernames: DbMap::open("username", &db),
db,
}
}
}
impl std::ops::Deref for Db {
type Target = Database;
fn deref(&self) -> &Self::Target {
&self.db
}
}

202
src/bin/server/db/util.rs Normal file
View File

@@ -0,0 +1,202 @@
use std::{marker::PhantomData, path::Path};
use bitcode::{DecodeOwned, Encode};
use fjall::{KeyspaceCreateOptions, OptimisticTxDatabase, OptimisticTxKeyspace, Readable, Slice};
#[derive(Clone)]
pub struct Database(fjall::OptimisticTxDatabase);
pub struct DbMap<K, V> {
db: Database,
keyspace: OptimisticTxKeyspace,
_pd: PhantomData<(K, V)>,
}
impl<K, V> Clone for DbMap<K, V> {
fn clone(&self) -> Self {
Self {
db: self.db.clone(),
keyspace: self.keyspace.clone(),
_pd: self._pd.clone(),
}
}
}
impl<K, V> DbMap<K, V> {
pub fn open(name: &str, db: &Database) -> DbMap<K, V> {
DbMap {
db: db.clone(),
keyspace: db.0.keyspace(name, KeyspaceCreateOptions::default).unwrap(),
_pd: PhantomData,
}
}
}
impl<K: Key<Output = K>, V: Encode + DecodeOwned> DbMap<K, V> {
// TODO: K2 IS NOT A SAFE ABSTRACTION!! need to have KeyLike which has key assoc type
pub fn insert<K2: Key<Output = K> + ?Sized>(&self, k: &K2, v: &V) {
let k = Slice::new(k.to_bytes().as_ref());
let v = Slice::new(&bitcode::encode(v));
self.keyspace.insert(k, v).unwrap();
}
pub fn get<K2: Key<Output = K> + ?Sized>(&self, k: &K2) -> Option<V> {
let k = Slice::new(k.to_bytes().as_ref());
let v = self.keyspace.get(k).unwrap()?;
Some(bitcode::decode(&v).unwrap())
}
pub fn iter(&self) -> impl Iterator<Item = (K, V)> {
self.db.read_tx().iter(self)
}
pub fn values(&self) -> impl Iterator<Item = V> {
self.db.read_tx().values(self)
}
}
impl Database {
pub fn open(path: impl AsRef<Path>) -> Self {
let inner = OptimisticTxDatabase::builder(path.as_ref())
.open()
.expect("failed to open database");
Self(inner)
}
pub fn read_tx(&self) -> ReadTx {
ReadTx(self.0.read_tx())
}
pub fn write_tx(&self) -> WriteTx {
WriteTx(self.0.write_tx().unwrap())
}
}
pub struct ReadTx(fjall::Snapshot);
impl ReadTx {
pub fn values<K, V: DecodeOwned>(
&self,
map: &DbMap<K, V>,
) -> impl Iterator<Item = V> + use<K, V> {
self.0.iter(&map.keyspace).map(|g| {
let v = g.value().unwrap();
bitcode::decode(&v).unwrap()
})
}
pub fn iter<K: Key<Output = K>, V: DecodeOwned>(
&self,
map: &DbMap<K, V>,
) -> impl Iterator<Item = (K, V)> + use<K, V> {
self.0.iter(&map.keyspace).map(|g| {
let (k, v) = g.into_inner().unwrap();
(K::from_bytes(&k), bitcode::decode(&v).unwrap())
})
}
}
pub struct WriteTx(fjall::OptimisticWriteTx);
impl WriteTx {
pub fn values<K, V: DecodeOwned>(
&self,
map: &DbMap<K, V>,
) -> impl Iterator<Item = V> + use<K, V> {
self.0.iter(&map.keyspace).map(|g| {
let v = g.value().unwrap();
bitcode::decode(&v).unwrap()
})
}
pub fn iter<K: Key<Output = K>, V: DecodeOwned>(
&self,
map: &DbMap<K, V>,
) -> impl Iterator<Item = (K, V)> + use<K, V> {
self.0.iter(&map.keyspace).map(|g| {
let (k, v) = g.into_inner().unwrap();
(K::from_bytes(&k), bitcode::decode(&v).unwrap())
})
}
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())
}
pub fn has_key<K: Key<Output = K>, V>(&self, map: &DbMap<K, V>, k: K) -> bool {
let k = Slice::new(k.to_bytes().as_ref());
self.0.get(&map.keyspace, k).unwrap().is_some()
}
// TODO: K2 IS NOT A SAFE ABSTRACTION!! need to have KeyLike which has key assoc type
pub fn insert<K: Key<Output = K>, K2: Key<Output = K> + ?Sized, V: Encode>(
&mut self,
map: &DbMap<K, V>,
k: &K2,
v: &V,
) {
let k = Slice::new(k.to_bytes().as_ref());
let v = Slice::new(&bitcode::encode(v));
self.0.insert(&map.keyspace, k, v);
}
pub fn commit(self) -> bool {
self.0.commit().unwrap().is_ok()
}
}
pub trait Key {
type Input<'a>: AsRef<[u8]>
where
Self: 'a;
type Output;
fn to_bytes(&self) -> Self::Input<'_>;
fn from_bytes(bytes: &[u8]) -> Self::Output;
}
impl Key for String {
type Input<'a> = &'a Self;
type Output = String;
fn to_bytes(&self) -> Self::Input<'_> {
self
}
fn from_bytes(bytes: &[u8]) -> Self {
Self::from_utf8(bytes.to_vec()).unwrap()
}
}
impl Key for str {
type Input<'a> = &'a Self;
type Output = String;
fn to_bytes(&self) -> Self::Input<'_> {
self
}
fn from_bytes(bytes: &[u8]) -> String {
String::from_utf8(bytes.to_vec()).unwrap()
}
}
impl Key for u64 {
type Input<'a> = [u8; 8];
type Output = u64;
fn to_bytes(&self) -> Self::Input<'_> {
self.to_be_bytes()
}
fn from_bytes(bytes: &[u8]) -> Self {
Self::from_be_bytes(bytes.try_into().unwrap())
}
}
impl Key for i128 {
type Input<'a> = [u8; 16];
type Output = i128;
fn to_bytes(&self) -> Self::Input<'_> {
self.to_be_bytes()
}
fn from_bytes(bytes: &[u8]) -> Self {
Self::from_be_bytes(bytes.try_into().unwrap())
}
}

23
src/bin/server/db/ver.rs Normal file
View File

@@ -0,0 +1,23 @@
pub type UserIdV0 = u64;
pub type MsgIdV0 = i128;
pub type ChannelIdV0 = u64;
pub type ImageIdV0 = u64;
#[derive(bitcode::Encode, bitcode::Decode)]
pub struct UserV0 {
pub username: String,
pub password_hash: String,
pub pfp: Option<ImageIdV0>,
pub bio: String,
}
#[derive(bitcode::Encode, bitcode::Decode)]
pub struct MsgV0 {
pub content: String,
pub author: UserIdV0,
}
pub struct ChannelV0 {
pub name: String,
pub desc: String,
}

View File

@@ -1,14 +1,13 @@
// mod data;
mod db;
mod net;
use crate::db::{Db, Msg, User, open_db};
use crate::db::{Db, Msg, User};
use clap::Parser;
use net::{ClientSender, ConAccepter, listen};
use openworm::{
net::{
ClientMsg, ClientMsgInst, CreateAccount, DisconnectReason, LoadMsg, RecvHandler,
ServerError, ServerMsg, install_crypto_provider,
AccountCreated, ClientMsg, ClientRequestMsg, CreateAccount, DisconnectReason, LoadMsg,
RecvHandler, ServerError, ServerMsg, install_crypto_provider,
},
rsc::DataDir,
};
@@ -43,7 +42,7 @@ fn main() {
pub async fn run_server(port: u16) {
let dir = DataDir::default();
let path = dir.get();
let db: Db = open_db(path.join("server.db"));
let db = Db::open(path.join("server_db"));
let handler = ServerListener {
senders: Default::default(),
count: 0.into(),
@@ -56,9 +55,6 @@ pub async fn run_server(port: u16) {
endpoint.close(0u32.into(), &[]);
let _ = handle.await;
endpoint.wait_idle().await;
println!("saving...");
db.flush();
println!("saved");
}
type ClientId = u64;
@@ -76,7 +72,7 @@ pub enum ClientState {
}
impl ConAccepter for ServerListener {
async fn accept(&self, send: ClientSender) -> impl RecvHandler<ClientMsgInst> {
async fn accept(&self, send: ClientSender) -> impl RecvHandler<ClientRequestMsg> {
let id = self.count.fetch_add(1, Ordering::Release);
self.senders.write().await.insert(id, send.clone());
ClientHandler {
@@ -97,29 +93,33 @@ struct ClientHandler {
state: Arc<RwLock<ClientState>>,
}
impl RecvHandler<ClientMsgInst> for ClientHandler {
impl RecvHandler<ClientRequestMsg> for ClientHandler {
async fn connect(&self) -> () {
println!("connected: {:?}", self.send.remote().ip());
}
async fn msg(&self, msg: ClientMsgInst) {
let msg = ClientMsg::from(msg);
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 _ = self.send.send(ServerError::NotLoggedIn).await;
let _ = replier.send(ServerError::NotLoggedIn).await;
return;
};
let msg = Msg {
user: *uid,
author: *uid,
content: msg.content,
};
let id = self.db.generate_id().unwrap();
self.db.msgs.insert(&id, &msg);
// 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(&timestamp, &msg);
let mut handles = Vec::new();
let user: User = self.db.users.get(uid).unwrap();
let msg = LoadMsg {
content: msg.content,
user: user.username,
author: *uid,
};
for (&id, send) in self.senders.read().await.iter() {
if id == self.id {
@@ -128,7 +128,12 @@ impl RecvHandler<ClientMsgInst> for ClientHandler {
let send = send.clone();
let msg = msg.clone();
let fut = async move {
let _ = send.send(msg).await;
let _ = send
.send(LoadMsg {
content: msg.content,
author: msg.author,
})
.await;
};
handles.push(tokio::spawn(fut));
}
@@ -138,27 +143,19 @@ impl RecvHandler<ClientMsgInst> for ClientHandler {
}
ClientMsg::RequestMsgs => {
let ClientState::Authed(_uid) = &*self.state.read().await else {
let _ = self.send.send(ServerError::NotLoggedIn).await;
let _ = replier.send(ServerError::NotLoggedIn).await;
return;
};
let msgs = self
.db
.msgs
.iter_all()
.map(|msg| {
let user = self
.db
.users
.get(&msg.user)
.map(|user| user.username.to_string())
.unwrap_or("deleted user".to_string());
LoadMsg {
content: msg.content,
user,
}
.values()
.map(|msg| LoadMsg {
content: msg.content,
author: msg.author,
})
.collect();
let _ = self.send.send(ServerMsg::LoadMsgs(msgs)).await;
let _ = replier.send(ServerMsg::LoadMsgs(msgs)).await;
}
ClientMsg::CreateAccount(info) => {
let CreateAccount {
@@ -167,28 +164,41 @@ impl RecvHandler<ClientMsgInst> for ClientHandler {
password,
login_key,
} = &info;
if !self.db.usernames.init_unique(username) {
let _ = self.send.send(ServerError::UsernameTaken).await;
return;
}
let id = self.db.generate_id().unwrap();
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();
self.db.users.insert(
&id,
&User {
username: username.clone(),
password_hash: hash,
},
);
let mut id;
loop {
let mut tx = self.db.write_tx();
if tx.has_key(&self.db.usernames, username.clone()) {
let _ = replier.send(ServerError::UsernameTaken).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,
},
);
tx.insert(&self.db.usernames, username, &id);
if tx.commit() {
break;
}
}
println!("account created: \"{username}\"");
self.db.usernames.insert(&username, &id);
*self.state.write().await = ClientState::Authed(id);
// let _ = self.send.send(ServerMsg::Login()).await;
let _ = replier.send(AccountCreated {}).await;
} // ClientMsgType::Login { username, password } => {
// let Some(id) = self.db.usernames.get(&username) else {
// let _ = self.send.send(ServerError::UnknownUsername).await;

View File

@@ -1,5 +1,5 @@
use openworm::net::{
ClientMsg, ClientMsgInst, RecvHandler, SERVER_NAME, SendResult, ServerMsg, ServerMsgInst,
ClientRequestMsg, RecvHandler, RequestId, SERVER_NAME, SendResult, ServerMsg, ServerRespMsg,
recv_uni, send_uni,
};
use quinn::{
@@ -63,8 +63,34 @@ impl ClientSender {
pub fn remote(&self) -> SocketAddr {
self.conn.remote_address()
}
pub fn replier(&self, id: RequestId) -> ClientReplier {
ClientReplier {
conn: self.conn.clone(),
req_id: id,
}
}
pub async fn send(&self, msg: impl Into<ServerMsg>) -> SendResult {
let msg = ServerMsgInst::from(msg.into());
let msg = ServerRespMsg {
id: None,
msg: msg.into().into(),
};
send_uni(&self.conn, msg).await
}
}
pub struct ClientReplier {
conn: Connection,
req_id: RequestId,
}
impl ClientReplier {
pub async fn send(&self, msg: impl Into<ServerMsg>) -> SendResult {
let msg = ServerRespMsg {
id: Some(self.req_id),
msg: msg.into().into(),
};
send_uni(&self.conn, msg).await
}
}
@@ -73,7 +99,7 @@ pub trait ConAccepter: Send + Sync + 'static {
fn accept(
&self,
send: ClientSender,
) -> impl Future<Output = impl RecvHandler<ClientMsgInst>> + Send;
) -> impl Future<Output = impl RecvHandler<ClientRequestMsg>> + Send;
}
pub fn listen(

View File

@@ -17,6 +17,8 @@ pub enum ServerMsgInst {
ServerErrorV0(ServerErrorV0) = 3,
}
pub type UserIdV0 = u64;
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct CreateAccountV0 {
pub username: String,
@@ -56,7 +58,7 @@ pub struct SendMsgV0 {
#[derive(Debug, Clone, bitcode::Encode, bitcode::Decode)]
pub struct LoadMsgV0 {
pub content: String,
pub user: String,
pub author: UserIdV0,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]

View File

@@ -21,6 +21,7 @@ pub type LoadMsg = LoadMsgV0;
pub type ServerError = ServerErrorV0;
pub type CreateAccount = CreateAccountV0;
pub type AccountCreated = AccountCreatedV0;
pub type UserId = UserIdV0;
impl From<CreateAccount> for ClientMsg {
fn from(value: CreateAccount) -> Self {
@@ -33,3 +34,15 @@ impl From<ServerError> for ServerMsg {
Self::ServerError(value)
}
}
impl From<LoadMsg> for ServerMsg {
fn from(value: LoadMsg) -> Self {
Self::LoadMsg(value)
}
}
impl From<AccountCreated> for ServerMsg {
fn from(value: AccountCreated) -> Self {
Self::AccountCreated(value)
}
}

View File

@@ -21,12 +21,12 @@ impl RequestId {
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct ClientRequestMsg {
pub request_id: Option<RequestId>,
pub id: RequestId,
pub msg: ClientMsgInst,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct ServerRespMsg {
pub request_id: Option<RequestId>,
pub id: Option<RequestId>,
pub msg: ServerMsgInst,
}