Skip to main content

veilid_core/veilid_api/
api.rs

1use super::*;
2
3impl_veilid_log_facility!("veilid_api");
4
5/////////////////////////////////////////////////////////////////////////////////////////////////////
6
7pub(super) struct VeilidAPIInner {
8    context: Option<VeilidCoreContext>,
9    pub(super) debug_cache: DebugCache,
10}
11
12impl fmt::Debug for VeilidAPIInner {
13    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        write!(f, "VeilidAPIInner")
15    }
16}
17
18impl Drop for VeilidAPIInner {
19    fn drop(&mut self) {
20        if let Some(context) = self.context.take() {
21            spawn_detached("api shutdown", api_shutdown(context));
22        }
23    }
24}
25
26/// The primary developer entrypoint into `veilid-core` functionality.
27///
28/// From [VeilidAPI] one can access various components:
29///
30/// * [VeilidConfig] - The Veilid configuration specified at startup time.
31/// * [Crypto] - The available set of cryptosystems provided by Veilid.
32/// * [TableStore] - The Veilid table-based encrypted persistent key-value store.
33/// * [ProtectedStore] - The Veilid abstract of the device's low-level 'protected secret storage'.
34/// * [VeilidState] - The current state of the Veilid node this API accesses.
35/// * [RoutingContext] - Communication methods between Veilid nodes and private routes.
36/// * Attach and detach from the network.
37/// * Create and import private routes.
38/// * Reply to `AppCall` RPCs.
39#[derive(Clone, Debug)]
40#[must_use]
41pub struct VeilidAPI {
42    inner: Arc<Mutex<VeilidAPIInner>>,
43}
44
45impl VeilidAPI {
46    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = context.log_key()), skip_all))]
47    pub(crate) fn new(context: VeilidCoreContext) -> Self {
48        veilid_log!(context debug "VeilidAPI::new()");
49        record_duration(|| Self {
50            inner: Arc::new(Mutex::new(VeilidAPIInner {
51                context: Some(context),
52                debug_cache: DebugCache {
53                    imported_routes: Vec::new(),
54                    opened_record_contexts: once_cell::sync::Lazy::new(
55                        hashlink::LinkedHashMap::new,
56                    ),
57                },
58            })),
59        })
60    }
61
62    /// Shut down Veilid and terminate the API.
63    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), skip_all))]
64    pub async fn shutdown(self) {
65        record_duration_fut(async {
66            veilid_log!(self debug "VeilidAPI::shutdown()");
67            let context = { self.inner.lock().context.take() };
68            if let Some(context) = context {
69                api_shutdown(context).await;
70            }
71        })
72        .await
73    }
74
75    /// Check to see if Veilid is already shut down.
76    #[must_use]
77    pub fn is_shutdown(&self) -> bool {
78        self.inner.lock().context.is_none()
79    }
80
81    ////////////////////////////////////////////////////////////////
82    // Public Accessors
83
84    /// Access the configuration that Veilid was initialized with.
85    pub fn config(&self) -> VeilidAPIResult<Arc<VeilidConfig>> {
86        let inner = self.inner.lock();
87        let Some(context) = &inner.context else {
88            return Err(VeilidAPIError::NotInitialized);
89        };
90        Ok(context.registry().config())
91    }
92
93    /// Get the cryptosystem component.
94    pub fn crypto(&self) -> VeilidAPIResult<VeilidComponentGuard<'_, Crypto>> {
95        let inner = self.inner.lock();
96        let Some(context) = &inner.context else {
97            return Err(VeilidAPIError::NotInitialized);
98        };
99        context
100            .registry()
101            .lookup::<Crypto>()
102            .ok_or(VeilidAPIError::NotInitialized)
103    }
104
105    /// Get the TableStore component.
106    pub fn table_store(&self) -> VeilidAPIResult<VeilidComponentGuard<'_, TableStore>> {
107        let inner = self.inner.lock();
108        let Some(context) = &inner.context else {
109            return Err(VeilidAPIError::NotInitialized);
110        };
111        context
112            .registry()
113            .lookup::<TableStore>()
114            .ok_or(VeilidAPIError::NotInitialized)
115    }
116
117    /// Get the ProtectedStore component.
118    pub fn protected_store(&self) -> VeilidAPIResult<VeilidComponentGuard<'_, ProtectedStore>> {
119        let inner = self.inner.lock();
120        let Some(context) = &inner.context else {
121            return Err(VeilidAPIError::NotInitialized);
122        };
123        context
124            .registry()
125            .lookup::<ProtectedStore>()
126            .ok_or(VeilidAPIError::NotInitialized)
127    }
128
129    /// Get the BlockStore component.
130    #[cfg(feature = "unstable-blockstore")]
131    pub fn block_store(&self) -> VeilidAPIResult<VeilidComponentGuard<'_, BlockStore>> {
132        let inner = self.inner.lock();
133        let Some(context) = &inner.context else {
134            return Err(VeilidAPIError::NotInitialized);
135        };
136        context
137            .registry()
138            .lookup::<BlockStore>()
139            .ok_or(VeilidAPIError::NotInitialized)
140    }
141
142    ////////////////////////////////////////////////////////////////
143    // Internal Accessors
144
145    pub(crate) fn core_context(&self) -> VeilidAPIResult<VeilidCoreContext> {
146        let inner = self.inner.lock();
147        let Some(context) = &inner.context else {
148            return Err(VeilidAPIError::NotInitialized);
149        };
150        Ok(context.clone())
151    }
152
153    pub(crate) fn with_debug_cache<R, F: FnOnce(&mut DebugCache) -> R>(&self, callback: F) -> R {
154        let mut inner = self.inner.lock();
155        callback(&mut inner.debug_cache)
156    }
157
158    #[must_use]
159    pub(crate) fn log_key(&self) -> &str {
160        let inner = self.inner.lock();
161        let Some(context) = &inner.context else {
162            return "";
163        };
164        context.log_key()
165    }
166
167    ////////////////////////////////////////////////////////////////
168    // Attach/Detach
169
170    /// Get a full copy of the current state of Veilid.
171    #[expect(clippy::unused_async)]
172    pub async fn get_state(&self) -> VeilidAPIResult<VeilidState> {
173        let attachment_manager = self.core_context()?.attachment_manager();
174        let network_manager = attachment_manager.network_manager();
175        let config = self.config()?;
176
177        let attachment = attachment_manager.get_veilid_state();
178        let network = network_manager.get_veilid_state();
179
180        Ok(VeilidState {
181            attachment,
182            network,
183            config: Box::new(VeilidStateConfig {
184                config: config.as_ref().clone(),
185            }),
186        })
187    }
188
189    /// Connect to the network.
190    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), skip_all, ret))]
191    pub async fn attach(&self) -> VeilidAPIResult<()> {
192        record_duration_fut(async {
193            veilid_log!(self debug
194                "VeilidAPI::attach()");
195            let attachment_manager = self.core_context()?.attachment_manager();
196            if !Box::pin(attachment_manager.attach()).await {
197                apibail_generic!("Already attached");
198            }
199            Ok(())
200        })
201        .await
202        .inspect_err(log_veilid_api_error!(self))
203    }
204
205    /// Disconnect from the network.
206    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), skip_all, ret))]
207    pub async fn detach(&self) -> VeilidAPIResult<()> {
208        record_duration_fut(async {
209            veilid_log!(self debug
210            "VeilidAPI::detach()");
211
212            let attachment_manager = self.core_context()?.attachment_manager();
213            if !Box::pin(attachment_manager.detach()).await {
214                apibail_generic!("Already detached");
215            }
216            Ok(())
217        })
218        .await
219        .inspect_err(log_veilid_api_error!(self))
220    }
221
222    ////////////////////////////////////////////////////////////////
223    // Routing Context
224
225    /// Get a new `RoutingContext` object to use to send messages over the Veilid network with default safety, sequencing, and stability parameters.
226    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), skip_all, ret))]
227    pub fn routing_context(&self) -> VeilidAPIResult<RoutingContext> {
228        record_duration(|| {
229            veilid_log!(self debug
230                "VeilidAPI::routing_context()");
231
232            RoutingContext::try_new(self.clone())
233        })
234        .inspect_err(log_veilid_api_error!(self))
235    }
236
237    ////////////////////////////////////////////////////////////////
238    // Non-RoutingContext DHT Operations
239
240    /// Deterministicly builds the record key for a given schema and owner public key.
241    /// The crypto kind of the record key will be that of the `owner` public key
242    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
243    pub fn get_dht_record_key(
244        &self,
245        schema: DHTSchema,
246        owner_key: PublicKey,
247        encryption_key: Option<SharedSecret>,
248    ) -> VeilidAPIResult<RecordKey> {
249        record_duration(|| {
250            veilid_log!(self debug
251            "RoutingContext::get_dht_record_key(self: {:?} schema: {:?}, owner_key: {:?}, encryption_key: {:?}", self, schema, owner_key, encryption_key);
252            schema.validate()?;
253
254            self.crypto()?.check_public_key(&owner_key)?;
255            if let Some(encryption_key) = encryption_key.as_ref() {
256                self.crypto()?.check_shared_secret(encryption_key)?;
257            }
258
259            let storage_manager = self.core_context()?.storage_manager();
260            storage_manager.get_record_key(schema, &owner_key, encryption_key)
261        }).inspect_err(log_veilid_api_error!(self))
262    }
263
264    /// Create a new MemberId for use with in creating `DHTSchema`s.
265    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", skip(self), fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
266    pub fn generate_member_id(&self, writer_key: &PublicKey) -> VeilidAPIResult<MemberId> {
267        record_duration(|| {
268            veilid_log!(self debug "VeilidAPI::generate_member_id(writer_key: {:?}", writer_key);
269
270            self.crypto()?.check_public_key(writer_key)?;
271
272            let storage_manager = self.core_context()?.storage_manager();
273            storage_manager.generate_member_id(writer_key)
274        })
275        .inspect_err(log_veilid_api_error!(self))
276    }
277
278    /// Start a transaction on a set of DHT records
279    /// Record keys must have been opened via a routing context already when passed to this function
280    /// The maximum number of records per transaction is currently 32.
281    /// Options can be specified that supply a default signing keypair for records that are not opened for writing
282    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
283    pub async fn transact_dht_records(
284        &self,
285        record_keys: Vec<RecordKey>,
286        options: Option<TransactDHTRecordsOptions>,
287    ) -> VeilidAPIResult<DHTTransaction> {
288        record_duration_fut(async {
289            veilid_log!(self debug
290                "VeilidAPI::transact_dht_records(self: {:?}, record_keys: {:?}, options: {:?})", self, record_keys, options);
291
292            let storage_manager = self.core_context()?.storage_manager();
293
294            for record_key in &record_keys {
295                storage_manager.check_record_key(record_key)?;
296            }
297
298            let handle = Box::pin(storage_manager.begin_transaction(record_keys, options)).await?;
299
300            DHTTransaction::new(self.clone(), handle.clone())
301        }).await.inspect_err(log_veilid_api_error!(self))
302    }
303
304    ////////////////////////////////////////////////////////////////
305    // Private route allocation
306
307    /// Allocate a new private route set with default cryptography and network options.
308    /// Default settings are for [Stability::Reliable] and [Sequencing::PreferOrdered].
309    /// Returns a route id and a publishable 'blob' with the route encrypted with each crypto kind.
310    /// Those nodes importing the blob will have their choice of which crypto kind to use.
311    ///
312    /// Returns a route id and 'blob' that can be published over some means (DHT or otherwise) to be
313    /// imported by another Veilid node.
314    pub async fn new_private_route(&self) -> VeilidAPIResult<RouteBlob> {
315        record_duration_fut(async {
316            Box::pin(self.new_custom_private_route(
317                &VALID_CRYPTO_KINDS,
318                Stability::Reliable,
319                Sequencing::PreferOrdered,
320            ))
321            .await
322        })
323        .await
324    }
325
326    /// Allocate a new private route and specify a specific cryptosystem, stability and sequencing preference.
327    /// Faster connections may be possible with [Stability::LowLatency], and [Sequencing::NoPreference] at the
328    /// expense of some loss of messages.
329    /// Returns a route id and a publishable 'blob' with the route encrypted with each crypto kind.
330    /// Those nodes importing the blob will have their choice of which crypto kind to use.
331    ///
332    /// Returns a route id and 'blob' that can be published over some means (DHT or otherwise) to be
333    /// imported by another Veilid node.
334    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), skip(self), ret))]
335    pub async fn new_custom_private_route(
336        &self,
337        crypto_kinds: &[CryptoKind],
338        stability: Stability,
339        sequencing: Sequencing,
340    ) -> VeilidAPIResult<RouteBlob> {
341        record_duration_fut(async {
342            veilid_log!(self debug
343                "VeilidAPI::new_custom_private_route(crypto_kinds: {:?}, stability: {:?}, sequencing: {:?})",
344                crypto_kinds,
345                stability,
346                sequencing);
347
348            for kind in crypto_kinds {
349                Crypto::validate_crypto_kind(*kind)?;
350            }
351
352            let default_route_hop_count: usize =
353                self.config()?.network.rpc.default_route_hop_count.into();
354
355            let safety_spec = SafetySpec {
356                preferred_route: None,
357                hop_count: default_route_hop_count,
358                stability,
359                sequencing,
360            };
361
362            let routing_table = self.core_context()?.routing_table();
363            let rss = routing_table.route_spec_store();
364            let route_id =
365                rss.allocate_route(crypto_kinds, &safety_spec, DirectionSet::all(), &[], false)?;
366            match Box::pin(rss.test_route(route_id.clone())).await? {
367                Some(true) => {
368                    // route tested okay
369                }
370                Some(false) => {
371                    rss.release_route(route_id.clone());
372                    apibail_try_again!("allocated route failed to test");
373                }
374                None => {
375                    rss.release_route(route_id.clone());
376                    apibail_try_again!("allocated route could not be tested");
377                }
378            }
379            let private_routes = rss.assemble_private_route_set(&route_id, Some(true))?;
380            let blob = match RouteSpecStore::private_routes_to_blob(&private_routes) {
381                Ok(v) => v,
382                Err(e) => {
383                    rss.release_route(route_id);
384                    return Err(e);
385                }
386            };
387
388            rss.mark_route_published(&route_id, true)?;
389
390            Ok(RouteBlob { route_id, blob })
391        }).await.inspect_err(log_veilid_api_error!(self))
392    }
393
394    /// Import a private route blob as a remote private route.
395    ///
396    /// Returns a route id that can be used to send private messages to the node creating this route.
397    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), skip(self), ret))]
398    pub fn import_remote_private_route(&self, blob: Vec<u8>) -> VeilidAPIResult<RouteId> {
399        record_duration(|| {
400            veilid_log!(self debug
401                "VeilidAPI::import_remote_private_route(blob: {:?})", blob);
402            let routing_table = self.core_context()?.routing_table();
403            let rss = routing_table.route_spec_store();
404            rss.import_remote_route_blob(blob)
405        })
406        .inspect_err(log_veilid_api_error!(self))
407    }
408
409    /// Release either a locally allocated or remotely imported private route.
410    ///
411    /// This will deactivate the route and free its resources and it can no longer be sent to
412    /// or received from.
413    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), skip(self), ret))]
414    pub fn release_private_route(&self, route_id: RouteId) -> VeilidAPIResult<()> {
415        record_duration(|| {
416            veilid_log!(self debug
417                "VeilidAPI::release_private_route(route_id: {:?})", route_id);
418
419            let routing_table = self.core_context()?.routing_table();
420            routing_table.check_route_id(&route_id)?;
421
422            let rss = routing_table.route_spec_store();
423            if !rss.release_route(route_id.clone()) {
424                apibail_invalid_argument!("release_private_route", "key", route_id);
425            }
426            Ok(())
427        })
428        .inspect_err(log_veilid_api_error!(self))
429    }
430
431    ////////////////////////////////////////////////////////////////
432    // App Calls
433
434    /// Respond to an AppCall received over a [VeilidUpdate::AppCall].
435    ///
436    /// * `call_id` - specifies which call to reply to, and it comes from a [VeilidUpdate::AppCall], specifically the [VeilidAppCall::id()] value.
437    /// * `message` - is an answer blob to be returned by the remote node's [RoutingContext::app_call()] function, and may be up to 32768 bytes.
438    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), skip(self), ret))]
439    pub async fn app_call_reply(
440        &self,
441        call_id: OperationId,
442        message: Vec<u8>,
443    ) -> VeilidAPIResult<()> {
444        record_duration_fut(async {
445            veilid_log!(self debug
446                "VeilidAPI::app_call_reply(call_id: {:?}, message_len: {})", call_id, message.len());
447            veilid_log!(self trace "message: {:?}", message);
448
449            let rpc_processor = self.core_context()?.rpc_processor();
450            rpc_processor
451                .app_call_reply(call_id, message)
452                .map_err(|e| e.into())
453        }).await.inspect_err(log_veilid_api_error!(self))
454    }
455
456    ////////////////////////////////////////////////////////////////
457    // Tunnel Building
458
459    #[cfg(feature = "unstable-tunnels")]
460    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), skip(self), ret))]
461    pub async fn start_tunnel(
462        &self,
463        _endpoint_mode: TunnelMode,
464        _depth: u8,
465    ) -> VeilidAPIResult<PartialTunnel> {
466        panic!("unimplemented");
467    }
468
469    #[cfg(feature = "unstable-tunnels")]
470    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), skip(self), ret))]
471    pub async fn complete_tunnel(
472        &self,
473        _endpoint_mode: TunnelMode,
474        _depth: u8,
475        _partial_tunnel: PartialTunnel,
476    ) -> VeilidAPIResult<FullTunnel> {
477        panic!("unimplemented");
478    }
479
480    #[cfg(feature = "unstable-tunnels")]
481    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), skip(self), ret))]
482    pub async fn cancel_tunnel(&self, _tunnel_id: TunnelId) -> VeilidAPIResult<bool> {
483        panic!("unimplemented");
484    }
485}