Skip to main content

rings_core/swarm/
callback.rs

1use std::str::FromStr;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use futures::lock::Mutex as FuturesMutex;
6use rings_transport::core::callback::TransportCallback;
7use rings_transport::core::transport::WebrtcConnectionState;
8
9use crate::chunk::MessageReassembler;
10use crate::dht::Did;
11use crate::message::HandleMsg;
12use crate::message::Message;
13use crate::message::MessageHandler;
14use crate::message::MessagePayload;
15use crate::message::MessageVerificationExt;
16use crate::swarm::transport::SwarmTransport;
17
18type CallbackError = Box<dyn std::error::Error>;
19
20/// The [InnerSwarmCallback] will accept shared [SwarmCallback] trait object.
21#[cfg(feature = "wasm")]
22pub type SharedSwarmCallback = Arc<dyn SwarmCallback>;
23
24/// The [InnerSwarmCallback] will accept shared [SwarmCallback] trait object.
25#[cfg(not(feature = "wasm"))]
26pub type SharedSwarmCallback = Arc<dyn SwarmCallback + Send + Sync>;
27
28/// Used to notify the application of events that occur in the swarm.
29#[derive(Debug)]
30#[non_exhaustive]
31pub enum SwarmEvent {
32    /// Indicates that the connection state of a peer has changed.
33    ConnectionStateChange {
34        /// The did of remote peer.
35        peer: Did,
36        /// The final state of the connection.
37        state: WebrtcConnectionState,
38    },
39}
40
41/// Any object that implements this trait can be used as a callback for the swarm.
42#[cfg_attr(feature = "wasm", async_trait(?Send))]
43#[cfg_attr(not(feature = "wasm"), async_trait)]
44pub trait SwarmCallback {
45    /// This method is invoked when a new message is received and before handling.
46    async fn on_validate(&self, _payload: &MessagePayload) -> Result<(), CallbackError> {
47        Ok(())
48    }
49
50    /// This method is invoked when a new message is received and after handling.
51    /// Will not be invoked if the message is not for this node.
52    async fn on_inbound(&self, _payload: &MessagePayload) -> Result<(), CallbackError> {
53        Ok(())
54    }
55
56    /// This method is invoked after the Swarm handling.
57    async fn on_event(&self, _event: &SwarmEvent) -> Result<(), CallbackError> {
58        Ok(())
59    }
60}
61
62/// [InnerSwarmCallback] wraps [SharedSwarmCallback] with inner handling for a specific connection.
63pub struct InnerSwarmCallback {
64    transport: Arc<SwarmTransport>,
65    message_handler: MessageHandler,
66    callback: SharedSwarmCallback,
67    reassembler: FuturesMutex<MessageReassembler>,
68}
69
70impl InnerSwarmCallback {
71    /// Create a new [InnerSwarmCallback] with the provided transport and callback.
72    pub fn new(transport: Arc<SwarmTransport>, callback: SharedSwarmCallback) -> Self {
73        let message_handler = MessageHandler::new(transport.clone(), callback.clone());
74        let reassembler = MessageReassembler::with_limits(transport.reassembly_limits());
75        Self {
76            transport,
77            message_handler,
78            callback,
79            reassembler: FuturesMutex::new(reassembler),
80        }
81    }
82
83    async fn handle_payload(
84        &self,
85        cid: &str,
86        payload: &MessagePayload,
87    ) -> Result<(), CallbackError> {
88        let message: Message = payload.transaction.data()?;
89
90        let result = match &message {
91            Message::ConnectNodeSend(ref msg) => self.message_handler.handle(payload, msg).await,
92            Message::ConnectNodeReport(ref msg) => self.message_handler.handle(payload, msg).await,
93            Message::FindSuccessorSend(ref msg) => self.message_handler.handle(payload, msg).await,
94            Message::FindSuccessorReport(ref msg) => {
95                self.message_handler.handle(payload, msg).await
96            }
97            Message::NotifyPredecessorSend(ref msg) => {
98                self.message_handler.handle(payload, msg).await
99            }
100            Message::NotifyPredecessorReport(ref msg) => {
101                self.message_handler.handle(payload, msg).await
102            }
103            Message::SearchEntry(ref msg) => self.message_handler.handle(payload, msg).await,
104            Message::FoundEntry(ref msg) => self.message_handler.handle(payload, msg).await,
105            Message::SyncEntriesWithSuccessor(ref msg) => {
106                self.message_handler.handle(payload, msg).await
107            }
108            Message::SyncEntriesWithSuccessorReport(ref msg) => {
109                self.message_handler.handle(payload, msg).await
110            }
111            Message::OperateEntry(ref msg) => self.message_handler.handle(payload, msg).await,
112            Message::CustomMessage(ref msg) => self.message_handler.handle(payload, msg).await,
113            Message::E2eHandshakeRequest(ref msg) => {
114                self.message_handler.handle(payload, msg).await
115            }
116            Message::E2eHandshakeResponse(ref msg) => {
117                self.message_handler.handle(payload, msg).await
118            }
119            Message::E2eStreamFrame(ref msg) => self.message_handler.handle(payload, msg).await,
120            Message::QueryForTopoInfoSend(ref msg) => {
121                self.message_handler.handle(payload, msg).await
122            }
123            Message::QueryForTopoInfoReport(ref msg) => {
124                self.message_handler.handle(payload, msg).await
125            }
126            Message::Chunk(ref msg) => {
127                // A chunk is an internal framing envelope, never an application message. When it
128                // completes a payload, re-enter with the reassembled bytes; when it does not, there
129                // is nothing to deliver. Either way we return here so the raw chunk envelope is
130                // *never* passed to `on_inbound` (the app only ever sees reassembled messages).
131                if let Some(data) = self.reassembler.lock().await.handle(msg.clone()) {
132                    return self.on_message(cid, &data).await;
133                }
134                return Ok(());
135            }
136        };
137
138        // A handler that errored must not then be reported to the application as a successful
139        // inbound message: surface the error and do not run `on_inbound` for it.
140        if let Err(e) = result {
141            tracing::error!("Failed to handle_payload: {e:?}");
142            return Err(e.into());
143        }
144
145        if payload.transaction.destination == self.transport.dht.did {
146            self.callback.on_inbound(payload).await?;
147        }
148
149        Ok(())
150    }
151}
152
153#[cfg_attr(feature = "wasm", async_trait(?Send))]
154#[cfg_attr(not(feature = "wasm"), async_trait)]
155impl TransportCallback for InnerSwarmCallback {
156    async fn on_message(&self, cid: &str, msg: &[u8]) -> Result<(), CallbackError> {
157        let peer = Did::from_str(cid).ok();
158        let payload = match MessagePayload::from_bincode(msg) {
159            Ok(payload) => payload,
160            Err(e) => {
161                if let Some(peer) = peer {
162                    self.transport
163                        .record_peer_message_receive_failed(peer)
164                        .await;
165                }
166                return Err(e.into());
167            }
168        };
169        if !(payload.verify() && payload.transaction.verify()) {
170            tracing::error!("Cannot verify msg or it's expired: {:?}", payload);
171            if let Some(peer) = peer {
172                self.transport
173                    .record_peer_message_receive_failed(peer)
174                    .await;
175            }
176            return Err("Cannot verify msg or it's expired".into());
177        }
178        if let Some(peer) = peer {
179            self.transport.record_peer_message_received(peer).await;
180        }
181        self.callback.on_validate(&payload).await?;
182        self.handle_payload(cid, &payload).await
183    }
184
185    async fn on_peer_connection_state_change(
186        &self,
187        cid: &str,
188        s: WebrtcConnectionState,
189    ) -> Result<(), CallbackError> {
190        let Ok(did) = Did::from_str(cid) else {
191            tracing::warn!("on_peer_connection_state_change parse did failed: {}", cid);
192            return Ok(());
193        };
194
195        match s {
196            // `Failed` and `Closed` are terminal states, so we remove the peer
197            // from the DHT here.
198            WebrtcConnectionState::Failed | WebrtcConnectionState::Closed => {
199                self.transport.record_peer_disconnected(did).await;
200                self.message_handler.leave_dht(did).await?;
201            }
202            // `Disconnected` is a transient ICE state that frequently recovers
203            // back to `Connected` on its own (e.g. a brief network blip or ICE
204            // consent refresh). Tearing the connection down here would kill a
205            // link that WebRTC could have healed, and drop the peer from the DHT
206            // with no reconnect path. We leave it alone: it will either recover,
207            // or degrade to `Failed`, which is handled above.
208            WebrtcConnectionState::Disconnected => {
209                self.transport.record_peer_disconnected(did).await;
210                tracing::info!("Connection to {did} is disconnected, waiting for recovery");
211            }
212            _ => {}
213        };
214
215        // Should use the `on_data_channel_open` function to notify the Connected state.
216        // It prevents users from blocking the channel creation while
217        // waiting for data channel opening in send_message.
218        if s != WebrtcConnectionState::Connected {
219            self.callback
220                .on_event(&SwarmEvent::ConnectionStateChange {
221                    peer: did,
222                    state: s,
223                })
224                .await?
225        }
226
227        Ok(())
228    }
229
230    async fn on_data_channel_open(&self, cid: &str) -> Result<(), CallbackError> {
231        let Ok(did) = Did::from_str(cid) else {
232            tracing::warn!("on_data_channel_open parse did failed: {}", cid);
233            return Ok(());
234        };
235
236        self.transport.record_peer_connected(did).await;
237        self.message_handler.join_dht(did).await?;
238
239        // Notify Connected state here instead of on_peer_connection_state_change.
240        // It prevents users from blocking the channel creation while
241        // waiting for data channel opening in send_message.
242        self.callback
243            .on_event(&SwarmEvent::ConnectionStateChange {
244                peer: did,
245                state: WebrtcConnectionState::Connected,
246            })
247            .await
248    }
249
250    async fn on_data_channel_close(&self, cid: &str) -> Result<(), CallbackError> {
251        let Ok(did) = Did::from_str(cid) else {
252            tracing::warn!("on_data_channel_close parse did failed: {}", cid);
253            return Ok(());
254        };
255
256        // The data channel closing is a reliable signal that the peer is gone
257        // (e.g. it closed the connection), so tear the connection down now
258        // instead of waiting for the ICE state to reach `Failed`. This is the
259        // graceful counterpart to a local `disconnect()`: the remote learns of
260        // it promptly without relying on the transient `Disconnected` state.
261        self.transport.record_peer_disconnected(did).await;
262        self.message_handler.leave_dht(did).await?;
263        Ok(())
264    }
265}