Skip to main content

veilid_core/veilid_api/
routing_context.rs

1use super::*;
2
3impl_veilid_log_facility!("veilid_api");
4
5///////////////////////////////////////////////////////////////////////////////////////
6
7/// Valid destinations for a message sent over a routing context.
8#[apply(api_data_enum!)]
9#[api(eq, ord, hash)]
10pub enum Target {
11    /// Node by its node id
12    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
13    NodeId(NodeId),
14    /// Remote private route by its id.
15    #[cfg_attr(feature = "schemars", schemars(with = "String"))]
16    RouteId(RouteId),
17}
18
19pub(crate) struct RoutingContextUnlockedInner {
20    /// Safety routing requirements.
21    safety_selection: SafetySelection,
22}
23
24/// Routing contexts are the way you specify the communication preferences for Veilid.
25///
26/// By default routing contexts have 'safety routing' enabled which offers sender privacy.
27/// privacy. To disable this and send RPC operations straight from the node use [RoutingContext::with_safety()] with a [SafetySelection::Unsafe] parameter.
28/// To enable receiver privacy, you should send to a private route RouteId that you have imported, rather than directly to a NodeId.
29#[derive(Clone)]
30#[must_use]
31pub struct RoutingContext {
32    /// Veilid API handle.
33    api: VeilidAPI,
34    unlocked_inner: Arc<RoutingContextUnlockedInner>,
35}
36
37impl fmt::Debug for RoutingContext {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        f.debug_struct("RoutingContext")
40            .field("ptr", &format!("{:p}", Arc::as_ptr(&self.unlocked_inner)))
41            .field("safety_selection", &self.unlocked_inner.safety_selection)
42            .finish()
43    }
44}
45
46impl RoutingContext {
47    ////////////////////////////////////////////////////////////////
48
49    pub(super) fn try_new(api: VeilidAPI) -> VeilidAPIResult<Self> {
50        let config = api.config()?;
51
52        Ok(Self {
53            api,
54            unlocked_inner: Arc::new(RoutingContextUnlockedInner {
55                safety_selection: SafetySelection::Safe(SafetySpec {
56                    preferred_route: None,
57                    hop_count: config.network.rpc.default_route_hop_count as usize,
58                    stability: Stability::Reliable,
59                    sequencing: Sequencing::PreferOrdered,
60                }),
61            }),
62        })
63    }
64
65    #[must_use]
66    pub(crate) fn log_key(&self) -> &str {
67        self.api.log_key()
68    }
69
70    /// Turn on sender privacy, enabling the use of safety routes. This is the default and
71    /// calling this function is only necessary if you have previously disable safety or used other parameters.
72    ///
73    /// Default values for hop count, stability and sequencing preferences are used.
74    ///
75    /// * Hop count default is dependent on config, but is set to 1 extra hop.
76    /// * Stability default is to choose reliable routes, preferring them over low latency.
77    /// * Sequencing default is to prefer ordered before unordered message delivery.
78    ///
79    /// To customize the safety selection in use, use [RoutingContext::with_safety()].
80    ///
81    /// Errors with `VeilidAPIError::NotInitialized` if the node is shut down (config unavailable).
82    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
83    pub fn with_default_safety(self) -> VeilidAPIResult<Self> {
84        let this = self.clone();
85        record_duration(|| {
86            veilid_log!(self debug
87            "RoutingContext::with_default_safety(self: {:?})", self);
88
89            let config = self.api.config()?;
90
91            self.with_safety(SafetySelection::Safe(SafetySpec {
92                preferred_route: None,
93                hop_count: config.network.rpc.default_route_hop_count as usize,
94                stability: Stability::Reliable,
95                sequencing: Sequencing::PreferOrdered,
96            }))
97        })
98        .inspect_err(log_veilid_api_error!(this))
99    }
100
101    /// Use a custom [SafetySelection]. Can be used to disable safety via [SafetySelection::Unsafe].
102    ///
103    /// Errors with `VeilidAPIError::Generic` if [SafetySelection::Unsafe] is requested without the
104    /// `footgun-nodeid-target` feature, or if `hop_count` exceeds the configured max route hop count.
105    /// Errors with `VeilidAPIError::InvalidArgument` if a `preferred_route` is set that is not a known route id.
106    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
107    pub fn with_safety(self, mut safety_selection: SafetySelection) -> VeilidAPIResult<Self> {
108        let this = self.clone();
109        record_duration(|| {
110            veilid_log!(self debug
111            "RoutingContext::with_safety(self: {:?}, safety_selection: {:?})", self, safety_selection);
112
113            if let SafetySelection::Unsafe(_) = &safety_selection {
114                #[cfg(not(feature = "footgun-nodeid-target"))]
115                {
116                    apibail_generic!("Unsafe routing mode is not allowed without the 'footgun-nodeid-target' feature enabled");
117                }
118            }
119
120            if let SafetySelection::Safe(safe) = &mut safety_selection {
121                if let Some(preferred_route) = &safe.preferred_route {
122                    self.api
123                        .core_context()?
124                        .routing_table()
125                        .check_route_id(preferred_route)?;
126                }
127                let config = self.api.config()?;
128                let default_route_hop_count = config.network.rpc.default_route_hop_count as usize;
129                let max_route_hop_count = config.internal().network.rpc.max_route_hop_count as usize;
130
131                if safe.hop_count == 0 {
132                    safe.hop_count = default_route_hop_count;
133                } else if safe.hop_count > max_route_hop_count {
134                    apibail_generic!("hop count must be less than or equal to configured max route hop count");
135                }
136            }
137
138            Ok(Self {
139                api: self.api.clone(),
140                unlocked_inner: Arc::new(RoutingContextUnlockedInner { safety_selection }),
141            })
142        }).inspect_err(log_veilid_api_error!(this))
143    }
144
145    /// Use a specified [Sequencing] preference, with or without privacy.
146    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
147    pub fn with_sequencing(self, sequencing: Sequencing) -> Self {
148        record_duration(|| {
149            veilid_log!(self debug
150            "RoutingContext::with_sequencing(self: {:?}, sequencing: {:?})", self, sequencing);
151
152            Self {
153                api: self.api.clone(),
154                unlocked_inner: Arc::new(RoutingContextUnlockedInner {
155                    safety_selection: match &self.unlocked_inner.safety_selection {
156                        SafetySelection::Unsafe(_) => SafetySelection::Unsafe(sequencing),
157                        SafetySelection::Safe(safety_spec) => SafetySelection::Safe(SafetySpec {
158                            preferred_route: safety_spec.preferred_route.clone(),
159                            hop_count: safety_spec.hop_count,
160                            stability: safety_spec.stability,
161                            sequencing,
162                        }),
163                    },
164                }),
165            }
166        })
167    }
168
169    /// Get the safety selection in use on this routing context.
170    pub fn safety(&self) -> SafetySelection {
171        self.unlocked_inner.safety_selection.clone()
172    }
173
174    /// Get the sequencing used by this routing context
175    pub fn sequencing(&self) -> Sequencing {
176        match &self.unlocked_inner.safety_selection {
177            SafetySelection::Unsafe(sequencing) => *sequencing,
178            SafetySelection::Safe(safety_spec) => safety_spec.sequencing,
179        }
180    }
181
182    /// Get the [VeilidAPI] object that created this [RoutingContext].
183    pub fn api(&self) -> VeilidAPI {
184        self.api.clone()
185    }
186
187    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
188    async fn get_destination(&self, target: Target) -> VeilidAPIResult<rpc_processor::Destination> {
189        async {
190            let rpc_processor = self.api.core_context()?.rpc_processor();
191            let recorder =
192                DurationRecorder::new("RoutingContext::get_destination", |name, start| {
193                    veilid_log!(self debug
194                    "{}[start={:#}](self: {:?}, target: {:?})", name, start, self, target);
195                });
196            recorder
197                .record_fut(
198                    async {
199                        let dest = Box::pin(rpc_processor.resolve_target_to_destination(
200                            target,
201                            self.unlocked_inner.safety_selection.clone(),
202                        ))
203                        .await?;
204                        VeilidAPIResult::Ok(dest)
205                    },
206                    |name, start, dur, ret| {
207                        veilid_log!(self debug
208                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
209                        ret
210                    },
211                )
212                .await
213        }
214        .await
215        .inspect_err(log_veilid_api_error!(self))
216    }
217
218    fn check_target(&self, target: &Target) -> VeilidAPIResult<()> {
219        match target {
220            Target::NodeId(node_id) => {
221                self.api
222                    .core_context()?
223                    .routing_table()
224                    .check_node_id(node_id)?;
225            }
226            Target::RouteId(route_id) => {
227                self.api
228                    .core_context()?
229                    .routing_table()
230                    .check_route_id(route_id)?;
231            }
232        }
233        Ok(())
234    }
235
236    ////////////////////////////////////////////////////////////////
237    // App-level Messaging
238
239    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", skip(message), fields(duration, __VEILID_LOG_KEY = self.log_key(), message_len = message.len(), ret.len)))]
240    async fn internal_app_call(&self, target: Target, message: Bytes) -> VeilidAPIResult<Bytes> {
241        async {
242            self.check_target(&target)?;
243            let rpc_processor = self.api.core_context()?.rpc_processor();
244
245            let message_len = message.len();
246            let recorder = DurationRecorder::new("RoutingContext::app_call", |name, start| {
247                veilid_log!(self debug
248                    "{}[start={:#}](self: {:?}, target: {:?}, message_len: {})", name, start, self, target, message_len);
249                veilid_log!(self trace "message: {:?}", message);
250            });
251            recorder.record_fut(
252                async {
253                    let dest = self.get_destination(target).await?;
254                    let answer = VeilidAPIError::from_network_result(Box::pin(rpc_processor.rpc_call_app_call(dest, message)).await?)?;
255                    tracing::Span::current().record("ret.len", answer.answer.len());
256                    VeilidAPIResult::Ok(answer.answer)
257                },
258                |name, start, dur, ret| {
259                    veilid_log!(self debug
260                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
261                    ret
262                },
263            ).await
264        }.await.inspect_err(log_veilid_api_error!(self))
265    }
266
267    #[cfg(feature = "footgun-nodeid-target")]
268    /// App-level bidirectional call that expects a response to be returned.
269    ///
270    /// Veilid apps may use this for arbitrary message passing.
271    ///
272    /// * `target` - can be either a direct node id or a private route.
273    /// * `message` - an arbitrary message blob of up to 32768 bytes.
274    ///
275    /// Returns an answer blob of up to 32768 bytes.
276    ///
277    /// Blocks on the network awaiting the reply; governed by `network.rpc.timeout_ms`.
278    ///
279    /// Errors with `VeilidAPIError::InvalidArgument` if `target` is an unsupported or malformed node id or route id.
280    /// Errors with `VeilidAPIError::NoConnection` if the target node id or remote private route could not be
281    /// resolved or no route could be allocated (retryable), `::Timeout` if the reply deadline elapsed (retryable),
282    /// `::TryAgain` if a route is temporarily unavailable (retryable), and `::InvalidTarget` if the target is unreachable.
283    pub async fn app_call(&self, target: Target, message: Vec<u8>) -> VeilidAPIResult<Vec<u8>> {
284        self.internal_app_call(target, message.into())
285            .await
286            .map(|x| x.into())
287    }
288
289    #[cfg(not(feature = "footgun-nodeid-target"))]
290    /// App-level bidirectional call that expects a response to be returned.
291    ///
292    /// Veilid apps may use this for arbitrary message passing.
293    ///
294    /// * `target` - a private route id
295    /// * `message` - an arbitrary message blob of up to 32768 bytes.
296    ///
297    /// Returns an answer blob of up to 32768 bytes.
298    ///
299    /// Blocks on the network awaiting the reply; governed by `network.rpc.timeout_ms`.
300    ///
301    /// Errors with `VeilidAPIError::InvalidTarget` if `target` is a `NodeId` (only `RouteId` is permitted without
302    /// the `footgun-nodeid-target` feature). Otherwise errors with `VeilidAPIError::NoConnection` if the route could
303    /// not be resolved or allocated (retryable), `::Timeout` if the reply deadline elapsed (retryable), or `::TryAgain`
304    /// if a route is temporarily unavailable (retryable).
305    pub async fn app_call(&self, target: Target, message: Vec<u8>) -> VeilidAPIResult<Vec<u8>> {
306        match target {
307            Target::RouteId(_) => self
308                .internal_app_call(target, message.into())
309                .await
310                .map(|x| x.into()),
311            Target::NodeId(_) => Err(VeilidAPIError::invalid_target(
312                "Only RouteId targets are allowed without the footgun feature",
313            )),
314        }
315    }
316
317    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", skip(message), fields(duration, __VEILID_LOG_KEY = self.log_key(), message_len = message.len()), ret))]
318    async fn internal_app_message(&self, target: Target, message: Bytes) -> VeilidAPIResult<()> {
319        async {
320            self.check_target(&target)?;
321            let rpc_processor = self.api.core_context()?.rpc_processor();
322
323            let message_len = message.len();
324            let recorder = DurationRecorder::new("RoutingContext::app_message", |name, start| {
325                veilid_log!(self debug
326                    "{}[start={:#}](self: {:?}, target: {:?}, message_len: {})", name, start, self, target, message_len);
327                veilid_log!(self trace "message: {:?}", message);
328            });
329            recorder.record_fut(
330                async {
331                    let dest = self.get_destination(target).await?;
332                    VeilidAPIError::from_network_result(Box::pin(rpc_processor.rpc_call_app_message(dest, message)).await?)
333                },
334                |name, start, dur, ret| {
335                    veilid_log!(self debug
336                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
337                    ret
338                },
339            ).await
340        }.await.inspect_err(log_veilid_api_error!(self))
341    }
342
343    #[cfg(feature = "footgun-nodeid-target")]
344    /// App-level unidirectional message that does not expect any value to be returned.
345    ///
346    /// Veilid apps may use this for arbitrary message passing.
347    ///
348    /// * `target` - can be either a direct node id or a private route.
349    /// * `message` - an arbitrary message blob of up to 32768 bytes.
350    ///
351    /// Sends over the network but does not await a reply; returns once the statement is dispatched.
352    ///
353    /// Errors with `VeilidAPIError::InvalidArgument` if `target` is an unsupported or malformed node id or route id.
354    /// Errors with `VeilidAPIError::NoConnection` if the target node id or remote private route could not be
355    /// resolved or no route could be allocated (retryable), `::Timeout` if dispatch timed out (retryable),
356    /// `::TryAgain` if a route is temporarily unavailable (retryable), and `::InvalidTarget` if the target is unreachable.
357    pub async fn app_message(&self, target: Target, message: Vec<u8>) -> VeilidAPIResult<()> {
358        self.internal_app_message(target, message.into()).await
359    }
360
361    #[cfg(not(feature = "footgun-nodeid-target"))]
362    /// App-level unidirectional message that does not expect any value to be returned.
363    ///
364    /// Veilid apps may use this for arbitrary message passing.
365    ///
366    /// * `target` - a private route.
367    /// * `message` - an arbitrary message blob of up to 32768 bytes.
368    ///
369    /// Sends over the network but does not await a reply; returns once the statement is dispatched.
370    ///
371    /// Errors with `VeilidAPIError::InvalidTarget` if `target` is a `NodeId` (only `RouteId` is permitted without
372    /// the `footgun-nodeid-target` feature). Otherwise errors with `VeilidAPIError::NoConnection` if the route could
373    /// not be resolved or allocated (retryable), `::Timeout` if dispatch timed out (retryable), or `::TryAgain`
374    /// if a route is temporarily unavailable (retryable).
375    pub async fn app_message(&self, target: Target, message: Vec<u8>) -> VeilidAPIResult<()> {
376        match target {
377            Target::RouteId(_) => self.internal_app_message(target, message.into()).await,
378            Target::NodeId(_) => Err(VeilidAPIError::invalid_target(
379                "Only PrivateRoute targets are allowed without the footgun feature",
380            )),
381        }
382    }
383
384    ///////////////////////////////////
385    // DHT Records
386
387    /// Creates a new DHT record
388    ///
389    /// The record is considered 'open' after the create operation succeeds.
390    /// * 'kind' - specify a cryptosystem kind to use
391    /// * 'schema' - the schema to use when creating the DHT record
392    /// * 'owner' - optionally specify an owner keypair to use. If you leave this as None then a random one will be generated. If specified, the crypto kind of the owner must match that of the `kind` parameter
393    ///
394    /// Returns the newly allocated DHT record's key if successful.
395    /// Note: if you pass in an owner keypair this call is a deterministic! This means that if you try to create a new record for a given owner and schema that already exists it *will* fail.
396    ///
397    /// Local-only: builds and opens the record in the local store without network fanout.
398    /// The returned record is left open; close it with [RoutingContext::close_dht_record] or it leaks the open handle.
399    ///
400    /// Errors with `VeilidAPIError::Generic` if `kind` is an unsupported crypto kind or `owner` is a malformed keypair.
401    /// Errors with `VeilidAPIError::InvalidArgument` if `schema` has an invalid subkey/member/writer count, if `owner`
402    /// is the wrong crypto kind for `kind`, or if this node's id would be a schema member. Errors with
403    /// `VeilidAPIError::NotInitialized` if the node is shut down.
404    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
405    pub async fn create_dht_record(
406        &self,
407        kind: CryptoKind,
408        schema: DHTSchema,
409        owner: Option<KeyPair>,
410    ) -> VeilidAPIResult<DHTRecordDescriptor> {
411        async {
412            Crypto::validate_crypto_kind(kind)?;
413            schema.validate()?;
414            if let Some(owner) = &owner {
415                self.api.crypto()?.check_keypair(owner)?;
416            }
417            let storage_manager = self.api.core_context()?.storage_manager();
418
419            let recorder = DurationRecorder::new("RoutingContext::create_dht_record", |name, start| {
420                veilid_log!(self debug
421                    "{}[start={:#}](self: {:?}, schema: {:?}, owner: {:?}, kind: {:?})", name, start, self, schema, owner, kind);
422            });
423            recorder.record_fut(
424                Box::pin(storage_manager.create_record(
425                    kind,
426                    schema,
427                    owner,
428                    self.unlocked_inner.safety_selection.clone(),
429                )),
430                |name, start, dur, ret| {
431                    veilid_log!(self debug
432                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
433                    ret
434                },
435            ).await
436        }.await.inspect_err(log_veilid_api_error!(self))
437    }
438
439    /// Opens a DHT record at a specific key.
440    ///
441    /// Associates a 'default_writer' secret if one is provided to provide writer capability. The
442    /// writer can be overridden if specified here via the set_dht_value writer.
443    ///
444    /// Records may only be opened or created. If a record is re-opened it will use the new writer and routing context
445    /// ignoring the settings of the last time it was opened. This allows one to open a record a second time
446    /// without first closing it, which will keep the active 'watches' on the record but change the default writer or
447    /// safety selection.
448    ///
449    /// Returns the DHT record descriptor for the opened record if successful.
450    ///
451    /// Half of an open/close pair: close it with [RoutingContext::close_dht_record] or the open handle and its watches leak.
452    /// Re-opening an already-open record is safe and replaces the writer and safety selection in place, preserving active watches.
453    /// Returns from the local store without a network round-trip when the record is already local; otherwise blocks on a network inspect (subkey 0), and returns `TryAgain` if offline.
454    ///
455    /// Errors with `VeilidAPIError::Generic` if `record_key` is an unsupported kind or malformed, or `default_writer`
456    /// is a malformed keypair. Errors with `VeilidAPIError::TryAgain` if the record is not yet local and the node is
457    /// offline (retryable), `::KeyNotFound` if the record does not exist on the network, and `::NotInitialized` if the
458    /// node is shut down.
459    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
460    pub async fn open_dht_record(
461        &self,
462        record_key: RecordKey,
463        default_writer: Option<KeyPair>,
464    ) -> VeilidAPIResult<DHTRecordDescriptor> {
465        async {
466            self.api
467                .core_context()?
468                .storage_manager()
469                .check_record_key(&record_key)?;
470            if let Some(default_writer) = &default_writer {
471                self.api.crypto()?.check_keypair(default_writer)?;
472            }
473            let storage_manager = self.api.core_context()?.storage_manager();
474
475            let recorder = DurationRecorder::new("RoutingContext::open_dht_record", |name, start| {
476                veilid_log!(self debug
477                    "{}[start={:#}](self: {:?}, key: {:?}, default_writer: {:?})", name, start, self, record_key, default_writer);
478            });
479            recorder.record_fut(
480                storage_manager.open_record(
481                    record_key,
482                    default_writer,
483                    self.unlocked_inner.safety_selection.clone(),
484                ),
485                |name, start, dur, ret| {
486                    veilid_log!(self debug
487                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
488                    ret
489                },
490            ).await
491        }.await.inspect_err(log_veilid_api_error!(self))
492    }
493
494    /// Closes a DHT record at a specific key that was opened with create_dht_record or open_dht_record.
495    ///
496    /// Closing a record allows you to re-open it with a different routing context.
497    ///
498    /// The release half of the open/close pair; cancels the record's watch (in the background) and drops any associated transaction.
499    /// Blocks holding the record lock until pending writes are flushed to the local store (awaits a disk flush).
500    /// Closing a record that is local but not currently open is a no-op; closing one not in the local store returns `KeyNotFound`.
501    ///
502    /// Errors with `VeilidAPIError::Generic` if `record_key` is an unsupported kind or malformed, and
503    /// `::NotInitialized` if the node is shut down. Neither `KeyNotFound` nor these errors are retryable.
504    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
505    pub async fn close_dht_record(&self, record_key: RecordKey) -> VeilidAPIResult<()> {
506        async {
507            self.api
508                .core_context()?
509                .storage_manager()
510                .check_record_key(&record_key)?;
511            let storage_manager = self.api.core_context()?.storage_manager();
512
513            let recorder =
514                DurationRecorder::new("RoutingContext::close_dht_record", |name, start| {
515                    veilid_log!(self debug
516                    "{}[start={:#}](self: {:?}, key: {:?})", name, start, self, record_key);
517                });
518            recorder
519                .record_fut(
520                    Box::pin(storage_manager.close_record(record_key)),
521                    |name, start, dur, ret| {
522                        veilid_log!(self debug
523                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
524                        ret
525                    },
526                )
527                .await
528        }
529        .await
530        .inspect_err(log_veilid_api_error!(self))
531    }
532
533    /// Waits for any pending offline subkey writes for a DHT record to be flushed to the network.
534    ///
535    /// Returns immediately with `Ok(true)` if there are no pending writes.
536    /// When a `timeout` is specified, returns `Ok(true)` if all pending writes were flushed, or `Ok(false)` if `timeout` elapsed first.
537    /// When no `timeout` is specified, waits indefinitely for writes to be flushed and then returns `Ok(true)`.
538    /// If the system shuts down while waiting, returns `Err(VeilidAPIError::NotInitialized)`.
539    /// Errors with `VeilidAPIError::Generic` if `record_key` is an unsupported kind or malformed.
540    ///
541    /// Blocks until pending writes flush, the `timeout` elapses, or shutdown; non-blocking when there are no pending writes.
542    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
543    pub async fn flush_dht_record(
544        &self,
545        record_key: RecordKey,
546        timeout: Option<Duration>,
547    ) -> VeilidAPIResult<bool> {
548        async {
549            self.api
550                .core_context()?
551                .storage_manager()
552                .check_record_key(&record_key)?;
553            let storage_manager = self.api.core_context()?.storage_manager();
554
555            let recorder = DurationRecorder::new("RoutingContext::flush_dht_record", |name, start| {
556                veilid_log!(self debug
557                    "{}[start={:#}](self: {:?}, key: {:?}, timeout: {:?})", name, start, self, record_key, timeout);
558            });
559            recorder.record_fut(
560                Box::pin(storage_manager.flush_record(record_key, timeout)),
561                |name, start, dur, ret| {
562                    veilid_log!(self debug
563                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
564                    ret
565                },
566            ).await
567        }
568        .await
569        .inspect_err(log_veilid_api_error!(self))
570    }
571
572    /// Deletes a DHT record at a specific key.
573    ///
574    /// If the record is opened, it must be closed before it is deleted.
575    /// Deleting a record does not delete it from the network, but will remove the storage of the record
576    /// locally, and will prevent its value from being refreshed on the network by this node.
577    ///
578    /// Local-only: closes the record if still open, then removes it from the local store; no network round-trip.
579    ///
580    /// Errors with `VeilidAPIError::Generic` if `record_key` is an unsupported kind or malformed, `::KeyNotFound`
581    /// if the record is not in the local store, and `::NotInitialized` if the node is shut down. None are retryable.
582    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
583    pub async fn delete_dht_record(&self, record_key: RecordKey) -> VeilidAPIResult<()> {
584        async {
585            self.api
586                .core_context()?
587                .storage_manager()
588                .check_record_key(&record_key)?;
589            let storage_manager = self.api.core_context()?.storage_manager();
590
591            let recorder =
592                DurationRecorder::new("RoutingContext::delete_dht_record", |name, start| {
593                    veilid_log!(self debug
594                    "{}[start={:#}](self: {:?}, key: {:?})", name, start, self, record_key);
595                });
596            recorder
597                .record_fut(
598                    Box::pin(storage_manager.delete_record(record_key)),
599                    |name, start, dur, ret| {
600                        veilid_log!(self debug
601                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
602                        ret
603                    },
604                )
605                .await
606        }
607        .await
608        .inspect_err(log_veilid_api_error!(self))
609    }
610
611    /// Gets the latest value of a subkey.
612    ///
613    /// May pull the latest value from the network, but by setting 'force_refresh' you can force a network data refresh. Can only be used on opened records.
614    ///
615    /// Returns `None` if the value subkey has not yet been set.
616    /// Returns `Some(data)` if the value subkey has valid data.
617    ///
618    /// Non-blocking when a local value exists and `force_refresh` is false; otherwise blocks on a network fanout and returns `TryAgain` if offline.
619    ///
620    /// Errors with `VeilidAPIError::InvalidArgument` if the record is not open, `::Generic` if `record_key` is an
621    /// unsupported kind or malformed, `::TryAgain` if a network refresh is needed and the node is offline (retryable),
622    /// `::KeyNotFound` if the record no longer exists, and `::NotInitialized` if shut down.
623    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
624    pub async fn get_dht_value(
625        &self,
626        record_key: RecordKey,
627        subkey: ValueSubkey,
628        force_refresh: bool,
629    ) -> VeilidAPIResult<Option<ValueData>> {
630        async {
631            self.api
632                .core_context()?
633                .storage_manager()
634                .check_record_key(&record_key)?;
635            let storage_manager = self.api.core_context()?.storage_manager();
636
637            let recorder = DurationRecorder::new("RoutingContext::get_dht_value", |name, start| {
638                veilid_log!(self debug
639                    "{}[start={:#}](self: {:?}, key: {:?}, subkey: {:?}, force_refresh: {:?})", name, start, self, record_key, subkey, force_refresh);
640            });
641            recorder.record_fut(
642                Box::pin(storage_manager.get_value(record_key, subkey, force_refresh)),
643                |name, start, dur, ret| {
644                    veilid_log!(self debug
645                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
646                    ret
647                },
648            ).await
649        }.await.inspect_err(log_veilid_api_error!(self))
650    }
651
652    /// Pushes a changed subkey value to the network.
653    /// The DHT record must first by opened via open_dht_record or create_dht_record.
654    ///
655    /// The writer, if specified, will override the 'default_writer' specified when the record is opened.
656    ///
657    /// Returns `None` if the value was successfully set.
658    /// Returns `Some(data)` if the value set was older than the one available on the network.
659    ///
660    /// Blocks on a network fanout to push the value; when offline or the fanout fails, queues the write for later flush (if `allow_offline`) and returns `Ok(None)`.
661    ///
662    /// Errors with `VeilidAPIError::InvalidArgument` if the record is not open, `::Generic` if `record_key` is an
663    /// unsupported kind or the record is not writable (no writer) or the value fails schema validation (subkey out of
664    /// schema range, `data` larger than the per-subkey limit, or wrong writer for the subkey), `::TryAgain` if the
665    /// record is currently in a transaction (retryable), `::KeyNotFound` if the record no longer exists, and
666    /// `::NotInitialized` if shut down. A failed network fanout does not error; the write is deferred (returns `Ok(None)`).
667    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", skip(data), fields(duration, __VEILID_LOG_KEY = self.log_key(), data.len = data.len()), ret))]
668    pub async fn set_dht_value(
669        &self,
670        record_key: RecordKey,
671        subkey: ValueSubkey,
672        data: Vec<u8>,
673        options: Option<SetDHTValueOptions>,
674    ) -> VeilidAPIResult<Option<ValueData>> {
675        async {
676            self.api
677                .core_context()?
678                .storage_manager()
679                .check_record_key(&record_key)?;
680            let storage_manager = self.api.core_context()?.storage_manager();
681
682            let data_len = data.len();
683            let recorder = DurationRecorder::new("RoutingContext::set_dht_value", |name, start| {
684                veilid_log!(self debug
685                    "{}[start={:#}](self: {:?}, key: {:?}, subkey: {:?}, data: len={}, options: {:?})", name, start, self, record_key, subkey, data_len, options);
686            });
687            recorder.record_fut(
688                Box::pin(storage_manager.set_value(record_key, subkey, data, options)),
689                |name, start, dur, ret| {
690                    veilid_log!(self debug
691                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
692                    ret
693                },
694            ).await
695        }.await.inspect_err(log_veilid_api_error!(self))
696    }
697
698    /// Add or update a watch to a DHT value that informs the user via an VeilidUpdate::ValueChange callback when the record has subkeys change.
699    /// One remote node will be selected to perform the watch and it will offer an expiration time based on a suggestion, and make an attempt to
700    /// continue to report changes via the callback. Nodes that agree to doing watches will be put on our 'ping' list to ensure they are still around
701    /// otherwise the watch will be cancelled and will have to be re-watched.  Can only be used on opened records.
702    ///
703    /// There is only one watch permitted per record. If a change to a watch is desired, the previous one will be overwritten.
704    /// * `key` is the record key to watch. it must first be opened for reading or writing.
705    /// * `subkeys`:
706    ///   - None: specifies watching the entire range of subkeys.
707    ///   - Some(range): is the the range of subkeys to watch. The range must not exceed 512 discrete non-overlapping or adjacent subranges. If no range is specified, this is equivalent to watching the entire range of subkeys.
708    /// * `expiration`:
709    ///   - None: specifies a watch with no expiration
710    ///   - Some(timestamp): the desired timestamp of when to automatically terminate the watch, in microseconds. If this value is less than `network.rpc.timeout_ms` milliseconds in the future, this function will return an error immediately.
711    /// * `count:
712    ///   - None: specifies a watch count of u32::MAX
713    ///   - Some(count): is the number of times the watch will be sent, maximum. A zero value here is equivalent to a cancellation.
714    ///
715    /// Returns Ok(true) if a watch is active for this record.
716    /// Returns Ok(false) if the entire watch has been cancelled.
717    ///
718    /// Re-watching the same record replaces the prior watch's desired parameters in place; only one watch exists per record.
719    /// Records the desired watch state and returns without a network round-trip; a background task reconciles it with a remote node.
720    ///
721    /// Errors with `VeilidAPIError::InvalidArgument` if the record is not open or a non-zero `expiration` is sooner than
722    /// `network.rpc.timeout_ms` in the future; `::Generic` if `record_key` is an unsupported kind or malformed, or no
723    /// local record is found; and `::NotInitialized` if shut down. None are retryable; no network errors surface here
724    /// since reconciliation is deferred to a background task.
725    ///
726    /// DHT watches are accepted with the following conditions:
727    /// * First-come first-served basis for arbitrary unauthenticated readers, up to network.dht.public_watch_limit per record.
728    /// * If a member (either the owner or a SMPL schema member) has opened the key for writing (even if no writing is performed) then the watch will be signed and guaranteed network.dht.member_watch_limit per writer.
729    ///
730    /// Members can be specified via the SMPL schema and do not need to allocate writable subkeys in order to offer a member watch capability.
731    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
732    pub async fn watch_dht_values(
733        &self,
734        record_key: RecordKey,
735        subkeys: Option<ValueSubkeyRangeSet>,
736        expiration: Option<Timestamp>,
737        count: Option<u32>,
738    ) -> VeilidAPIResult<bool> {
739        async {
740            self.api
741                .core_context()?
742                .storage_manager()
743                .check_record_key(&record_key)?;
744            let storage_manager = self.api.core_context()?.storage_manager();
745
746            let recorder = DurationRecorder::new("RoutingContext::watch_dht_values", |name, start| {
747                veilid_log!(self debug
748                    "{}[start={:#}](self: {:?}, key: {:?}, subkeys: {:?}, expiration: {:?}, count: {:?})", name, start, self, record_key, subkeys, expiration, count);
749            });
750            let subkeys = subkeys.unwrap_or_default();
751            let expiration = expiration.unwrap_or_default();
752            let count = count.unwrap_or(u32::MAX);
753            recorder.record_fut(
754                Box::pin(storage_manager.watch_values(record_key, subkeys, expiration, count)),
755                |name, start, dur, ret| {
756                    veilid_log!(self debug
757                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
758                    ret
759                },
760            ).await
761        }.await.inspect_err(log_veilid_api_error!(self))
762    }
763
764    /// Cancels a watch early.
765    ///
766    /// This is a convenience function that cancels watching all subkeys in a range. The subkeys specified here
767    /// are subtracted from the currently-watched subkey range.  Can only be used on opened records.
768    /// * `subkeys`:
769    ///   - None: specifies watching the entire range of subkeys.
770    ///   - Some(range): is the the range of subkeys to watch. The range must not exceed 512 discrete non-overlapping or adjacent subranges. If no range is specified, this is equivalent to watching the entire range of subkeys.
771    ///
772    /// Only the subkey range is changed, the expiration and count remain the same.
773    /// If no subkeys remain, the watch is entirely cancelled and will receive no more updates.
774    ///
775    /// Returns Ok(true) if a watch is active for this record.
776    /// Returns Ok(false) if the entire watch has been cancelled.
777    ///
778    /// A no-op returning `Ok(false)` when no watch is active for the record.
779    /// Records the reduced desired watch state and returns without a network round-trip; a background task sends the cancel.
780    ///
781    /// Errors with `VeilidAPIError::InvalidArgument` if the record is not open, `::Generic` if `record_key` is an
782    /// unsupported kind or malformed or no local record is found, and `::NotInitialized` if shut down. None are retryable.
783    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
784    pub async fn cancel_dht_watch(
785        &self,
786        record_key: RecordKey,
787        subkeys: Option<ValueSubkeyRangeSet>,
788    ) -> VeilidAPIResult<bool> {
789        async {
790            self.api
791                .core_context()?
792                .storage_manager()
793                .check_record_key(&record_key)?;
794            let storage_manager = self.api.core_context()?.storage_manager();
795
796            let recorder = DurationRecorder::new("RoutingContext::cancel_dht_watch", |name, start| {
797                veilid_log!(self debug
798                    "{}[start={:#}](self: {:?}, key: {:?}, subkeys: {:?})", name, start, self, record_key, subkeys);
799            });
800            let subkeys = subkeys.unwrap_or_default();
801            recorder.record_fut(
802                Box::pin(storage_manager.cancel_watch_values(record_key, subkeys)),
803                |name, start, dur, ret| {
804                    veilid_log!(self debug
805                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
806                    ret
807                },
808            ).await
809        }.await.inspect_err(log_veilid_api_error!(self))
810    }
811
812    /// Inspects a DHT record for subkey state.
813    /// This is useful for checking if you should push new subkeys to the network, or retrieve the current state of a record from the network
814    /// to see what needs updating locally. Can only be used on opened records.
815    ///
816    /// * `key` is the record key to inspect. it must first be opened for reading or writing.
817    /// * `subkeys`:
818    ///   - None: specifies inspecting the entire range of subkeys.
819    ///   - Some(range): is the the range of subkeys to inspect. The range must not exceed 512 discrete non-overlapping or adjacent subranges.
820    ///     If no range is specified, this is equivalent to watching the entire range of subkeys.
821    ///
822    /// * `scope` is what kind of range the inspection has:
823    ///   - DHTReportScope::Local`
824    ///     Results will be only for a locally stored record.
825    ///     Useful for seeing what subkeys you have locally and which ones have not been retrieved.
826    ///
827    ///   - `DHTReportScope::SyncGet`
828    ///     Return the local sequence numbers and the network sequence numbers with GetValue fanout parameters.
829    ///     Provides an independent view of both the local sequence numbers and the network sequence numbers for nodes that
830    ///     would be reached as if the local copy did not exist locally.
831    ///     Useful for determining if the current local copy should be updated from the network.
832    ///
833    ///   - `DHTReportScope::SyncSet`
834    ///     Return the local sequence numbers and the network sequence numbers with SetValue fanout parameters.
835    ///     Provides an independent view of both the local sequence numbers and the network sequence numbers for nodes that
836    ///     would be reached as if the local copy did not exist locally.
837    ///     Useful for determining if the unchanged local copy should be pushed to the network.
838    ///
839    ///   - `DHTReportScope::UpdateGet`
840    ///     Return the local sequence numbers and the network sequence numbers with GetValue fanout parameters.
841    ///     Provides an view of both the local sequence numbers and the network sequence numbers for nodes that
842    ///     would be reached as if a GetValue operation were being performed, including accepting newer values from the network.
843    ///     Useful for determining which subkeys would change with a GetValue operation.
844    ///
845    ///   - `DHTReportScope::UpdateSet`
846    ///     Return the local sequence numbers and the network sequence numbers with SetValue fanout parameters.
847    ///     Provides an view of both the local sequence numbers and the network sequence numbers for nodes that
848    ///     would be reached as if a SetValue operation were being performed, including accepting newer values from the network.
849    ///     This simulates a SetValue with the initial sequence number incremented by 1, like a real SetValue would when updating.
850    ///     Useful for determine which subkeys would change with an SetValue operation.
851    ///
852    /// Returns `Ok(DHTRecordReport)` with the subkey ranges that were returned that overlapped the schema, and sequence numbers for each of the subkeys in the range.
853    ///
854    /// `DHTReportScope::Local` is local-only and non-blocking; the Sync/Update scopes block on a network inspect fanout and return `TryAgain` if offline.
855    ///
856    /// Errors with `VeilidAPIError::InvalidArgument` if the record is not open, `::Generic` if `record_key` is an
857    /// unsupported kind or malformed, `::TryAgain` if a network scope is requested and the node is offline (retryable),
858    /// and `::NotInitialized` if shut down.
859    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
860    pub async fn inspect_dht_record(
861        &self,
862        record_key: RecordKey,
863        subkeys: Option<ValueSubkeyRangeSet>,
864        scope: DHTReportScope,
865    ) -> VeilidAPIResult<DHTRecordReport> {
866        async {
867            self.api
868                .core_context()?
869                .storage_manager()
870                .check_record_key(&record_key)?;
871            let storage_manager = self.api.core_context()?.storage_manager();
872
873            let recorder = DurationRecorder::new("RoutingContext::inspect_dht_record", |name, start| {
874                veilid_log!(self debug
875                    "{}[start={:#}](self: {:?}, record_key: {:?}, subkeys: {:?}, scope: {:?})", name, start, self, record_key, subkeys, scope);
876            });
877            let subkeys = subkeys.unwrap_or_default();
878            recorder.record_fut(
879                Box::pin(storage_manager.inspect_record(record_key, subkeys, scope)),
880                |name, start, dur, ret| {
881                    veilid_log!(self debug
882                        "{}[start={:#} dur={:#}](ret: {:?})", name, start, dur, ret);
883                    ret
884                },
885            ).await
886        }.await.inspect_err(log_veilid_api_error!(self))
887    }
888
889    ///////////////////////////////////
890    // Block Store
891
892    #[cfg(feature = "unstable-blockstore")]
893    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret))]
894    pub async fn find_block(&self, _block_id: BlockId) -> VeilidAPIResult<Vec<u8>> {
895        apibail_internal!("unimplemented");
896    }
897
898    #[cfg(feature = "unstable-blockstore")]
899    #[cfg_attr(feature = "instrument", instrument(target = "veilid_api", level = "debug", fields(duration, __VEILID_LOG_KEY = self.log_key()), ret,))]
900    pub async fn supply_block(&self, _block_id: BlockId) -> VeilidAPIResult<bool> {
901        apibail_internal!("unimplemented");
902    }
903}