Skip to main content

hashtree_network/
transport.rs

1//! Signaling and peer-link transport abstractions
2//!
3//! Defines traits for signaling transports and direct peer links that can be
4//! implemented by Nostr websockets, LAN buses, BLE, WebRTC, or mocks.
5
6use async_trait::async_trait;
7use std::sync::Arc;
8use thiserror::Error;
9
10use crate::types::{IceCandidate, SignalingMessage};
11
12/// Errors from signaling and peer-link transport operations.
13#[derive(Debug, Error, Clone)]
14pub enum TransportError {
15    #[error("Connection failed: {0}")]
16    ConnectionFailed(String),
17    #[error("Send failed: {0}")]
18    SendFailed(String),
19    #[error("Receive failed: {0}")]
20    ReceiveFailed(String),
21    #[error("Timeout")]
22    Timeout,
23    #[error("Disconnected")]
24    Disconnected,
25    #[error("Not connected")]
26    NotConnected,
27}
28
29/// Signaling transport for peer discovery and negotiation messages.
30///
31/// Abstracts the message bus used to exchange signaling frames so router logic
32/// can be shared between production and simulation.
33#[async_trait]
34pub trait SignalingTransport: Send + Sync {
35    /// Connect the signaling transport and start listening.
36    async fn connect(&self, relays: &[String]) -> Result<(), TransportError>;
37
38    /// Disconnect from the signaling transport.
39    async fn disconnect(&self);
40
41    /// Publish a signaling message to the transport.
42    async fn publish(&self, msg: SignalingMessage) -> Result<(), TransportError>;
43
44    /// Receive the next signaling message (blocking).
45    async fn recv(&self) -> Option<SignalingMessage>;
46
47    /// Try to receive without blocking.
48    fn try_recv(&self) -> Option<SignalingMessage>;
49
50    /// Get our peer ID.
51    fn peer_id(&self) -> &str;
52
53    /// Get our public key.
54    fn pubkey(&self) -> &str;
55}
56
57pub use SignalingTransport as RelayTransport;
58
59/// Bidirectional peer link for direct data exchange.
60///
61/// Abstracts the underlying byte stream so the data protocol can be shared
62/// across WebRTC, BLE, and mock links.
63#[async_trait]
64pub trait PeerLink: Send + Sync {
65    /// Send data to the peer.
66    async fn send(&self, data: Vec<u8>) -> Result<(), TransportError>;
67
68    /// Receive data from the peer.
69    async fn recv(&self) -> Option<Vec<u8>>;
70
71    /// Try to receive data without blocking.
72    /// Returns `None` when no message is currently available.
73    fn try_recv(&self) -> Option<Vec<u8>> {
74        None
75    }
76
77    /// Check if the link is open.
78    fn is_open(&self) -> bool;
79
80    /// Close the link.
81    async fn close(&self);
82}
83
84pub use PeerLink as DataChannel;
85
86/// Factory for creating negotiated direct peer links.
87///
88/// When we receive an offer and want to accept, or when we want to
89/// initiate a connection, this factory creates the appropriate link.
90#[async_trait]
91pub trait PeerLinkFactory: Send + Sync {
92    /// Create an outgoing negotiated link.
93    /// Returns `(our_link, offer_sdp)`.
94    async fn create_offer(
95        &self,
96        target_peer_id: &str,
97    ) -> Result<(Arc<dyn PeerLink>, String), TransportError>;
98
99    /// Accept an incoming negotiated link.
100    /// Returns `(our_link, answer_sdp)`.
101    async fn accept_offer(
102        &self,
103        from_peer_id: &str,
104        offer_sdp: &str,
105    ) -> Result<(Arc<dyn PeerLink>, String), TransportError>;
106
107    /// Complete a link after receiving the answer.
108    async fn handle_answer(
109        &self,
110        target_peer_id: &str,
111        answer_sdp: &str,
112    ) -> Result<Arc<dyn PeerLink>, TransportError>;
113
114    /// Apply a trickle candidate update for an in-flight link, if relevant.
115    async fn handle_candidate(
116        &self,
117        _peer_id: &str,
118        _candidate: IceCandidate,
119    ) -> Result<(), TransportError> {
120        Ok(())
121    }
122
123    /// Apply a batch of trickle candidate updates.
124    async fn handle_candidates(
125        &self,
126        peer_id: &str,
127        candidates: Vec<IceCandidate>,
128    ) -> Result<(), TransportError> {
129        for candidate in candidates {
130            self.handle_candidate(peer_id, candidate).await?;
131        }
132        Ok(())
133    }
134
135    /// Drop any factory-owned state for a peer that has been removed.
136    async fn remove_peer(&self, _peer_id: &str) -> Result<(), TransportError> {
137        Ok(())
138    }
139}
140
141pub use PeerLinkFactory as PeerConnectionFactory;
142
143/// Configuration for generic router behavior.
144#[derive(Debug, Clone)]
145pub struct MeshRouterConfig {
146    /// Our peer ID
147    pub peer_id: String,
148    /// Maximum number of peers to connect to
149    pub max_peers: usize,
150    /// Interval between hello broadcasts (ms)
151    pub hello_interval_ms: u64,
152    /// Root hashes to advertise in hello messages
153    pub roots: Vec<String>,
154    /// Enable debug logging
155    pub debug: bool,
156}
157
158impl Default for MeshRouterConfig {
159    fn default() -> Self {
160        Self {
161            peer_id: String::new(),
162            max_peers: 10,
163            hello_interval_ms: 30000,
164            roots: Vec::new(),
165            debug: false,
166        }
167    }
168}
169
170pub type SignalingConfig = MeshRouterConfig;
171
172// Blanket implementations for Arc<T> to allow calling trait methods on Arc-wrapped transports
173
174#[async_trait]
175impl<T: SignalingTransport + ?Sized> SignalingTransport for Arc<T> {
176    async fn connect(&self, relays: &[String]) -> Result<(), TransportError> {
177        (**self).connect(relays).await
178    }
179
180    async fn disconnect(&self) {
181        (**self).disconnect().await
182    }
183
184    async fn publish(&self, msg: SignalingMessage) -> Result<(), TransportError> {
185        (**self).publish(msg).await
186    }
187
188    async fn recv(&self) -> Option<SignalingMessage> {
189        (**self).recv().await
190    }
191
192    fn try_recv(&self) -> Option<SignalingMessage> {
193        (**self).try_recv()
194    }
195
196    fn peer_id(&self) -> &str {
197        (**self).peer_id()
198    }
199
200    fn pubkey(&self) -> &str {
201        (**self).pubkey()
202    }
203}
204
205#[async_trait]
206impl<T: PeerLink + ?Sized> PeerLink for Arc<T> {
207    async fn send(&self, data: Vec<u8>) -> Result<(), TransportError> {
208        (**self).send(data).await
209    }
210
211    async fn recv(&self) -> Option<Vec<u8>> {
212        (**self).recv().await
213    }
214
215    fn try_recv(&self) -> Option<Vec<u8>> {
216        (**self).try_recv()
217    }
218
219    fn is_open(&self) -> bool {
220        (**self).is_open()
221    }
222
223    async fn close(&self) {
224        (**self).close().await
225    }
226}