Skip to main content

rings_node/processor/
mod.rs

1//! Processor of rings-node rpc server.
2
3use std::num::NonZeroUsize;
4use std::str::FromStr;
5use std::sync::Arc;
6use std::time::Duration;
7
8use futures::future::join_all;
9use rings_core::chunk::ReassemblyLimits;
10use rings_core::dht::Did;
11use rings_core::dht::EntryStorage;
12use rings_core::dht::DEFAULT_FINGER_TABLE_SIZE;
13use rings_core::ecc::PublicKey;
14use rings_core::ecc::SecretKey;
15use rings_core::lifecycle::StopSource;
16use rings_core::lifecycle::StopToken;
17use rings_core::measure::MeasureImpl;
18use rings_core::measure::PeerMeasurement;
19use rings_core::measure::PeerMeasurementPage;
20use rings_core::measure::PeerQuality;
21use rings_core::message::e2e;
22use rings_core::message::e2e::E2eHandshakeRequest;
23use rings_core::message::e2e::E2eHandshakeResponse;
24use rings_core::message::e2e::E2eStreamDecryptor;
25use rings_core::message::e2e::E2eStreamFrame;
26use rings_core::message::DhtProtocolMode;
27use rings_core::message::Encoded;
28use rings_core::message::Encoder;
29use rings_core::message::Message;
30use rings_core::storage::MemStorage;
31use rings_core::swarm::Swarm;
32use rings_core::swarm::SwarmBuilder;
33use rings_core::utils::get_epoch_ms;
34use rings_rpc::protos::rings_node::*;
35use rings_transport::webrtc_config::WebrtcUdpPortRange;
36use serde::Deserialize;
37use serde::Serialize;
38use uuid;
39
40use crate::consts::DATA_REDUNDANT;
41use crate::error::Error;
42use crate::error::Result;
43use crate::measure::PeriodicMeasure;
44use crate::onion::default_advertise_onion_exit;
45use crate::onion::default_advertise_onion_relay;
46use crate::onion::default_onion_exit_heartbeat_interval_secs;
47use crate::onion::default_onion_exit_policy;
48use crate::onion::default_onion_exit_services;
49use crate::onion::default_onion_exit_ttl_secs;
50use crate::onion::directory;
51use crate::onion::directory::OnionDirectoryReader;
52use crate::onion::https_onion_exit_services;
53use crate::onion::proxy::OnionProxyConfig;
54use crate::onion::proxy::OnionProxyRoute;
55use crate::onion::proxy::OnionProxyTarget;
56#[cfg(all(feature = "browser", target_family = "wasm"))]
57use crate::onion::proxy::ONION_PROXY_HTTPS_SERVICE;
58use crate::onion::validate_onion_exit_registration_timing;
59use crate::onion::OnionExitDescriptor;
60use crate::onion::OnionExitPolicy;
61use crate::onion::OnionExitRegistration;
62use crate::onion::OnionExitService;
63use crate::onion::OnionRoute;
64use crate::onion::ONION_EXITS_TOPIC;
65use crate::onion::ONION_RELAY_CAPABILITY;
66use crate::online::OnlineNodeDescriptor;
67use crate::online::OnlineNodeType;
68use crate::online::ONLINE_NODES_TOPIC;
69use crate::prelude::entry;
70use crate::prelude::wasm_export;
71use crate::prelude::ChordStorageInterface;
72use crate::prelude::ChordStorageInterfaceCacheChecker;
73use crate::prelude::SessionSk;
74use crate::registration::default_advertise_presence;
75use crate::registration::default_online_node_heartbeat_interval_secs;
76use crate::registration::default_online_node_ttl_secs;
77use crate::registration::default_online_node_type;
78use crate::registration::sleep_registration_interval;
79use crate::registration::validate_online_node_registration_timing;
80use crate::registration::OnlineNodeRegistration;
81use crate::registration::RegistrationContext;
82use crate::registration::RegistrationTask;
83
84const MEASUREMENT_FLUSH_TIMEOUT: Duration = Duration::from_secs(5);
85
86mod builder;
87mod config;
88
89pub use builder::ProcessorBuilder;
90#[cfg(feature = "node")]
91pub(crate) use config::parse_webrtc_udp_port_range;
92pub use config::ProcessorConfig;
93pub use config::ProcessorConfigSerialized;
94
95const DHT_LOOKUP_CACHE_POLL_INTERVAL: Duration = Duration::from_millis(50);
96const DHT_LOOKUP_CACHE_POLL_ATTEMPTS: usize = 40;
97const REGISTRATION_STOP_POLL_INTERVAL: Duration = Duration::from_millis(50);
98
99#[cfg(not(all(feature = "browser", target_family = "wasm")))]
100async fn sleep_dht_lookup_poll_interval(interval: Duration) -> Result<()> {
101    futures_timer::Delay::new(interval).await;
102    Ok(())
103}
104
105#[cfg(all(feature = "browser", target_family = "wasm"))]
106async fn sleep_dht_lookup_poll_interval(interval: Duration) -> Result<()> {
107    let interval_ms = i32::try_from(interval.as_millis()).unwrap_or(i32::MAX);
108    rings_core::utils::js_utils::window_sleep(interval_ms)
109        .await
110        .map_err(|error| Error::JsError(format!("{error:?}")))?;
111    Ok(())
112}
113
114async fn sleep_registration_interval_with_stop(
115    interval: Duration,
116    stop: &StopToken,
117    sibling_stop: &StopToken,
118) -> Result<bool> {
119    let mut remaining = interval;
120    while !remaining.is_zero() {
121        if stop.should_stop() || sibling_stop.should_stop() {
122            return Ok(false);
123        }
124        let step = std::cmp::min(remaining, REGISTRATION_STOP_POLL_INTERVAL);
125        sleep_registration_interval(step).await?;
126        remaining = remaining.saturating_sub(step);
127    }
128    Ok(!(stop.should_stop() || sibling_stop.should_stop()))
129}
130
131/// Processor for rings-node rpc server.
132///
133/// Cloning shares the same node handle; publishes from any clone are serialized
134/// against each other.
135#[derive(Clone)]
136pub struct Processor {
137    /// a swarm instance
138    pub swarm: Arc<Swarm>,
139    /// Same session key held by the swarm transport; kept here for node-layer descriptor signing.
140    session_sk: SessionSk,
141    stabilize_interval: Duration,
142    online_node_registration: OnlineNodeRegistration,
143    measure: Option<Arc<PeriodicMeasure>>,
144    #[cfg(all(feature = "browser", target_family = "wasm"))]
145    advertise_onion_relay: bool,
146    registration_tasks: Vec<Arc<dyn RegistrationTask>>,
147}
148
149impl Processor {
150    /// Get current did
151    pub fn did(&self) -> Did {
152        self.swarm.did()
153    }
154
155    pub(crate) fn session_sk(&self) -> &SessionSk {
156        &self.session_sk
157    }
158
159    #[cfg(all(feature = "browser", target_family = "wasm"))]
160    pub(crate) fn advertise_onion_relay(&self) -> bool {
161        self.advertise_onion_relay
162    }
163
164    fn registration_context(&self) -> RegistrationContext<'_> {
165        RegistrationContext::new(self)
166    }
167
168    fn registration_context_with_stop(&self, stop: StopToken) -> RegistrationContext<'_> {
169        RegistrationContext::new_with_stop(self, stop)
170    }
171
172    pub(crate) fn add_online_node_capabilities<I>(&self, capabilities: I) -> Result<()>
173    where I: IntoIterator<Item = &'static str> {
174        self.online_node_registration.add_capabilities(capabilities)
175    }
176
177    #[cfg(all(test, feature = "node"))]
178    fn online_node_descriptor_at(&self, now_ms: u128) -> Result<OnlineNodeDescriptor> {
179        self.online_node_registration
180            .descriptor_at(&self.registration_context(), now_ms)
181    }
182
183    fn online_node_descriptors_from_entry(entry: &entry::Entry) -> Vec<OnlineNodeDescriptor> {
184        OnlineNodeRegistration::descriptors_from_entry(entry)
185    }
186
187    fn onion_exit_descriptors_from_entry(entry: &entry::Entry) -> Vec<OnionExitDescriptor> {
188        OnionExitRegistration::descriptors_from_entry(entry)
189    }
190
191    #[cfg(all(test, feature = "node"))]
192    fn online_node_registry_entry(descriptors: Vec<OnlineNodeDescriptor>) -> Result<entry::Entry> {
193        let data = descriptors
194            .into_iter()
195            .map(|descriptor| descriptor.encode().map_err(Error::CoreError))
196            .collect::<Result<Vec<_>>>()?;
197
198        Ok(entry::Entry::new(
199            entry::Entry::gen_did(ONLINE_NODES_TOPIC)?,
200            data,
201            entry::EntryKind::Data,
202        ))
203    }
204
205    #[cfg(all(test, feature = "node"))]
206    fn onion_exit_registry_entry(descriptors: Vec<OnionExitDescriptor>) -> Result<entry::Entry> {
207        let data = descriptors
208            .into_iter()
209            .map(|descriptor| descriptor.encode().map_err(Error::CoreError))
210            .collect::<Result<Vec<_>>>()?;
211
212        Ok(entry::Entry::new(
213            entry::Entry::gen_did(ONION_EXITS_TOPIC)?,
214            data,
215            entry::EntryKind::Data,
216        ))
217    }
218
219    /// Publish this node's signed online descriptor to the online-node registry.
220    pub async fn publish_online_node_descriptor(&self) -> Result<OnlineNodeDescriptor> {
221        self.online_node_registration
222            .publish_descriptor(&self.registration_context())
223            .await
224    }
225
226    /// List signed online-node descriptors from the registry.
227    pub async fn lookup_online_nodes(
228        &self,
229        include_expired: bool,
230    ) -> Result<Vec<OnlineNodeDescriptor>> {
231        let entry_key = entry::Entry::gen_did(ONLINE_NODES_TOPIC)?;
232
233        let Some(entry) = self.fetch_storage_entry(entry_key).await? else {
234            return Ok(vec![]);
235        };
236
237        let descriptors = Self::online_node_descriptors_from_entry(&entry)
238            .into_iter()
239            .filter(|descriptor| descriptor.matches_dht_protocol(self.swarm.dht_protocol_mode()));
240
241        Ok(OnlineNodeDescriptor::latest_valid_by_did(
242            descriptors,
243            get_epoch_ms(),
244            include_expired,
245        ))
246    }
247
248    /// List signed onion-exit descriptors from the application-layer exit registry.
249    pub async fn lookup_onion_exits(
250        &self,
251        service: &str,
252        include_expired: bool,
253    ) -> Result<Vec<OnionExitDescriptor>> {
254        let entry_key = entry::Entry::gen_did(ONION_EXITS_TOPIC)?;
255
256        let Some(entry) = self.fetch_storage_entry(entry_key).await? else {
257            return Ok(vec![]);
258        };
259
260        let service = service.trim();
261        let exits = self.select_onion_exits_from_entry(&entry, service, include_expired);
262        if include_expired
263            || !exits.is_empty()
264            || !self.entry_has_expired_onion_exit_service(&entry, service)
265        {
266            return Ok(exits);
267        }
268
269        let Some(refreshed_entry) = self
270            .fetch_storage_entry_after_cache_refresh(entry_key, &entry)
271            .await?
272        else {
273            return Ok(exits);
274        };
275        Ok(self.select_onion_exits_from_entry(&refreshed_entry, service, include_expired))
276    }
277
278    pub(crate) async fn fetch_storage_entry(&self, entry_key: Did) -> Result<Option<entry::Entry>> {
279        let stop = StopToken::never();
280        self.fetch_storage_entry_with_stop(entry_key, &stop).await
281    }
282
283    pub(crate) async fn fetch_storage_entry_with_stop(
284        &self,
285        entry_key: Did,
286        stop: &StopToken,
287    ) -> Result<Option<entry::Entry>> {
288        if stop.should_stop() {
289            return Err(Error::RegistrationStopped);
290        }
291        self.storage_fetch(entry_key).await?;
292        for attempt in 0..DHT_LOOKUP_CACHE_POLL_ATTEMPTS {
293            if stop.should_stop() {
294                return Err(Error::RegistrationStopped);
295            }
296            if let Some(entry) = self.storage_check_cache(entry_key).await {
297                return Ok(Some(entry));
298            }
299            if attempt + 1 == DHT_LOOKUP_CACHE_POLL_ATTEMPTS {
300                break;
301            }
302            sleep_dht_lookup_poll_interval(DHT_LOOKUP_CACHE_POLL_INTERVAL).await?;
303        }
304        Ok(None)
305    }
306
307    fn select_onion_exits_from_entry(
308        &self,
309        entry: &entry::Entry,
310        service: &str,
311        include_expired: bool,
312    ) -> Vec<OnionExitDescriptor> {
313        OnionExitDescriptor::latest_valid_by_service_did(
314            Self::onion_exit_descriptors_from_entry(entry)
315                .into_iter()
316                .filter(|descriptor| descriptor.matches_network(self.swarm.network_id())),
317            get_epoch_ms(),
318            include_expired,
319        )
320        .into_iter()
321        .filter(|descriptor| service.is_empty() || descriptor.offers_service(service))
322        .collect()
323    }
324
325    fn entry_has_expired_onion_exit_service(&self, entry: &entry::Entry, service: &str) -> bool {
326        let now_ms = get_epoch_ms();
327        Self::onion_exit_descriptors_from_entry(entry)
328            .into_iter()
329            .filter(|descriptor| descriptor.matches_network(self.swarm.network_id()))
330            .any(|descriptor| {
331                (service.is_empty() || descriptor.offers_service(service))
332                    && descriptor.verify_signature()
333                    && descriptor.is_expired_at(now_ms)
334            })
335    }
336
337    async fn fetch_storage_entry_after_cache_refresh(
338        &self,
339        entry_key: Did,
340        previous_entry: &entry::Entry,
341    ) -> Result<Option<entry::Entry>> {
342        self.storage_fetch(entry_key).await?;
343        for _ in 0..DHT_LOOKUP_CACHE_POLL_ATTEMPTS {
344            sleep_dht_lookup_poll_interval(DHT_LOOKUP_CACHE_POLL_INTERVAL).await?;
345            let Some(entry) = self.storage_check_cache(entry_key).await else {
346                continue;
347            };
348            if &entry != previous_entry {
349                return Ok(Some(entry));
350            }
351        }
352        Ok(self.storage_check_cache(entry_key).await)
353    }
354
355    /// Build an onion route from live presence descriptors and live exit descriptors.
356    pub async fn build_onion_route(
357        &self,
358        service: String,
359        hop_count: usize,
360        allow_short_paths: bool,
361    ) -> Result<OnionRoute> {
362        directory::build_onion_route(self, service, hop_count, allow_short_paths).await
363    }
364
365    /// Build an onion proxy route for a client target through a target-agnostic proxy config.
366    pub async fn build_onion_proxy_route(
367        &self,
368        proxy: OnionProxyConfig,
369        target: OnionProxyTarget,
370    ) -> Result<OnionProxyRoute> {
371        directory::build_onion_proxy_route(self, proxy, target).await
372    }
373
374    async fn run_registration_once(
375        &self,
376        task: &dyn RegistrationTask,
377        stop: StopToken,
378    ) -> Result<()> {
379        let context = self.registration_context_with_stop(stop);
380        task.register_once(&context).await
381    }
382
383    async fn registration_task_daemon_with(
384        &self,
385        task: &dyn RegistrationTask,
386        stop: StopToken,
387        sibling_stop: StopToken,
388    ) {
389        loop {
390            if stop.should_stop() || sibling_stop.should_stop() {
391                return;
392            }
393            if let Err(error) = self.run_registration_once(task, stop.clone()).await {
394                if matches!(error, Error::RegistrationStopped) {
395                    tracing::debug!(
396                        "Stopping {} registration task after cooperative stop",
397                        task.name()
398                    );
399                    return;
400                }
401                tracing::warn!("Failed to run {} registration task: {error:?}", task.name());
402            }
403            if stop.should_stop() || sibling_stop.should_stop() {
404                return;
405            }
406            match sleep_registration_interval_with_stop(task.interval(), &stop, &sibling_stop).await
407            {
408                Ok(true) => {}
409                Ok(false) => return,
410                Err(error) => {
411                    tracing::warn!(
412                        "Stopping {} registration task after timer error: {error:?}",
413                        task.name()
414                    );
415                    return;
416                }
417            }
418        }
419    }
420
421    async fn registration_daemons_with(&self, stop: StopToken, sibling_stop: StopToken) {
422        join_all(self.registration_tasks.iter().map(|task| {
423            self.registration_task_daemon_with(task.as_ref(), stop.clone(), sibling_stop.clone())
424        }))
425        .await;
426    }
427
428    /// Run stabilization and node registration tasks until this future is dropped or aborted.
429    ///
430    /// This is a long-running task; do not await completion as a readiness signal.
431    pub async fn listen(&self) {
432        self.listen_with(StopToken::never()).await;
433    }
434
435    /// Run stabilization and node registration tasks until `stop` asks them to exit.
436    ///
437    /// The shutdown is cooperative: it waits for the current stabilization or
438    /// registration operation to finish before returning. This avoids dropping
439    /// browser IndexedDB request futures while their JavaScript callbacks are
440    /// still pending.
441    pub async fn listen_with(&self, stop: StopToken) {
442        let stabilizer = self.swarm.stabilizer();
443        let stabilizer = Arc::new(stabilizer);
444        if self.registration_tasks.is_empty() {
445            stabilizer.wait_with(self.stabilize_interval, stop).await;
446        } else {
447            let registration_stop_source = StopSource::new();
448            let registration_stop = registration_stop_source.token();
449            let stabilizer_stop = stop.clone();
450            let stabilization = async {
451                stabilizer
452                    .wait_with(self.stabilize_interval, stabilizer_stop)
453                    .await;
454                registration_stop_source.request_stop();
455            };
456            let _ = futures::future::join(
457                stabilization,
458                self.registration_daemons_with(stop, registration_stop),
459            )
460            .await;
461        }
462        if let Err(error) = self.flush_measurements().await {
463            tracing::error!(%error, "failed to flush measurements during graceful shutdown");
464        }
465    }
466
467    /// Flush all applied measurement updates with the graceful-shutdown deadline.
468    pub async fn flush_measurements(&self) -> Result<()> {
469        if let Some(measure) = &self.measure {
470            measure
471                .flush_with_timeout(MEASUREMENT_FLUSH_TIMEOUT)
472                .await?;
473        }
474        Ok(())
475    }
476
477    #[cfg(all(test, feature = "ffi"))]
478    pub(crate) async fn record_authenticated_measurement_for_test(
479        &self,
480        peer: Did,
481    ) -> std::result::Result<(), rings_core::measure::MeasureError> {
482        if let Some(measure) = &self.measure {
483            rings_core::measure::Measure::record(
484                measure.as_ref(),
485                peer,
486                rings_core::measure::Authentication::Authenticated,
487                rings_core::measure::MeasurementEvent::Connected,
488            )
489            .await?;
490        }
491        Ok(())
492    }
493
494    /// Connect peer with web3 did.
495    /// There are 3 peers: PeerA, PeerB, PeerC.
496    /// 1. PeerA has a connection with PeerB.
497    /// 2. PeerC has a connection with PeerB.
498    /// 3. PeerC can connect PeerA with PeerA's web3 address.
499    ///
500    /// This operation is idempotent: if topology convergence already produced
501    /// the direct connection, the requested connection is satisfied.
502    pub async fn connect_with_did(&self, did: Did) -> Result<()> {
503        match self.swarm.connect(did).await {
504            Ok(()) | Err(rings_core::error::Error::AlreadyConnected) => Ok(()),
505            Err(error) => Err(Error::ConnectError(error)),
506        }
507    }
508
509    /// Disconnect a peer with web3 did.
510    pub async fn disconnect(&self, did: Did) -> Result<()> {
511        self.swarm
512            .disconnect(did)
513            .await
514            .map_err(Error::CloseConnectionError)
515    }
516
517    /// Send custom message to a did.
518    pub async fn send_message(&self, destination: Did, msg: &[u8]) -> Result<uuid::Uuid> {
519        tracing::trace!("send_message, message size: {:?}", msg.len());
520
521        let msg = Message::custom(msg).map_err(Error::SendMessage)?;
522
523        self.swarm
524            .send_message(msg, destination)
525            .await
526            .map_err(Error::SendMessage)
527    }
528
529    /// Send a custom message to an already connected peer without Chord routing.
530    ///
531    /// Protocols with their own authenticated hop selection, such as onion circuits, use this
532    /// to keep the core transport from replacing their selected next hop.
533    pub async fn send_direct_message(&self, destination: Did, msg: &[u8]) -> Result<uuid::Uuid> {
534        tracing::trace!("send_direct_message, message size: {:?}", msg.len());
535
536        let msg = Message::custom(msg).map_err(Error::SendMessage)?;
537
538        self.swarm
539            .send_direct_message(msg, destination)
540            .await
541            .map_err(Error::SendMessage)
542    }
543
544    /// Send an E2E handshake request to a DID.
545    ///
546    /// The negotiated key is the peer's account/identity secp256k1 key, not
547    /// the ephemeral session key.
548    pub async fn send_e2e_handshake(&self, destination: Did) -> Result<uuid::Uuid> {
549        let public_key = self.swarm.account_pubkey().map_err(Error::SendMessage)?;
550        self.swarm
551            .send_message(
552                Message::E2eHandshakeRequest(E2eHandshakeRequest::new(public_key)),
553                destination,
554            )
555            .await
556            .map_err(Error::SendMessage)
557    }
558
559    /// Send an ElGamal-encrypted E2E message to a DID with a verified recipient key.
560    ///
561    /// Returns the stream id shared by all emitted E2E stream frames.
562    pub async fn send_e2e_message(
563        &self,
564        destination: Did,
565        recipient_public_key: PublicKey<33>,
566        msg: &[u8],
567    ) -> Result<uuid::Uuid> {
568        self.send_e2e_message_with_frame_len(
569            destination,
570            recipient_public_key,
571            msg,
572            e2e::DEFAULT_E2E_PLAINTEXT_FRAME_LEN,
573        )
574        .await
575    }
576
577    /// Send an ElGamal-encrypted E2E stream with an explicit plaintext frame size.
578    ///
579    /// Returns the stream id shared by all emitted E2E stream frames.
580    pub async fn send_e2e_message_with_frame_len(
581        &self,
582        destination: Did,
583        recipient_public_key: PublicKey<33>,
584        msg: &[u8],
585        max_plaintext_frame_len: usize,
586    ) -> Result<uuid::Uuid> {
587        e2e::ensure_public_key_matches_did(recipient_public_key, destination)
588            .map_err(Error::SendMessage)?;
589        let sender_public_key = self.swarm.account_pubkey().map_err(Error::SendMessage)?;
590        let stream_id = uuid::Uuid::new_v4();
591        let frames = e2e::encrypt_stream_frames(
592            msg,
593            stream_id,
594            sender_public_key,
595            recipient_public_key,
596            max_plaintext_frame_len,
597        )
598        .map_err(Error::SendMessage)?
599        .collect::<rings_core::error::Result<Vec<_>>>()
600        .map_err(Error::SendMessage)?;
601
602        for frame in frames {
603            self.swarm
604                .send_message(Message::E2eStreamFrame(frame), destination)
605                .await
606                .map_err(Error::SendMessage)?;
607        }
608
609        Ok(stream_id)
610    }
611
612    /// Verify an E2E handshake request and return the requester's identity public key.
613    pub fn verify_e2e_handshake_request(
614        &self,
615        requester: Did,
616        request: &E2eHandshakeRequest,
617    ) -> Result<PublicKey<33>> {
618        request
619            .verify_requester(requester)
620            .map_err(Error::CoreError)?;
621        Ok(request.requester_public_key)
622    }
623
624    /// Verify an E2E handshake response and return the responder's identity public key.
625    pub fn verify_e2e_handshake_response(
626        &self,
627        responder: Did,
628        response: &E2eHandshakeResponse,
629    ) -> Result<PublicKey<33>> {
630        response
631            .verify_responder(responder)
632            .map_err(Error::CoreError)?;
633        Ok(response.responder_public_key)
634    }
635
636    /// Create an E2E stream decryptor with this node's identity/signing secret key.
637    ///
638    /// The ciphertext is encrypted to the DID/account key negotiated by the
639    /// handshake. A session private key cannot decrypt it unless the session key
640    /// is also the account key, so callers must supply the local identity key
641    /// explicitly.
642    pub fn e2e_stream_decryptor(
643        &self,
644        expected_sender: Did,
645        stream_id: e2e::E2eStreamId,
646        recipient_identity_key: SecretKey,
647    ) -> Result<E2eStreamDecryptor> {
648        e2e::ensure_public_key_matches_did(recipient_identity_key.pubkey(), self.did())
649            .map_err(Error::CoreError)?;
650        Ok(E2eStreamDecryptor::new(
651            stream_id,
652            expected_sender,
653            recipient_identity_key,
654        ))
655    }
656
657    /// Decrypt one E2E stream frame with an already-created stream decryptor.
658    pub fn decrypt_e2e_stream_frame(
659        &self,
660        decryptor: &mut E2eStreamDecryptor,
661        frame: &E2eStreamFrame,
662    ) -> Result<Vec<u8>> {
663        decryptor.decrypt_next(frame).map_err(Error::CoreError)
664    }
665
666    /// Send a namespaced [`Envelope`](crate::extension::ext::Envelope) to a did over the
667    /// P2P transport (the wire codec
668    /// of the extension layer). `send_envelope : (Did, Envelope) → IO TxId`.
669    pub async fn send_envelope(
670        &self,
671        destination: Did,
672        envelope: &crate::extension::ext::Envelope,
673    ) -> Result<uuid::Uuid> {
674        let msg_bytes = envelope.encode()?;
675        self.send_message(destination, &msg_bytes).await
676    }
677
678    /// Send a namespaced envelope directly to an already connected peer.
679    ///
680    /// This bypasses Chord routing while retaining the normal custom-message envelope codec.
681    pub async fn send_direct_envelope(
682        &self,
683        destination: Did,
684        envelope: &crate::extension::ext::Envelope,
685    ) -> Result<uuid::Uuid> {
686        let msg_bytes = envelope.encode()?;
687        self.send_direct_message(destination, &msg_bytes).await
688    }
689
690    /// check local cache of dht
691    pub async fn storage_check_cache(&self, entry_key: Did) -> Option<entry::Entry> {
692        self.swarm.storage_check_cache(entry_key).await
693    }
694
695    /// Fetch an entry from DHT storage
696    pub async fn storage_fetch(&self, entry_key: Did) -> Result<()> {
697        <Swarm as ChordStorageInterface<DATA_REDUNDANT>>::storage_fetch(&self.swarm, entry_key)
698            .await
699            .map_err(Error::EntryError)
700    }
701
702    /// Store an entry on DHT storage
703    pub async fn storage_store(&self, entry: entry::Entry) -> Result<()> {
704        <Swarm as ChordStorageInterface<DATA_REDUNDANT>>::storage_store(&self.swarm, entry)
705            .await
706            .map_err(Error::EntryError)
707    }
708
709    /// Append data to an entry on DHT storage
710    pub async fn storage_append_data(&self, topic: &str, data: Encoded) -> Result<()> {
711        <Swarm as ChordStorageInterface<DATA_REDUNDANT>>::storage_append_data(
712            &self.swarm,
713            topic,
714            data,
715        )
716        .await
717        .map_err(Error::EntryError)
718    }
719
720    /// Touch data in an entry on DHT storage, moving existing equal payloads to the end.
721    pub async fn storage_touch_data(&self, topic: &str, data: Encoded) -> Result<()> {
722        <Swarm as ChordStorageInterface<DATA_REDUNDANT>>::storage_touch_data(
723            &self.swarm,
724            topic,
725            data,
726        )
727        .await
728        .map_err(Error::EntryError)
729    }
730
731    /// Tombstone observed data in an entry on DHT storage.
732    pub async fn storage_tombstone_data(&self, topic: &str, data: Encoded) -> Result<()> {
733        <Swarm as ChordStorageInterface<DATA_REDUNDANT>>::storage_tombstone_data(
734            &self.swarm,
735            topic,
736            data,
737        )
738        .await
739        .map_err(Error::EntryError)
740    }
741
742    /// Compact observed data in an entry on DHT storage.
743    pub async fn storage_compact_data(&self, topic: &str, removals: Vec<Encoded>) -> Result<()> {
744        <Swarm as ChordStorageInterface<DATA_REDUNDANT>>::storage_compact_data(
745            &self.swarm,
746            topic,
747            removals,
748        )
749        .await
750        .map_err(Error::EntryError)
751    }
752
753    /// Return local measurement counters for a peer, if observed.
754    pub async fn peer_measurement(&self, did: Did) -> Option<PeerMeasurement> {
755        self.swarm.peer_measurement(did).await
756    }
757
758    /// Return every retained local peer measurement.
759    pub async fn peer_measurements(&self) -> Vec<PeerMeasurement> {
760        let mut measurements = self.swarm.peer_measurements().await;
761        measurements.sort_by_key(|measurement| measurement.did);
762        measurements
763    }
764
765    /// Return one bounded page of retained local peer measurements.
766    pub async fn peer_measurements_page(
767        &self,
768        after: Option<Did>,
769        limit: NonZeroUsize,
770    ) -> PeerMeasurementPage {
771        self.swarm.peer_measurements_page(after, limit).await
772    }
773
774    /// register service
775    pub async fn register_service(&self, name: &str) -> Result<()> {
776        let encoded_did = self
777            .did()
778            .to_string()
779            .encode()
780            .map_err(Error::ServiceRegisterError)?;
781        self.storage_touch_data(name, encoded_did)
782            .await
783            .map_err(|error| match error {
784                Error::EntryError(error) => Error::ServiceRegisterError(error),
785                error => error,
786            })
787    }
788
789    /// get node info
790    pub async fn get_node_info(&self) -> Result<NodeInfoResponse> {
791        Ok(NodeInfoResponse {
792            version: crate::util::build_version(),
793            swarm: Some(self.swarm.inspect().await.into()),
794        })
795    }
796}
797
798#[cfg_attr(all(feature = "browser", target_family = "wasm"), async_trait::async_trait(?Send))]
799#[cfg_attr(
800    not(all(feature = "browser", target_family = "wasm")),
801    async_trait::async_trait
802)]
803impl OnionDirectoryReader for Processor {
804    fn local_did(&self) -> Did {
805        self.did()
806    }
807
808    fn dht_protocol_mode(&self) -> DhtProtocolMode {
809        self.swarm.dht_protocol_mode()
810    }
811
812    async fn live_online_nodes(&self) -> Result<Vec<OnlineNodeDescriptor>> {
813        self.lookup_online_nodes(false).await
814    }
815
816    async fn live_onion_exits(&self, service: &str) -> Result<Vec<OnionExitDescriptor>> {
817        self.lookup_onion_exits(service, false).await
818    }
819
820    async fn peer_qualities(&self) -> Vec<(Did, PeerQuality)> {
821        self.peer_measurements()
822            .await
823            .into_iter()
824            .map(|measurement| (measurement.did, measurement.quality))
825            .collect()
826    }
827}
828
829#[cfg(all(test, feature = "node"))]
830mod tests;