Skip to main content

rings_core/swarm/
callback.rs

1#[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
2use std::cell::Cell;
3use std::str::FromStr;
4use std::sync::Arc;
5use std::sync::Mutex;
6
7use async_trait::async_trait;
8use bytes::Bytes;
9use futures::lock::Mutex as FuturesMutex;
10use rings_transport::core::callback::AdmittedInboundMessage;
11use rings_transport::core::callback::InboundFrameCapacityLease;
12use rings_transport::core::callback::TransportCallback;
13use rings_transport::core::transport::WebrtcConnectionState;
14
15use crate::chunk::MessageReassembler;
16use crate::dht::Did;
17use crate::measure::Authentication;
18use crate::message::with_message_variants;
19use crate::message::HandleMsg;
20use crate::message::Message;
21use crate::message::MessageHandler;
22use crate::message::MessageKind;
23use crate::message::MessagePayload;
24use crate::message::MessageVerificationExt;
25use crate::swarm::transport::ConnectionEventDisposition;
26use crate::swarm::transport::PendingConnectionAttempt;
27use crate::swarm::transport::SwarmTransport;
28
29mod inbound;
30pub(crate) use inbound::InboundCapacity;
31pub(crate) use inbound::InboundLane;
32
33#[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
34pub(crate) const fn inbound_mailbox_capacity_for_test() -> usize {
35    inbound::capacity_for_test()
36}
37
38#[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
39pub(crate) const fn inbound_application_capacity_for_test() -> usize {
40    inbound::application_capacity_for_test()
41}
42
43#[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
44pub(crate) const fn inbound_peer_capacity_for_test() -> usize {
45    inbound::peer_capacity_for_test()
46}
47use inbound::InboundMailbox;
48use inbound::ReassemblyCleanupClock;
49
50pub use crate::error::CallbackError;
51type TransportCallbackError = Box<dyn std::error::Error>;
52
53fn into_transport_callback_error(error: CallbackError) -> TransportCallbackError {
54    error
55}
56
57fn log_inbound_verification_failure(
58    peer: Option<Did>,
59    payload: &MessagePayload,
60    wire_bytes: usize,
61) {
62    let message_kind = MessageKind::from_wire(&payload.transaction.data)
63        .ok()
64        .map(MessageKind::as_str);
65    tracing::error!(
66        peer = ?peer,
67        tx_id = %payload.transaction.tx_id,
68        destination = %payload.transaction.destination,
69        message_kind,
70        data_bytes = payload.transaction.data.len(),
71        wire_bytes,
72        "inbound message verification failed or expired"
73    );
74}
75
76#[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
77thread_local! {
78    static ON_MESSAGE_RECURSION_DEPTH: Cell<usize> = const { Cell::new(0) };
79    static MAX_ON_MESSAGE_RECURSION_DEPTH: Cell<usize> = const { Cell::new(0) };
80}
81
82#[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
83struct OnMessageRecursionDepthGuard;
84
85#[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
86impl OnMessageRecursionDepthGuard {
87    fn enter() -> Self {
88        ON_MESSAGE_RECURSION_DEPTH.with(|depth| {
89            let current = depth.get().saturating_add(1);
90            depth.set(current);
91            MAX_ON_MESSAGE_RECURSION_DEPTH.with(|max_depth| {
92                max_depth.set(max_depth.get().max(current));
93            });
94        });
95        Self
96    }
97}
98
99#[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
100impl Drop for OnMessageRecursionDepthGuard {
101    fn drop(&mut self) {
102        ON_MESSAGE_RECURSION_DEPTH.with(|depth| {
103            depth.set(depth.get().saturating_sub(1));
104        });
105    }
106}
107
108#[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
109pub(crate) fn reset_on_message_recursion_depth_for_test() {
110    ON_MESSAGE_RECURSION_DEPTH.with(|depth| depth.set(0));
111    MAX_ON_MESSAGE_RECURSION_DEPTH.with(|depth| depth.set(0));
112}
113
114#[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
115pub(crate) fn max_on_message_recursion_depth_for_test() -> usize {
116    MAX_ON_MESSAGE_RECURSION_DEPTH.with(Cell::get)
117}
118
119/// The [InnerSwarmCallback] will accept shared [SwarmCallback] trait object.
120#[cfg(all(feature = "wasm", target_family = "wasm"))]
121pub type SharedSwarmCallback = Arc<dyn SwarmCallback>;
122
123/// The [InnerSwarmCallback] will accept shared [SwarmCallback] trait object.
124#[cfg(not(all(feature = "wasm", target_family = "wasm")))]
125pub type SharedSwarmCallback = Arc<dyn SwarmCallback + Send + Sync>;
126
127/// Used to notify the application of events that occur in the swarm.
128#[derive(Debug)]
129#[non_exhaustive]
130pub enum SwarmEvent {
131    /// Indicates that the connection state of a peer has changed.
132    ConnectionStateChange {
133        /// The did of remote peer.
134        peer: Did,
135        /// The final state of the connection.
136        state: WebrtcConnectionState,
137    },
138}
139
140/// Any object that implements this trait can be used as a callback for the swarm.
141#[cfg_attr(all(feature = "wasm", target_family = "wasm"), async_trait(?Send))]
142#[cfg_attr(not(all(feature = "wasm", target_family = "wasm")), async_trait)]
143pub trait SwarmCallback {
144    /// This method is invoked when a new message is received and before handling.
145    ///
146    /// The swarm enforces a deadline and cancels this future if it expires.
147    /// Implementations must therefore be cancellation-safe at every suspension.
148    async fn on_validate(&self, _payload: &MessagePayload) -> Result<(), CallbackError> {
149        Ok(())
150    }
151
152    /// This method is invoked when a new message is received and after handling.
153    /// Will not be invoked if the message is not for this node.
154    ///
155    /// The swarm enforces a deadline and cancels this future if it expires.
156    /// Implementations must therefore be cancellation-safe at every suspension.
157    async fn on_inbound(&self, _payload: &MessagePayload) -> Result<(), CallbackError> {
158        Ok(())
159    }
160
161    /// This method is invoked after the Swarm handling.
162    ///
163    /// Connection events for one peer have an **ordered-start** contract when delivered by the
164    /// swarm: `start(A) < start(B)` in transport order. A callback releases that ordering turn
165    /// after its first poll, so `A` and `B` may remain suspended concurrently and completion is
166    /// not serialized. Events for different peers are unordered.
167    ///
168    /// Implementations must publish any state that later same-peer callbacks need before their
169    /// first suspension point. Work after an `.await` must tolerate overlap; callers that need
170    /// completion ordering should add an application-owned sequencer instead of relying on the
171    /// swarm delivery turn.
172    async fn on_event(&self, _event: &SwarmEvent) -> Result<(), CallbackError> {
173        Ok(())
174    }
175}
176
177#[derive(Clone)]
178pub(super) struct InboundProcessor {
179    transport: Arc<SwarmTransport>,
180    message_handler: MessageHandler,
181    callback: SharedSwarmCallback,
182    reassembler: Arc<FuturesMutex<MessageReassembler>>,
183    pending_attempt: Arc<Mutex<Option<PendingConnectionAttempt>>>,
184}
185
186/// [InnerSwarmCallback] wraps [SharedSwarmCallback] with inner handling for a specific connection.
187pub struct InnerSwarmCallback {
188    processor: InboundProcessor,
189    inbound: InboundMailbox,
190}
191
192impl InboundProcessor {
193    fn pending_attempt(&self) -> Option<PendingConnectionAttempt> {
194        *self
195            .pending_attempt
196            .lock()
197            .unwrap_or_else(std::sync::PoisonError::into_inner)
198    }
199
200    fn set_pending_attempt(&self, attempt: PendingConnectionAttempt) {
201        *self
202            .pending_attempt
203            .lock()
204            .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(attempt);
205    }
206
207    fn peer_authentication(&self, peer: Did) -> Authentication {
208        let Some(attempt) = self.pending_attempt() else {
209            return Authentication::Unauthenticated;
210        };
211        if attempt.peer() == peer && self.transport.is_admitted_connection_attempt(attempt) {
212            Authentication::Authenticated
213        } else {
214            Authentication::Unauthenticated
215        }
216    }
217
218    pub(super) async fn record_receive_failure(
219        &self,
220        peer: Option<Did>,
221        authentication: Authentication,
222    ) {
223        if let Some(peer) = peer {
224            self.transport
225                .record_peer_message_receive_failed(peer, authentication)
226                .await;
227        }
228    }
229}
230
231impl InnerSwarmCallback {
232    fn pending_attempt(&self) -> Option<PendingConnectionAttempt> {
233        self.processor.pending_attempt()
234    }
235
236    /// Create a new [InnerSwarmCallback] with the provided transport and callback.
237    pub fn new(transport: Arc<SwarmTransport>, callback: SharedSwarmCallback) -> Self {
238        Self::new_with_reassembly_cleanup_clock(
239            transport,
240            callback,
241            ReassemblyCleanupClock::system(),
242        )
243    }
244
245    fn new_with_reassembly_cleanup_clock(
246        transport: Arc<SwarmTransport>,
247        callback: SharedSwarmCallback,
248        cleanup_clock: ReassemblyCleanupClock,
249    ) -> Self {
250        let inbound_capacity = transport.inbound_capacity();
251        let message_handler = MessageHandler::new(transport.clone(), callback.clone());
252        let reassembler = MessageReassembler::with_limits_and_budget(
253            transport.reassembly_limits(),
254            transport.reassembly_budget(),
255        );
256        let processor = InboundProcessor {
257            transport,
258            message_handler,
259            callback,
260            reassembler: Arc::new(FuturesMutex::new(reassembler)),
261            pending_attempt: Arc::new(Mutex::new(None)),
262        };
263        let inbound = InboundMailbox::spawn(processor.clone(), inbound_capacity, cleanup_clock);
264        Self { processor, inbound }
265    }
266
267    #[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
268    /// Construct an inbound actor with an injected periodic-cleanup clock.
269    pub(crate) fn new_with_reassembly_cleanup_clock_for_test(
270        transport: Arc<SwarmTransport>,
271        callback: SharedSwarmCallback,
272        now_ms: Arc<Mutex<u128>>,
273    ) -> Self {
274        Self::new_with_reassembly_cleanup_clock(
275            transport,
276            callback,
277            ReassemblyCleanupClock::controlled(now_ms),
278        )
279    }
280
281    /// Bind this callback to the pending handshake that created its transport.
282    pub(crate) fn with_pending_connection_attempt(
283        self,
284        pending_attempt: PendingConnectionAttempt,
285    ) -> Self {
286        self.processor.set_pending_attempt(pending_attempt);
287        self
288    }
289
290    #[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
291    pub(crate) fn inbound_admitted_count_for_test(&self) -> usize {
292        self.inbound.admitted_count_for_test()
293    }
294
295    #[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
296    pub(crate) fn hold_application_admission_for_test(&self) -> crate::error::Result<impl Drop> {
297        self.inbound.hold_application_admission_for_test()
298    }
299
300    #[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
301    pub(crate) fn hold_application_capacity_for_test(
302        &self,
303        peer: Did,
304    ) -> crate::error::Result<impl Drop> {
305        self.inbound.hold_application_capacity_for_test(peer)
306    }
307
308    #[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
309    pub(crate) fn close_inbound_for_test(&self) {
310        self.inbound.close_for_test();
311    }
312
313    async fn admit_pending_connection(&self, did: Did) -> Result<bool, CallbackError> {
314        let Some(attempt) = self.pending_attempt() else {
315            return Ok(false);
316        };
317        if attempt.peer() != did {
318            tracing::warn!(
319                "ignoring data-channel open for {did}; pending attempt belongs to {}",
320                attempt.peer()
321            );
322            self.processor
323                .transport
324                .cancel_pending_connection(attempt)
325                .await?;
326            return Ok(false);
327        }
328        if !self
329            .processor
330            .transport
331            .begin_ready_connection_admission(attempt)?
332        {
333            return Ok(false);
334        }
335
336        match self
337            .processor
338            .message_handler
339            .admit_dht_attempt(attempt)
340            .await
341        {
342            Ok(true) => {}
343            Ok(false) => return Ok(false),
344            Err(error) => {
345                if let Err(cleanup_error) = self
346                    .processor
347                    .transport
348                    .cancel_pending_connection(attempt)
349                    .await
350                {
351                    tracing::warn!(
352                        peer = %did,
353                        generation = attempt.generation(),
354                        error = ?cleanup_error,
355                        "failed to close connection after admission error"
356                    );
357                }
358                return Err(error.into());
359            }
360        }
361
362        self.processor
363            .transport
364            .record_peer_connected(attempt)
365            .await;
366        if !self
367            .processor
368            .transport
369            .is_admitted_connection_attempt(attempt)
370        {
371            return Ok(false);
372        }
373        self.emit_connected_event_for_attempt(did, attempt).await
374    }
375
376    async fn emit_connected_event_for_attempt(
377        &self,
378        did: Did,
379        attempt: PendingConnectionAttempt,
380    ) -> Result<bool, CallbackError> {
381        let delivery = self.processor.transport.swarm_event_delivery_lock(did);
382        let result = async {
383            let delivery_turn = delivery.acquire().await;
384            if !self
385                .processor
386                .transport
387                .is_admitted_connection_attempt(attempt)
388            {
389                tracing::debug!("suppressing connected event for {did}; connection was retired before event delivery");
390                return Ok(false);
391            }
392            self.emit_connection_state_change_after_ordered_start(
393                delivery_turn,
394                did,
395                WebrtcConnectionState::Connected,
396            )
397            .await?;
398            Ok(true)
399        }
400        .await;
401        self.processor
402            .transport
403            .prune_swarm_event_delivery_lock(did, &delivery);
404        result
405    }
406
407    async fn emit_connection_state_change(
408        &self,
409        did: Did,
410        state: WebrtcConnectionState,
411        attempt: Option<PendingConnectionAttempt>,
412    ) -> Result<(), CallbackError> {
413        let delivery = self.processor.transport.swarm_event_delivery_lock(did);
414        let result = async {
415            let delivery_turn = delivery.acquire().await;
416            if let Some(attempt) = attempt {
417                match self
418                    .processor
419                    .transport
420                    .connection_event_disposition(attempt)?
421                {
422                    ConnectionEventDisposition::Deliver => {}
423                    ConnectionEventDisposition::Suppress { active } => {
424                        tracing::debug!(
425                            peer = %did,
426                            generation = attempt.generation(),
427                            active_generation = active.generation(),
428                            state = ?state,
429                            "suppressing connection event from superseded generation"
430                        );
431                        return Ok(());
432                    }
433                }
434            }
435            self.emit_connection_state_change_after_ordered_start(delivery_turn, did, state)
436                .await
437        }
438        .await;
439        self.processor
440            .transport
441            .prune_swarm_event_delivery_lock(did, &delivery);
442        result
443    }
444
445    async fn emit_connection_state_change_after_ordered_start(
446        &self,
447        delivery_turn: crate::swarm::transport::SwarmEventDeliveryTurn,
448        did: Did,
449        state: WebrtcConnectionState,
450    ) -> Result<(), CallbackError> {
451        let event = SwarmEvent::ConnectionStateChange { peer: did, state };
452        delivery_turn
453            .poll_once_then_release(self.processor.callback.on_event(&event))
454            .await
455    }
456
457    fn pending_disconnected_before_admission(&self, did: Did) -> bool {
458        let Some(attempt) = self.pending_attempt() else {
459            return false;
460        };
461        attempt.peer() == did
462            && !self
463                .processor
464                .transport
465                .is_admitted_connection_attempt(attempt)
466    }
467
468    fn is_local_did_event(&self, did: Did, operation: &str) -> bool {
469        if did != self.processor.transport.dht.did {
470            return false;
471        }
472        tracing::warn!("ignoring {operation} for local DID {did}");
473        true
474    }
475
476    async fn cancel_mismatched_pending_connection(
477        &self,
478        did: Did,
479        operation: &str,
480    ) -> Result<bool, CallbackError> {
481        let Some(attempt) = self.pending_attempt() else {
482            return Ok(false);
483        };
484        if attempt.peer() == did {
485            return Ok(false);
486        }
487        tracing::warn!(
488            "ignoring {operation} for {did}; pending attempt belongs to {}",
489            attempt.peer()
490        );
491        if self
492            .processor
493            .transport
494            .cancel_pending_connection(attempt)
495            .await?
496        {
497            self.processor
498                .transport
499                .record_peer_disconnected(attempt)
500                .await;
501        }
502        Ok(true)
503    }
504
505    async fn handle_pending_terminal_event(
506        &self,
507        did: Did,
508        operation: &str,
509    ) -> Result<bool, CallbackError> {
510        let Some(attempt) = self.pending_attempt() else {
511            return Ok(false);
512        };
513        if self
514            .processor
515            .transport
516            .cancel_pending_connection(attempt)
517            .await?
518        {
519            self.processor
520                .transport
521                .record_peer_disconnected(attempt)
522                .await;
523            return Ok(true);
524        }
525        if self
526            .processor
527            .transport
528            .is_admitted_connection_attempt(attempt)
529        {
530            return Ok(false);
531        }
532        tracing::debug!(
533            "ignoring late {operation} for {did}; pending attempt belongs to generation already superseded"
534        );
535        Ok(true)
536    }
537}
538
539impl InboundProcessor {
540    pub(super) async fn pending_connection_allows_message(
541        &self,
542        peer: Option<Did>,
543    ) -> crate::error::Result<bool> {
544        let Some(attempt) = self.pending_attempt() else {
545            return Ok(true);
546        };
547        let Some(peer) = peer else {
548            tracing::warn!(
549                "ignoring message from unparsable peer; pending attempt belongs to {}",
550                attempt.peer()
551            );
552            return Ok(false);
553        };
554        if attempt.peer() != peer {
555            tracing::warn!(
556                "ignoring message from {peer}; pending attempt belongs to {}",
557                attempt.peer()
558            );
559            self.transport.cancel_pending_connection(attempt).await?;
560            return Ok(false);
561        }
562        if !self.transport.is_admitted_connection_attempt(attempt) {
563            tracing::debug!("ignoring message from {peer}; pending connection is not admitted yet");
564            return Ok(false);
565        }
566        Ok(true)
567    }
568
569    pub(super) async fn handle_payload(
570        &self,
571        payload: &MessagePayload,
572        prepared_message: Option<Message>,
573    ) -> crate::error::Result<()> {
574        let message = match prepared_message {
575            Some(message) => message,
576            None => payload.transaction.data()?,
577        };
578
579        macro_rules! dispatch_message_body {
580            (Chunk, $msg:expr) => {{
581                let _ = $msg;
582                Err(crate::error::Error::InboundActorInvariantViolation)
583            }};
584            ($variant:ident, $msg:expr) => {
585                self.message_handler.handle(payload, $msg).await
586            };
587        }
588        macro_rules! dispatch_message {
589            ($( $(#[$docs:meta])* $index:literal => $variant:ident($body:ty): $class:ident, $storage_route:ident ),+ $(,)?) => {
590                match message {
591                    $(Message::$variant(ref msg) => dispatch_message_body!($variant, msg)),+
592                }
593            };
594        }
595
596        let result = with_message_variants!(dispatch_message);
597
598        // A handler that errored must not then be reported to the application as a successful
599        // inbound message: surface the error and do not run `on_inbound` for it.
600        if let Err(e) = result {
601            tracing::error!("Failed to handle_payload: {e:?}");
602            return Err(e);
603        }
604
605        Ok(())
606    }
607
608    pub(super) fn is_local_destination(&self, payload: &MessagePayload) -> bool {
609        payload.transaction.destination == self.transport.dht.did
610    }
611
612    pub(super) async fn on_inbound(
613        &self,
614        payload: &MessagePayload,
615    ) -> std::result::Result<(), CallbackError> {
616        self.callback.on_inbound(payload).await
617    }
618
619    pub(super) async fn decode_verified_payload(
620        &self,
621        peer: Option<Did>,
622        authentication: Authentication,
623        msg: &[u8],
624    ) -> crate::error::Result<MessagePayload> {
625        let payload = match MessagePayload::from_wire(msg) {
626            Ok(payload) => payload,
627            Err(e) => {
628                self.record_receive_failure(peer, authentication).await;
629                return Err(e);
630            }
631        };
632        if !(payload.verify() && payload.transaction.verify()) {
633            log_inbound_verification_failure(peer, &payload, msg.len());
634            self.record_receive_failure(peer, authentication).await;
635            return Err(crate::error::Error::InvalidMessage(
636                "message verification failed or message expired".to_string(),
637            ));
638        }
639        Ok(payload)
640    }
641
642    pub(super) async fn validate_preverified_payload(
643        &self,
644        peer: Option<Did>,
645        authentication: Authentication,
646        payload: &MessagePayload,
647    ) -> crate::error::Result<()> {
648        if payload.is_expired() || payload.transaction.is_expired() {
649            self.record_receive_failure(peer, authentication).await;
650            return Err(crate::error::Error::InvalidMessage(
651                "message expired after transport admission".to_string(),
652            ));
653        }
654        Ok(())
655    }
656
657    pub(super) async fn accept_verified_logical_message(
658        &self,
659        peer: Option<Did>,
660        authentication: Authentication,
661        payload: MessagePayload,
662    ) -> crate::error::Result<MessagePayload> {
663        self.validate_preverified_payload(peer, authentication, &payload)
664            .await?;
665        let useful_bytes = u64::try_from(payload.transaction.data.len())
666            .map_err(|_| crate::error::Error::MessageSizeOverflow)?;
667        if let (Some(peer), Some(attempt)) = (peer, self.pending_attempt()) {
668            if attempt.peer() == peer {
669                self.transport
670                    .record_peer_message_received(attempt, authentication, useful_bytes)
671                    .await;
672            }
673        }
674        Ok(payload)
675    }
676}
677
678pub(super) struct PreparedInboundFrame {
679    payload: MessagePayload,
680    message: Message,
681    kind: MessageKind,
682    lane: InboundLane,
683}
684
685fn prepare_transport_frame(
686    peer: Option<Did>,
687    bytes: &[u8],
688) -> crate::error::Result<PreparedInboundFrame> {
689    let payload = MessagePayload::from_wire(bytes)?;
690    if !(payload.transaction.verify() && payload.verify()) {
691        log_inbound_verification_failure(peer, &payload, bytes.len());
692        return Err(crate::error::Error::InvalidMessage(
693            "message verification failed or message expired".to_string(),
694        ));
695    }
696    let message = payload.transaction.data::<Message>()?;
697    let kind = MessageKind::from_message(&message);
698    let lane = InboundLane::from_kind(kind);
699    Ok(PreparedInboundFrame {
700        payload,
701        message,
702        kind,
703        lane,
704    })
705}
706
707#[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
708pub(crate) fn prepare_transport_frame_lane_for_test(
709    bytes: &[u8],
710) -> crate::error::Result<InboundLane> {
711    prepare_transport_frame(None, bytes).map(|prepared| prepared.lane)
712}
713
714impl InnerSwarmCallback {
715    async fn submit_inbound_message(
716        &self,
717        cid: &str,
718        msg: Bytes,
719        transport_capacity: Option<InboundFrameCapacityLease>,
720    ) -> Result<(), TransportCallbackError> {
721        #[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
722        let _depth_guard = OnMessageRecursionDepthGuard::enter();
723
724        let peer = Did::from_str(cid).ok();
725        let authentication = peer.map_or(Authentication::Unauthenticated, |peer| {
726            self.processor.peer_authentication(peer)
727        });
728        let prepared = match prepare_transport_frame(peer, msg.as_ref()) {
729            Ok(prepared) => prepared,
730            Err(error) => {
731                self.processor
732                    .record_receive_failure(peer, authentication)
733                    .await;
734                return Err(error.into());
735            }
736        };
737        self.inbound
738            .submit_prepared(
739                &self.processor,
740                peer,
741                authentication,
742                msg,
743                prepared,
744                transport_capacity,
745            )
746            .await
747            .map_err(Into::into)
748    }
749
750    #[cfg(all(test, feature = "dummy", not(target_family = "wasm")))]
751    pub(crate) async fn on_admitted_message_for_test(
752        &self,
753        cid: &str,
754        msg: &[u8],
755    ) -> Result<(), TransportCallbackError> {
756        self.submit_inbound_message(cid, Bytes::copy_from_slice(msg), None)
757            .await
758    }
759}
760
761#[cfg_attr(all(feature = "wasm", target_family = "wasm"), async_trait(?Send))]
762#[cfg_attr(not(all(feature = "wasm", target_family = "wasm")), async_trait)]
763impl TransportCallback for InnerSwarmCallback {
764    async fn on_admitted_message(
765        &self,
766        message: AdmittedInboundMessage<'_>,
767    ) -> Result<(), TransportCallbackError> {
768        let (cid, msg, transport_capacity) = message.into_parts();
769        self.submit_inbound_message(cid, msg, Some(transport_capacity))
770            .await
771    }
772
773    async fn on_invalid_inbound_frame(&self, cid: &str) -> Result<(), TransportCallbackError> {
774        let peer = Did::from_str(cid).ok();
775        let authentication = peer.map_or(Authentication::Unauthenticated, |peer| {
776            self.processor.peer_authentication(peer)
777        });
778        self.processor
779            .record_receive_failure(peer, authentication)
780            .await;
781        Ok(())
782    }
783
784    async fn on_peer_connection_state_change(
785        &self,
786        cid: &str,
787        s: WebrtcConnectionState,
788    ) -> Result<(), TransportCallbackError> {
789        let Ok(did) = Did::from_str(cid) else {
790            tracing::warn!("on_peer_connection_state_change parse did failed: {}", cid);
791            return Ok(());
792        };
793        if self
794            .cancel_mismatched_pending_connection(did, "connection state change")
795            .await
796            .map_err(into_transport_callback_error)?
797        {
798            return Ok(());
799        }
800        if self.is_local_did_event(did, "connection state change") {
801            return Ok(());
802        }
803
804        let admission_completed = match s {
805            // Peer-state progress may complete admission, but only when the
806            // product snapshot also observes an open data channel. This makes
807            // either browser callback order converge on the same transition.
808            WebrtcConnectionState::Connecting | WebrtcConnectionState::Connected => self
809                .admit_pending_connection(did)
810                .await
811                .map_err(into_transport_callback_error)?,
812            // `Failed` and `Closed` are terminal states. Pending handshakes are
813            // discarded without touching the DHT; active peers leave it.
814            WebrtcConnectionState::Failed | WebrtcConnectionState::Closed => {
815                if self
816                    .handle_pending_terminal_event(did, "connection terminal state")
817                    .await
818                    .map_err(into_transport_callback_error)?
819                {
820                    return Ok(());
821                }
822                let Some(attempt) = self.pending_attempt() else {
823                    tracing::warn!("ignoring unbound terminal connection event for {did}");
824                    return Ok(());
825                };
826                if !self
827                    .processor
828                    .transport
829                    .is_admitted_connection_attempt(attempt)
830                {
831                    return Ok(());
832                }
833                self.processor
834                    .transport
835                    .record_peer_disconnected(attempt)
836                    .await;
837                self.processor
838                    .message_handler
839                    .leave_dht_attempt(attempt)
840                    .await?;
841                false
842            }
843            // `Disconnected` is a transient ICE state that frequently recovers
844            // back to `Connected` on its own (e.g. a brief network blip or ICE
845            // consent refresh). Tearing the connection down here would kill a
846            // link that WebRTC could have healed, and drop the peer from the DHT
847            // with no reconnect path. We leave it alone: it will either recover,
848            // or degrade to `Failed`, which is handled above.
849            WebrtcConnectionState::Disconnected => {
850                if self.pending_disconnected_before_admission(did) {
851                    tracing::debug!(
852                        "ignoring pre-admission disconnected state for pending connection {did}"
853                    );
854                    return Ok(());
855                }
856                let Some(attempt) = self.pending_attempt() else {
857                    tracing::warn!("ignoring unbound disconnected connection event for {did}");
858                    return Ok(());
859                };
860                self.processor
861                    .transport
862                    .record_peer_disconnected(attempt)
863                    .await;
864                tracing::debug!("Connection to {did} is disconnected, waiting for recovery");
865                false
866            }
867            _ => false,
868        };
869
870        // Data-channel admission emits the application-level Connected event.
871        // Other state changes are passed through directly, unless this exact
872        // callback completed admission and already emitted the ordered Connected event.
873        if s != WebrtcConnectionState::Connected && !admission_completed {
874            self.emit_connection_state_change(did, s, self.pending_attempt())
875                .await
876                .map_err(into_transport_callback_error)?
877        }
878
879        Ok(())
880    }
881
882    async fn on_data_channel_open(&self, cid: &str) -> Result<(), TransportCallbackError> {
883        let Ok(did) = Did::from_str(cid) else {
884            tracing::warn!("on_data_channel_open parse did failed: {}", cid);
885            return Ok(());
886        };
887        if self
888            .cancel_mismatched_pending_connection(did, "data-channel open")
889            .await
890            .map_err(into_transport_callback_error)?
891        {
892            return Ok(());
893        }
894        if self.is_local_did_event(did, "data-channel open") {
895            return Ok(());
896        }
897
898        if !self
899            .admit_pending_connection(did)
900            .await
901            .map_err(into_transport_callback_error)?
902            && !self.processor.transport.is_admitted_connection(did)
903        {
904            tracing::debug!("ignoring late data-channel open for {did}");
905        }
906        Ok(())
907    }
908
909    async fn on_data_channel_close(&self, cid: &str) -> Result<(), TransportCallbackError> {
910        let Ok(did) = Did::from_str(cid) else {
911            tracing::warn!("on_data_channel_close parse did failed: {}", cid);
912            return Ok(());
913        };
914        if self
915            .cancel_mismatched_pending_connection(did, "data-channel close")
916            .await
917            .map_err(into_transport_callback_error)?
918        {
919            return Ok(());
920        }
921        if self.is_local_did_event(did, "data-channel close") {
922            return Ok(());
923        }
924
925        // The data channel closing is a reliable signal that the peer is gone
926        // (e.g. it closed the connection), so tear the connection down now
927        // instead of waiting for the ICE state to reach `Failed`. This is the
928        // graceful counterpart to a local `disconnect()`: the remote learns of
929        // it promptly without relying on the transient `Disconnected` state.
930        if self
931            .handle_pending_terminal_event(did, "data-channel close")
932            .await
933            .map_err(into_transport_callback_error)?
934        {
935            return Ok(());
936        }
937        let Some(attempt) = self.pending_attempt() else {
938            tracing::warn!("ignoring unbound data-channel close for {did}");
939            return Ok(());
940        };
941        if !self
942            .processor
943            .transport
944            .is_admitted_connection_attempt(attempt)
945        {
946            return Ok(());
947        }
948        self.processor
949            .transport
950            .record_peer_disconnected(attempt)
951            .await;
952        self.processor
953            .message_handler
954            .leave_dht_attempt(attempt)
955            .await?;
956        Ok(())
957    }
958}