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}