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 payload = MessagePayload::from_bincode(msg)?;
158        if !(payload.verify() && payload.transaction.verify()) {
159            tracing::error!("Cannot verify msg or it's expired: {:?}", payload);
160            return Err("Cannot verify msg or it's expired".into());
161        }
162        self.callback.on_validate(&payload).await?;
163        self.handle_payload(cid, &payload).await
164    }
165
166    async fn on_peer_connection_state_change(
167        &self,
168        cid: &str,
169        s: WebrtcConnectionState,
170    ) -> Result<(), CallbackError> {
171        let Ok(did) = Did::from_str(cid) else {
172            tracing::warn!("on_peer_connection_state_change parse did failed: {}", cid);
173            return Ok(());
174        };
175
176        match s {
177            // `Failed` and `Closed` are terminal states, so we remove the peer
178            // from the DHT here.
179            WebrtcConnectionState::Failed | WebrtcConnectionState::Closed => {
180                self.message_handler.leave_dht(did).await?;
181            }
182            // `Disconnected` is a transient ICE state that frequently recovers
183            // back to `Connected` on its own (e.g. a brief network blip or ICE
184            // consent refresh). Tearing the connection down here would kill a
185            // link that WebRTC could have healed, and drop the peer from the DHT
186            // with no reconnect path. We leave it alone: it will either recover,
187            // or degrade to `Failed`, which is handled above.
188            WebrtcConnectionState::Disconnected => {
189                tracing::info!("Connection to {did} is disconnected, waiting for recovery");
190            }
191            _ => {}
192        };
193
194        // Should use the `on_data_channel_open` function to notify the Connected state.
195        // It prevents users from blocking the channel creation while
196        // waiting for data channel opening in send_message.
197        if s != WebrtcConnectionState::Connected {
198            self.callback
199                .on_event(&SwarmEvent::ConnectionStateChange {
200                    peer: did,
201                    state: s,
202                })
203                .await?
204        }
205
206        Ok(())
207    }
208
209    async fn on_data_channel_open(&self, cid: &str) -> Result<(), CallbackError> {
210        let Ok(did) = Did::from_str(cid) else {
211            tracing::warn!("on_data_channel_open parse did failed: {}", cid);
212            return Ok(());
213        };
214
215        self.message_handler.join_dht(did).await?;
216
217        // Notify Connected state here instead of on_peer_connection_state_change.
218        // It prevents users from blocking the channel creation while
219        // waiting for data channel opening in send_message.
220        self.callback
221            .on_event(&SwarmEvent::ConnectionStateChange {
222                peer: did,
223                state: WebrtcConnectionState::Connected,
224            })
225            .await
226    }
227
228    async fn on_data_channel_close(&self, cid: &str) -> Result<(), CallbackError> {
229        let Ok(did) = Did::from_str(cid) else {
230            tracing::warn!("on_data_channel_close parse did failed: {}", cid);
231            return Ok(());
232        };
233
234        // The data channel closing is a reliable signal that the peer is gone
235        // (e.g. it closed the connection), so tear the connection down now
236        // instead of waiting for the ICE state to reach `Failed`. This is the
237        // graceful counterpart to a local `disconnect()`: the remote learns of
238        // it promptly without relying on the transient `Disconnected` state.
239        self.message_handler.leave_dht(did).await?;
240        Ok(())
241    }
242}