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
54/// Bidirectional peer link for direct data exchange.
55///
56/// Abstracts the underlying byte stream so the data protocol can be shared
57/// across WebRTC, BLE, and mock links.
58#[async_trait]
59pub trait PeerLink: Send + Sync {
60    /// Send data to the peer.
61    async fn send(&self, data: Vec<u8>) -> Result<(), TransportError>;
62
63    /// Receive data from the peer.
64    async fn recv(&self) -> Option<Vec<u8>>;
65
66    /// Try to receive data without blocking.
67    /// Returns `None` when no message is currently available.
68    fn try_recv(&self) -> Option<Vec<u8>> {
69        None
70    }
71
72    /// Check if the link is open.
73    fn is_open(&self) -> bool;
74
75    /// Close the link.
76    async fn close(&self);
77}
78
79/// Factory for creating negotiated direct peer links.
80///
81/// When we receive an offer and want to accept, or when we want to
82/// initiate a connection, this factory creates the appropriate link.
83#[async_trait]
84pub trait PeerLinkFactory: Send + Sync {
85    /// Create an outgoing negotiated link.
86    /// Returns `(our_link, offer_sdp)`.
87    async fn create_offer(
88        &self,
89        target_peer_id: &str,
90    ) -> Result<(Arc<dyn PeerLink>, String), TransportError>;
91
92    /// Accept an incoming negotiated link.
93    /// Returns `(our_link, answer_sdp)`.
94    async fn accept_offer(
95        &self,
96        from_peer_id: &str,
97        offer_sdp: &str,
98    ) -> Result<(Arc<dyn PeerLink>, String), TransportError>;
99
100    /// Complete a link after receiving the answer.
101    async fn handle_answer(
102        &self,
103        target_peer_id: &str,
104        answer_sdp: &str,
105    ) -> Result<Arc<dyn PeerLink>, TransportError>;
106
107    /// Apply a trickle candidate update for an in-flight link, if relevant.
108    async fn handle_candidate(
109        &self,
110        _peer_id: &str,
111        _candidate: IceCandidate,
112    ) -> Result<(), TransportError> {
113        Ok(())
114    }
115
116    /// Apply a batch of trickle candidate updates.
117    async fn handle_candidates(
118        &self,
119        peer_id: &str,
120        candidates: Vec<IceCandidate>,
121    ) -> Result<(), TransportError> {
122        for candidate in candidates {
123            self.handle_candidate(peer_id, candidate).await?;
124        }
125        Ok(())
126    }
127
128    /// Drop any factory-owned state for a peer that has been removed.
129    async fn remove_peer(&self, _peer_id: &str) -> Result<(), TransportError> {
130        Ok(())
131    }
132}
133
134// Blanket implementations for Arc<T> to allow calling trait methods on Arc-wrapped transports
135
136#[async_trait]
137impl<T: SignalingTransport + ?Sized> SignalingTransport for Arc<T> {
138    async fn connect(&self, relays: &[String]) -> Result<(), TransportError> {
139        (**self).connect(relays).await
140    }
141
142    async fn disconnect(&self) {
143        (**self).disconnect().await
144    }
145
146    async fn publish(&self, msg: SignalingMessage) -> Result<(), TransportError> {
147        (**self).publish(msg).await
148    }
149
150    async fn recv(&self) -> Option<SignalingMessage> {
151        (**self).recv().await
152    }
153
154    fn try_recv(&self) -> Option<SignalingMessage> {
155        (**self).try_recv()
156    }
157
158    fn peer_id(&self) -> &str {
159        (**self).peer_id()
160    }
161}
162
163#[async_trait]
164impl<T: PeerLink + ?Sized> PeerLink for Arc<T> {
165    async fn send(&self, data: Vec<u8>) -> Result<(), TransportError> {
166        (**self).send(data).await
167    }
168
169    async fn recv(&self) -> Option<Vec<u8>> {
170        (**self).recv().await
171    }
172
173    fn try_recv(&self) -> Option<Vec<u8>> {
174        (**self).try_recv()
175    }
176
177    fn is_open(&self) -> bool {
178        (**self).is_open()
179    }
180
181    async fn close(&self) {
182        (**self).close().await
183    }
184}