subduction_websocket 0.8.0

WebSocket transport layer for the Subduction sync protocol
Documentation
//! Unified WebSocket connection type for both accepted and dialed connections.

use alloc::vec::Vec;

use crate::{
    error::{DisconnectionError, RecvError, RunError, SendError},
    websocket::WebSocket,
};

use async_tungstenite::tokio::{ConnectStream, TokioAdapter};
use future_form::Sendable;
use futures::future::BoxFuture;

use subduction_core::transport::Transport;
use tokio::net::TcpStream;

/// A unified WebSocket connection that can hold either an accepted or dialed connection.
///
/// This allows the server to use the same `Subduction` instance for both
/// accepting incoming connections and dialing outgoing connections to peers.
#[derive(Debug, Clone)]
pub enum UnifiedWebSocket {
    /// A connection we accepted (peer connected to us).
    Accepted(WebSocket<TokioAdapter<TcpStream>, Sendable>),

    /// A connection we dialed (we connected to peer).
    Dialed(WebSocket<ConnectStream, Sendable>),
}

impl UnifiedWebSocket {
    /// Start listening for incoming messages.
    ///
    /// # Errors
    ///
    /// Returns an error if the WebSocket connection fails.
    pub async fn listen(&self) -> Result<(), RunError> {
        match self {
            UnifiedWebSocket::Accepted(in_ws) => in_ws.listen().await,
            UnifiedWebSocket::Dialed(out_ws) => out_ws.listen().await,
        }
    }
}

impl Transport<Sendable> for UnifiedWebSocket {
    type SendError = SendError;
    type RecvError = RecvError;
    type DisconnectionError = DisconnectionError;

    fn disconnect(&self) -> BoxFuture<'_, Result<(), Self::DisconnectionError>> {
        match self {
            UnifiedWebSocket::Accepted(in_ws) => Transport::<Sendable>::disconnect(in_ws),
            UnifiedWebSocket::Dialed(out_ws) => Transport::<Sendable>::disconnect(out_ws),
        }
    }

    fn send_bytes(&self, bytes: &[u8]) -> BoxFuture<'_, Result<(), Self::SendError>> {
        match self {
            UnifiedWebSocket::Accepted(in_ws) => Transport::<Sendable>::send_bytes(in_ws, bytes),
            UnifiedWebSocket::Dialed(out_ws) => Transport::<Sendable>::send_bytes(out_ws, bytes),
        }
    }

    fn recv_bytes(&self) -> BoxFuture<'_, Result<Vec<u8>, Self::RecvError>> {
        match self {
            UnifiedWebSocket::Accepted(in_ws) => Transport::<Sendable>::recv_bytes(in_ws),
            UnifiedWebSocket::Dialed(out_ws) => Transport::<Sendable>::recv_bytes(out_ws),
        }
    }
}

/// [`PartialEq`] compares connections only within the same variant type.
///
/// An `Accepted` connection is never equal to a `Dialed` connection, even if they
/// represent a connection to the same peer. This is intentional: the same physical
/// peer connection cannot be both accepted and dialed simultaneously. However, this
/// means a server could have two separate connections to the same peer (one where
/// they connected to us, one where we connected to them). If this is undesirable,
/// deduplication should be handled at a higher level using peer IDs.
impl PartialEq for UnifiedWebSocket {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (UnifiedWebSocket::Accepted(a), UnifiedWebSocket::Accepted(b)) => a == b,
            (UnifiedWebSocket::Dialed(a), UnifiedWebSocket::Dialed(b)) => a == b,
            _ => false,
        }
    }
}