1use super::*;
5use data_encoding::BASE64URL_NOPAD;
6use hashlink::LinkedHashMap;
7use network_manager::*;
8use once_cell::sync::Lazy;
9use routing_table::*;
10use std::fmt::Write;
11
12impl_veilid_log_facility!("veilid_debug");
13
14#[derive(Default)]
15pub(crate) struct DebugCache {
16 pub imported_routes: Vec<RouteId>,
17 pub opened_record_contexts: Lazy<LinkedHashMap<RecordKey, RoutingContext>>,
18}
19
20#[must_use]
21pub(crate) fn format_opt_ts(ts: Option<TimestampDuration>) -> String {
22 let Some(ts) = ts else {
23 return "---".to_owned();
24 };
25 let secs = ts.seconds_f64();
26 if secs >= 1.0 {
27 format!("{:.2}s", secs)
28 } else {
29 format!("{:.2}ms", secs * 1000.0)
30 }
31}
32
33#[must_use]
34pub(crate) fn format_opt_bps(bps: Option<ByteCount>) -> String {
35 let Some(bps) = bps else {
36 return "---".to_owned();
37 };
38 let bps = bps.as_u64();
39 if bps >= 1024u64 * 1024u64 * 1024u64 {
40 format!("{:.2}GB/s", (bps / (1024u64 * 1024u64)) as f64 / 1024.0)
41 } else if bps >= 1024u64 * 1024u64 {
42 format!("{:.2}MB/s", (bps / 1024u64) as f64 / 1024.0)
43 } else if bps >= 1024u64 {
44 format!("{:.2}KB/s", bps as f64 / 1024.0)
45 } else {
46 format!("{:.2}B/s", bps as f64)
47 }
48}
49
50fn get_bucket_entry_state(text: &str) -> Option<BucketEntryState> {
51 if text == "punished" {
52 Some(BucketEntryState::Punished)
53 } else if text == "dead" {
54 Some(BucketEntryState::Dead)
55 } else if text == "reliable" {
56 Some(BucketEntryState::Reliable)
57 } else if text == "unreliable" {
58 Some(BucketEntryState::Unreliable)
59 } else {
60 None
61 }
62}
63
64fn get_string(text: &str) -> Option<String> {
65 Some(text.to_owned())
66}
67
68fn get_data(text: &str) -> Option<Vec<u8>> {
69 if let Some(stripped_text) = text.strip_prefix('#') {
70 hex::decode(stripped_text).ok()
71 } else if text.starts_with('"') || text.starts_with('\'') {
72 json::parse(text)
73 .ok()?
74 .as_str()
75 .map(|x| x.to_owned().as_bytes().to_vec())
76 } else {
77 Some(text.as_bytes().to_vec())
78 }
79}
80
81fn get_subkeys(text: &str) -> Option<ValueSubkeyRangeSet> {
82 if let Some(n) = get_number::<u32>(text) {
83 Some(ValueSubkeyRangeSet::single(n))
84 } else {
85 ValueSubkeyRangeSet::from_str(text).ok()
86 }
87}
88
89fn get_dht_report_scope(text: &str) -> Option<DHTReportScope> {
90 match text.to_ascii_lowercase().trim() {
91 "local" => Some(DHTReportScope::Local),
92 "syncget" => Some(DHTReportScope::SyncGet),
93 "syncset" => Some(DHTReportScope::SyncSet),
94 "updateget" => Some(DHTReportScope::UpdateGet),
95 "updateset" => Some(DHTReportScope::UpdateSet),
96 _ => None,
97 }
98}
99
100fn get_route_id(
101 registry: VeilidComponentRegistry,
102 allow_allocated: bool,
103 allow_remote: bool,
104) -> impl Fn(&str) -> Option<RouteId> {
105 move |text: &str| {
106 if text.is_empty() {
107 return None;
108 }
109 let routing_table = registry.routing_table();
110 let rss = routing_table.route_spec_store();
111
112 match RouteId::from_str(text).ok() {
113 Some(key) => {
114 if allow_allocated {
115 let routes = rss.list_allocated_routes(|k, _| Some(k.clone()));
116 if routes.contains(&key) {
117 return Some(key);
118 }
119 }
120 if allow_remote {
121 let rroutes = rss.list_remote_routes(|k, _| Some(k.clone()));
122 if rroutes.contains(&key) {
123 return Some(key);
124 }
125 }
126 }
127 None => {
128 if allow_allocated {
129 let routes = rss.list_allocated_routes(|k, _| Some(k.clone()));
130 for r in routes {
131 let rkey = r.to_string();
132 if rkey.starts_with(text)
133 || rkey
134 .split_once(':')
135 .map(|(_, b)| b.starts_with(text))
136 .unwrap_or(false)
137 {
138 return Some(r);
139 }
140 }
141 }
142 if allow_remote {
143 let routes = rss.list_remote_routes(|k, _| Some(k.clone()));
144 for r in routes {
145 let rkey = r.to_string();
146 if rkey.starts_with(text)
147 || rkey
148 .split_once(':')
149 .map(|(_, b)| b.starts_with(text))
150 .unwrap_or(false)
151 {
152 return Some(r);
153 }
154 }
155 }
156 }
157 }
158 None
159 }
160}
161
162fn get_route_key(
163 registry: VeilidComponentRegistry,
164 allow_allocated: bool,
165 allow_remote: bool,
166) -> impl Fn(&str) -> Option<PublicKey> {
167 move |text: &str| {
168 if text.is_empty() {
169 return None;
170 }
171 let routing_table = registry.routing_table();
172 let rss = routing_table.route_spec_store();
173
174 match PublicKey::from_str(text).ok() {
175 Some(key) => {
176 if allow_allocated {
177 let allocated_route_keys = rss.list_allocated_routes(|_, v| {
178 Some(v.iter_route_set().map(|k| k.0.clone()).collect::<Vec<_>>())
179 });
180 if let Some(found_key) = allocated_route_keys
181 .into_iter()
182 .flatten()
183 .find(|x| x == &key)
184 {
185 return Some(found_key);
186 }
187 }
188 if allow_remote {
189 let remote_route_keys = rss.list_remote_routes(|_, v| {
190 Some(
191 v.get_private_routes()
192 .iter()
193 .map(|x| x.public_key.clone())
194 .collect::<Vec<_>>(),
195 )
196 });
197 if let Some(found_key) =
198 remote_route_keys.into_iter().flatten().find(|x| x == &key)
199 {
200 return Some(found_key);
201 }
202 }
203 }
204 None => {
205 if allow_allocated {
206 let allocated_route_keys = rss.list_allocated_routes(|_, v| {
207 Some(v.iter_route_set().map(|k| k.0.clone()).collect::<Vec<_>>())
208 });
209 for r in allocated_route_keys.into_iter().flatten() {
210 let rkey = r.to_string();
211 if rkey.starts_with(text)
212 || rkey
213 .split_once(':')
214 .map(|(_, b)| b.starts_with(text))
215 .unwrap_or(false)
216 {
217 return Some(r);
218 }
219 }
220 }
221 if allow_remote {
222 let remote_route_keys = rss.list_remote_routes(|_, v| {
223 Some(
224 v.get_private_routes()
225 .iter()
226 .map(|x| x.public_key.clone())
227 .collect::<Vec<_>>(),
228 )
229 });
230 for r in remote_route_keys.into_iter().flatten() {
231 let rkey = r.to_string();
232 if rkey.starts_with(text)
233 || rkey
234 .split_once(':')
235 .map(|(_, b)| b.starts_with(text))
236 .unwrap_or(false)
237 {
238 return Some(r);
239 }
240 }
241 }
242 }
243 }
244 None
245 }
246}
247
248fn get_dht_schema(text: &str) -> Option<VeilidAPIResult<DHTSchema>> {
249 if text.is_empty() {
250 return None;
251 }
252 if let Ok(n) = u16::from_str(text) {
253 return Some(DHTSchema::dflt(n));
254 }
255 Some(deserialize_json::<DHTSchema>(text))
256}
257
258fn get_safety_selection(
259 registry: VeilidComponentRegistry,
260) -> impl Fn(&str) -> Option<SafetySelection> {
261 move |text| {
262 let default_route_hop_count =
263 registry.config().network.rpc.default_route_hop_count as usize;
264
265 if !text.is_empty() && &text[0..1] == "-" {
266 let text = &text[1..];
268 let seq = get_sequencing(text).unwrap_or_default();
269 Some(SafetySelection::Unsafe(seq))
270 } else {
271 let mut preferred_route = None;
273 let mut hop_count = default_route_hop_count;
274 let mut stability = Stability::default();
275 let mut sequencing = Sequencing::default();
276 for x in text.split(',') {
277 let x = x.trim();
278 if let Some(pr) = get_route_id(registry.clone(), true, false)(x) {
279 preferred_route = Some(pr)
280 }
281 if let Some(n) = get_number(x) {
282 hop_count = n;
283 }
284 if let Some(s) = get_stability(x) {
285 stability = s;
286 }
287 if let Some(s) = get_sequencing(x) {
288 sequencing = s;
289 }
290 }
291
292 let ss = SafetySpec {
293 preferred_route,
294 hop_count,
295 stability,
296 sequencing,
297 };
298 Some(SafetySelection::Safe(ss))
299 }
300 }
301}
302
303fn get_node_ref_modifiers(node_ref: NodeRef) -> impl FnOnce(&str) -> Option<FilteredNodeRef> {
304 move |text| {
305 let mut node_ref = node_ref.default_filtered();
306 for m in text.split('/') {
307 if let Some(pt) = get_protocol_type(m) {
308 node_ref.merge_filter(NodeRefFilter::new().with_protocol_type(pt));
309 } else if let Some(at) = get_address_type(m) {
310 node_ref.merge_filter(NodeRefFilter::new().with_address_type(at));
311 } else if let Some(rd) = get_routing_domain(m) {
312 node_ref.merge_filter(NodeRefFilter::new().with_routing_domain(rd));
313 } else {
314 return None;
315 }
316 }
317 Some(node_ref)
318 }
319}
320
321fn get_number<T: num_traits::Num + FromStr>(text: &str) -> Option<T> {
322 T::from_str(text).ok()
323}
324
325fn get_record_key(text: &str) -> Option<RecordKey> {
326 RecordKey::from_str(text).ok()
327}
328fn get_bare_node_id(text: &str) -> Option<BareNodeId> {
329 let bare_node_id = BareNodeId::from_str(text).ok()?;
330
331 if bare_node_id.len() != 32 {
333 return None;
334 }
335 Some(bare_node_id)
336}
337fn get_node_id(text: &str) -> Option<NodeId> {
338 let node_id = NodeId::from_str(text).ok()?;
339
340 if node_id.value().len() != 32 {
342 return None;
343 }
344 Some(node_id)
345}
346fn get_keypair(text: &str) -> Option<KeyPair> {
347 KeyPair::from_str(text).ok()
348}
349fn get_keypair_group(text: &str) -> Option<KeyPairGroup> {
350 KeyPairGroup::from_str(text).ok()
351}
352
353fn get_crypto_system_version<'a>(
354 crypto: &'a Crypto,
355) -> impl FnOnce(&str) -> Option<CryptoSystemGuard<'a>> {
356 move |text| {
357 let kindstr = get_string(text)?;
358 let kind = CryptoKind::from_str(&kindstr).ok()?;
359 crypto.get(kind)
360 }
361}
362
363fn get_dht_key_no_safety(text: &str) -> Option<RecordKey> {
364 let key = get_record_key(text)?;
365
366 Some(key)
367}
368
369fn get_dht_key(
370 registry: VeilidComponentRegistry,
371) -> impl FnOnce(&str) -> Option<(RecordKey, Option<SafetySelection>)> {
372 move |text| {
373 let (text, ss) = if let Some((first, second)) = text.split_once('+') {
375 let ss = get_safety_selection(registry)(second)?;
376 (first, Some(ss))
377 } else {
378 (text, None)
379 };
380 if text.is_empty() {
381 return None;
382 }
383
384 let key = get_record_key(text)?;
385
386 Some((key, ss))
387 }
388}
389
390fn resolve_node_ref(
391 registry: VeilidComponentRegistry,
392 safety_selection: SafetySelection,
393) -> impl FnOnce(&str) -> PinBoxFutureStatic<Option<NodeRef>> {
394 move |text| {
395 let text = text.to_owned();
396 Box::pin(async move {
397 let nr = if let Some(node_id) = get_node_id(&text) {
398 registry
399 .rpc_processor()
400 .resolve_node(node_id, safety_selection)
401 .await
402 .ok()
403 .flatten()?
404 } else {
405 return None;
406 };
407 Some(nr)
408 })
409 }
410}
411
412fn resolve_filtered_node_ref(
413 registry: VeilidComponentRegistry,
414 safety_selection: SafetySelection,
415) -> impl FnOnce(&str) -> PinBoxFutureStatic<Option<FilteredNodeRef>> {
416 move |text| {
417 let text = text.to_owned();
418 Box::pin(async move {
419 let (text, mods) = text
420 .split_once('/')
421 .map(|x| (x.0, Some(x.1)))
422 .unwrap_or((&text, None));
423
424 let nr = if let Some(node_id) = get_node_id(text) {
425 registry
426 .rpc_processor()
427 .resolve_node(node_id, safety_selection)
428 .await
429 .ok()
430 .flatten()?
431 } else {
432 return None;
433 };
434 if let Some(mods) = mods {
435 Some(get_node_ref_modifiers(nr)(mods)?)
436 } else {
437 Some(nr.default_filtered())
438 }
439 })
440 }
441}
442
443fn get_node_ref(registry: VeilidComponentRegistry) -> impl FnOnce(&str) -> Option<NodeRef> {
444 move |text| {
445 let routing_table = registry.routing_table();
446 let nr = if let Some(key) = get_bare_node_id(text) {
447 routing_table.lookup_any_node_ref(key).ok().flatten()?
448 } else if let Some(node_id) = get_node_id(text) {
449 routing_table.lookup_node_ref(node_id).ok().flatten()?
450 } else {
451 return None;
452 };
453 Some(nr)
454 }
455}
456
457fn get_filtered_node_ref(
458 registry: VeilidComponentRegistry,
459) -> impl FnOnce(&str) -> Option<FilteredNodeRef> {
460 move |text| {
461 let routing_table = registry.routing_table();
462 FilteredNodeRef::parse(&routing_table, text).ok().flatten()
463 }
464}
465
466fn get_protocol_type(text: &str) -> Option<ProtocolType> {
467 ProtocolType::from_str(text).ok()
468}
469fn get_sequencing(text: &str) -> Option<Sequencing> {
470 Sequencing::from_str(text).ok()
471}
472fn get_stability(text: &str) -> Option<Stability> {
473 Stability::from_str(text).ok()
474}
475fn get_direction_set(text: &str) -> Option<DirectionSet> {
476 Direction::set_from_str(text).ok()
477}
478
479fn get_address_type(text: &str) -> Option<AddressType> {
480 AddressType::from_str(text).ok()
481}
482fn get_routing_domain(text: &str) -> Option<RoutingDomain> {
483 RoutingDomain::from_str(text).ok()
484}
485
486fn get_ip_addr(text: &str) -> Option<IpAddr> {
487 IpAddr::from_str(text).ok()
488}
489
490fn get_published(text: &str) -> Option<bool> {
491 let ptext = text.to_ascii_lowercase();
492 if ptext == "published" {
493 Some(true)
494 } else if ptext == "current" {
495 Some(false)
496 } else {
497 None
498 }
499}
500
501fn get_debug_argument<T, G: FnOnce(&str) -> Option<T>>(
502 value: &str,
503 context: &str,
504 argument: &str,
505 getter: G,
506) -> VeilidAPIResult<T> {
507 let Some(val) = getter(value) else {
508 apibail_invalid_argument!(context, argument, value);
509 };
510 Ok(val)
511}
512
513async fn async_get_debug_argument<T, G: FnOnce(&str) -> PinBoxFutureStatic<Option<T>>>(
514 value: &str,
515 context: &str,
516 argument: &str,
517 getter: G,
518) -> VeilidAPIResult<T> {
519 let Some(val) = getter(value).await else {
520 apibail_invalid_argument!(context, argument, value);
521 };
522 Ok(val)
523}
524
525fn get_debug_argument_at<T, G: FnOnce(&str) -> Option<T>>(
526 debug_args: &[String],
527 pos: usize,
528 context: &str,
529 argument: &str,
530 getter: G,
531) -> VeilidAPIResult<T> {
532 if pos >= debug_args.len() {
533 apibail_missing_argument!(context, argument);
534 }
535 let value = &debug_args[pos];
536 let Some(val) = getter(value) else {
537 apibail_invalid_argument!(context, argument, value);
538 };
539 Ok(val)
540}
541
542async fn async_get_debug_argument_at<T, G: FnOnce(&str) -> PinBoxFutureStatic<Option<T>>>(
543 debug_args: &[String],
544 pos: usize,
545 context: &str,
546 argument: &str,
547 getter: G,
548) -> VeilidAPIResult<T> {
549 if pos >= debug_args.len() {
550 apibail_missing_argument!(context, argument);
551 }
552 let value = &debug_args[pos];
553 let Some(val) = getter(value).await else {
554 apibail_invalid_argument!(context, argument, value);
555 };
556 Ok(val)
557}
558
559#[must_use]
560pub fn print_data(data: &[u8], truncate_len: Option<usize>) -> String {
561 let mut printable = true;
563 for c in data {
564 if *c < 32 || *c > 126 {
565 printable = false;
566 break;
567 }
568 }
569
570 let (data, truncated) = if let Some(truncate_len) = truncate_len {
571 if data.len() > truncate_len {
572 (&data[0..truncate_len], true)
573 } else {
574 (data, false)
575 }
576 } else {
577 (data, false)
578 };
579
580 let strdata = if printable {
581 String::from_utf8_lossy(data).to_string()
582 } else {
583 let sw = shell_words::quote(String::from_utf8_lossy(data).as_ref()).to_string();
584 let h = hex::encode(data);
585 if h.len() < sw.len() {
586 h
587 } else {
588 sw
589 }
590 };
591 if truncated {
592 format!("{}...", strdata)
593 } else {
594 strdata
595 }
596}
597
598impl VeilidAPI {
599 fn debug_buckets(&self, args: String) -> VeilidAPIResult<String> {
600 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
601 let mut min_state = BucketEntryState::Unreliable;
602 if args.len() == 1 {
603 min_state = get_debug_argument(
604 &args[0],
605 "debug_buckets",
606 "min_state",
607 get_bucket_entry_state,
608 )?;
609 }
610 let routing_table = self.core_context()?.routing_table();
612 Ok(routing_table.debug_info_buckets(min_state))
613 }
614
615 fn debug_dialinfo(&self, _args: String) -> VeilidAPIResult<String> {
616 let routing_table = self.core_context()?.routing_table();
618 Ok(routing_table.debug_info_dialinfo())
619 }
620 fn debug_peerinfo(&self, args: String) -> VeilidAPIResult<String> {
621 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
623 let routing_table = self.core_context()?.routing_table();
624
625 let mut ai = 0;
626 let mut opt_routing_domain = None;
627 let mut opt_published = None;
628
629 while ai < args.len() {
630 if let Ok(routing_domain) = get_debug_argument_at(
631 &args,
632 ai,
633 "debug_peerinfo",
634 "routing_domain",
635 get_routing_domain,
636 ) {
637 opt_routing_domain = Some(routing_domain);
638 } else if let Ok(published) =
639 get_debug_argument_at(&args, ai, "debug_peerinfo", "published", get_published)
640 {
641 opt_published = Some(published);
642 }
643 ai += 1;
644 }
645
646 let routing_domain = opt_routing_domain.unwrap_or(RoutingDomain::PublicInternet);
647 let published = opt_published.unwrap_or(true);
648
649 Ok(routing_table.debug_info_peerinfo(routing_domain, published))
650 }
651
652 async fn debug_txtrecord(&self, args: String) -> VeilidAPIResult<String> {
653 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
655
656 let signing_key_pairs = get_debug_argument_at(
657 &args,
658 0,
659 "debug_txtrecord",
660 "signing_key_pairs",
661 get_keypair_group,
662 )
663 .ok()
664 .unwrap_or_else(KeyPairGroup::new);
665
666 let network_manager = self.core_context()?.network_manager();
667 Ok(network_manager
668 .debug_info_txtrecord(signing_key_pairs)
669 .await)
670 }
671
672 fn debug_keypair(&self, args: String) -> VeilidAPIResult<String> {
673 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
674 let crypto = self.crypto()?;
675
676 let vcrypto = get_debug_argument_at(
677 &args,
678 0,
679 "debug_keypair",
680 "kind",
681 get_crypto_system_version(&crypto),
682 )
683 .unwrap_or_else(|_| crypto.best());
684
685 let out = vcrypto.generate_keypair().to_string();
687 Ok(out)
688 }
689
690 fn debug_entries(&self, args: String) -> VeilidAPIResult<String> {
691 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
692
693 let mut min_state = BucketEntryState::Unreliable;
694 let mut capabilities = vec![];
695 let mut fastest = false;
696 for arg in args {
697 if let Some(ms) = get_bucket_entry_state(&arg) {
698 min_state = ms;
699 } else if arg == "fastest" {
700 fastest = true;
701 } else {
702 for cap in arg.split(',') {
703 if let Ok(cap) = VeilidCapability::from_str(cap) {
704 capabilities.push(cap);
705 } else {
706 apibail_invalid_argument!("debug_entries", "unknown", arg);
707 }
708 }
709 }
710 }
711
712 let routing_table = self.core_context()?.routing_table();
714 Ok(match fastest {
715 true => routing_table.debug_info_entries_fastest(min_state, capabilities, 100000),
716 false => routing_table.debug_info_entries(min_state, capabilities),
717 })
718 }
719
720 fn debug_entry(&self, args: String) -> VeilidAPIResult<String> {
721 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
722 let registry = self.core_context()?.registry();
723
724 let node_ref = get_debug_argument_at(
725 &args,
726 0,
727 "debug_entry",
728 "node_id",
729 get_node_ref(registry.clone()),
730 )?;
731
732 Ok(registry.routing_table().debug_info_entry(node_ref))
734 }
735
736 async fn debug_relay(&self, args: String) -> VeilidAPIResult<String> {
737 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
738 let registry = self.core_context()?.registry();
739
740 let mut relay_nodes_refs: Vec<NodeRef> = vec![];
741 let mut routing_domain = RoutingDomain::PublicInternet;
742 for n in 0..args.len() {
743 let opt_relay_node = async_get_debug_argument_at(
744 &args,
745 n,
746 "debug_relay",
747 "node_id",
748 resolve_node_ref(registry.clone(), SafetySelection::default()),
749 )
750 .await
751 .ok();
752 if let Some(relay_node) = opt_relay_node {
753 relay_nodes_refs.push(relay_node);
754 continue;
755 }
756
757 let opt_routing_domain = get_debug_argument_at(
758 &args,
759 n,
760 "debug_relay",
761 "routing_domain",
762 get_routing_domain,
763 )
764 .ok();
765 if let Some(rd) = opt_routing_domain {
766 routing_domain = rd;
767 break;
768 }
769 }
770
771 let relays: Vec<_> = relay_nodes_refs
772 .iter()
773 .cloned()
774 .map(|nr| RoutingDomainRelay::new(routing_domain, nr, RelayKind::Inbound))
775 .collect();
776
777 let routing_table = registry.routing_table();
779 match routing_domain {
780 RoutingDomain::LocalNetwork => {
781 let mut editor = routing_table.edit_local_network_routing_domain();
782 if editor.set_relays(relays).commit(true).await {
783 editor.publish();
784 }
785 }
786 RoutingDomain::PublicInternet => {
787 let mut editor = routing_table.edit_public_internet_routing_domain();
788 if editor.set_relays(relays).commit(true).await {
789 editor.publish();
790 }
791 }
792 }
793
794 Ok("Relay changed".to_owned())
795 }
796
797 async fn debug_nodeinfo(&self, _args: String) -> VeilidAPIResult<String> {
798 let registry = self.core_context()?.registry();
800 let nodeinfo_rtab = registry.routing_table().debug_info_nodeinfo();
801 let nodeinfo_net = registry.network_manager().debug_info_nodeinfo();
802 let nodeinfo_rpc = registry.rpc_processor().debug_info_nodeinfo();
803 let nodeinfo_crypto = registry.crypto().debug_info_nodeinfo();
804
805 let state = self.get_state().await?;
807
808 let mut peertable = format!(
809 "Recent Peers: {} (max {})\n",
810 state.network.peers.len(),
811 RECENT_PEERS_TABLE_SIZE
812 );
813 for peer in state.network.peers {
814 peertable += &format!(
815 " {} | {} | {} | {} down | {} up\n",
816 peer.node_ids.first().unwrap_or_log(),
817 peer.peer_address,
818 format_opt_ts(peer.peer_stats.latency.map(|l| l.average)),
819 format_opt_bps(Some(peer.peer_stats.transfer.down.average)),
820 format_opt_bps(Some(peer.peer_stats.transfer.up.average)),
821 );
822 }
823
824 let connman =
826 if let Some(connection_manager) = registry.network_manager().opt_connection_manager() {
827 connection_manager.debug_print()
828 } else {
829 "Connection manager unavailable when detached".to_owned()
830 };
831
832 Ok(format!(
833 "{}\n{}\n{}\n{}\n{}\n{}\n",
834 nodeinfo_rtab, nodeinfo_net, nodeinfo_rpc, peertable, connman, nodeinfo_crypto
835 ))
836 }
837
838 fn debug_nodeid(&self, _args: String) -> VeilidAPIResult<String> {
839 let registry = self.core_context()?.registry();
841 let nodeid = registry.routing_table().debug_info_nodeid();
842 Ok(nodeid)
843 }
844
845 #[expect(clippy::unused_async)]
846 async fn debug_config(&self, args: String) -> VeilidAPIResult<String> {
847 let mut args = args.as_str();
848 let mut config = self.config()?;
849 if !args.starts_with("insecure") {
850 config = config.safe();
851 } else {
852 args = &args[8..];
853 }
854 let args = args.trim_start();
855
856 if args.is_empty() {
857 return config.get_key_json("", true);
858 }
859 let (arg, rest) = args.split_once(' ').unwrap_or((args, ""));
860 let rest = rest.trim_start().to_owned();
861
862 if !rest.is_empty() {
864 apibail_internal!("too many arguments");
865 }
866
867 config.get_key_json(arg, true)
868 }
869
870 async fn debug_network(&self, args: String) -> VeilidAPIResult<String> {
871 let args = args.trim_start();
872 if args.is_empty() {
873 apibail_missing_argument!("debug_network", "arg_0");
874 }
875 let (arg, _rest) = args.split_once(' ').unwrap_or((args, ""));
876 if arg == "restart" {
879 if matches!(
881 self.get_state().await?.attachment.state,
882 AttachmentState::Detached
883 ) {
884 apibail_internal!("Must be attached to restart network");
885 }
886
887 let registry = self.core_context()?.registry();
888 registry.network_manager().restart_network();
889
890 Ok("Network restarted".to_owned())
891 } else if arg == "stats" {
892 let registry = self.core_context()?.registry();
893 let debug_stats = registry.network_manager().debug();
894
895 Ok(debug_stats)
896 } else {
897 apibail_invalid_argument!("debug_restart", "arg_1", arg);
898 }
899 }
900
901 async fn debug_purge(&self, args: String) -> VeilidAPIResult<String> {
902 let registry = self.core_context()?.registry();
903
904 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
905 if !args.is_empty() {
906 if args[0] == "buckets" {
907 if !matches!(
909 self.get_state().await?.attachment.state,
910 AttachmentState::Detached | AttachmentState::Detaching
911 ) {
912 apibail_internal!("Must be detached to purge");
913 }
914 registry.routing_table().purge_buckets();
915 Ok("Buckets purged".to_owned())
916 } else if args[0] == "connections" {
917 let opt_connection_manager = registry.network_manager().opt_connection_manager();
919
920 if let Some(connection_manager) = &opt_connection_manager {
921 connection_manager.shutdown().await;
922 }
923
924 registry.routing_table().purge_last_connections();
926
927 if let Some(connection_manager) = &opt_connection_manager {
928 connection_manager
929 .startup()
930 .map_err(VeilidAPIError::internal)?;
931 }
932 Ok("Connections purged".to_owned())
933 } else if args[0] == "routes" {
934 self.with_debug_cache(|dc| {
936 dc.imported_routes.clear();
937 });
938 match registry.routing_table().route_spec_store().purge().await {
939 Ok(_) => Ok("Routes purged".to_owned()),
940 Err(e) => Ok(format!("Routes purged but failed to save: {}", e)),
941 }
942 } else {
943 Err(VeilidAPIError::InvalidArgument {
944 context: "debug_purge".to_owned(),
945 argument: "parameter".to_owned(),
946 value: args[0].clone(),
947 })
948 }
949 } else {
950 Err(VeilidAPIError::MissingArgument {
951 context: "debug_purge".to_owned(),
952 argument: "parameter".to_owned(),
953 })
954 }
955 }
956
957 async fn debug_attach(&self, _args: String) -> VeilidAPIResult<String> {
958 if !matches!(
959 self.get_state().await?.attachment.state,
960 AttachmentState::Detached
961 ) {
962 apibail_internal!("Not detached");
963 }
964
965 self.attach().await?;
966
967 Ok("Attached".to_owned())
968 }
969
970 async fn debug_detach(&self, _args: String) -> VeilidAPIResult<String> {
971 if matches!(
972 self.get_state().await?.attachment.state,
973 AttachmentState::Detaching
974 ) {
975 apibail_internal!("Not attached");
976 };
977
978 self.detach().await?;
979
980 Ok("Detached".to_owned())
981 }
982
983 fn debug_contact(&self, args: String) -> VeilidAPIResult<String> {
984 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
985
986 let registry = self.core_context()?.registry();
987
988 let node_ref = get_debug_argument_at(
989 &args,
990 0,
991 "debug_contact",
992 "node_ref",
993 get_filtered_node_ref(registry.clone()),
994 )?;
995
996 let cm = registry
997 .network_manager()
998 .get_node_contact_method(node_ref)
999 .map_err(VeilidAPIError::internal)?;
1000
1001 Ok(format!("{:#?}", cm))
1002 }
1003
1004 async fn debug_resolve(&self, args: String) -> VeilidAPIResult<String> {
1005 let registry = self.core_context()?.registry();
1006 if !registry.attachment_manager().is_attached() {
1007 apibail_internal!("Must be attached first");
1008 };
1009
1010 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
1011
1012 let dest = async_get_debug_argument_at(
1013 &args,
1014 0,
1015 "debug_resolve",
1016 "destination",
1017 self.clone().get_destination(registry.clone()),
1018 )
1019 .await?;
1020
1021 let routing_table = registry.routing_table();
1022 match &dest {
1023 Destination::Direct {
1024 node,
1025 safety_selection: _,
1026 } => Ok(format!(
1027 "Destination: {:#?}\nNode:\n{}\n",
1028 &dest,
1029 routing_table.debug_info_entry(node.unfiltered())
1030 )),
1031 Destination::Relay { relay_di, node } => Ok(format!(
1032 "Destination: {:#?}\nNode:\n{}\nDialInfo:\n{}\n",
1033 &dest,
1034 routing_table.debug_info_entry(node.clone()),
1035 relay_di,
1036 )),
1037 Destination::PrivateRoute {
1038 private_route: _,
1039 safety_selection: _,
1040 } => Ok(format!("Destination: {:#?}", &dest)),
1041 }
1042 }
1043
1044 async fn debug_ping(&self, args: String) -> VeilidAPIResult<String> {
1045 let registry = self.core_context()?.registry();
1046 if !registry.attachment_manager().is_attached() {
1047 apibail_internal!("Must be attached first");
1048 };
1049
1050 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
1051
1052 let dest = async_get_debug_argument_at(
1053 &args,
1054 0,
1055 "debug_ping",
1056 "destination",
1057 self.clone().get_destination(registry.clone()),
1058 )
1059 .await?;
1060
1061 let rpc_processor = registry.rpc_processor();
1063 let out = match Box::pin(rpc_processor.rpc_call_status(dest))
1064 .await
1065 .map_err(VeilidAPIError::internal)?
1066 {
1067 NetworkResult::Value(v) => v,
1068 r => {
1069 return Ok(r.to_string());
1070 }
1071 };
1072
1073 Ok(format!("{:#?}", out))
1074 }
1075
1076 async fn debug_app_message(&self, args: String) -> VeilidAPIResult<String> {
1077 let registry = self.core_context()?.registry();
1078 if !registry.attachment_manager().is_attached() {
1079 apibail_internal!("Must be attached first");
1080 };
1081
1082 let (arg, rest) = args.split_once(' ').unwrap_or((&args, ""));
1083 let rest = rest.trim_start().to_owned();
1084
1085 let dest = async_get_debug_argument(
1086 arg,
1087 "debug_app_message",
1088 "destination",
1089 self.clone().get_destination(registry.clone()),
1090 )
1091 .await?;
1092
1093 let data = get_debug_argument(&rest, "debug_app_message", "data", get_data)?;
1094 let data_len = data.len();
1095
1096 let rpc_processor = registry.rpc_processor();
1098
1099 let out = match rpc_processor
1100 .rpc_call_app_message(dest, data)
1101 .await
1102 .map_err(VeilidAPIError::internal)?
1103 {
1104 NetworkResult::Value(_) => format!("Sent {} bytes", data_len),
1105 r => {
1106 return Ok(r.to_string());
1107 }
1108 };
1109
1110 Ok(out)
1111 }
1112
1113 async fn debug_app_call(&self, args: String) -> VeilidAPIResult<String> {
1114 let registry = self.core_context()?.registry();
1115 if !registry.attachment_manager().is_attached() {
1116 apibail_internal!("Must be attached first");
1117 };
1118
1119 let (arg, rest) = args.split_once(' ').unwrap_or((&args, ""));
1120 let rest = rest.trim_start().to_owned();
1121
1122 let dest = async_get_debug_argument(
1123 arg,
1124 "debug_app_call",
1125 "destination",
1126 self.clone().get_destination(registry.clone()),
1127 )
1128 .await?;
1129
1130 let data = get_debug_argument(&rest, "debug_app_call", "data", get_data)?;
1131 let data_len = data.len();
1132
1133 let rpc_processor = registry.rpc_processor();
1135
1136 let out = match Box::pin(rpc_processor.rpc_call_app_call(dest, data))
1137 .await
1138 .map_err(VeilidAPIError::internal)?
1139 {
1140 NetworkResult::Value(v) => format!(
1141 "Sent {} bytes, received: {}",
1142 data_len,
1143 print_data(&v.answer, Some(512))
1144 ),
1145 r => {
1146 return Ok(r.to_string());
1147 }
1148 };
1149
1150 Ok(out)
1151 }
1152
1153 async fn debug_app_reply(&self, args: String) -> VeilidAPIResult<String> {
1154 let registry = self.core_context()?.registry();
1155 if !registry.attachment_manager().is_attached() {
1156 apibail_internal!("Must be attached first");
1157 };
1158
1159 let (call_id, data) = if let Some(stripped_args) = args.strip_prefix('#') {
1160 let (arg, rest) = stripped_args.split_once(' ').unwrap_or((&args, ""));
1161 let call_id =
1162 OperationId::new(u64::from_str_radix(arg, 16).map_err(VeilidAPIError::generic)?);
1163 let rest = rest.trim_start().to_owned();
1164 let data = get_debug_argument(&rest, "debug_app_reply", "data", get_data)?;
1165 (call_id, data)
1166 } else {
1167 let rpc_processor = registry.rpc_processor();
1168
1169 let call_id = rpc_processor
1170 .get_app_call_ids()
1171 .first()
1172 .cloned()
1173 .ok_or_else(|| VeilidAPIError::generic("no app calls waiting"))?;
1174 let data = get_debug_argument(&args, "debug_app_reply", "data", get_data)?;
1175 (call_id, data)
1176 };
1177
1178 let data_len = data.len();
1179
1180 self.app_call_reply(call_id, data)
1182 .await
1183 .map_err(VeilidAPIError::internal)?;
1184
1185 Ok(format!("Replied with {} bytes", data_len))
1186 }
1187
1188 fn debug_route_allocate(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1189 let registry = self.core_context()?.registry();
1192 let routing_table = registry.routing_table();
1193 let rss = routing_table.route_spec_store();
1194 let default_route_hop_count = self.config()?.network.rpc.default_route_hop_count as usize;
1195
1196 let mut ai = 1;
1197 let mut sequencing = Sequencing::default();
1198 let mut stability = Stability::default();
1199 let mut hop_count = default_route_hop_count;
1200 let mut directions = DirectionSet::all();
1201
1202 while ai < args.len() {
1203 if let Ok(seq) =
1204 get_debug_argument_at(&args, ai, "debug_route", "sequencing", get_sequencing)
1205 {
1206 sequencing = seq;
1207 } else if let Ok(sta) =
1208 get_debug_argument_at(&args, ai, "debug_route", "stability", get_stability)
1209 {
1210 stability = sta;
1211 } else if let Ok(hc) =
1212 get_debug_argument_at(&args, ai, "debug_route", "hop_count", get_number)
1213 {
1214 hop_count = hc;
1215 } else if let Ok(ds) =
1216 get_debug_argument_at(&args, ai, "debug_route", "direction_set", get_direction_set)
1217 {
1218 directions = ds;
1219 } else {
1220 return Ok(format!("Invalid argument specified: {}", args[ai]));
1221 }
1222 ai += 1;
1223 }
1224
1225 let safety_spec = SafetySpec {
1226 preferred_route: None,
1227 hop_count,
1228 stability,
1229 sequencing,
1230 };
1231
1232 let out =
1234 match rss.allocate_route(&VALID_CRYPTO_KINDS, &safety_spec, directions, &[], false) {
1235 Ok(v) => v.to_string(),
1236 Err(e) => {
1237 format!("Route allocation failed: {}", e)
1238 }
1239 };
1240
1241 Ok(out)
1242 }
1243 fn debug_route_release(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1244 let registry = self.core_context()?.registry();
1246 let routing_table = registry.routing_table();
1247 let rss = routing_table.route_spec_store();
1248
1249 let route_id = get_debug_argument_at(
1250 &args,
1251 1,
1252 "debug_route",
1253 "route_id",
1254 get_route_id(registry.clone(), true, true),
1255 )?;
1256
1257 let out = match rss.release_route(route_id.clone()) {
1259 true => {
1260 self.with_debug_cache(|dc| {
1262 for (n, ir) in dc.imported_routes.iter().enumerate() {
1263 if *ir == route_id {
1264 let _ = dc.imported_routes.remove(n);
1265 break;
1266 }
1267 }
1268 });
1269 "Released".to_owned()
1270 }
1271 false => "Route does not exist".to_owned(),
1272 };
1273
1274 Ok(out)
1275 }
1276 fn debug_route_publish(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1277 let registry = self.core_context()?.registry();
1279 let routing_table = registry.routing_table();
1280 let rss = routing_table.route_spec_store();
1281
1282 let route_id = get_debug_argument_at(
1283 &args,
1284 1,
1285 "debug_route",
1286 "route_id",
1287 get_route_id(registry.clone(), true, false),
1288 )?;
1289 let full = {
1290 if args.len() > 2 {
1291 let full_val = get_debug_argument_at(&args, 2, "debug_route", "full", get_string)?
1292 .to_ascii_lowercase();
1293 if full_val == "full" {
1294 true
1295 } else {
1296 apibail_invalid_argument!("debug_route", "full", full_val);
1297 }
1298 } else {
1299 false
1300 }
1301 };
1302
1303 let out = match rss.assemble_private_route_set(&route_id, Some(!full)) {
1305 Ok(private_routes) => {
1306 if let Err(e) = rss.mark_route_published(&route_id, true) {
1307 return Ok(format!("Couldn't mark route published: {}", e));
1308 }
1309 let blob_data = RouteSpecStore::private_routes_to_blob(&private_routes)
1311 .map_err(VeilidAPIError::internal)?;
1312 let out = BASE64URL_NOPAD.encode(&blob_data);
1313 veilid_log!(registry info
1314 "Published route {} as {} bytes:\n{}",
1315 route_id,
1316 blob_data.len(),
1317 out
1318 );
1319 format!("Published route {}", route_id)
1320 }
1321 Err(e) => {
1322 format!("Couldn't assemble private route: {}", e)
1323 }
1324 };
1325
1326 Ok(out)
1327 }
1328 fn debug_route_unpublish(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1329 let registry = self.core_context()?.registry();
1331 let routing_table = registry.routing_table();
1332 let rss = routing_table.route_spec_store();
1333
1334 let route_id = get_debug_argument_at(
1335 &args,
1336 1,
1337 "debug_route",
1338 "route_id",
1339 get_route_id(registry.clone(), true, false),
1340 )?;
1341
1342 let out = if let Err(e) = rss.mark_route_published(&route_id, false) {
1344 return Ok(format!("Couldn't mark route unpublished: {}", e));
1345 } else {
1346 "Route unpublished".to_owned()
1347 };
1348 Ok(out)
1349 }
1350 fn debug_route_print(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1351 let registry = self.core_context()?.registry();
1353 let routing_table = registry.routing_table();
1354 let rss = routing_table.route_spec_store();
1355
1356 if let Ok(route_id) = get_debug_argument_at(
1357 &args,
1358 1,
1359 "debug_route",
1360 "route_id",
1361 get_route_id(registry.clone(), true, true),
1362 ) {
1363 Ok(rss.debug_route_by_id(&route_id))
1364 } else if let Ok(route_key) = get_debug_argument_at(
1365 &args,
1366 1,
1367 "debug_route",
1368 "route_key",
1369 get_route_key(registry.clone(), true, true),
1370 ) {
1371 Ok(rss.debug_route_by_key(&route_key))
1372 } else {
1373 Ok("Route key not found".to_string())
1374 }
1375 }
1376 fn debug_route_list(&self, _args: Vec<String>) -> VeilidAPIResult<String> {
1377 let registry = self.core_context()?.registry();
1379 let routing_table = registry.routing_table();
1380 let rss = routing_table.route_spec_store();
1381
1382 let routes = rss.list_allocated_routes(|k, v| Some((k.clone(), v.to_string())));
1383 let mut out = format!("Allocated Routes: (count = {}):\n", routes.len());
1384 for (r, s) in routes {
1385 out.push_str(&format!("{}: {}\n", r, s));
1386 }
1387
1388 let remote_routes = rss.list_remote_routes(|k, v| Some((k.clone(), v.to_string())));
1389 out.push_str(&format!(
1390 "Remote Routes: (count = {}):\n",
1391 remote_routes.len()
1392 ));
1393 for (r, s) in remote_routes {
1394 out.push_str(&format!("{}: {}\n", r, s));
1395 }
1396
1397 Ok(out)
1398 }
1399 fn debug_route_import(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1400 let registry = self.core_context()?.registry();
1402 let routing_table = registry.routing_table();
1403 let rss = routing_table.route_spec_store();
1404
1405 let blob = get_debug_argument_at(&args, 1, "debug_route", "blob", get_string)?;
1406 let blob_dec = BASE64URL_NOPAD
1407 .decode(blob.as_bytes())
1408 .map_err(VeilidAPIError::generic)?;
1409
1410 let route_id = rss
1411 .import_remote_route_blob(blob_dec)
1412 .map_err(VeilidAPIError::generic)?;
1413
1414 let out = self.with_debug_cache(|dc| {
1415 let n = dc.imported_routes.len();
1416 let out = format!("Private route #{} imported: {}", n, route_id);
1417 dc.imported_routes.push(route_id);
1418 out
1419 });
1420
1421 Ok(out)
1422 }
1423
1424 async fn debug_route_test(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1425 let registry = self.core_context()?.registry();
1427 let routing_table = registry.routing_table();
1428 let rss = routing_table.route_spec_store();
1429
1430 let route_id = get_debug_argument_at(
1431 &args,
1432 1,
1433 "debug_route",
1434 "route_id",
1435 get_route_id(registry.clone(), true, true),
1436 )?;
1437
1438 let success = rss
1439 .test_route(route_id)
1440 .await
1441 .map_err(VeilidAPIError::internal)?;
1442
1443 let out = match success {
1444 Some(true) => "SUCCESS".to_owned(),
1445 Some(false) => "FAILED".to_owned(),
1446 None => "UNTESTED".to_owned(),
1447 };
1448
1449 Ok(out)
1450 }
1451
1452 async fn debug_route(&self, args: String) -> VeilidAPIResult<String> {
1453 let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
1454
1455 let command = get_debug_argument_at(&args, 0, "debug_route", "command", get_string)?;
1456
1457 if command == "allocate" {
1458 self.debug_route_allocate(args)
1459 } else if command == "release" {
1460 self.debug_route_release(args)
1461 } else if command == "publish" {
1462 self.debug_route_publish(args)
1463 } else if command == "unpublish" {
1464 self.debug_route_unpublish(args)
1465 } else if command == "print" {
1466 self.debug_route_print(args)
1467 } else if command == "list" {
1468 self.debug_route_list(args)
1469 } else if command == "import" {
1470 self.debug_route_import(args)
1471 } else if command == "test" {
1472 self.debug_route_test(args).await
1473 } else {
1474 Ok(">>> Unknown command\n".to_owned())
1475 }
1476 }
1477
1478 fn debug_record_list(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1479 let registry = self.core_context()?.registry();
1481 let storage_manager = registry.storage_manager();
1482
1483 let scope = get_debug_argument_at(&args, 1, "debug_record_list", "scope", get_string)?;
1484 let out = match scope.as_str() {
1485 "local" => {
1486 let mut out = "Local Records:\n".to_string();
1487 out += &storage_manager.debug_local_records();
1488 out
1489 }
1490 "remote" => {
1491 let mut out = "Remote Records:\n".to_string();
1492 out += &storage_manager.debug_remote_records();
1493 out
1494 }
1495 "opened" => {
1496 let mut out = "Opened Records:\n".to_string();
1497 out += &storage_manager.debug_opened_records();
1498 out
1499 }
1500 "watched" => {
1501 let mut out = "Watched Records:\n".to_string();
1502 out += &storage_manager.debug_watched_records();
1503 out
1504 }
1505 "offline" => {
1506 let mut out = "Offline Records:\n".to_string();
1507 out += &storage_manager.debug_offline_records();
1508 out
1509 }
1510 "transactions" => {
1511 let mut out = "Record Transaction:\n".to_string();
1512 out += &storage_manager.debug_transactions();
1513 out
1514 }
1515 _ => "Invalid scope\n".to_owned(),
1516 };
1517 Ok(out)
1518 }
1519
1520 async fn debug_record_purge(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1521 let registry = self.core_context()?.registry();
1523 let storage_manager = registry.storage_manager();
1524
1525 self.with_debug_cache(|dc| {
1526 dc.opened_record_contexts.clear();
1527 });
1528 storage_manager.close_all_records().await?;
1529
1530 let scope = get_debug_argument_at(&args, 1, "debug_record_purge", "scope", get_string)?;
1531 let bytes = get_debug_argument_at(&args, 2, "debug_record_purge", "bytes", get_number).ok();
1532 let out = match scope.as_str() {
1533 "local" => storage_manager.purge_local_records(bytes).await,
1534 "remote" => storage_manager.purge_remote_records(bytes).await,
1535 _ => "Invalid scope\n".to_owned(),
1536 };
1537 Ok(out)
1538 }
1539
1540 async fn debug_record_create(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1541 let crypto = self.crypto()?;
1542
1543 let schema = get_debug_argument_at(
1544 &args,
1545 1,
1546 "debug_record_create",
1547 "dht_schema",
1548 get_dht_schema,
1549 )
1550 .unwrap_or_else(|_| Ok(DHTSchema::default()))?;
1551
1552 let csv = get_debug_argument_at(
1553 &args,
1554 2,
1555 "debug_record_create",
1556 "kind",
1557 get_crypto_system_version(&crypto),
1558 )
1559 .unwrap_or_else(|_| crypto.best());
1560
1561 let ss = get_debug_argument_at(
1562 &args,
1563 3,
1564 "debug_record_create",
1565 "safety_selection",
1566 get_safety_selection(self.core_context()?.registry()),
1567 )
1568 .ok();
1569
1570 let rc = self.routing_context()?;
1572 let rc = if let Some(ss) = ss {
1573 match rc.with_safety(ss) {
1574 Err(e) => return Ok(format!("Can't use safety selection: {}", e)),
1575 Ok(v) => v,
1576 }
1577 } else {
1578 rc
1579 };
1580
1581 let record = match rc.create_dht_record(csv.kind(), schema, None).await {
1583 Err(e) => return Ok(format!("Can't open DHT record: {}", e)),
1584 Ok(v) => v,
1585 };
1586
1587 self.with_debug_cache(|dc| {
1589 dc.opened_record_contexts.insert(record.key(), rc);
1590 });
1591
1592 Ok(format!(
1593 "Created: {} {}\n{:?}",
1594 record.key(),
1595 record.owner_keypair().unwrap_or_log(),
1596 record
1597 ))
1598 }
1599
1600 async fn debug_record_open(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1601 let registry = self.core_context()?.registry();
1602
1603 let (key, ss) = get_debug_argument_at(
1604 &args,
1605 1,
1606 "debug_record_open",
1607 "key",
1608 get_dht_key(registry.clone()),
1609 )?;
1610 let writer =
1611 get_debug_argument_at(&args, 2, "debug_record_open", "writer", get_keypair).ok();
1612
1613 let rc = self.routing_context()?;
1615 let rc = if let Some(ss) = ss {
1616 match rc.with_safety(ss) {
1617 Err(e) => return Ok(format!("Can't use safety selection: {}", e)),
1618 Ok(v) => v,
1619 }
1620 } else {
1621 rc
1622 };
1623
1624 let record = match rc.open_dht_record(key.clone(), writer).await {
1626 Err(e) => return Ok(format!("Can't open DHT record: {}", e)),
1627 Ok(v) => v,
1628 };
1629
1630 self.with_debug_cache(|dc| {
1632 dc.opened_record_contexts.insert(record.key(), rc);
1633 });
1634
1635 Ok(format!("Opened: {}\n{:#?}", key, record))
1636 }
1637
1638 async fn debug_record_close(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1639 let (key, rc) =
1640 self.clone()
1641 .get_opened_dht_record_context(&args, "debug_record_close", "key", 1)?;
1642
1643 if let Err(e) = rc.close_dht_record(key.clone()).await {
1645 return Ok(format!("Can't close DHT record: {}", e));
1646 };
1647
1648 Ok(format!("Closed: {:?}", key))
1649 }
1650
1651 async fn debug_record_set(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1652 let mut opt_arg_add = if args.len() >= 2 && get_dht_key_no_safety(&args[1]).is_some() {
1653 1
1654 } else {
1655 0
1656 };
1657 let (key, rc) =
1658 self.clone()
1659 .get_opened_dht_record_context(&args, "debug_record_set", "key", 1)?;
1660 let subkey = get_debug_argument_at(
1661 &args,
1662 1 + opt_arg_add,
1663 "debug_record_set",
1664 "subkey",
1665 get_number::<u32>,
1666 )?;
1667 let data =
1668 get_debug_argument_at(&args, 2 + opt_arg_add, "debug_record_set", "data", get_data)?;
1669 let writer = match get_debug_argument_at(
1670 &args,
1671 3 + opt_arg_add,
1672 "debug_record_set",
1673 "writer",
1674 get_keypair,
1675 ) {
1676 Ok(v) => {
1677 opt_arg_add += 1;
1678 Some(v)
1679 }
1680 Err(_) => None,
1681 };
1682 let allow_offline = if args.len() > 3 + opt_arg_add {
1683 get_debug_argument_at(
1684 &args,
1685 3 + opt_arg_add,
1686 "debug_record_set",
1687 "allow_offline",
1688 get_string,
1689 )
1690 .ok()
1691 } else {
1692 None
1693 };
1694
1695 let allow_offline = if let Some(allow_offline) = allow_offline {
1696 if &allow_offline == "online" || &allow_offline == "false" {
1697 Some(AllowOffline(false))
1698 } else if &allow_offline == "offline" || &allow_offline == "true" {
1699 Some(AllowOffline(true))
1700 } else {
1701 return Ok(format!("Unknown allow_offline: {}", allow_offline));
1702 }
1703 } else {
1704 None
1705 };
1706
1707 let value = match rc
1709 .set_dht_value(
1710 key,
1711 subkey as ValueSubkey,
1712 data,
1713 Some(SetDHTValueOptions {
1714 writer,
1715 allow_offline,
1716 }),
1717 )
1718 .await
1719 {
1720 Err(e) => {
1721 return Ok(format!("Can't set DHT value: {}", e));
1722 }
1723 Ok(v) => v,
1724 };
1725 let out = if let Some(value) = value {
1726 format!("Newer value found: {:?}", value)
1727 } else {
1728 "Success".to_owned()
1729 };
1730 Ok(out)
1731 }
1732
1733 async fn debug_record_get(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1734 let opt_arg_add = if args.len() >= 2 && get_dht_key_no_safety(&args[1]).is_some() {
1735 1
1736 } else {
1737 0
1738 };
1739
1740 let (key, rc) =
1741 self.clone()
1742 .get_opened_dht_record_context(&args, "debug_record_get", "key", 1)?;
1743 let subkey = get_debug_argument_at(
1744 &args,
1745 1 + opt_arg_add,
1746 "debug_record_get",
1747 "subkey",
1748 get_number::<u32>,
1749 )?;
1750 let force_refresh = if args.len() > 2 + opt_arg_add {
1751 Some(get_debug_argument_at(
1752 &args,
1753 2 + opt_arg_add,
1754 "debug_record_get",
1755 "force_refresh",
1756 get_string,
1757 )?)
1758 } else {
1759 None
1760 };
1761
1762 let force_refresh = if let Some(force_refresh) = force_refresh {
1763 if &force_refresh == "force" {
1764 true
1765 } else {
1766 return Ok(format!("Unknown force: {}", force_refresh));
1767 }
1768 } else {
1769 false
1770 };
1771
1772 let value = match rc
1774 .get_dht_value(key, subkey as ValueSubkey, force_refresh)
1775 .await
1776 {
1777 Err(e) => {
1778 return Ok(format!("Can't get DHT value: {}", e));
1779 }
1780 Ok(v) => v,
1781 };
1782 let out = if let Some(value) = value {
1783 format!("{:?}", value)
1784 } else {
1785 "No value data returned".to_owned()
1786 };
1787 Ok(out)
1788 }
1789
1790 async fn debug_record_delete(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1791 let key = get_debug_argument_at(
1792 &args,
1793 1,
1794 "debug_record_delete",
1795 "key",
1796 get_dht_key_no_safety,
1797 )?;
1798
1799 let rc = self.routing_context()?;
1801 match rc.delete_dht_record(key).await {
1802 Err(e) => return Ok(format!("Can't delete DHT record: {}", e)),
1803 Ok(v) => v,
1804 };
1805 Ok("DHT record deleted".to_string())
1806 }
1807
1808 async fn debug_record_info(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1809 let registry = self.core_context()?.registry();
1810 let storage_manager = registry.storage_manager();
1811
1812 let key =
1813 get_debug_argument_at(&args, 1, "debug_record_info", "key", get_dht_key_no_safety)?;
1814
1815 let subkey = get_debug_argument_at(
1816 &args,
1817 2,
1818 "debug_record_info",
1819 "subkey",
1820 get_number::<ValueSubkey>,
1821 )
1822 .ok();
1823
1824 let out = if let Some(subkey) = subkey {
1825 let li = storage_manager
1826 .debug_local_record_subkey_info(key.clone(), subkey)
1827 .await;
1828 let ri = storage_manager
1829 .debug_remote_record_subkey_info(key.clone(), subkey)
1830 .await;
1831 format!(
1832 "Local Subkey Info:\n{}\n\nRemote Subkey Info:\n{}\n",
1833 li, ri
1834 )
1835 } else {
1836 let li = storage_manager.debug_local_record_info(key.clone());
1837 let ri = storage_manager.debug_remote_record_info(key.clone());
1838 format!("Local Info:\n{}\n\nRemote Info:\n{}\n", li, ri)
1839 };
1840 Ok(out)
1841 }
1842
1843 async fn debug_record_watch(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1844 let opt_arg_add = if args.len() >= 2 && get_dht_key_no_safety(&args[1]).is_some() {
1845 1
1846 } else {
1847 0
1848 };
1849
1850 let (key, rc) =
1851 self.clone()
1852 .get_opened_dht_record_context(&args, "debug_record_watch", "key", 1)?;
1853
1854 let mut rest_defaults = false;
1855 let subkeys = get_debug_argument_at(
1856 &args,
1857 1 + opt_arg_add,
1858 "debug_record_watch",
1859 "subkeys",
1860 get_subkeys,
1861 )
1862 .ok()
1863 .map(Some)
1864 .unwrap_or_else(|| {
1865 rest_defaults = true;
1866 None
1867 });
1868
1869 let opt_expiration = if rest_defaults {
1870 None
1871 } else {
1872 get_debug_argument_at(
1873 &args,
1874 2 + opt_arg_add,
1875 "debug_record_watch",
1876 "expiration",
1877 parse_duration,
1878 )
1879 .ok()
1880 .map(|dur| {
1881 if dur == 0 {
1882 None
1883 } else {
1884 Some(Timestamp::now_non_decreasing().later(TimestampDuration::new(dur)))
1885 }
1886 })
1887 .unwrap_or_else(|| {
1888 rest_defaults = true;
1889 None
1890 })
1891 };
1892 let count = if rest_defaults {
1893 None
1894 } else {
1895 get_debug_argument_at(
1896 &args,
1897 3 + opt_arg_add,
1898 "debug_record_watch",
1899 "count",
1900 get_number,
1901 )
1902 .ok()
1903 .map(Some)
1904 .unwrap_or_else(|| {
1905 rest_defaults = true;
1906 Some(u32::MAX)
1907 })
1908 };
1909
1910 let active = match rc
1912 .watch_dht_values(key, subkeys, opt_expiration, count)
1913 .await
1914 {
1915 Err(e) => {
1916 return Ok(format!("Can't watch DHT value: {}", e));
1917 }
1918 Ok(v) => v,
1919 };
1920 if !active {
1921 return Ok("Failed to watch value".to_owned());
1922 }
1923 Ok("Success".to_owned())
1924 }
1925
1926 async fn debug_record_cancel(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1927 let opt_arg_add = if args.len() >= 2 && get_dht_key_no_safety(&args[1]).is_some() {
1928 1
1929 } else {
1930 0
1931 };
1932
1933 let (key, rc) =
1934 self.clone()
1935 .get_opened_dht_record_context(&args, "debug_record_watch", "key", 1)?;
1936 let subkeys = get_debug_argument_at(
1937 &args,
1938 1 + opt_arg_add,
1939 "debug_record_watch",
1940 "subkeys",
1941 get_subkeys,
1942 )
1943 .ok();
1944
1945 let still_active = match rc.cancel_dht_watch(key, subkeys).await {
1947 Err(e) => {
1948 return Ok(format!("Can't cancel DHT watch: {}", e));
1949 }
1950 Ok(v) => v,
1951 };
1952
1953 Ok(if still_active {
1954 "Watch partially cancelled".to_owned()
1955 } else {
1956 "Watch cancelled".to_owned()
1957 })
1958 }
1959
1960 async fn debug_record_inspect(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1961 let opt_arg_add = if args.len() >= 2 && get_dht_key_no_safety(&args[1]).is_some() {
1962 1
1963 } else {
1964 0
1965 };
1966
1967 let (key, rc) =
1968 self.clone()
1969 .get_opened_dht_record_context(&args, "debug_record_inspect", "key", 1)?;
1970
1971 let mut rest_defaults = false;
1972
1973 let scope = if rest_defaults {
1974 Default::default()
1975 } else {
1976 get_debug_argument_at(
1977 &args,
1978 1 + opt_arg_add,
1979 "debug_record_inspect",
1980 "scope",
1981 get_dht_report_scope,
1982 )
1983 .ok()
1984 .unwrap_or_else(|| {
1985 rest_defaults = true;
1986 Default::default()
1987 })
1988 };
1989
1990 let subkeys = if rest_defaults {
1991 None
1992 } else {
1993 get_debug_argument_at(
1994 &args,
1995 2 + opt_arg_add,
1996 "debug_record_inspect",
1997 "subkeys",
1998 get_subkeys,
1999 )
2000 .ok()
2001 };
2002
2003 let report = match rc.inspect_dht_record(key, subkeys, scope).await {
2005 Err(e) => {
2006 return Ok(format!("Can't inspect DHT record: {}", e));
2007 }
2008 Ok(v) => v,
2009 };
2010
2011 Ok(format!("Success: report={:?}", report))
2012 }
2013
2014 fn debug_record_rehydrate(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2015 let registry = self.core_context()?.registry();
2016 let storage_manager = registry.storage_manager();
2017
2018 let key = get_debug_argument_at(
2019 &args,
2020 1,
2021 "debug_record_rehydrate",
2022 "key",
2023 get_dht_key_no_safety,
2024 )?;
2025
2026 let mut rest_defaults = false;
2027
2028 let subkeys = if rest_defaults {
2029 None
2030 } else {
2031 get_debug_argument_at(&args, 2, "debug_record_rehydrate", "subkeys", get_subkeys)
2032 .inspect_err(|_| {
2033 rest_defaults = true;
2034 })
2035 .ok()
2036 };
2037
2038 let consensus_count = if rest_defaults {
2039 None
2040 } else {
2041 get_debug_argument_at(
2042 &args,
2043 3,
2044 "debug_record_rehydrate",
2045 "consensus_count",
2046 get_number,
2047 )
2048 .inspect_err(|_| {
2049 rest_defaults = true;
2050 })
2051 .ok()
2052 };
2053
2054 storage_manager.add_rehydration_request(
2056 key.opaque(),
2057 subkeys.unwrap_or_default(),
2058 consensus_count
2059 .unwrap_or_else(|| registry.config().network.dht.set_value_count as usize),
2060 );
2061
2062 Ok("Request added".to_owned())
2063 }
2064
2065 async fn debug_record(&self, args: String) -> VeilidAPIResult<String> {
2066 let args: Vec<String> =
2067 shell_words::split(&args).map_err(|e| VeilidAPIError::parse_error(e, args))?;
2068
2069 let command = get_debug_argument_at(&args, 0, "debug_record", "command", get_string)?;
2070
2071 if command == "list" {
2072 self.debug_record_list(args)
2073 } else if command == "purge" {
2074 self.debug_record_purge(args).await
2075 } else if command == "create" {
2076 self.debug_record_create(args).await
2077 } else if command == "open" {
2078 self.debug_record_open(args).await
2079 } else if command == "close" {
2080 self.debug_record_close(args).await
2081 } else if command == "get" {
2082 self.debug_record_get(args).await
2083 } else if command == "set" {
2084 self.debug_record_set(args).await
2085 } else if command == "delete" {
2086 self.debug_record_delete(args).await
2087 } else if command == "info" {
2088 self.debug_record_info(args).await
2089 } else if command == "watch" {
2090 self.debug_record_watch(args).await
2091 } else if command == "cancel" {
2092 self.debug_record_cancel(args).await
2093 } else if command == "inspect" {
2094 self.debug_record_inspect(args).await
2095 } else if command == "rehydrate" {
2096 self.debug_record_rehydrate(args)
2097 } else {
2098 Ok(">>> Unknown command\n".to_owned())
2099 }
2100 }
2101
2102 fn debug_table_list(&self, _args: Vec<String>) -> VeilidAPIResult<String> {
2103 let table_store = self.table_store()?;
2105 let table_names = table_store.list_all();
2106 let out = format!(
2107 "TableStore tables:\n{}",
2108 table_names
2109 .iter()
2110 .map(|(k, v)| format!("{} ({})", k, v))
2111 .collect::<Vec<String>>()
2112 .join("\n")
2113 );
2114 Ok(out)
2115 }
2116
2117 fn _format_columns(columns: &[table_store::ColumnInfo]) -> String {
2118 let mut out = String::new();
2119 for (n, col) in columns.iter().enumerate() {
2120 out += &format!("Column {}:\n", n);
2122 out += &format!(" Key Count: {}\n", col.key_count);
2123 }
2124 out
2125 }
2126
2127 async fn debug_table_info(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2128 let table_store = self.table_store()?;
2130
2131 let table_name = get_debug_argument_at(&args, 1, "debug_table_info", "name", get_string)?;
2132
2133 let Some(info) = table_store.info(&table_name).await? else {
2134 return Ok(format!("Table '{}' does not exist", table_name));
2135 };
2136
2137 let info_str = format!(
2138 "Table Name: {}\n\
2139 Column Count: {}\n\
2140 IO Stats (since previous query):\n{}\n\
2141 IO Stats (overall):\n{}\n\
2142 Columns:\n{}\n",
2143 info.table_name,
2144 info.column_count,
2145 indent::indent_all_by(4, format!("{:#?}", info.io_stats_since_previous)),
2146 indent::indent_all_by(4, format!("{:#?}", info.io_stats_overall)),
2147 Self::_format_columns(&info.columns),
2148 );
2149
2150 let out = format!("Table info for '{}':\n{}", table_name, info_str);
2151 Ok(out)
2152 }
2153
2154 async fn debug_table(&self, args: String) -> VeilidAPIResult<String> {
2155 let args: Vec<String> =
2156 shell_words::split(&args).map_err(|e| VeilidAPIError::parse_error(e, args))?;
2157
2158 let command = get_debug_argument_at(&args, 0, "debug_table", "command", get_string)?;
2159
2160 if command == "list" {
2161 self.debug_table_list(args)
2162 } else if command == "info" {
2163 self.debug_table_info(args).await
2164 } else {
2165 Ok(">>> Unknown command\n".to_owned())
2166 }
2167 }
2168
2169 fn debug_punish_list(&self, _args: Vec<String>) -> VeilidAPIResult<String> {
2170 let registry = self.core_context()?.registry();
2172 let network_manager = registry.network_manager();
2173 let address_filter = network_manager.address_filter();
2174
2175 let out = format!("Address filter punishments:\n{:#?}", address_filter);
2176 Ok(out)
2177 }
2178
2179 fn debug_punish_clear(&self, _args: Vec<String>) -> VeilidAPIResult<String> {
2180 let registry = self.core_context()?.registry();
2182 let network_manager = registry.network_manager();
2183 let address_filter = network_manager.address_filter();
2184
2185 address_filter.clear_punishments();
2186
2187 Ok("Address Filter punishments cleared\n".to_owned())
2188 }
2189
2190 fn debug_punish_add(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2191 let registry = self.core_context()?.registry();
2193 let network_manager = registry.network_manager();
2194 let address_filter = network_manager.address_filter();
2195
2196 if let Ok(node_id) =
2197 get_debug_argument_at(&args, 1, "debug_punish_add", "target", get_node_id)
2198 {
2199 address_filter.punish_node_id(node_id, PunishmentReason::Manual);
2200 Ok("Address Filter node id punishment added\n".to_owned())
2201 } else if let Ok(ip_addr) =
2202 get_debug_argument_at(&args, 1, "debug_punish_add", "target", get_ip_addr)
2203 {
2204 address_filter.punish_ip_addr(ip_addr, PunishmentReason::Manual);
2205 Ok("Address Filter address punishment added\n".to_owned())
2206 } else {
2207 Ok(">>> Unknown command argument\n".to_owned())
2208 }
2209 }
2210
2211 fn debug_punish_remove(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2212 let registry = self.core_context()?.registry();
2214 let network_manager = registry.network_manager();
2215 let address_filter = network_manager.address_filter();
2216
2217 if let Ok(node_id) =
2218 get_debug_argument_at(&args, 1, "debug_punish_remove", "target", get_node_id)
2219 {
2220 address_filter.forgive_node_id(node_id);
2221 Ok("Address Filter node id punishment forgiven\n".to_owned())
2222 } else if let Ok(ip_addr) =
2223 get_debug_argument_at(&args, 1, "debug_punish_remove", "target", get_ip_addr)
2224 {
2225 address_filter.forgive_ip_addr(ip_addr);
2226 Ok("Address Filter address punishment forgiven\n".to_owned())
2227 } else {
2228 Ok(">>> Unknown command argument\n".to_owned())
2229 }
2230 }
2231
2232 fn debug_punish(&self, args: String) -> VeilidAPIResult<String> {
2233 let args: Vec<String> =
2234 shell_words::split(&args).map_err(|e| VeilidAPIError::parse_error(e, args))?;
2235
2236 let command = get_debug_argument_at(&args, 0, "debug_punish", "command", get_string)?;
2237
2238 if command == "list" {
2239 self.debug_punish_list(args)
2240 } else if command == "clear" {
2241 self.debug_punish_clear(args)
2242 } else if command == "add" {
2243 self.debug_punish_add(args)
2244 } else if command == "remove" {
2245 self.debug_punish_remove(args)
2246 } else {
2247 Ok(">>> Unknown command\n".to_owned())
2248 }
2249 }
2250
2251 pub fn debug_help(&self, _args: String) -> VeilidAPIResult<String> {
2253 Ok(r#"Node Information:
2254 nodeid - display a node's id(s)
2255 nodeinfo - display detailed information about this node
2256 dialinfo - display the dialinfo in the routing domains of this node
2257 peerinfo [routingdomain] [published|current] - display the full PeerInfo for a routing domain of this node
2258 uptime - display node uptime
2259
2260Routing:
2261 buckets [dead|reliable] - Display the routing table bucket statistics (default is only non-dead nodes)
2262 entries [dead|reliable] [<capabilities>] - Display the index of nodes in the routing table
2263 entry <node> - Display all the details about a particular node in the routing table
2264 contact <filtered node> - Explain what mechanism would be used to contact a particular node
2265 resolve <destination> - Search the network for a particular node or private route
2266 relay [<relay>...] [public|local] - Change the relays in use for this node
2267 punish list - List all punishments this node has assigned to other nodes / networks
2268 clear - Clear all punishments from this node
2269 add <node> - Punish a node manually for testing purposes
2270 remove <node> - Remove a single node's punishment
2271
2272 route allocate [<sequencing>] [rel] [<count>] [in|out] - Allocate a route
2273 release <route> - Release a route
2274 publish <route> [full] - Publish a route 'blob' that can be imported on another machine
2275 unpublish <route> - Mark a route as 'no longer published'
2276 print <route> - Display details about a route
2277 list - List allocated routes
2278 import <blob> - Import a remote route blob generated by another node's 'publish' command.
2279 test <route> - Test an allocated or imported remote route
2280
2281Utilities:
2282 config [insecure] [configkey [new value]] - Display or temporarily change the node config
2283 (most values should not be changed this way, careful!)
2284 txtrecord [keypairs] - Generate a TXT record for making this node into a bootstrap node capable of DNS bootstrap
2285 keypair [cryptokind] - Generate and display a random public/private keypair
2286 purge <buckets|connections|routes> - Throw away the node's routing table, connections, or routes
2287
2288Network:
2289 attach - Attach the node to the network if it is detached
2290 detach - Detach the node from the network if it is attached
2291 network restart - Restart the low level network
2292 stats - Print network manager statistics
2293
2294RPC Operations:
2295 ping <destination> - Send a 'Status' RPC question to a destination node and display the returned ping status
2296 appmessage <destination> <data> - Send an 'App Message' RPC statement to a destination node
2297 appcall <destination> <data> - Send a 'App Call' RPC question to a destination node and display the answer
2298 appreply [#id] <data> - Reply to an 'App Call' RPC received by this node
2299
2300DHT Operations:
2301 record list <local|remote|opened|offline|watched|transactions> - display the dht records in the store
2302 purge <local|remote> [bytes] - clear all dht records optionally down to some total size
2303 create <dhtschema> [<cryptokind> [<safety>]] - create a new dht record
2304 open <key>[+<safety>] [<writer>] - open an existing dht record
2305 close [<key>] - close an opened/created dht record
2306 set [<key>] <subkey> <data> [<writer>] [offline|online]- write a value to a dht record subkey
2307 get [<key>] <subkey> [force] - read a value from a dht record subkey
2308 delete <key> - delete the local copy of a dht record (not from the network)
2309 info [<key>] [subkey] - display information about a dht record or subkey
2310 watch [<key>] [<subkeys> [<expiration> [<count>]]] - watch a record for changes
2311 cancel [<key>] [<subkeys>] - cancel a dht record watch
2312 inspect [<key>] [<scope> [<subkeys>]] - display a dht record's subkey status
2313 rehydrate <key> [<subkeys>] [<consensus count>] - send a dht record's expired local data back to the network
2314
2315TableDB Operations:
2316 table list - list the names of all the tables in the TableDB
2317
2318--------------------------------------------------------------------
2319<key> is: VLD0:GsgXCRPrzSK6oBNgxhNpm-rTYFd02R0ySx6j9vbQBG4
2320 * also <node>, <relay>, <target>, <route>
2321<capabilities> is: a list of VeilidCapability four-character codes: ROUT,SGNL,RLAY,DIAL,DHTV,APPM etc.
2322<configkey> is: dot path like network.protocol.udp.enabled
2323<filtered node> is: <node>[+<sequencing>][<modifiers>]
2324<destination> is:
2325 * direct: <node>[+<safety>][<modifiers>]
2326 * relay: <relay>@<target>[+<safety>][<modifiers>]
2327 * private: #<id>[+<safety>]
2328<sequencing> is:
2329 * prefer_ordered: 'pre' or 'ord'
2330 * ensure_ordered: 'ens' or '*ord'
2331<safety> is:
2332 * unsafe: -<sequencing>
2333 * safe: [route][,<sequencing>][,rel][,<count>]
2334<modifiers> is: [/<protocoltype>][/<addresstype>][/<routingdomain>]
2335<protocoltype> is: 'udp', 'tcp', 'ws', or 'wss'
2336<addresstype> is: 'ipv4', or 'ipv6'
2337<routingdomain> is: 'pub', or 'loc'
2338<cryptokind> is: VLD0
2339<dhtschema> is:
2340 * a single-quoted json dht schema, or
2341 * an integer number for a DFLT schema subkey count.
2342 default is '{"kind":"DFLT","o_cnt":1}'
2343<scope> is: local, syncget, syncset, updateget, updateset
2344<subkey> is: a number: 2
2345<subkeys> is:
2346 * a number: 2
2347 * a comma-separated inclusive range list: 1..=3,5..=8
2348<data> is:
2349 * a single-word string: foobar
2350 * a shell-quoted string: "foo\nbar\n"
2351 * a '#' followed by hex data: #12AB34CD...
2352"#
2353 .to_owned())
2354 }
2355
2356 pub async fn debug_uptime(&self, _args: String) -> VeilidAPIResult<String> {
2358 let mut result = String::new();
2359
2360 writeln!(result, "Uptime...").ok();
2361
2362 let state = self.get_state().await?;
2363
2364 let uptime = state.attachment.uptime;
2365 writeln!(result, " since launch: {uptime}").ok();
2366
2367 if let Some(attached_uptime) = state.attachment.attached_uptime {
2368 writeln!(result, " since attachment: {attached_uptime}").ok();
2369 }
2370
2371 Ok(result)
2372 }
2373
2374 #[cfg(debug_assertions)]
2376 #[expect(clippy::unused_async)]
2377 pub async fn debug_die(&self, args: String) -> VeilidAPIResult<String> {
2378 let args = args.trim_start();
2379 let (arg, rest) = args.split_once(' ').unwrap_or((args, ""));
2380 match arg {
2381 "panic" => {
2382 if rest.is_empty() {
2383 panic!();
2384 } else {
2385 panic!("{}", rest);
2386 }
2387 }
2388 "unwrap" => {
2389 #[expect(clippy::unnecessary_literal_unwrap)]
2390 Option::<()>::None.unwrap();
2391 unreachable!("unwrap must panic");
2392 }
2393 "unwrap_or_log" => {
2394 Option::<()>::None.unwrap_or_log();
2395 unreachable!("unwrap_or_log must panic");
2396 }
2397 "expect" => {
2398 #[expect(clippy::unnecessary_literal_unwrap)]
2399 Option::<()>::None.expect(rest);
2400 unreachable!("expect must panic");
2401 }
2402 "expect_or_log" => {
2403 Option::<()>::None.expect_or_log(rest);
2404 unreachable!("expect_or_log must panic");
2405 }
2406 "div0" => {
2407 let x = 0u32;
2408 let y = x / 0u32;
2409 unreachable!("divide by zero must panic: {}", y);
2410 }
2411 "overflow" => {
2412 let x = u32::MAX;
2413 let y = x + 1;
2414 unreachable!("integer overflow must panic: {}", y);
2415 }
2416 "oob" => {
2417 let x = &[3];
2418 #[expect(clippy::out_of_bounds_indexing)]
2419 let y = x[1];
2420 unreachable!("array out of bounds must panic: {}", y);
2421 }
2422 "nullptr" => {
2423 let x: *const i32 = std::ptr::null();
2424 let y = unsafe { *x };
2425 unreachable!("array out of bounds must panic: {}", y);
2426 }
2427 "unreachable" => {
2428 unreachable!("direct call of unreachable macro");
2429 }
2430 _ => {
2431 Ok("One of 'panic [message]', 'unwrap', 'unwrap_or_log', 'expect [message]', 'expect_or_log [message]', 'div0', 'overflow', 'oob', 'nullptr', or 'unreachable' is required".to_string())
2432 }
2433 }
2434 }
2435
2436 pub async fn debug(&self, args: String) -> VeilidAPIResult<String> {
2438 let res = {
2439 let args = args.trim_start();
2440 if args.is_empty() {
2441 return self.debug_help("".to_owned());
2443 }
2444 let (arg, rest) = args.split_once(' ').unwrap_or((args, ""));
2445 let rest = rest.trim_start().to_owned();
2446
2447 if arg == "help" {
2448 self.debug_help(rest)
2449 } else if arg == "nodeid" {
2450 self.debug_nodeid(rest)
2451 } else if arg == "buckets" {
2452 self.debug_buckets(rest)
2453 } else if arg == "dialinfo" {
2454 self.debug_dialinfo(rest)
2455 } else if arg == "peerinfo" {
2456 self.debug_peerinfo(rest)
2457 } else if arg == "contact" {
2458 self.debug_contact(rest)
2459 } else if arg == "keypair" {
2460 self.debug_keypair(rest)
2461 } else if arg == "entries" {
2462 self.debug_entries(rest)
2463 } else if arg == "entry" {
2464 self.debug_entry(rest)
2465 } else if arg == "punish" {
2466 self.debug_punish(rest)
2467 } else {
2468 let fut = if arg == "txtrecord" {
2469 pin_dyn_future!(self.debug_txtrecord(rest))
2470 } else if arg == "relay" {
2471 pin_dyn_future!(self.debug_relay(rest))
2472 } else if arg == "ping" {
2473 pin_dyn_future!(self.debug_ping(rest))
2474 } else if arg == "appmessage" {
2475 pin_dyn_future!(self.debug_app_message(rest))
2476 } else if arg == "appcall" {
2477 pin_dyn_future!(self.debug_app_call(rest))
2478 } else if arg == "appreply" {
2479 pin_dyn_future!(self.debug_app_reply(rest))
2480 } else if arg == "resolve" {
2481 pin_dyn_future!(self.debug_resolve(rest))
2482 } else if arg == "nodeinfo" {
2483 pin_dyn_future!(self.debug_nodeinfo(rest))
2484 } else if arg == "purge" {
2485 pin_dyn_future!(self.debug_purge(rest))
2486 } else if arg == "attach" {
2487 pin_dyn_future!(self.debug_attach(rest))
2488 } else if arg == "detach" {
2489 pin_dyn_future!(self.debug_detach(rest))
2490 } else if arg == "config" {
2491 pin_dyn_future!(self.debug_config(rest))
2492 } else if arg == "network" {
2493 pin_dyn_future!(self.debug_network(rest))
2494 } else if arg == "route" {
2495 pin_dyn_future!(self.debug_route(rest))
2496 } else if arg == "record" {
2497 pin_dyn_future!(self.debug_record(rest))
2498 } else if arg == "table" {
2499 pin_dyn_future!(self.debug_table(rest))
2500 } else if arg == "uptime" {
2501 pin_dyn_future!(self.debug_uptime(rest))
2502 } else {
2503 #[cfg(debug_assertions)]
2504 if arg == "die" {
2505 return self.debug_die(rest).await;
2506 }
2507
2508 return Err(VeilidAPIError::generic("Unknown debug command"));
2509 };
2510 fut.await
2511 }
2512 };
2513 res
2514 }
2515
2516 fn get_destination(
2517 self,
2518 registry: VeilidComponentRegistry,
2519 ) -> impl FnOnce(&str) -> PinBoxFutureStatic<Option<Destination>> {
2520 move |text| {
2521 let text = text.to_owned();
2522 Box::pin(async move {
2523 let (text, ss) = if let Some((first, second)) = text.split_once('+') {
2525 let ss = get_safety_selection(registry.clone())(second)?;
2526 (first, Some(ss))
2527 } else {
2528 (text.as_str(), None)
2529 };
2530 if text.is_empty() {
2531 return None;
2532 }
2533 if &text[0..1] == "#" {
2534 let routing_table = registry.routing_table();
2535 let rss = routing_table.route_spec_store();
2536
2537 let text = &text[1..];
2539
2540 let private_route = if let Some(prid) =
2541 get_route_id(registry.clone(), false, true)(text)
2542 {
2543 rss.best_remote_private_route(&prid)?
2544 } else {
2545 let n = get_number(text)?;
2546
2547 self.with_debug_cache(|dc| {
2548 let prid = dc.imported_routes.get(n)?;
2549 let Some(private_route) = rss.best_remote_private_route(prid) else {
2550 let _ = dc.imported_routes.remove(n);
2552 veilid_log!(registry info "removed dead imported route {}", n);
2553 return None;
2554 };
2555 Some(private_route)
2556 })?
2557 };
2558
2559 Some(Destination::private_route(
2560 private_route,
2561 ss.unwrap_or(SafetySelection::Unsafe(Sequencing::default())),
2562 ))
2563 } else if let Some((first, second)) = text.split_once('@') {
2564 if ss.is_some() {
2565 return None;
2566 }
2567 let relay_di = DialInfo::from_str(second).ok()?;
2569 let target_nr = get_node_ref(registry.clone())(first)?;
2570
2571 Some(Destination::relay(relay_di, target_nr))
2572 } else {
2573 let target_nr = resolve_filtered_node_ref(
2575 registry.clone(),
2576 ss.clone().unwrap_or_default(),
2577 )(text)
2578 .await?;
2579
2580 Some(Destination::direct(target_nr, ss))
2581 }
2582 })
2583 }
2584 }
2585
2586 fn get_opened_dht_record_context(
2587 self,
2588 args: &[String],
2589 context: &str,
2590 key: &str,
2591 arg: usize,
2592 ) -> VeilidAPIResult<(RecordKey, RoutingContext)> {
2593 let key = match get_debug_argument_at(args, arg, context, key, get_dht_key_no_safety)
2594 .ok()
2595 .or_else(|| {
2596 self.with_debug_cache(|dc| dc.opened_record_contexts.back().map(|kv| kv.0).cloned())
2598 }) {
2599 Some(k) => k,
2600 None => {
2601 apibail_missing_argument!("no keys are opened", "key");
2602 }
2603 };
2604
2605 let Some(rc) = self.with_debug_cache(|dc| dc.opened_record_contexts.get(&key).cloned())
2608 else {
2609 apibail_missing_argument!("key is not opened", "key");
2610 };
2611
2612 Ok((key, rc))
2613 }
2614}
2615
2616const DEFAULT_INDENT: usize = 4;
2617pub(crate) fn indent_string<S: ToString>(s: &S) -> String {
2618 indent_by(DEFAULT_INDENT, s.to_string())
2619}
2620pub(crate) fn indent_all_string<S: ToString>(s: &S) -> String {
2621 indent_all_by(DEFAULT_INDENT, s.to_string())
2622}
2623
2624pub(crate) trait ToMultilineString {
2625 fn to_multiline_string(&self) -> String;
2626 fn to_multiline_indexed_string(&self) -> String;
2627}
2628
2629impl<T> ToMultilineString for Vec<T>
2630where
2631 T: fmt::Display,
2632{
2633 fn to_multiline_string(&self) -> String {
2634 let mut out = String::new();
2635 for x in self {
2636 out += &x.to_string();
2637 out += "\n";
2638 }
2639 out
2640 }
2641 fn to_multiline_indexed_string(&self) -> String {
2642 let mut out = String::new();
2643 for (n, x) in self.iter().enumerate() {
2644 out += &format!("{}:\n", n);
2645 out += &indent_all_string(x);
2646 out += "\n";
2647 }
2648 out
2649 }
2650}
2651
2652pub(crate) trait ToTableString {
2653 fn to_table_string(&self) -> String {
2654 self.to_table_string_custom(32, 4, true, true)
2655 }
2656 fn to_table_string_custom(
2657 &self,
2658 columns: usize,
2659 indent: usize,
2660 brackets: bool,
2661 newlines: bool,
2662 ) -> String;
2663}
2664
2665impl<T> ToTableString for Vec<T>
2666where
2667 T: fmt::Display,
2668{
2669 fn to_table_string_custom(
2670 &self,
2671 columns: usize,
2672 indent: usize,
2673 brackets: bool,
2674 newlines: bool,
2675 ) -> String {
2676 let mut col = 0usize;
2677 let mut out = String::new();
2678 let mut left = self.len();
2679
2680 let use_columns = columns > 0 && columns < self.len();
2681
2682 if brackets {
2683 out += "[";
2684 }
2685 if use_columns && newlines {
2686 out += "\n";
2687 }
2688 let indent = " ".repeat(indent);
2689
2690 for s in self {
2691 if use_columns && col == 0 {
2692 out += &indent;
2693 }
2694 let sc = s.to_string();
2695
2696 out += ≻
2697 col += 1;
2698 left -= 1;
2699 if left != 0 {
2700 out += ",";
2701 if use_columns && col == columns {
2702 col = 0;
2703 out += "\n"
2704 }
2705 }
2706 }
2707 if use_columns && newlines {
2708 out += "\n";
2709 }
2710 if brackets {
2711 out += "]";
2712 }
2713
2714 out
2715 }
2716}
2717
2718pub(crate) trait StripTrailingNewline {
2719 fn strip_trailing_newline(&self) -> &str;
2720}
2721
2722impl<T: AsRef<str>> StripTrailingNewline for T {
2723 fn strip_trailing_newline(&self) -> &str {
2724 self.as_ref()
2725 .strip_suffix("\r\n")
2726 .or(self.as_ref().strip_suffix("\n"))
2727 .unwrap_or(self.as_ref())
2728 }
2729}