use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
use tokio::sync::{mpsc, oneshot, RwLock};
use crate::core::SyncState;
#[derive(Debug, Error)]
pub enum ClientError {
#[error("connection failed: {0}")]
ConnectionFailed(String),
#[error("handshake failed: {0}")]
HandshakeFailed(String),
#[error("session terminated: {0}")]
SessionTerminated(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("sync error: {0}")]
SyncError(String),
#[error("client disconnected")]
Disconnected,
#[error("operation timed out")]
Timeout,
}
#[derive(Debug, Clone)]
pub struct ClientConfig {
pub server_addr: SocketAddr,
pub server_public_key: [u8; 32],
pub client_private_key: Option<[u8; 32]>,
pub connect_timeout: Duration,
pub enable_compression: bool,
}
impl Default for ClientConfig {
fn default() -> Self {
Self {
server_addr: "127.0.0.1:19999".parse().unwrap(),
server_public_key: [0u8; 32],
client_private_key: None,
connect_timeout: Duration::from_secs(10),
enable_compression: true,
}
}
}
#[derive(Debug)]
pub struct NomadClientBuilder {
config: ClientConfig,
}
impl NomadClientBuilder {
pub fn new() -> Self {
Self {
config: ClientConfig::default(),
}
}
pub fn server_addr(mut self, addr: SocketAddr) -> Self {
self.config.server_addr = addr;
self
}
pub fn server_public_key(mut self, key: [u8; 32]) -> Self {
self.config.server_public_key = key;
self
}
pub fn client_private_key(mut self, key: [u8; 32]) -> Self {
self.config.client_private_key = Some(key);
self
}
pub fn connect_timeout(mut self, timeout: Duration) -> Self {
self.config.connect_timeout = timeout;
self
}
pub fn compression(mut self, enabled: bool) -> Self {
self.config.enable_compression = enabled;
self
}
pub fn build(self) -> ClientConfig {
self.config
}
}
impl Default for NomadClientBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClientState {
Disconnected,
Connecting,
Connected,
Closed,
}
pub struct StateSender<S: SyncState> {
tx: mpsc::Sender<S>,
}
impl<S: SyncState> StateSender<S> {
pub async fn send(&self, state: S) -> Result<(), ClientError> {
self.tx
.send(state)
.await
.map_err(|_| ClientError::Disconnected)
}
}
impl<S: SyncState> Clone for StateSender<S> {
fn clone(&self) -> Self {
Self {
tx: self.tx.clone(),
}
}
}
pub struct StateReceiver<S: SyncState> {
rx: mpsc::Receiver<S>,
}
impl<S: SyncState> StateReceiver<S> {
pub async fn recv(&mut self) -> Option<S> {
self.rx.recv().await
}
}
pub struct NomadClient<S: SyncState> {
state: Arc<RwLock<ClientState>>,
local_state: Arc<RwLock<S>>,
state_tx: mpsc::Sender<S>,
shutdown_tx: Option<oneshot::Sender<()>>,
config: ClientConfig,
}
impl<S: SyncState> NomadClient<S> {
pub async fn connect(
config: ClientConfig,
initial_state: S,
) -> Result<(Self, StateReceiver<S>), ClientError> {
let (state_tx, _state_rx) = mpsc::channel::<S>(32);
let (server_state_tx, server_state_rx) = mpsc::channel::<S>(32);
let (shutdown_tx, _shutdown_rx) = oneshot::channel();
let client_state = Arc::new(RwLock::new(ClientState::Connecting));
let local_state = Arc::new(RwLock::new(initial_state));
{
let mut state = client_state.write().await;
*state = ClientState::Connected;
}
let _io_state = client_state.clone();
let _io_local = local_state.clone();
let _io_config = config.clone();
let _io_server_tx = server_state_tx;
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(1)).await;
}
});
let client = Self {
state: client_state,
local_state,
state_tx,
shutdown_tx: Some(shutdown_tx),
config,
};
let receiver = StateReceiver { rx: server_state_rx };
Ok((client, receiver))
}
pub async fn client_state(&self) -> ClientState {
*self.state.read().await
}
pub async fn local_state(&self) -> S {
self.local_state.read().await.clone()
}
pub async fn update_state(&self, new_state: S) -> Result<(), ClientError> {
{
let mut state = self.local_state.write().await;
*state = new_state.clone();
}
self.state_tx
.send(new_state)
.await
.map_err(|_| ClientError::Disconnected)
}
pub fn state_sender(&self) -> StateSender<S> {
StateSender {
tx: self.state_tx.clone(),
}
}
pub async fn is_connected(&self) -> bool {
matches!(*self.state.read().await, ClientState::Connected)
}
pub async fn disconnect(mut self) -> Result<(), ClientError> {
if let Some(tx) = self.shutdown_tx.take() {
let _ = tx.send(());
}
{
let mut state = self.state.write().await;
*state = ClientState::Closed;
}
Ok(())
}
pub fn server_addr(&self) -> SocketAddr {
self.config.server_addr
}
}
impl<S: SyncState> Drop for NomadClient<S> {
fn drop(&mut self) {
if let Some(tx) = self.shutdown_tx.take() {
let _ = tx.send(());
}
}
}
#[cfg(test)]
mod tests {
}