Skip to main content

rings_node/provider/
mod.rs

1//! General Provider, this module provide Provider implementation for FFI and WASM
2
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6#[cfg(all(feature = "browser", target_family = "wasm"))]
7use std::sync::Mutex;
8
9use rings_core::dht::Did;
10use rings_core::dht::EntryStorage;
11#[cfg(feature = "node")]
12use rings_core::lifecycle::StopToken;
13use rings_core::measure::PeerMeasurement;
14use rings_core::session::SessionSkBuilder;
15use rings_core::storage::MemStorage;
16use rings_core::swarm::callback::SharedSwarmCallback;
17use rings_core::swarm::callback::SwarmCallback;
18use rings_rpc::protos::rings_node_handler::InternalRpcHandler;
19
20use crate::error::Error;
21use crate::error::Result;
22use crate::extension::Backend;
23use crate::measure::MeasureStorage;
24use crate::measure::PeriodicMeasure;
25use crate::prelude::wasm_export;
26use crate::processor::Processor;
27use crate::processor::ProcessorBuilder;
28use crate::processor::ProcessorConfig;
29
30#[cfg(all(feature = "browser", target_family = "wasm"))]
31pub mod browser;
32#[cfg(feature = "ffi")]
33pub mod ffi;
34
35/// General Provider, which holding reference of Processor
36/// Provider should be obey memory layout of CLang
37/// Provider should be export for wasm-bindgen
38#[derive(Clone)]
39#[allow(dead_code)]
40#[repr(C)]
41#[wasm_export]
42pub struct Provider {
43    processor: Arc<Processor>,
44    handler: InternalRpcHandler,
45    extensions: crate::extension::ext::Extensions,
46    #[cfg(all(feature = "browser", target_family = "wasm"))]
47    onion_https_runtime: Arc<Mutex<Option<Arc<crate::onion::https::OnionHttpsRuntime>>>>,
48    #[cfg(all(feature = "browser", target_family = "wasm"))]
49    onion_directory_endpoint: Arc<Mutex<Option<String>>>,
50}
51
52/// Async signer, without Send required
53#[cfg(all(feature = "browser", target_family = "wasm"))]
54pub type AsyncSigner = Box<dyn Fn(String) -> Pin<Box<dyn Future<Output = Vec<u8>>>>>;
55
56/// Async signer, use for non-wasm envirement, Send is necessary
57#[cfg(not(all(feature = "browser", target_family = "wasm")))]
58pub type AsyncSigner = Box<dyn Fn(String) -> Pin<Box<dyn Future<Output = Vec<u8>> + Send>>>;
59
60/// Signer can be async and sync
61#[allow(clippy::type_complexity)]
62pub enum Signer {
63    /// Sync signer
64    Sync(Box<dyn Fn(String) -> Vec<u8>>),
65    /// Async signer
66    Async(AsyncSigner),
67}
68
69struct NoopSwarmCallback;
70
71impl SwarmCallback for NoopSwarmCallback {}
72
73#[allow(dead_code)]
74impl Provider {
75    /// Create provider from processor directly
76    pub fn from_processor(processor: Arc<Processor>) -> Self {
77        let extensions = crate::extension::ext::Extensions::new(processor.clone());
78        Self {
79            processor,
80            handler: InternalRpcHandler,
81            extensions,
82            #[cfg(all(feature = "browser", target_family = "wasm"))]
83            onion_https_runtime: Arc::new(Mutex::new(None)),
84            #[cfg(all(feature = "browser", target_family = "wasm"))]
85            onion_directory_endpoint: Arc::new(Mutex::new(None)),
86        }
87    }
88
89    /// The shared protocol registry. The inbound callback clones this so
90    /// registration (via the provider) and dispatch see the same table.
91    pub fn extensions(&self) -> crate::extension::ext::Extensions {
92        self.extensions.clone()
93    }
94
95    /// The capability handle — overlay `send` / `did` / self-addressed `inject`. (Authenticated
96    /// `dispatch` is router-only; `pub(crate)` so it never reaches public callers.)
97    pub(crate) fn core(&self) -> crate::extension::ext::Core {
98        self.extensions.core()
99    }
100
101    /// Register a pure [`Protocol`](crate::extension::ext::Protocol) together with its
102    /// [`Interpret`](crate::extension::ext::Interpret) shell under the protocol's namespace.
103    /// Errors if the namespace is already taken.
104    pub fn register_protocol<P, I>(&self, protocol: P, interpret: I) -> Result<()>
105    where
106        P: crate::extension::ext::Protocol + crate::extension::ext::MaybeSend + 'static,
107        P::State: crate::extension::ext::MaybeSend + 'static,
108        P::Effect: crate::extension::ext::MaybeSend,
109        I: crate::extension::ext::Interpret<Effect = P::Effect>
110            + crate::extension::ext::MaybeSend
111            + 'static,
112    {
113        self.extensions.register(protocol, interpret)
114    }
115
116    /// Send a namespaced payload to a peer. This is the uniform upper-layer send — a core
117    /// capability, identical on native and browser.
118    pub async fn send(
119        &self,
120        to: rings_core::dht::Did,
121        namespace: &str,
122        payload: bytes::Bytes,
123    ) -> Result<()> {
124        self.core().send(to, namespace, payload).await
125    }
126
127    /// Return local measurement counters for a peer, if observed.
128    pub async fn peer_measurement(&self, did: Did) -> Option<PeerMeasurement> {
129        self.processor.peer_measurement(did).await
130    }
131
132    /// Return every retained local peer measurement.
133    pub async fn peer_measurements(&self) -> Vec<PeerMeasurement> {
134        self.processor.peer_measurements().await
135    }
136
137    pub(crate) async fn flush_measurements(&self) -> Result<()> {
138        self.processor.flush_measurements().await
139    }
140
141    /// Create a provider instance with storage name
142    pub(crate) async fn new_provider_with_storage_internal(
143        config: ProcessorConfig,
144        entry_storage: Option<EntryStorage>,
145        measure_storage: Option<MeasureStorage>,
146    ) -> Result<Provider> {
147        let entry_storage = entry_storage.unwrap_or_else(|| Box::new(MemStorage::new()));
148        let measure_storage = measure_storage.unwrap_or_else(|| Box::new(MemStorage::new()));
149
150        let measure = PeriodicMeasure::new(measure_storage).await?;
151
152        let processor_builder = ProcessorBuilder::from_config(&config)?
153            .storage(entry_storage)
154            .measure(measure);
155
156        let processor = Arc::new(processor_builder.build()?);
157
158        let extensions = crate::extension::ext::Extensions::new(processor.clone());
159
160        Ok(Provider {
161            processor,
162            handler: InternalRpcHandler,
163            extensions,
164            #[cfg(all(feature = "browser", target_family = "wasm"))]
165            onion_https_runtime: Arc::new(Mutex::new(None)),
166            #[cfg(all(feature = "browser", target_family = "wasm"))]
167            onion_directory_endpoint: Arc::new(Mutex::new(None)),
168        })
169    }
170
171    /// Create a new provider instanice with everything in detail
172    /// Ice_servers should obey forrmat: `"[turn|strun]://<Address>:<Port>;..."`
173    /// Account is hex string
174    /// Account should format as same as account_type declared
175    /// Account_type is lowercase string, possible input are: `eip191`, `ed25519`, `bip137`, for more information,
176    /// please check [rings_core::ecc]
177    /// Signer should accept a String and returns bytes.
178    /// Signer should function as same as account_type declared, Eg: eip191 or secp256k1 or ed25519.
179    #[allow(clippy::too_many_arguments)]
180    pub(crate) async fn new_provider_internal(
181        network_id: u32,
182        ice_servers: String,
183        stabilize_interval: u64,
184        account: String,
185        account_type: String,
186        signer: Signer,
187        entry_storage: Option<EntryStorage>,
188        measure_storage: Option<MeasureStorage>,
189    ) -> Result<Provider> {
190        Self::new_provider_internal_with_config(
191            network_id,
192            ice_servers,
193            stabilize_interval,
194            account,
195            account_type,
196            signer,
197            entry_storage,
198            measure_storage,
199            core::convert::identity,
200        )
201        .await
202    }
203
204    #[allow(clippy::too_many_arguments)]
205    pub(crate) async fn new_provider_internal_with_config(
206        network_id: u32,
207        ice_servers: String,
208        stabilize_interval: u64,
209        account: String,
210        account_type: String,
211        signer: Signer,
212        entry_storage: Option<EntryStorage>,
213        measure_storage: Option<MeasureStorage>,
214        configure: impl FnOnce(ProcessorConfig) -> ProcessorConfig,
215    ) -> Result<Provider> {
216        let mut sk_builder = SessionSkBuilder::new(account, account_type);
217        let proof = sk_builder.unsigned_proof();
218        let sig = match signer {
219            Signer::Sync(s) => s(proof),
220            Signer::Async(s) => s(proof).await,
221        };
222        sk_builder = sk_builder.set_session_sig(sig.to_vec());
223        let session_sk = sk_builder.build().map_err(Error::InternalError)?;
224        let config = ProcessorConfig::new(network_id, ice_servers, session_sk, stabilize_interval);
225        let config = configure(config);
226        Self::new_provider_with_storage_internal(config, entry_storage, measure_storage).await
227    }
228
229    /// Install the extension [`Backend`] as the swarm's inbound callback, so inbound
230    /// custom messages are decoded as [`Envelope`](crate::extension::ext::Envelope)s and
231    /// routed to their namespace's protocol. Call once after registering protocols.
232    pub fn set_backend(&self) -> Result<()> {
233        let backend = Backend::new(Arc::new(self.clone()));
234        self.processor
235            .swarm
236            .set_callback(Arc::new(backend))
237            .map_err(Error::InternalError)
238    }
239
240    /// Set callback for swarm.
241    #[deprecated(
242        note = "set_swarm_callback will be removed in next version, plz use set_backend instead"
243    )]
244    pub fn set_swarm_callback(&self, callback: SharedSwarmCallback) -> Result<()> {
245        self.processor
246            .swarm
247            .set_callback(callback)
248            .map_err(Error::InternalError)
249    }
250
251    pub(crate) fn set_swarm_callback_internal(&self, callback: SharedSwarmCallback) -> Result<()> {
252        self.processor
253            .swarm
254            .set_callback(callback)
255            .map_err(Error::InternalError)
256    }
257
258    pub(crate) fn clear_swarm_callback_internal(&self) -> Result<()> {
259        self.processor
260            .swarm
261            .set_callback(Arc::new(NoopSwarmCallback))
262            .map_err(Error::InternalError)
263    }
264
265    /// Request local rpc interface
266    /// the internal rpc interface is provide by rings_rpc
267    pub async fn request_internal(
268        &self,
269        method: String,
270        params: serde_json::Value,
271    ) -> Result<serde_json::Value> {
272        tracing::debug!("request {}", method);
273        #[cfg(all(feature = "browser", target_family = "wasm"))]
274        let onion_directory_endpoint = onion_directory_endpoint_from_rpc(method.as_str(), &params);
275        let result = self
276            .handler
277            .handle_request(self.processor.clone(), method, params)
278            .await
279            .map_err(Error::InternalRpcError)?;
280        #[cfg(all(feature = "browser", target_family = "wasm"))]
281        if let Some(endpoint) = onion_directory_endpoint {
282            self.set_onion_directory_endpoint(Some(endpoint))?;
283        }
284        Ok(result)
285    }
286}
287
288#[cfg(feature = "node")]
289impl Provider {
290    /// A request function implementation for native provider
291    pub async fn request<T>(
292        &self,
293        method: rings_rpc::method::Method,
294        params: T,
295    ) -> Result<serde_json::Value>
296    where
297        T: serde::Serialize,
298    {
299        let params = serde_json::to_value(params)?;
300        self.request_internal(method.to_string(), params).await
301    }
302
303    /// Listen for messages until this future is dropped or aborted.
304    ///
305    /// This is a long-running task; do not await completion as a readiness signal.
306    pub async fn listen(&self) {
307        self.processor.listen().await;
308    }
309
310    /// Listen for messages until `stop` requests cooperative shutdown.
311    ///
312    /// This is a long-running task; do not await completion as a readiness signal.
313    pub async fn listen_with(&self, stop: StopToken) {
314        self.processor.listen_with(stop).await;
315    }
316}
317
318#[cfg(all(feature = "browser", target_family = "wasm"))]
319fn onion_directory_endpoint_from_rpc(method: &str, params: &serde_json::Value) -> Option<String> {
320    if !matches!(method, "connectPeerViaHttp" | "ConnectPeerViaHttp") {
321        return None;
322    }
323    params
324        .get("url")
325        .and_then(serde_json::Value::as_str)
326        .map(str::trim)
327        .filter(|url| !url.is_empty())
328        .map(ToOwned::to_owned)
329}
330
331#[cfg(all(feature = "browser", target_family = "wasm"))]
332impl Provider {
333    pub(crate) fn set_onion_directory_endpoint(&self, endpoint: Option<String>) -> Result<()> {
334        let mut slot = self
335            .onion_directory_endpoint
336            .lock()
337            .map_err(|_| Error::Lock)?;
338        *slot = endpoint;
339        Ok(())
340    }
341
342    pub(crate) fn onion_directory_endpoint(&self) -> Result<Option<String>> {
343        self.onion_directory_endpoint
344            .lock()
345            .map(|slot| slot.clone())
346            .map_err(|_| Error::Lock)
347    }
348}