server side preparation

This commit is contained in:
2026-02-18 21:52:22 -05:00
parent 1c6b6de8f6
commit 6fad5b1852
15 changed files with 547 additions and 535 deletions

View File

@@ -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;
}
}
}

View 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)
// }

View File

@@ -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
View 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,
}

View File

@@ -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);

View File

@@ -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())

View File

@@ -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
View 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(&timestamp, &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;
// }

View File

@@ -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(&timestamp, &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(_))
}
}

View File

@@ -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(