1use super::*;
5use clap::{error::ErrorKind, CommandFactory, Parser, Subcommand};
6use data_encoding::BASE64URL_NOPAD;
7use hashlink::LinkedHashMap;
8use network_manager::*;
9use routing_table::*;
10use std::fmt::Write;
11
12impl_veilid_log_facility!("veilid_debug");
13
14const DEBUG_HELP_TEMPLATE: &str = "{about-with-newline}{all-args}{after-help}";
15
16#[derive(Debug, Parser)]
18#[command(
19 name = "debug",
20 disable_help_flag = true,
21 disable_version_flag = true,
22 help_template = "{subcommands}"
23)]
24struct DebugCommandParser {
25 #[command(subcommand)]
26 command: DebugCommand,
27}
28
29#[derive(Debug, Subcommand)]
31#[command(help_template = DEBUG_HELP_TEMPLATE)]
32enum DebugCommand {
33 Nodeid,
35 Buckets {
37 #[arg(value_name = "min_state")]
39 min_state: Option<String>,
40 },
41 Dialinfo,
43 Peerinfo {
45 #[arg(value_name = "peerinfo_token")]
49 args: Vec<String>,
50 },
51 Contact {
53 #[arg(value_name = "node_ref")]
58 node_ref: String,
59 },
60 Keypair {
62 #[arg(value_name = "cryptokind")]
64 cryptokind: Option<String>,
65 },
66 Entries {
68 #[arg(value_name = "entries_token")]
74 args: Vec<String>,
75 },
76 Entry {
78 #[arg(value_name = "node")]
80 node: String,
81 },
82 Punish {
84 #[command(subcommand)]
85 command: Option<DebugPunishSubcommand>,
86 },
87 Txtrecord {
89 #[arg(value_name = "keypairs")]
91 keypairs: Option<String>,
92 },
93 Relay {
95 #[arg(value_name = "relay_token")]
100 args: Vec<String>,
101 },
102 Ping {
104 #[arg(value_name = "destination")]
109 destination: String,
110 },
111 Appmessage {
113 #[arg(value_name = "destination")]
115 destination: String,
116 #[arg(value_name = "data")]
121 data: Vec<String>,
122 },
123 Appcall {
125 #[arg(value_name = "destination")]
127 destination: String,
128 #[arg(value_name = "data")]
133 data: Vec<String>,
134 },
135 Appreply {
137 #[arg(value_name = "id")]
139 id: Option<String>,
140 #[arg(value_name = "data")]
145 data: Vec<String>,
146 },
147 Resolve {
149 #[arg(value_name = "destination")]
151 destination: String,
152 },
153 Nodeinfo,
155 Purge {
157 #[command(subcommand)]
158 command: Option<DebugPurgeSubcommand>,
159 },
160 Attach,
162 Detach,
164 Config {
166 #[arg(value_name = "mode_or_key")]
170 arg_0: Option<String>,
171 #[arg(value_name = "config_key")]
176 arg_1: Option<String>,
177 },
178 Network {
180 #[command(subcommand)]
181 command: Option<DebugNetworkSubcommand>,
182 },
183 Route {
185 #[command(subcommand)]
186 command: Option<DebugRouteSubcommand>,
187 },
188 Record {
190 #[command(subcommand)]
191 command: Option<DebugRecordSubcommand>,
192 },
193 Table {
195 #[command(subcommand)]
196 command: Option<DebugTableSubcommand>,
197 },
198 Uptime,
200 #[cfg(debug_assertions)]
201 Die {
203 #[arg(value_name = "mode")]
205 mode: Option<String>,
206 #[arg(value_name = "message")]
208 message: Vec<String>,
209 },
210}
211
212#[derive(Debug, Subcommand)]
214#[command(
215 help_template = DEBUG_HELP_TEMPLATE,
216 disable_help_subcommand = true
217)]
218enum DebugPurgeSubcommand {
219 #[command(help_template = DEBUG_HELP_TEMPLATE)]
221 Buckets,
222 #[command(help_template = DEBUG_HELP_TEMPLATE)]
224 Connections,
225 #[command(help_template = DEBUG_HELP_TEMPLATE)]
227 Routes,
228}
229
230#[derive(Debug, Subcommand)]
232#[command(
233 help_template = DEBUG_HELP_TEMPLATE,
234 disable_help_subcommand = true
235)]
236enum DebugNetworkSubcommand {
237 #[command(help_template = DEBUG_HELP_TEMPLATE)]
239 Restart,
240 #[command(help_template = DEBUG_HELP_TEMPLATE)]
242 Stats,
243}
244
245#[derive(Debug, Subcommand)]
247#[command(
248 help_template = DEBUG_HELP_TEMPLATE,
249 disable_help_subcommand = true
250)]
251enum DebugRouteSubcommand {
252 #[command(help_template = DEBUG_HELP_TEMPLATE)]
254 Allocate {
255 #[arg(allow_hyphen_values = true, value_name = "allocate_arg")]
261 params: Vec<String>,
262 },
263 #[command(help_template = DEBUG_HELP_TEMPLATE)]
265 Release {
266 #[arg(value_name = "route_id")]
268 route_id: String,
269 },
270 #[command(help_template = DEBUG_HELP_TEMPLATE)]
272 Publish {
273 #[arg(value_name = "route_id")]
275 route_id: String,
276 #[arg(value_name = "full")]
278 full: Option<String>,
279 },
280 #[command(help_template = DEBUG_HELP_TEMPLATE)]
282 Unpublish {
283 #[arg(value_name = "route_id")]
285 route_id: String,
286 },
287 #[command(help_template = DEBUG_HELP_TEMPLATE)]
289 Print {
290 #[arg(value_name = "route")]
292 route: String,
293 },
294 #[command(help_template = DEBUG_HELP_TEMPLATE)]
296 List,
297 #[command(help_template = DEBUG_HELP_TEMPLATE)]
299 Import {
300 #[arg(value_name = "blob")]
302 blob: String,
303 },
304 #[command(help_template = DEBUG_HELP_TEMPLATE)]
306 Test {
307 #[arg(value_name = "route_id")]
309 route_id: String,
310 },
311}
312
313#[derive(Debug, Subcommand)]
315#[command(
316 help_template = DEBUG_HELP_TEMPLATE,
317 disable_help_subcommand = true
318)]
319enum DebugRecordSubcommand {
320 #[command(help_template = DEBUG_HELP_TEMPLATE)]
322 List {
323 #[arg(value_name = "scope")]
325 scope: String,
326 },
327 #[command(help_template = DEBUG_HELP_TEMPLATE)]
329 Purge {
330 #[arg(value_name = "scope")]
332 scope: String,
333 #[arg(value_name = "bytes", group = "target")]
335 bytes: Option<u64>,
336 #[arg(long, value_name = "keys", value_delimiter = ',', group = "target")]
338 keys: Option<Vec<String>>,
339 },
340 #[command(help_template = DEBUG_HELP_TEMPLATE)]
342 Create {
343 #[arg(value_name = "dht_schema")]
345 dht_schema: Option<String>,
346 #[arg(value_name = "crypto_kind")]
348 crypto_kind: Option<String>,
349 #[arg(value_name = "safety")]
351 safety: Option<String>,
352 },
353 #[command(help_template = DEBUG_HELP_TEMPLATE)]
355 Open {
356 #[arg(value_name = "key")]
358 key: String,
359 #[arg(value_name = "writer")]
361 writer: Option<String>,
362 },
363 #[command(help_template = DEBUG_HELP_TEMPLATE)]
365 Close {
366 #[arg(value_name = "key")]
368 key: Option<String>,
369 },
370 #[command(help_template = DEBUG_HELP_TEMPLATE)]
372 Get {
373 #[arg(allow_hyphen_values = true, value_name = "get_arg")]
378 params: Vec<String>,
379 },
380 #[command(help_template = DEBUG_HELP_TEMPLATE)]
382 Set {
383 #[arg(allow_hyphen_values = true, value_name = "set_arg")]
388 params: Vec<String>,
389 },
390 #[command(help_template = DEBUG_HELP_TEMPLATE)]
392 Delete {
393 #[arg(value_name = "key")]
395 key: String,
396 },
397 #[command(help_template = DEBUG_HELP_TEMPLATE)]
399 Info {
400 #[arg(value_name = "key")]
402 key: String,
403 #[arg(value_name = "subkey")]
405 subkey: Option<u32>,
406 },
407 #[command(help_template = DEBUG_HELP_TEMPLATE)]
409 Watch {
410 #[arg(value_name = "key")]
412 key: Option<String>,
413 #[arg(value_name = "subkeys")]
415 subkeys: Option<String>,
416 #[arg(value_name = "expiration")]
418 expiration: Option<String>,
419 #[arg(value_name = "count")]
421 count: Option<u32>,
422 },
423 #[command(help_template = DEBUG_HELP_TEMPLATE)]
425 Cancel {
426 #[arg(value_name = "key")]
428 key: Option<String>,
429 #[arg(value_name = "subkeys")]
431 subkeys: Option<String>,
432 },
433 #[command(help_template = DEBUG_HELP_TEMPLATE)]
435 Inspect {
436 #[arg(value_name = "key")]
438 key: Option<String>,
439 #[arg(value_name = "scope")]
441 scope: Option<String>,
442 #[arg(value_name = "subkeys")]
444 subkeys: Option<String>,
445 },
446 #[command(help_template = DEBUG_HELP_TEMPLATE)]
448 Rehydrate {
449 #[arg(value_name = "key")]
451 key: String,
452 #[arg(value_name = "subkeys")]
454 subkeys: Option<String>,
455 #[arg(value_name = "consensus_count")]
457 consensus_count: Option<usize>,
458 },
459}
460
461#[derive(Debug, Subcommand)]
463#[command(
464 help_template = DEBUG_HELP_TEMPLATE,
465 disable_help_subcommand = true
466)]
467enum DebugTableSubcommand {
468 #[command(help_template = DEBUG_HELP_TEMPLATE)]
470 List,
471 #[command(help_template = DEBUG_HELP_TEMPLATE)]
473 Info {
474 #[arg(value_name = "name")]
476 name: String,
477 },
478}
479
480#[derive(Debug, Subcommand)]
482#[command(
483 help_template = DEBUG_HELP_TEMPLATE,
484 disable_help_subcommand = true
485)]
486enum DebugPunishSubcommand {
487 #[command(help_template = DEBUG_HELP_TEMPLATE)]
489 List,
490 #[command(help_template = DEBUG_HELP_TEMPLATE)]
492 Clear,
493 #[command(help_template = DEBUG_HELP_TEMPLATE)]
495 Add {
496 #[arg(value_name = "target")]
498 target: String,
499 },
500 #[command(help_template = DEBUG_HELP_TEMPLATE)]
502 Remove {
503 #[arg(value_name = "target")]
505 target: String,
506 },
507}
508
509fn render_parser_help<T: CommandFactory>() -> String {
510 let mut command = T::command();
511 let mut bytes = Vec::new();
512 if command.write_long_help(&mut bytes).is_err() {
513 return String::new();
514 }
515 String::from_utf8(bytes).unwrap_or_else(|e| String::from_utf8_lossy(&e.into_bytes()).into())
516}
517
518fn join_args(args: Vec<String>) -> String {
519 args.join(" ")
520}
521
522#[derive(Default)]
523pub(crate) struct DebugCache {
524 pub imported_routes: Vec<RouteId>,
525 pub opened_record_contexts: LinkedHashMap<RecordKey, RoutingContext>,
526}
527
528#[must_use]
529pub(crate) fn format_opt_ts(ts: Option<TimestampDuration>) -> String {
530 let Some(ts) = ts else {
531 return "---".to_owned();
532 };
533 format!("{:#}", ts)
534}
535
536#[must_use]
537pub(crate) fn format_opt_bps(bps: Option<ByteCount>) -> String {
538 let Some(bps) = bps else {
539 return "---".to_owned();
540 };
541 format!("{:#}/s", bps)
542}
543
544fn parse_bucket_entry_state(text: &str) -> Result<BucketEntryState, String> {
545 match text {
546 "punished" => Ok(BucketEntryState::Punished),
547 "dead" => Ok(BucketEntryState::Dead),
548 "reliable" => Ok(BucketEntryState::Reliable),
549 "unreliable" => Ok(BucketEntryState::Unreliable),
550 _ => Err(format!("invalid bucket entry state: {text}")),
551 }
552}
553
554fn get_bucket_entry_state(text: &str) -> Option<BucketEntryState> {
555 parse_bucket_entry_state(text).ok()
556}
557
558fn get_string(text: &str) -> Option<String> {
559 Some(text.to_owned())
560}
561
562fn parse_data(text: &str) -> Result<Vec<u8>, String> {
563 if let Some(stripped_text) = text.strip_prefix('#') {
564 hex::decode(stripped_text).map_err(|e| format!("invalid hex data: {e}"))
565 } else if text.starts_with('"') || text.starts_with('\'') {
566 serde_json::from_str::<String>(text)
567 .map(|x| x.into_bytes())
568 .map_err(|e| format!("invalid quoted data: {e}"))
569 } else {
570 Ok(text.as_bytes().to_vec())
571 }
572}
573
574fn get_data(text: &str) -> Option<Vec<u8>> {
575 parse_data(text).ok()
576}
577
578fn parse_subkeys(text: &str) -> Result<ValueSubkeyRangeSet, String> {
579 if let Some(n) = get_number::<u32>(text) {
580 Ok(ValueSubkeyRangeSet::single(n))
581 } else {
582 ValueSubkeyRangeSet::from_str(text).map_err(|e| format!("invalid subkeys: {e}"))
583 }
584}
585
586fn get_subkeys(text: &str) -> Option<ValueSubkeyRangeSet> {
587 parse_subkeys(text).ok()
588}
589
590fn parse_dht_report_scope(text: &str) -> Result<DHTReportScope, String> {
591 match text.to_ascii_lowercase().trim() {
592 "local" => Ok(DHTReportScope::Local),
593 "syncget" => Ok(DHTReportScope::SyncGet),
594 "syncset" => Ok(DHTReportScope::SyncSet),
595 "updateget" => Ok(DHTReportScope::UpdateGet),
596 "updateset" => Ok(DHTReportScope::UpdateSet),
597 _ => Err(format!("invalid dht report scope: {text}")),
598 }
599}
600
601fn get_dht_report_scope(text: &str) -> Option<DHTReportScope> {
602 parse_dht_report_scope(text).ok()
603}
604
605enum PublishedState {
606 Published,
607 Current,
608}
609
610impl PublishedState {
611 fn as_bool(&self) -> bool {
612 matches!(self, Self::Published)
613 }
614}
615
616impl FromStr for PublishedState {
617 type Err = String;
618
619 fn from_str(s: &str) -> Result<Self, Self::Err> {
620 match s.to_ascii_lowercase().as_str() {
621 "published" => Ok(Self::Published),
622 "current" => Ok(Self::Current),
623 _ => Err(format!("invalid published mode: {s}")),
624 }
625 }
626}
627
628fn get_nested_debug_family_name(arg: Option<&str>) -> Option<String> {
629 let top_level = arg?;
630 let parser = DebugCommandParser::command();
631 let command = parser.find_subcommand(top_level)?;
632 if command.get_subcommands().next().is_some() {
633 Some(top_level.to_owned())
634 } else {
635 None
636 }
637}
638
639fn get_known_debug_command_name(arg: Option<&str>) -> Option<String> {
640 let top_level = arg?;
641 let parser = DebugCommandParser::command();
642 if parser.find_subcommand(top_level).is_some() {
643 Some(top_level.to_owned())
644 } else {
645 None
646 }
647}
648
649fn get_route_id(
650 registry: VeilidComponentRegistry,
651 allow_allocated: bool,
652 allow_remote: bool,
653) -> impl Fn(&str) -> Option<RouteId> {
654 move |text: &str| {
655 if text.is_empty() {
656 return None;
657 }
658 let routing_table = registry.routing_table();
659 let rss = routing_table.route_spec_store();
660
661 match RouteId::from_str(text).ok() {
662 Some(key) => {
663 if allow_allocated {
664 let routes: Vec<RouteId> =
665 rss.list_allocated_routes(|k, _| Some(k.clone().into()));
666 if routes.contains(&key) {
667 return Some(key);
668 }
669 }
670 if allow_remote {
671 let rroutes: Vec<RouteId> =
672 rss.list_remote_routes(|k, _| Some(k.clone().into()));
673 if rroutes.contains(&key) {
674 return Some(key);
675 }
676 }
677 }
678 None => {
679 if allow_allocated {
680 let routes: Vec<RouteId> =
681 rss.list_allocated_routes(|k, _| Some(k.clone().into()));
682 for r in routes {
683 let rkey = r.to_string();
684 if rkey.starts_with(text)
685 || rkey
686 .split_once(':')
687 .map(|(_, b)| b.starts_with(text))
688 .unwrap_or(false)
689 {
690 return Some(r);
691 }
692 }
693 }
694 if allow_remote {
695 let routes: Vec<RouteId> =
696 rss.list_remote_routes(|k, _| Some(k.clone().into()));
697 for r in routes {
698 let rkey = r.to_string();
699 if rkey.starts_with(text)
700 || rkey
701 .split_once(':')
702 .map(|(_, b)| b.starts_with(text))
703 .unwrap_or(false)
704 {
705 return Some(r);
706 }
707 }
708 }
709 }
710 }
711 None
712 }
713}
714
715fn get_route_key(
716 registry: VeilidComponentRegistry,
717 allow_allocated: bool,
718 allow_remote: bool,
719) -> impl Fn(&str) -> Option<PublicKey> {
720 move |text: &str| {
721 if text.is_empty() {
722 return None;
723 }
724 let routing_table = registry.routing_table();
725 let rss = routing_table.route_spec_store();
726
727 match PublicKey::from_str(text).ok() {
728 Some(key) => {
729 if allow_allocated {
730 let allocated_route_keys =
731 rss.list_allocated_routes(|_, arce| Some(arce.route_set_keys().to_vec()));
732 if let Some(found_key) = allocated_route_keys
733 .into_iter()
734 .flatten()
735 .find(|x| x == &key)
736 {
737 return Some(found_key);
738 }
739 }
740 if allow_remote {
741 let remote_route_keys = rss.list_remote_routes(|_, v| {
742 Some(
743 v.get_private_routes()
744 .iter()
745 .map(|x| x.public_key.clone())
746 .collect::<Vec<_>>(),
747 )
748 });
749 if let Some(found_key) =
750 remote_route_keys.into_iter().flatten().find(|x| x == &key)
751 {
752 return Some(found_key);
753 }
754 }
755 }
756 None => {
757 if allow_allocated {
758 let allocated_route_keys =
759 rss.list_allocated_routes(|_, arce| Some(arce.route_set_keys().to_vec()));
760 for r in allocated_route_keys.into_iter().flatten() {
761 let rkey = r.to_string();
762 if rkey.starts_with(text)
763 || rkey
764 .split_once(':')
765 .map(|(_, b)| b.starts_with(text))
766 .unwrap_or(false)
767 {
768 return Some(r);
769 }
770 }
771 }
772 if allow_remote {
773 let remote_route_keys = rss.list_remote_routes(|_, v| {
774 Some(
775 v.get_private_routes()
776 .iter()
777 .map(|x| x.public_key.clone())
778 .collect::<Vec<_>>(),
779 )
780 });
781 for r in remote_route_keys.into_iter().flatten() {
782 let rkey = r.to_string();
783 if rkey.starts_with(text)
784 || rkey
785 .split_once(':')
786 .map(|(_, b)| b.starts_with(text))
787 .unwrap_or(false)
788 {
789 return Some(r);
790 }
791 }
792 }
793 }
794 }
795 None
796 }
797}
798
799fn get_dht_schema(text: &str) -> Option<VeilidAPIResult<DHTSchema>> {
800 if text.is_empty() {
801 return None;
802 }
803 if let Ok(n) = u16::from_str(text) {
804 return Some(DHTSchema::dflt(n));
805 }
806 Some(deserialize_json::<DHTSchema>(text))
807}
808
809fn get_safety_selection(
810 registry: VeilidComponentRegistry,
811) -> impl Fn(&str) -> Option<SafetySelection> {
812 move |text| {
813 let default_route_hop_count =
814 registry.config().network.rpc.default_route_hop_count as usize;
815
816 if !text.is_empty() && &text[0..1] == "-" {
817 let text = &text[1..];
819 let seq = get_sequencing(text).unwrap_or_default();
820 Some(SafetySelection::Unsafe(seq))
821 } else {
822 let mut preferred_route = None;
824 let mut hop_count = default_route_hop_count;
825 let mut stability = Stability::Reliable;
826 let mut sequencing = Sequencing::PreferUnordered;
827 for x in text.split(',') {
828 let x = x.trim();
829 if let Some(pr) = get_route_id(registry.clone(), true, false)(x) {
830 preferred_route = Some(pr)
831 }
832 if let Some(n) = get_number(x) {
833 hop_count = n;
834 }
835 if let Some(s) = get_stability(x) {
836 stability = s;
837 }
838 if let Some(s) = get_sequencing(x) {
839 sequencing = s;
840 }
841 }
842
843 let ss = SafetySpec {
844 preferred_route,
845 hop_count,
846 stability,
847 sequencing,
848 };
849 Some(SafetySelection::Safe(ss))
850 }
851 }
852}
853
854fn get_node_ref_modifiers(node_ref: NodeRef) -> impl FnOnce(&str) -> Option<FilteredNodeRef> {
855 move |text| {
856 let mut node_ref = node_ref.sequencing_filtered(Sequencing::PreferUnordered);
857 for m in text.split('/') {
858 if let Some(pt) = get_protocol_type(m) {
859 node_ref.merge_filter(NodeRefFilter::new().with_protocol_type(pt));
860 } else if let Some(at) = get_address_type(m) {
861 node_ref.merge_filter(NodeRefFilter::new().with_address_type(at));
862 } else if let Some(rd) = get_routing_domain(m) {
863 node_ref.merge_filter(NodeRefFilter::new().with_routing_domain(rd));
864 } else {
865 return None;
866 }
867 }
868 Some(node_ref)
869 }
870}
871
872fn get_number<T: num_traits::Num + FromStr>(text: &str) -> Option<T> {
873 T::from_str(text).ok()
874}
875
876fn get_record_key(text: &str) -> Option<RecordKey> {
877 RecordKey::from_str(text).ok()
878}
879fn get_bare_node_id(text: &str) -> Option<BareNodeId> {
880 let bare_node_id = BareNodeId::from_str(text).ok()?;
881
882 if bare_node_id.len() != 32 {
884 return None;
885 }
886 Some(bare_node_id)
887}
888fn get_node_id(text: &str) -> Option<NodeId> {
889 let node_id = NodeId::from_str(text).ok()?;
890
891 if node_id.value().len() != 32 {
893 return None;
894 }
895 Some(node_id)
896}
897fn get_keypair(text: &str) -> Option<KeyPair> {
898 KeyPair::from_str(text).ok()
899}
900fn get_keypair_group(text: &str) -> Option<KeyPairGroup> {
901 KeyPairGroup::from_str(text).ok()
902}
903
904fn get_crypto_system_version<'a>(
905 crypto: &'a Crypto,
906) -> impl FnOnce(&str) -> Option<CryptoSystemGuard<'a>> {
907 move |text| {
908 let kindstr = get_string(text)?;
909 let kind = CryptoKind::from_str(&kindstr).ok()?;
910 crypto.get(kind)
911 }
912}
913
914fn get_dht_key_no_safety(text: &str) -> Option<RecordKey> {
915 let key = get_record_key(text)?;
916
917 Some(key)
918}
919
920fn get_dht_key(
921 registry: VeilidComponentRegistry,
922) -> impl FnOnce(&str) -> Option<(RecordKey, Option<SafetySelection>)> {
923 move |text| {
924 let (text, ss) = if let Some((first, second)) = text.split_once('+') {
926 let ss = get_safety_selection(registry)(second)?;
927 (first, Some(ss))
928 } else {
929 (text, None)
930 };
931 if text.is_empty() {
932 return None;
933 }
934
935 let key = get_record_key(text)?;
936
937 Some((key, ss))
938 }
939}
940
941fn resolve_node_ref(
942 registry: VeilidComponentRegistry,
943 safety_selection: SafetySelection,
944) -> impl FnOnce(&str) -> PinBoxFutureStatic<Option<NodeRef>> {
945 move |text| {
946 let text = text.to_owned();
947 Box::pin(async move {
948 let nr = if let Some(node_id) = get_node_id(&text) {
949 registry
950 .rpc_processor()
951 .resolve_node(node_id, safety_selection)
952 .await
953 .ok()
954 .flatten()?
955 } else {
956 return None;
957 };
958 Some(nr)
959 })
960 }
961}
962
963fn resolve_filtered_node_ref(
964 registry: VeilidComponentRegistry,
965 safety_selection: SafetySelection,
966) -> impl FnOnce(&str) -> PinBoxFutureStatic<Option<FilteredNodeRef>> {
967 move |text| {
968 let text = text.to_owned();
969 Box::pin(async move {
970 let (text, mods) = text
971 .split_once('/')
972 .map(|x| (x.0, Some(x.1)))
973 .unwrap_or((&text, None));
974
975 let nr = if let Some(node_id) = get_node_id(text) {
976 registry
977 .rpc_processor()
978 .resolve_node(node_id, safety_selection)
979 .await
980 .ok()
981 .flatten()?
982 } else {
983 return None;
984 };
985 if let Some(mods) = mods {
986 Some(get_node_ref_modifiers(nr)(mods)?)
987 } else {
988 Some(nr.sequencing_filtered(Sequencing::PreferUnordered))
989 }
990 })
991 }
992}
993
994fn get_node_ref(registry: VeilidComponentRegistry) -> impl FnOnce(&str) -> Option<NodeRef> {
995 move |text| {
996 let routing_table = registry.routing_table();
997 let nr = if let Some(key) = get_bare_node_id(text) {
998 routing_table.lookup_bare_node_id(key).ok().flatten()?
999 } else if let Some(node_id) = get_node_id(text) {
1000 routing_table.lookup_node_id(node_id).ok().flatten()?
1001 } else {
1002 return None;
1003 };
1004 Some(nr)
1005 }
1006}
1007
1008fn get_filtered_node_ref(
1009 registry: VeilidComponentRegistry,
1010) -> impl FnOnce(&str) -> Option<FilteredNodeRef> {
1011 move |text| {
1012 let routing_table = registry.routing_table();
1013 FilteredNodeRef::parse(&routing_table, text).ok().flatten()
1014 }
1015}
1016
1017fn get_protocol_type(text: &str) -> Option<ProtocolType> {
1018 ProtocolType::from_str(text).ok()
1019}
1020fn get_sequencing(text: &str) -> Option<Sequencing> {
1021 Sequencing::from_str(text).ok()
1022}
1023fn get_stability(text: &str) -> Option<Stability> {
1024 Stability::from_str(text).ok()
1025}
1026fn get_direction_set(text: &str) -> Option<DirectionSet> {
1027 Direction::set_from_str(text).ok()
1028}
1029
1030fn get_address_type(text: &str) -> Option<AddressType> {
1031 AddressType::from_str(text).ok()
1032}
1033fn get_routing_domain(text: &str) -> Option<RoutingDomain> {
1034 RoutingDomain::from_str(text).ok()
1035}
1036
1037fn get_ip_addr(text: &str) -> Option<IpAddr> {
1038 IpAddr::from_str(text).ok()
1039}
1040
1041fn get_published(text: &str) -> Option<bool> {
1042 PublishedState::from_str(text).ok().map(|x| x.as_bool())
1043}
1044
1045fn get_debug_argument<T, G: FnOnce(&str) -> Option<T>>(
1046 value: &str,
1047 context: &str,
1048 argument: &str,
1049 getter: G,
1050) -> VeilidAPIResult<T> {
1051 let Some(val) = getter(value) else {
1052 apibail_invalid_argument!(context, argument, value);
1053 };
1054 Ok(val)
1055}
1056
1057async fn async_get_debug_argument<T, G: FnOnce(&str) -> PinBoxFutureStatic<Option<T>>>(
1058 value: &str,
1059 context: &str,
1060 argument: &str,
1061 getter: G,
1062) -> VeilidAPIResult<T> {
1063 let Some(val) = getter(value).await else {
1064 apibail_invalid_argument!(context, argument, value);
1065 };
1066 Ok(val)
1067}
1068
1069fn get_debug_argument_at<T, G: FnOnce(&str) -> Option<T>>(
1070 debug_args: &[String],
1071 pos: usize,
1072 context: &str,
1073 argument: &str,
1074 getter: G,
1075) -> VeilidAPIResult<T> {
1076 if pos >= debug_args.len() {
1077 apibail_missing_argument!(context, argument);
1078 }
1079 let value = &debug_args[pos];
1080 let Some(val) = getter(value) else {
1081 apibail_invalid_argument!(context, argument, value.clone());
1082 };
1083 Ok(val)
1084}
1085
1086async fn async_get_debug_argument_at<T, G: FnOnce(&str) -> PinBoxFutureStatic<Option<T>>>(
1087 debug_args: &[String],
1088 pos: usize,
1089 context: &str,
1090 argument: &str,
1091 getter: G,
1092) -> VeilidAPIResult<T> {
1093 if pos >= debug_args.len() {
1094 apibail_missing_argument!(context, argument);
1095 }
1096 let value = &debug_args[pos];
1097 let Some(val) = getter(value).await else {
1098 apibail_invalid_argument!(context, argument, value.clone());
1099 };
1100 Ok(val)
1101}
1102
1103impl VeilidAPI {
1104 fn debug_buckets(&self, opt_min_state: Option<String>) -> VeilidAPIResult<String> {
1105 let mut min_state = BucketEntryState::Unreliable;
1106 if let Some(min_state_str) = opt_min_state {
1107 min_state = get_debug_argument(
1108 &min_state_str,
1109 "debug_buckets",
1110 "min_state",
1111 get_bucket_entry_state,
1112 )?;
1113 }
1114 let routing_table = self.core_context()?.routing_table();
1116 Ok(routing_table.debug_info_buckets(min_state))
1117 }
1118
1119 fn debug_dialinfo(&self) -> VeilidAPIResult<String> {
1120 let routing_table = self.core_context()?.routing_table();
1122 Ok(routing_table.debug_info_dialinfo())
1123 }
1124 fn debug_peerinfo(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1125 let routing_table = self.core_context()?.routing_table();
1127
1128 let mut ai = 0;
1129 let mut opt_routing_domain = None;
1130 let mut opt_published = None;
1131
1132 while ai < args.len() {
1133 if let Ok(routing_domain) = get_debug_argument_at(
1134 &args,
1135 ai,
1136 "debug_peerinfo",
1137 "routing_domain",
1138 get_routing_domain,
1139 ) {
1140 opt_routing_domain = Some(routing_domain);
1141 } else if let Ok(published) =
1142 get_debug_argument_at(&args, ai, "debug_peerinfo", "published", get_published)
1143 {
1144 opt_published = Some(published);
1145 }
1146 ai += 1;
1147 }
1148
1149 let routing_domain = opt_routing_domain.unwrap_or(RoutingDomain::PublicInternet);
1150 let published = opt_published.unwrap_or(true);
1151
1152 Ok(routing_table.debug_info_peerinfo(routing_domain, published))
1153 }
1154
1155 async fn debug_txtrecord(&self, keypairs: Option<String>) -> VeilidAPIResult<String> {
1156 let signing_key_pairs = if let Some(keypairs) = keypairs {
1158 get_debug_argument(
1159 &keypairs,
1160 "debug_txtrecord",
1161 "signing_key_pairs",
1162 get_keypair_group,
1163 )?
1164 } else {
1165 KeyPairGroup::new()
1166 };
1167
1168 let network_manager = self.core_context()?.network_manager();
1169 Ok(network_manager
1170 .debug_info_txtrecord(signing_key_pairs)
1171 .await)
1172 }
1173
1174 fn debug_keypair(&self, cryptokind: Option<String>) -> VeilidAPIResult<String> {
1175 let crypto = self.crypto()?;
1176
1177 let vcrypto = if let Some(cryptokind) = cryptokind {
1178 get_debug_argument(
1179 &cryptokind,
1180 "debug_keypair",
1181 "kind",
1182 get_crypto_system_version(&crypto),
1183 )?
1184 } else {
1185 crypto.best()
1186 };
1187
1188 let out = vcrypto.generate_keypair().to_string();
1190 Ok(out)
1191 }
1192
1193 fn debug_entries(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1194 let mut min_state = BucketEntryState::Missing;
1195 let mut capabilities = vec![];
1196 let mut fastest = false;
1197 for arg in args {
1198 if let Some(ms) = get_bucket_entry_state(&arg) {
1199 min_state = ms;
1200 } else if arg == "fastest" {
1201 fastest = true;
1202 } else {
1203 for cap in arg.split(',') {
1204 if let Ok(cap) = VeilidCapability::from_str(cap) {
1205 capabilities.push(cap);
1206 } else {
1207 apibail_invalid_argument!("debug_entries", "unknown", arg);
1208 }
1209 }
1210 }
1211 }
1212
1213 let routing_table = self.core_context()?.routing_table();
1215 Ok(match fastest {
1216 true => routing_table.debug_info_entries_fastest(min_state, capabilities, 100000),
1217 false => routing_table.debug_info_entries(min_state, capabilities),
1218 })
1219 }
1220
1221 fn debug_entry(&self, node: String) -> VeilidAPIResult<String> {
1222 let registry = self.core_context()?.registry();
1223
1224 let node_ref = get_debug_argument(
1225 &node,
1226 "debug_entry",
1227 "node_id",
1228 get_node_ref(registry.clone()),
1229 )?;
1230
1231 Ok(registry.routing_table().debug_info_entry(node_ref))
1233 }
1234
1235 async fn debug_relay(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1236 let registry = self.core_context()?.registry();
1237
1238 let mut relay_nodes_refs: Vec<NodeRef> = vec![];
1239 let mut routing_domain = RoutingDomain::PublicInternet;
1240 for n in 0..args.len() {
1241 let opt_relay_node = async_get_debug_argument_at(
1242 &args,
1243 n,
1244 "debug_relay",
1245 "node_id",
1246 resolve_node_ref(
1247 registry.clone(),
1248 SafetySelection::Unsafe(Sequencing::PreferUnordered),
1249 ),
1250 )
1251 .await
1252 .ok();
1253 if let Some(relay_node) = opt_relay_node {
1254 relay_nodes_refs.push(relay_node);
1255 continue;
1256 }
1257
1258 let opt_routing_domain = get_debug_argument_at(
1259 &args,
1260 n,
1261 "debug_relay",
1262 "routing_domain",
1263 get_routing_domain,
1264 )
1265 .ok();
1266 if let Some(rd) = opt_routing_domain {
1267 routing_domain = rd;
1268 break;
1269 }
1270 }
1271
1272 let relays: Vec<_> = relay_nodes_refs
1273 .iter()
1274 .cloned()
1275 .map(|nr| RoutingDomainRelay::new(routing_domain, nr, RelayKind::Inbound))
1276 .collect();
1277
1278 let routing_table = registry.routing_table();
1280 {
1281 let rdc = routing_table.get_routing_domain_controller(routing_domain);
1282
1283 let opt_relay_compilation = if relays.is_empty() {
1285 None
1286 } else {
1287 let rdd = rdc.read_dyn();
1288 let relay_requirements = rdd.relay_requirements();
1289 let mut rc = relay_requirements.make_relay_compiler();
1290 for relay in relays {
1291 rc.apply_relay(relay);
1292 }
1293
1294 let c = rc.compile();
1295 if c.is_none() {
1296 return Ok("Relays selected do not meet publication requirements".to_owned());
1297 }
1298 c
1299 };
1300
1301 let mut editor = rdc.edit_dyn();
1302 editor.set_relay_compilation(opt_relay_compilation);
1303 editor.commit();
1304 rdc.publish_peer_info();
1305 }
1306
1307 Ok("Relay changed".to_owned())
1308 }
1309
1310 async fn debug_nodeinfo(&self) -> VeilidAPIResult<String> {
1311 let registry = self.core_context()?.registry();
1313 let nodeinfo_rtab = registry.routing_table().debug_info_nodeinfo();
1314 let nodeinfo_net = registry.network_manager().debug_info_nodeinfo();
1315 let nodeinfo_rpc = registry.rpc_processor().debug_info_nodeinfo();
1316 let nodeinfo_crypto = registry.crypto().debug_info_nodeinfo();
1317 let nodeinfo_attach = registry.attachment_manager().debug_info_nodeinfo();
1318
1319 let state = self.get_state().await?;
1321
1322 let mut peertable = Vec::new();
1323 peertable.push(format!(
1324 "Recent Peers: {} (max {})",
1325 state.network.peers.len(),
1326 RECENT_PEERS_TABLE_SIZE
1327 ));
1328 for peer in state.network.peers {
1329 peertable.push(format!(
1330 " {} | {} | {} | {} down | {} up",
1331 peer.node_ids.first().unwrap_or_log(),
1332 peer.peer_address,
1333 format_opt_ts(peer.peer_stats.latency.map(|l| l.average)),
1334 format_opt_bps(Some(peer.peer_stats.transfer.down.average)),
1335 format_opt_bps(Some(peer.peer_stats.transfer.up.average)),
1336 ));
1337 }
1338
1339 let connman =
1341 if let Some(connection_manager) = registry.network_manager().opt_connection_manager() {
1342 connection_manager.debug_print()
1343 } else {
1344 "Connection manager unavailable when detached".to_owned()
1345 };
1346
1347 Ok(format!(
1348 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
1349 nodeinfo_rtab,
1350 nodeinfo_net,
1351 nodeinfo_rpc,
1352 peertable.join("\n"),
1353 connman,
1354 nodeinfo_crypto,
1355 nodeinfo_attach
1356 ))
1357 }
1358
1359 fn debug_nodeid(&self) -> VeilidAPIResult<String> {
1360 let registry = self.core_context()?.registry();
1362 let nodeid = registry.routing_table().debug_info_nodeid();
1363 Ok(nodeid)
1364 }
1365
1366 #[expect(clippy::unused_async)]
1367 async fn debug_config(
1368 &self,
1369 arg_0: Option<String>,
1370 arg_1: Option<String>,
1371 ) -> VeilidAPIResult<String> {
1372 let mut args = arg_0.as_deref().unwrap_or_default();
1373 let mut config = self.config()?;
1374 if !args.starts_with("insecure") {
1375 if let Some(extra) = arg_1 {
1376 apibail_invalid_argument!("debug_config", "arg_1", extra);
1377 }
1378 config = config.safe();
1379 } else {
1380 if args != "insecure" {
1381 apibail_invalid_argument!("debug_config", "arg_0", args);
1382 }
1383 args = arg_1.as_deref().unwrap_or_default();
1384 }
1385 let args = args.trim_start();
1386
1387 if args.is_empty() {
1388 return config.get_key_json("", true);
1389 }
1390 config.get_key_json(args, true)
1391 }
1392
1393 async fn debug_network(
1394 &self,
1395 command: Option<DebugNetworkSubcommand>,
1396 ) -> VeilidAPIResult<String> {
1397 let Some(command) = command else {
1398 apibail_missing_argument!("debug_network", "arg_0");
1399 };
1400
1401 match command {
1402 DebugNetworkSubcommand::Restart => {
1403 if matches!(
1405 self.get_state().await?.attachment.state,
1406 AttachmentState::Detached
1407 ) {
1408 apibail_internal!("Must be attached to restart network");
1409 }
1410
1411 let registry = self.core_context()?.registry();
1412 registry.network_manager().restart_network();
1413
1414 Ok("Network restarted".to_owned())
1415 }
1416 DebugNetworkSubcommand::Stats => {
1417 let registry = self.core_context()?.registry();
1418 let debug_stats = registry.network_manager().debug();
1419
1420 Ok(debug_stats)
1421 }
1422 }
1423 }
1424
1425 async fn debug_purge(&self, command: Option<DebugPurgeSubcommand>) -> VeilidAPIResult<String> {
1426 let registry = self.core_context()?.registry();
1427
1428 let Some(command) = command else {
1429 apibail_missing_argument!("debug_purge", "parameter");
1430 };
1431
1432 match command {
1433 DebugPurgeSubcommand::Buckets => {
1434 if !matches!(
1436 self.get_state().await?.attachment.state,
1437 AttachmentState::Detached | AttachmentState::Detaching
1438 ) {
1439 apibail_internal!("Must be detached to purge");
1440 }
1441 match registry.routing_table().purge_buckets().await {
1442 Ok(_) => Ok("Buckets purged".to_owned()),
1443 Err(e) => Ok(format!("{}", e)),
1444 }
1445 }
1446 DebugPurgeSubcommand::Connections => {
1447 let opt_connection_manager = registry.network_manager().opt_connection_manager();
1449
1450 if let Some(connection_manager) = &opt_connection_manager {
1451 connection_manager.shutdown().await;
1452 }
1453
1454 registry.routing_table().purge_last_connections();
1456
1457 if let Some(connection_manager) = &opt_connection_manager {
1458 connection_manager
1459 .startup()
1460 .map_err(VeilidAPIError::internal)?;
1461 }
1462 Ok("Connections purged".to_owned())
1463 }
1464 DebugPurgeSubcommand::Routes => {
1465 self.with_debug_cache(|dc| {
1467 dc.imported_routes.clear();
1468 });
1469 match registry.routing_table().route_spec_store().purge().await {
1470 Ok(_) => Ok("Routes purged".to_owned()),
1471 Err(e) => Ok(format!("{}", e)),
1472 }
1473 }
1474 }
1475 }
1476
1477 async fn debug_attach(&self) -> VeilidAPIResult<String> {
1478 if !matches!(
1479 self.get_state().await?.attachment.state,
1480 AttachmentState::Detached
1481 ) {
1482 apibail_internal!("Not detached");
1483 }
1484
1485 self.attach().await?;
1486
1487 Ok("Attached".to_owned())
1488 }
1489
1490 async fn debug_detach(&self) -> VeilidAPIResult<String> {
1491 if matches!(
1492 self.get_state().await?.attachment.state,
1493 AttachmentState::Detaching
1494 ) {
1495 apibail_internal!("Not attached");
1496 };
1497
1498 self.detach().await?;
1499
1500 Ok("Detached".to_owned())
1501 }
1502
1503 fn debug_contact(&self, node_ref: String) -> VeilidAPIResult<String> {
1504 let registry = self.core_context()?.registry();
1505
1506 let node_ref = get_debug_argument(
1507 &node_ref,
1508 "debug_contact",
1509 "node_ref",
1510 get_filtered_node_ref(registry.clone()),
1511 )?;
1512
1513 let cm = registry
1514 .network_manager()
1515 .get_node_contact_method(node_ref)
1516 .map_err(VeilidAPIError::internal)?;
1517
1518 Ok(format!("{:#?}", cm))
1519 }
1520
1521 async fn debug_resolve(&self, destination: String) -> VeilidAPIResult<String> {
1522 let registry = self.core_context()?.registry();
1523 if !registry.attachment_manager().is_attached() {
1524 apibail_internal!("Must be attached first");
1525 };
1526
1527 let dest = async_get_debug_argument(
1528 &destination,
1529 "debug_resolve",
1530 "destination",
1531 self.clone().get_destination(registry.clone()),
1532 )
1533 .await?;
1534
1535 let routing_table = registry.routing_table();
1536 match &dest {
1537 Destination::Direct {
1538 node,
1539 safety_selection: _,
1540 } => Ok(format!(
1541 "Destination: {:#?}\nNode:\n{}\n",
1542 &dest,
1543 routing_table.debug_info_entry(node.unfiltered())
1544 )),
1545 Destination::DialInfo {
1546 dial_info: relay_di,
1547 node,
1548 } => Ok(format!(
1549 "Destination: {:#?}\nNode:\n{}\nDialInfo:\n{}\n",
1550 &dest,
1551 routing_table.debug_info_entry(node.clone()),
1552 relay_di,
1553 )),
1554 Destination::PrivateRoute {
1555 private_route: _,
1556 safety_selection: _,
1557 } => Ok(format!("Destination: {:#?}", &dest)),
1558 }
1559 }
1560
1561 async fn debug_ping(&self, destination: String) -> VeilidAPIResult<String> {
1562 let registry = self.core_context()?.registry();
1563 if !registry.attachment_manager().is_attached() {
1564 apibail_internal!("Must be attached first");
1565 };
1566
1567 let dest = async_get_debug_argument(
1568 &destination,
1569 "debug_ping",
1570 "destination",
1571 self.clone().get_destination(registry.clone()),
1572 )
1573 .await?;
1574
1575 let rpc_processor = registry.rpc_processor();
1577 let result = Box::pin(rpc_processor.rpc_call_status(dest))
1578 .await
1579 .map_err(VeilidAPIError::internal)?;
1580 match result {
1581 StatusResult::Answer { answer, .. } => Ok(format!("{:#?}", answer)),
1582 StatusResult::Failed(sdr) => Ok(format!("send failed: {}", sdr)),
1583 StatusResult::NotSent(nr) => Ok(format!("not sent: {:?}", nr)),
1584 }
1585 }
1586
1587 async fn debug_app_message(
1588 &self,
1589 destination: String,
1590 data_parts: Vec<String>,
1591 ) -> VeilidAPIResult<String> {
1592 let registry = self.core_context()?.registry();
1593 if !registry.attachment_manager().is_attached() {
1594 apibail_internal!("Must be attached first");
1595 };
1596 let dest = async_get_debug_argument(
1597 &destination,
1598 "debug_app_message",
1599 "destination",
1600 self.clone().get_destination(registry.clone()),
1601 )
1602 .await?;
1603
1604 let data_text = join_args(data_parts);
1605 let data = get_debug_argument(&data_text, "debug_app_message", "data", get_data)?;
1606 let data_len = data.len();
1607
1608 let rpc_processor = registry.rpc_processor();
1610
1611 let out = match rpc_processor
1612 .rpc_call_app_message(dest, data.into())
1613 .await
1614 .map_err(VeilidAPIError::internal)?
1615 {
1616 NetworkResult::Value(_) => format!("Sent {} bytes", data_len),
1617 r => {
1618 return Ok(r.to_string());
1619 }
1620 };
1621
1622 Ok(out)
1623 }
1624
1625 async fn debug_app_call(
1626 &self,
1627 destination: String,
1628 data_parts: Vec<String>,
1629 ) -> VeilidAPIResult<String> {
1630 let registry = self.core_context()?.registry();
1631 if !registry.attachment_manager().is_attached() {
1632 apibail_internal!("Must be attached first");
1633 };
1634 let dest = async_get_debug_argument(
1635 &destination,
1636 "debug_app_call",
1637 "destination",
1638 self.clone().get_destination(registry.clone()),
1639 )
1640 .await?;
1641
1642 let data_text = join_args(data_parts);
1643 let data = get_debug_argument(&data_text, "debug_app_call", "data", get_data)?;
1644 let data_len = data.len();
1645
1646 let rpc_processor = registry.rpc_processor();
1648
1649 let out = match Box::pin(rpc_processor.rpc_call_app_call(dest, data.into()))
1650 .await
1651 .map_err(VeilidAPIError::internal)?
1652 {
1653 NetworkResult::Value(v) => format!(
1654 "Sent {} bytes, received: {}",
1655 data_len,
1656 human_byte_data(&v.answer, Some(512))
1657 ),
1658 r => {
1659 return Ok(r.to_string());
1660 }
1661 };
1662
1663 Ok(out)
1664 }
1665
1666 async fn debug_app_reply(
1667 &self,
1668 id: Option<String>,
1669 data_parts: Vec<String>,
1670 ) -> VeilidAPIResult<String> {
1671 let registry = self.core_context()?.registry();
1672 if !registry.attachment_manager().is_attached() {
1673 apibail_internal!("Must be attached first");
1674 };
1675
1676 let (call_id, data) = if let Some(id) = id {
1677 let stripped_id = id.strip_prefix('#').unwrap_or(id.as_str());
1678 let call_id = OperationId::new(
1679 u64::from_str_radix(stripped_id, 16)
1680 .map_err(|e| VeilidAPIError::parse_error(e.to_string(), stripped_id))?,
1681 );
1682 let data_text = join_args(data_parts);
1683 let data = get_debug_argument(&data_text, "debug_app_reply", "data", get_data)?;
1684 (call_id, data)
1685 } else {
1686 let rpc_processor = registry.rpc_processor();
1687
1688 let call_id = rpc_processor
1689 .get_app_call_ids()
1690 .first()
1691 .cloned()
1692 .ok_or_else(|| VeilidAPIError::generic("no app calls waiting"))?;
1693 let data_text = join_args(data_parts);
1694 let data = get_debug_argument(&data_text, "debug_app_reply", "data", get_data)?;
1695 (call_id, data)
1696 };
1697
1698 let data_len = data.len();
1699
1700 self.app_call_reply(call_id, data)
1702 .await
1703 .map_err(VeilidAPIError::internal)?;
1704
1705 Ok(format!("Replied with {} bytes", data_len))
1706 }
1707
1708 async fn debug_route_allocate(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1709 let registry = self.core_context()?.registry();
1712 let routing_table = registry.routing_table();
1713 let rss = routing_table.route_spec_store();
1714 let default_route_hop_count = self.config()?.network.rpc.default_route_hop_count as usize;
1715
1716 let mut ai = 0;
1717 let mut sequencing = Sequencing::default();
1718 let mut stability = Stability::default();
1719 let mut hop_count = default_route_hop_count;
1720 let mut directions = DirectionSet::all();
1721
1722 while ai < args.len() {
1723 if let Ok(seq) =
1724 get_debug_argument_at(&args, ai, "debug_route", "sequencing", get_sequencing)
1725 {
1726 sequencing = seq;
1727 } else if let Ok(sta) =
1728 get_debug_argument_at(&args, ai, "debug_route", "stability", get_stability)
1729 {
1730 stability = sta;
1731 } else if let Ok(hc) =
1732 get_debug_argument_at(&args, ai, "debug_route", "hop_count", get_number)
1733 {
1734 hop_count = hc;
1735 } else if let Ok(ds) =
1736 get_debug_argument_at(&args, ai, "debug_route", "direction_set", get_direction_set)
1737 {
1738 directions = ds;
1739 } else {
1740 return Ok(format!("Invalid argument specified: {}", args[ai]));
1741 }
1742 ai += 1;
1743 }
1744
1745 let allocate_route_params = AllocateRouteParams {
1747 crypto_kinds: VALID_CRYPTO_KINDS.to_vec(),
1748 hop_count,
1749 stability,
1750 sequencing,
1751 directions,
1752 avoid_nodes: Vec::new(),
1753 automatic: false,
1754 };
1755 let out = match rss.allocate_route(allocate_route_params).await {
1756 Ok(v) => v.route_id.to_string(),
1757 Err(e) => format!("Route allocation failed: {}", e),
1758 };
1759
1760 Ok(out)
1761 }
1762 fn debug_route_release(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1763 let registry = self.core_context()?.registry();
1765 let routing_table = registry.routing_table();
1766 let rss = routing_table.route_spec_store();
1767
1768 let route_id = get_debug_argument_at(
1769 &args,
1770 0,
1771 "debug_route",
1772 "route_id",
1773 get_route_id(registry.clone(), true, true),
1774 )?;
1775
1776 let out = match rss.release_route(route_id.clone()) {
1778 true => {
1779 self.with_debug_cache(|dc| {
1781 for (n, ir) in dc.imported_routes.iter().enumerate() {
1782 if *ir == route_id {
1783 let _ = dc.imported_routes.remove(n);
1784 break;
1785 }
1786 }
1787 });
1788 "Released".to_owned()
1789 }
1790 false => "Route does not exist".to_owned(),
1791 };
1792
1793 Ok(out)
1794 }
1795 async fn debug_route_publish(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1796 let registry = self.core_context()?.registry();
1798 let routing_table = registry.routing_table();
1799 let rss = routing_table.route_spec_store();
1800
1801 let route_id = get_debug_argument_at(
1802 &args,
1803 0,
1804 "debug_route",
1805 "route_id",
1806 get_route_id(registry.clone(), true, false),
1807 )?;
1808 let full = {
1809 if args.len() > 1 {
1810 let full_val = get_debug_argument_at(&args, 1, "debug_route", "full", get_string)?
1811 .to_ascii_lowercase();
1812 if full_val == "full" {
1813 true
1814 } else {
1815 apibail_invalid_argument!("debug_route", "full", full_val);
1816 }
1817 } else {
1818 false
1819 }
1820 };
1821
1822 let arsid = AllocatedRouteSetId::from_route_id(route_id.clone());
1824 let out = match rss.assemble_private_route_set(&arsid, Some(!full)).await {
1825 Ok(private_routes) => {
1826 if let Err(e) = rss.mark_route_published(&arsid, true) {
1827 return Ok(format!("Couldn't mark route published: {}", e));
1828 }
1829 let blob_data = RouteSpecStore::private_routes_to_blob(&private_routes)
1831 .map_err(VeilidAPIError::internal)?;
1832 let out = BASE64URL_NOPAD.encode(&blob_data);
1833 veilid_log!(registry info
1834 "Published route {} as {} bytes:\n{}",
1835 route_id,
1836 blob_data.len(),
1837 out
1838 );
1839 format!("Published route {}", route_id)
1840 }
1841 Err(e) => {
1842 format!("Couldn't assemble private route: {}", e)
1843 }
1844 };
1845
1846 Ok(out)
1847 }
1848 fn debug_route_unpublish(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1849 let registry = self.core_context()?.registry();
1851 let routing_table = registry.routing_table();
1852 let rss = routing_table.route_spec_store();
1853
1854 let route_id = get_debug_argument_at(
1855 &args,
1856 0,
1857 "debug_route",
1858 "route_id",
1859 get_route_id(registry.clone(), true, false),
1860 )?;
1861
1862 let arsid = AllocatedRouteSetId::from_route_id(route_id);
1864 let out = if let Err(e) = rss.mark_route_published(&arsid, false) {
1865 return Ok(format!("Couldn't mark route unpublished: {}", e));
1866 } else {
1867 "Route unpublished".to_owned()
1868 };
1869 Ok(out)
1870 }
1871 fn debug_route_print(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1872 let registry = self.core_context()?.registry();
1874 let routing_table = registry.routing_table();
1875 let rss = routing_table.route_spec_store();
1876
1877 if let Ok(route_id) = get_debug_argument_at(
1878 &args,
1879 0,
1880 "debug_route",
1881 "route_id",
1882 get_route_id(registry.clone(), true, true),
1883 ) {
1884 Ok(format!(
1886 "{}\n{}",
1887 rss.debug_route_by_id(&route_id),
1888 rss.display_route_by_id(&route_id)
1889 ))
1890 } else if let Ok(route_key) = get_debug_argument_at(
1891 &args,
1892 0,
1893 "debug_route",
1894 "route_key",
1895 get_route_key(registry.clone(), true, true),
1896 ) {
1897 Ok(rss.debug_route_by_key(&route_key))
1898 } else {
1899 Ok("Route key not found".to_string())
1900 }
1901 }
1902 fn debug_route_list(&self, _args: Vec<String>) -> VeilidAPIResult<String> {
1903 let registry = self.core_context()?.registry();
1905 let routing_table = registry.routing_table();
1906 let rss = routing_table.route_spec_store();
1907
1908 let routes = rss.list_allocated_routes(|k, v| Some((k.clone(), format!("{:#}", v))));
1909 let mut out = format!("Allocated Routes: (count = {}):\n", routes.len());
1910 for (r, s) in routes {
1911 out.push_str(&format!("{}: {}\n", r, s));
1912 }
1913
1914 let remote_routes = rss.list_remote_routes(|k, v| Some((k.clone(), format!("{:#}", v))));
1915 out.push_str(&format!(
1916 "Remote Routes: (count = {}):\n",
1917 remote_routes.len()
1918 ));
1919 for (r, s) in remote_routes {
1920 out.push_str(&format!("{}: {:#}\n", r, s));
1921 }
1922
1923 Ok(out)
1924 }
1925 fn debug_route_import(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1926 let registry = self.core_context()?.registry();
1928 let routing_table = registry.routing_table();
1929 let rss = routing_table.route_spec_store();
1930
1931 let blob = get_debug_argument_at(&args, 0, "debug_route", "blob", get_string)?;
1932 let blob_dec = BASE64URL_NOPAD
1933 .decode(blob.as_bytes())
1934 .map_err(|e| VeilidAPIError::parse_error(e.to_string(), blob))?;
1935
1936 let route_id = rss.import_remote_route_blob(blob_dec)?;
1937
1938 let out = self.with_debug_cache(|dc| {
1939 let n = dc.imported_routes.len();
1940 let out = format!("Private route #{} imported: {}", n, route_id);
1941 dc.imported_routes.push(route_id);
1942 out
1943 });
1944
1945 Ok(out)
1946 }
1947
1948 async fn debug_route_test(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1949 let registry = self.core_context()?.registry();
1951 let routing_table = registry.routing_table();
1952 let rss = routing_table.route_spec_store();
1953
1954 let route_id = get_debug_argument_at(
1955 &args,
1956 0,
1957 "debug_route",
1958 "route_id",
1959 get_route_id(registry.clone(), true, true),
1960 )?;
1961
1962 let success = rss.test_route(route_id).await?;
1963
1964 let out = match success {
1965 Some(true) => "SUCCESS".to_owned(),
1966 Some(false) => "FAILED".to_owned(),
1967 None => "UNTESTED".to_owned(),
1968 };
1969
1970 Ok(out)
1971 }
1972
1973 async fn debug_route(&self, command: Option<DebugRouteSubcommand>) -> VeilidAPIResult<String> {
1974 let Some(command) = command else {
1975 apibail_missing_argument!("debug_route", "command");
1976 };
1977
1978 match command {
1979 DebugRouteSubcommand::Allocate { params } => self.debug_route_allocate(params).await,
1980 DebugRouteSubcommand::Release { route_id } => self.debug_route_release(vec![route_id]),
1981 DebugRouteSubcommand::Publish { route_id, full } => {
1982 let mut args = vec![route_id];
1983 if let Some(full) = full {
1984 args.push(full);
1985 }
1986 self.debug_route_publish(args).await
1987 }
1988 DebugRouteSubcommand::Unpublish { route_id } => {
1989 self.debug_route_unpublish(vec![route_id])
1990 }
1991 DebugRouteSubcommand::Print { route } => self.debug_route_print(vec![route]),
1992 DebugRouteSubcommand::List => self.debug_route_list(vec![]),
1993 DebugRouteSubcommand::Import { blob } => self.debug_route_import(vec![blob]),
1994 DebugRouteSubcommand::Test { route_id } => self.debug_route_test(vec![route_id]).await,
1995 }
1996 }
1997
1998 fn debug_record_list(&self, args: Vec<String>) -> VeilidAPIResult<String> {
1999 let registry = self.core_context()?.registry();
2001 let storage_manager = registry.storage_manager();
2002
2003 let scope = get_debug_argument_at(&args, 0, "debug_record_list", "scope", get_string)?;
2004 let out = match scope.as_str() {
2005 "local" => {
2006 let mut out = "Local Records:\n".to_string();
2007 out += &storage_manager.debug_local_records();
2008 out
2009 }
2010 "remote" => {
2011 let mut out = "Remote Records:\n".to_string();
2012 out += &storage_manager.debug_remote_records();
2013 out
2014 }
2015 "opened" => {
2016 let mut out = "Opened Records:\n".to_string();
2017 out += &storage_manager.debug_opened_records();
2018 out
2019 }
2020 "watched" => {
2021 let mut out = "Watched Records:\n".to_string();
2022 out += &storage_manager.debug_watched_records();
2023 out
2024 }
2025 "offline" => {
2026 let mut out = "Offline Records:\n".to_string();
2027 out += &storage_manager.debug_offline_records();
2028 out
2029 }
2030 "transactions" => {
2031 let mut out = "Record Transaction:\n".to_string();
2032 out += &storage_manager.debug_transactions();
2033 out
2034 }
2035 _ => "Invalid scope\n".to_owned(),
2036 };
2037 Ok(out)
2038 }
2039
2040 async fn debug_record_purge_bytes(
2041 &self,
2042 scope: String,
2043 bytes: Option<u64>,
2044 ) -> VeilidAPIResult<String> {
2045 let registry = self.core_context()?.registry();
2047 let storage_manager = registry.storage_manager();
2048
2049 self.with_debug_cache(|dc| {
2050 dc.opened_record_contexts.clear();
2051 });
2052 storage_manager.close_all_records().await?;
2053
2054 let out = match scope.as_str() {
2055 "local" => storage_manager.purge_local_records_by_bytes(bytes).await,
2056 "remote" => storage_manager.purge_remote_records_by_bytes(bytes).await,
2057 _ => "Invalid scope\n".to_owned(),
2058 };
2059 Ok(out)
2060 }
2061
2062 async fn debug_record_purge_keys(
2063 &self,
2064 scope: String,
2065 keys: Vec<String>,
2066 ) -> VeilidAPIResult<String> {
2067 let registry = self.core_context()?.registry();
2069 let storage_manager = registry.storage_manager();
2070
2071 self.with_debug_cache(|dc| {
2072 dc.opened_record_contexts.clear();
2073 });
2074 storage_manager.close_all_records().await?;
2075
2076 let keys = keys
2077 .iter()
2078 .map(|k| {
2079 RecordKey::from_str(k)
2080 .map(|rk| rk.opaque())
2081 .or_else(|_| OpaqueRecordKey::from_str(k))
2082 })
2083 .collect::<Result<Vec<OpaqueRecordKey>, VeilidAPIError>>()?;
2084
2085 let out = match scope.as_str() {
2086 "local" => storage_manager.purge_local_records_by_keys(keys).await,
2087 "remote" => storage_manager.purge_remote_records_by_keys(keys).await,
2088 _ => "Invalid scope\n".to_owned(),
2089 };
2090 Ok(out)
2091 }
2092
2093 async fn debug_record_create(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2094 let crypto = self.crypto()?;
2095
2096 let schema = get_debug_argument_at(
2097 &args,
2098 0,
2099 "debug_record_create",
2100 "dht_schema",
2101 get_dht_schema,
2102 )
2103 .unwrap_or_else(|_| Ok(DHTSchema::default()))?;
2104
2105 let csv = get_debug_argument_at(
2106 &args,
2107 1,
2108 "debug_record_create",
2109 "kind",
2110 get_crypto_system_version(&crypto),
2111 )
2112 .unwrap_or_else(|_| crypto.best());
2113
2114 let ss = get_debug_argument_at(
2115 &args,
2116 2,
2117 "debug_record_create",
2118 "safety_selection",
2119 get_safety_selection(self.core_context()?.registry()),
2120 )
2121 .ok();
2122
2123 let rc = self.routing_context()?;
2125 let rc = if let Some(ss) = ss {
2126 match rc.with_safety(ss) {
2127 Err(e) => return Ok(format!("Can't use safety selection: {}", e)),
2128 Ok(v) => v,
2129 }
2130 } else {
2131 rc
2132 };
2133
2134 let record = match rc.create_dht_record(csv.kind(), schema, None).await {
2136 Err(e) => return Ok(format!("Can't open DHT record: {}", e)),
2137 Ok(v) => v,
2138 };
2139
2140 self.with_debug_cache(|dc| {
2142 dc.opened_record_contexts.insert(record.key(), rc);
2143 });
2144
2145 Ok(format!(
2146 "Created: {} {}\n{:?}",
2147 record.key(),
2148 record.owner_keypair().unwrap_or_log(),
2149 record
2150 ))
2151 }
2152
2153 async fn debug_record_open(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2154 let registry = self.core_context()?.registry();
2155
2156 let (key, ss) = get_debug_argument_at(
2157 &args,
2158 0,
2159 "debug_record_open",
2160 "key",
2161 get_dht_key(registry.clone()),
2162 )?;
2163 let writer =
2164 get_debug_argument_at(&args, 1, "debug_record_open", "writer", get_keypair).ok();
2165
2166 let rc = self.routing_context()?;
2168 let rc = if let Some(ss) = ss {
2169 match rc.with_safety(ss) {
2170 Err(e) => return Ok(format!("Can't use safety selection: {}", e)),
2171 Ok(v) => v,
2172 }
2173 } else {
2174 rc
2175 };
2176
2177 let record = match rc.open_dht_record(key.clone(), writer).await {
2179 Err(e) => return Ok(format!("Can't open DHT record: {}", e)),
2180 Ok(v) => v,
2181 };
2182
2183 self.with_debug_cache(|dc| {
2185 dc.opened_record_contexts.insert(record.key(), rc);
2186 });
2187
2188 Ok(format!("Opened: {}\n{:#?}", key, record))
2189 }
2190
2191 async fn debug_record_close(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2192 let (key, rc) =
2193 self.clone()
2194 .get_opened_dht_record_context(&args, "debug_record_close", "key", 0)?;
2195
2196 if let Err(e) = rc.close_dht_record(key.clone()).await {
2198 return Ok(format!("Can't close DHT record: {}", e));
2199 };
2200
2201 Ok(format!("Closed: {:?}", key))
2202 }
2203
2204 async fn debug_record_set(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2205 let mut opt_arg_add = if !args.is_empty() && get_dht_key_no_safety(&args[0]).is_some() {
2206 1
2207 } else {
2208 0
2209 };
2210 let (key, rc) =
2211 self.clone()
2212 .get_opened_dht_record_context(&args, "debug_record_set", "key", 0)?;
2213 let subkey = get_debug_argument_at(
2214 &args,
2215 opt_arg_add,
2216 "debug_record_set",
2217 "subkey",
2218 get_number::<u32>,
2219 )?;
2220 let data =
2221 get_debug_argument_at(&args, 1 + opt_arg_add, "debug_record_set", "data", get_data)?;
2222 let writer = match get_debug_argument_at(
2223 &args,
2224 2 + opt_arg_add,
2225 "debug_record_set",
2226 "writer",
2227 get_keypair,
2228 ) {
2229 Ok(v) => {
2230 opt_arg_add += 1;
2231 Some(v)
2232 }
2233 Err(_) => None,
2234 };
2235 let allow_offline = if args.len() > 2 + opt_arg_add {
2236 get_debug_argument_at(
2237 &args,
2238 2 + opt_arg_add,
2239 "debug_record_set",
2240 "allow_offline",
2241 get_string,
2242 )
2243 .ok()
2244 } else {
2245 None
2246 };
2247
2248 let allow_offline = if let Some(allow_offline) = allow_offline {
2249 if &allow_offline == "online" || &allow_offline == "false" {
2250 Some(AllowOffline(false))
2251 } else if &allow_offline == "offline" || &allow_offline == "true" {
2252 Some(AllowOffline(true))
2253 } else {
2254 return Ok(format!("Unknown allow_offline: {}", allow_offline));
2255 }
2256 } else {
2257 None
2258 };
2259
2260 let value = match rc
2262 .set_dht_value(
2263 key,
2264 subkey as ValueSubkey,
2265 data,
2266 Some(SetDHTValueOptions {
2267 writer,
2268 allow_offline,
2269 }),
2270 )
2271 .await
2272 {
2273 Err(e) => {
2274 return Ok(format!("Can't set DHT value: {}", e));
2275 }
2276 Ok(v) => v,
2277 };
2278 let out = if let Some(value) = value {
2279 format!("Newer value found: {:?}", value)
2280 } else {
2281 "Success".to_owned()
2282 };
2283 Ok(out)
2284 }
2285
2286 async fn debug_record_get(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2287 let opt_arg_add = if !args.is_empty() && get_dht_key_no_safety(&args[0]).is_some() {
2288 1
2289 } else {
2290 0
2291 };
2292
2293 let (key, rc) =
2294 self.clone()
2295 .get_opened_dht_record_context(&args, "debug_record_get", "key", 0)?;
2296 let subkey = get_debug_argument_at(
2297 &args,
2298 opt_arg_add,
2299 "debug_record_get",
2300 "subkey",
2301 get_number::<u32>,
2302 )?;
2303 let force_refresh = if args.len() > 1 + opt_arg_add {
2304 Some(get_debug_argument_at(
2305 &args,
2306 1 + opt_arg_add,
2307 "debug_record_get",
2308 "force_refresh",
2309 get_string,
2310 )?)
2311 } else {
2312 None
2313 };
2314
2315 let force_refresh = if let Some(force_refresh) = force_refresh {
2316 if &force_refresh == "force" {
2317 true
2318 } else {
2319 return Ok(format!("Unknown force: {}", force_refresh));
2320 }
2321 } else {
2322 false
2323 };
2324
2325 let value = match rc
2327 .get_dht_value(key, subkey as ValueSubkey, force_refresh)
2328 .await
2329 {
2330 Err(e) => {
2331 return Ok(format!("Can't get DHT value: {}", e));
2332 }
2333 Ok(v) => v,
2334 };
2335 let out = if let Some(value) = value {
2336 format!("{:?}", value)
2337 } else {
2338 "No value data returned".to_owned()
2339 };
2340 Ok(out)
2341 }
2342
2343 async fn debug_record_delete(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2344 let key = get_debug_argument_at(
2345 &args,
2346 0,
2347 "debug_record_delete",
2348 "key",
2349 get_dht_key_no_safety,
2350 )?;
2351
2352 let rc = self.routing_context()?;
2354 match rc.delete_dht_record(key).await {
2355 Err(e) => return Ok(format!("Can't delete DHT record: {}", e)),
2356 Ok(v) => v,
2357 };
2358 Ok("DHT record deleted".to_string())
2359 }
2360
2361 async fn debug_record_info(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2362 let registry = self.core_context()?.registry();
2363 let storage_manager = registry.storage_manager();
2364
2365 let key =
2366 get_debug_argument_at(&args, 0, "debug_record_info", "key", get_dht_key_no_safety)?;
2367
2368 let subkey = get_debug_argument_at(
2369 &args,
2370 1,
2371 "debug_record_info",
2372 "subkey",
2373 get_number::<ValueSubkey>,
2374 )
2375 .ok();
2376
2377 let out = if let Some(subkey) = subkey {
2378 let li = storage_manager
2379 .debug_local_record_subkey_info(key.clone(), subkey)
2380 .await;
2381 let ri = storage_manager
2382 .debug_remote_record_subkey_info(key.clone(), subkey)
2383 .await;
2384 format!(
2385 "Local Subkey Info:\n{}\n\nRemote Subkey Info:\n{}\n",
2386 li, ri
2387 )
2388 } else {
2389 let li = storage_manager.debug_local_record_info(key.clone());
2390 let ri = storage_manager.debug_remote_record_info(key.clone());
2391 format!("Local Info:\n{}\n\nRemote Info:\n{}\n", li, ri)
2392 };
2393 Ok(out)
2394 }
2395
2396 async fn debug_record_watch(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2397 let opt_arg_add = if !args.is_empty() && get_dht_key_no_safety(&args[0]).is_some() {
2398 1
2399 } else {
2400 0
2401 };
2402
2403 let (key, rc) =
2404 self.clone()
2405 .get_opened_dht_record_context(&args, "debug_record_watch", "key", 0)?;
2406
2407 let mut rest_defaults = false;
2408 let subkeys = get_debug_argument_at(
2409 &args,
2410 opt_arg_add,
2411 "debug_record_watch",
2412 "subkeys",
2413 get_subkeys,
2414 )
2415 .ok()
2416 .map(Some)
2417 .unwrap_or_else(|| {
2418 rest_defaults = true;
2419 None
2420 });
2421
2422 let opt_expiration = if rest_defaults {
2423 None
2424 } else {
2425 get_debug_argument_at(
2426 &args,
2427 1 + opt_arg_add,
2428 "debug_record_watch",
2429 "expiration",
2430 parse_duration,
2431 )
2432 .ok()
2433 .map(|dur| {
2434 if dur == 0 {
2435 None
2436 } else {
2437 Some(Timestamp::now_non_decreasing().later(TimestampDuration::new(dur)))
2438 }
2439 })
2440 .unwrap_or_else(|| {
2441 rest_defaults = true;
2442 None
2443 })
2444 };
2445 let count = if rest_defaults {
2446 None
2447 } else {
2448 get_debug_argument_at(
2449 &args,
2450 2 + opt_arg_add,
2451 "debug_record_watch",
2452 "count",
2453 get_number,
2454 )
2455 .ok()
2456 .map(Some)
2457 .unwrap_or_else(|| {
2458 rest_defaults = true;
2459 Some(u32::MAX)
2460 })
2461 };
2462
2463 let active = match rc
2465 .watch_dht_values(key, subkeys, opt_expiration, count)
2466 .await
2467 {
2468 Err(e) => {
2469 return Ok(format!("Can't watch DHT value: {}", e));
2470 }
2471 Ok(v) => v,
2472 };
2473 if !active {
2474 return Ok("Failed to watch value".to_owned());
2475 }
2476 Ok("Success".to_owned())
2477 }
2478
2479 async fn debug_record_cancel(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2480 let opt_arg_add = if !args.is_empty() && get_dht_key_no_safety(&args[0]).is_some() {
2481 1
2482 } else {
2483 0
2484 };
2485
2486 let (key, rc) =
2487 self.clone()
2488 .get_opened_dht_record_context(&args, "debug_record_watch", "key", 0)?;
2489 let subkeys = get_debug_argument_at(
2490 &args,
2491 opt_arg_add,
2492 "debug_record_watch",
2493 "subkeys",
2494 get_subkeys,
2495 )
2496 .ok();
2497
2498 let still_active = match rc.cancel_dht_watch(key, subkeys).await {
2500 Err(e) => {
2501 return Ok(format!("Can't cancel DHT watch: {}", e));
2502 }
2503 Ok(v) => v,
2504 };
2505
2506 Ok(if still_active {
2507 "Watch partially cancelled".to_owned()
2508 } else {
2509 "Watch cancelled".to_owned()
2510 })
2511 }
2512
2513 async fn debug_record_inspect(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2514 let opt_arg_add = if !args.is_empty() && get_dht_key_no_safety(&args[0]).is_some() {
2515 1
2516 } else {
2517 0
2518 };
2519
2520 let (key, rc) =
2521 self.clone()
2522 .get_opened_dht_record_context(&args, "debug_record_inspect", "key", 0)?;
2523
2524 let mut rest_defaults = false;
2525
2526 let scope = if rest_defaults {
2527 Default::default()
2528 } else {
2529 get_debug_argument_at(
2530 &args,
2531 opt_arg_add,
2532 "debug_record_inspect",
2533 "scope",
2534 get_dht_report_scope,
2535 )
2536 .ok()
2537 .unwrap_or_else(|| {
2538 rest_defaults = true;
2539 Default::default()
2540 })
2541 };
2542
2543 let subkeys = if rest_defaults {
2544 None
2545 } else {
2546 get_debug_argument_at(
2547 &args,
2548 1 + opt_arg_add,
2549 "debug_record_inspect",
2550 "subkeys",
2551 get_subkeys,
2552 )
2553 .ok()
2554 };
2555
2556 let report = match rc.inspect_dht_record(key, subkeys, scope).await {
2558 Err(e) => {
2559 return Ok(format!("Can't inspect DHT record: {}", e));
2560 }
2561 Ok(v) => v,
2562 };
2563
2564 Ok(format!("Success: report={:?}", report))
2565 }
2566
2567 fn debug_record_rehydrate(&self, args: Vec<String>) -> VeilidAPIResult<String> {
2568 let registry = self.core_context()?.registry();
2569 let storage_manager = registry.storage_manager();
2570
2571 let key = get_debug_argument_at(
2572 &args,
2573 0,
2574 "debug_record_rehydrate",
2575 "key",
2576 get_dht_key_no_safety,
2577 )?;
2578
2579 let mut rest_defaults = false;
2580
2581 let subkeys = if rest_defaults {
2582 None
2583 } else {
2584 get_debug_argument_at(&args, 1, "debug_record_rehydrate", "subkeys", get_subkeys)
2585 .inspect_err(|_| {
2586 rest_defaults = true;
2587 })
2588 .ok()
2589 };
2590
2591 let consensus_count = if rest_defaults {
2592 None
2593 } else {
2594 get_debug_argument_at(
2595 &args,
2596 2,
2597 "debug_record_rehydrate",
2598 "consensus_count",
2599 get_number,
2600 )
2601 .inspect_err(|_| {
2602 rest_defaults = true;
2603 })
2604 .ok()
2605 };
2606
2607 storage_manager.add_rehydration_request(
2609 key.opaque(),
2610 subkeys.unwrap_or_default(),
2611 consensus_count.unwrap_or_else(|| {
2612 registry.config().internal().network.dht.set_value_count as usize
2613 }),
2614 );
2615
2616 Ok("Request added".to_owned())
2617 }
2618
2619 async fn debug_record(
2620 &self,
2621 command: Option<DebugRecordSubcommand>,
2622 ) -> VeilidAPIResult<String> {
2623 let Some(command) = command else {
2624 apibail_missing_argument!("debug_record", "command");
2625 };
2626
2627 match command {
2628 DebugRecordSubcommand::List { scope } => self.debug_record_list(vec![scope]),
2629 DebugRecordSubcommand::Purge { scope, bytes, keys } => {
2630 if let Some(keys) = keys {
2631 self.debug_record_purge_keys(scope, keys).await
2632 } else {
2633 self.debug_record_purge_bytes(scope, bytes).await
2634 }
2635 }
2636 DebugRecordSubcommand::Create {
2637 dht_schema,
2638 crypto_kind,
2639 safety,
2640 } => {
2641 let mut args = Vec::new();
2642 if let Some(dht_schema) = dht_schema {
2643 args.push(dht_schema);
2644 }
2645 if let Some(crypto_kind) = crypto_kind {
2646 args.push(crypto_kind);
2647 }
2648 if let Some(safety) = safety {
2649 args.push(safety);
2650 }
2651 self.debug_record_create(args).await
2652 }
2653 DebugRecordSubcommand::Open { key, writer } => {
2654 let mut args = vec![key];
2655 if let Some(writer) = writer {
2656 args.push(writer);
2657 }
2658 self.debug_record_open(args).await
2659 }
2660 DebugRecordSubcommand::Close { key } => {
2661 let mut args = Vec::new();
2662 if let Some(key) = key {
2663 args.push(key);
2664 }
2665 self.debug_record_close(args).await
2666 }
2667 DebugRecordSubcommand::Get { params } => self.debug_record_get(params).await,
2668 DebugRecordSubcommand::Set { params } => self.debug_record_set(params).await,
2669 DebugRecordSubcommand::Delete { key } => self.debug_record_delete(vec![key]).await,
2670 DebugRecordSubcommand::Info { key, subkey } => {
2671 let mut args = vec![key];
2672 if let Some(subkey) = subkey {
2673 args.push(subkey.to_string());
2674 }
2675 self.debug_record_info(args).await
2676 }
2677 DebugRecordSubcommand::Watch {
2678 key,
2679 subkeys,
2680 expiration,
2681 count,
2682 } => {
2683 let mut args = Vec::new();
2684 if let Some(key) = key {
2685 args.push(key);
2686 }
2687 if let Some(subkeys) = subkeys {
2688 args.push(subkeys);
2689 }
2690 if let Some(expiration) = expiration {
2691 args.push(expiration);
2692 }
2693 if let Some(count) = count {
2694 args.push(count.to_string());
2695 }
2696 self.debug_record_watch(args).await
2697 }
2698 DebugRecordSubcommand::Cancel { key, subkeys } => {
2699 let mut args = Vec::new();
2700 if let Some(key) = key {
2701 args.push(key);
2702 }
2703 if let Some(subkeys) = subkeys {
2704 args.push(subkeys);
2705 }
2706 self.debug_record_cancel(args).await
2707 }
2708 DebugRecordSubcommand::Inspect {
2709 key,
2710 scope,
2711 subkeys,
2712 } => {
2713 let mut args = Vec::new();
2714 if let Some(key) = key {
2715 args.push(key);
2716 }
2717 if let Some(scope) = scope {
2718 args.push(scope);
2719 }
2720 if let Some(subkeys) = subkeys {
2721 args.push(subkeys);
2722 }
2723 self.debug_record_inspect(args).await
2724 }
2725 DebugRecordSubcommand::Rehydrate {
2726 key,
2727 subkeys,
2728 consensus_count,
2729 } => {
2730 let mut args = vec![key];
2731 if let Some(subkeys) = subkeys {
2732 args.push(subkeys);
2733 }
2734 if let Some(consensus_count) = consensus_count {
2735 args.push(consensus_count.to_string());
2736 }
2737 self.debug_record_rehydrate(args)
2738 }
2739 }
2740 }
2741
2742 fn debug_table_list(&self) -> VeilidAPIResult<String> {
2743 let table_store = self.table_store()?;
2745 let table_names = table_store.list_all();
2746 let out = format!(
2747 "TableStore tables:\n{}",
2748 table_names
2749 .iter()
2750 .map(|(k, v)| format!("{} ({})", k, v))
2751 .collect::<Vec<String>>()
2752 .join("\n")
2753 );
2754 Ok(out)
2755 }
2756
2757 fn _format_columns(columns: &[table_store::ColumnInfo]) -> String {
2758 let mut out = String::new();
2759 for (n, col) in columns.iter().enumerate() {
2760 out += &format!("Column {}:\n", n);
2762 out += &format!(" Key Count: {}\n", col.key_count);
2763 }
2764 out
2765 }
2766
2767 async fn debug_table_info(&self, table_name: String) -> VeilidAPIResult<String> {
2768 let table_store = self.table_store()?;
2770
2771 let Some(info) = table_store.info(&table_name).await? else {
2772 return Ok(format!("Table '{}' does not exist", table_name));
2773 };
2774
2775 fn debug_print_io_stats(stats: &table_store::IOStatsInfo) -> String {
2776 let mut info_str = format!(
2777 "Started: {}\n\
2778 Span: {}\n\
2779 Transactions: {:6}\n\
2780 Reads: {:6}\n\
2781 Cache reads: {:6}\n\
2782 Writes: {:6}\n\
2783 Bytes read: {:>6}\n\
2784 Cache bytes read: {:>6}\n\
2785 Bytes written: {:>6}\n\
2786 Deletes: {:6}\n\
2787 Prefix deletes: {:6}\n",
2788 stats.started,
2789 stats.span,
2790 stats.transactions,
2791 stats.reads,
2792 stats.cache_reads,
2793 stats.writes,
2794 bytesize::ByteSize(stats.bytes_read.as_u64())
2795 .display()
2796 .iec_short()
2797 .to_string(),
2798 bytesize::ByteSize(stats.cache_read_bytes.as_u64())
2799 .display()
2800 .iec_short()
2801 .to_string(),
2802 bytesize::ByteSize(stats.bytes_written.as_u64())
2803 .display()
2804 .iec_short()
2805 .to_string(),
2806 stats.deletes,
2807 stats.prefix_deletes,
2808 );
2809
2810 if !stats.write_size_buckets.is_empty() {
2811 info_str += "Write size buckets:\n";
2812 for (size, count) in stats.write_size_buckets.iter() {
2813 info_str += &format!(" {size:6}: {count:6}\n");
2814 }
2815 }
2816
2817 if !stats.tx_write_size_buckets.is_empty() {
2818 info_str += "Transaction write size buckets:\n";
2819 for (size, (count, avg_duration)) in stats.tx_write_size_buckets.iter() {
2820 info_str += &format!(" {size:6}: {count:6} {avg_duration}\n");
2821 }
2822 }
2823
2824 info_str
2825 }
2826
2827 let info_str = format!(
2828 "Table Name: {}\n\
2829 Column Count: {}\n\
2830 IO Stats (since previous query):\n{}\
2831 IO Stats (overall):\n{}\
2832 Columns:\n{}\n",
2833 info.table_name,
2834 info.column_count,
2835 indent_all_by(4, debug_print_io_stats(&info.io_stats_since_previous)),
2836 indent_all_by(4, debug_print_io_stats(&info.io_stats_overall)),
2837 Self::_format_columns(&info.columns),
2838 );
2839
2840 let out = format!("Table info for '{}':\n{}", table_name, info_str);
2841 Ok(out)
2842 }
2843
2844 async fn debug_table(&self, command: Option<DebugTableSubcommand>) -> VeilidAPIResult<String> {
2845 let Some(command) = command else {
2846 apibail_missing_argument!("debug_table", "command");
2847 };
2848
2849 match command {
2850 DebugTableSubcommand::List => self.debug_table_list(),
2851 DebugTableSubcommand::Info { name } => self.debug_table_info(name).await,
2852 }
2853 }
2854
2855 fn debug_punish_list(&self, _args: Vec<String>) -> VeilidAPIResult<String> {
2856 let registry = self.core_context()?.registry();
2858 let network_manager = registry.network_manager();
2859 let address_filter = network_manager.address_filter();
2860
2861 let out = format!("Address filter punishments:\n{:#?}", address_filter);
2862 Ok(out)
2863 }
2864
2865 fn debug_punish_clear(&self, _args: Vec<String>) -> VeilidAPIResult<String> {
2866 let registry = self.core_context()?.registry();
2868 let network_manager = registry.network_manager();
2869 let address_filter = network_manager.address_filter();
2870
2871 address_filter.clear_punishments();
2872
2873 Ok("Address Filter punishments cleared\n".to_owned())
2874 }
2875
2876 fn debug_punish_add(&self, target: String) -> VeilidAPIResult<String> {
2877 let registry = self.core_context()?.registry();
2879 let network_manager = registry.network_manager();
2880 let address_filter = network_manager.address_filter();
2881
2882 if let Some(node_id) = get_node_id(&target) {
2883 address_filter.punish_node_id(node_id, PunishmentReason::Manual);
2884 Ok("Address Filter node id punishment added\n".to_owned())
2885 } else if let Some(ip_addr) = get_ip_addr(&target) {
2886 address_filter.punish_ip_addr(ip_addr, PunishmentReason::Manual);
2887 Ok("Address Filter address punishment added\n".to_owned())
2888 } else {
2889 apibail_invalid_argument!("debug_punish_add", "target", target);
2890 }
2891 }
2892
2893 fn debug_punish_remove(&self, target: String) -> VeilidAPIResult<String> {
2894 let registry = self.core_context()?.registry();
2896 let network_manager = registry.network_manager();
2897 let address_filter = network_manager.address_filter();
2898
2899 if let Some(node_id) = get_node_id(&target) {
2900 address_filter.forgive_node_id(node_id);
2901 Ok("Address Filter node id punishment forgiven\n".to_owned())
2902 } else if let Some(ip_addr) = get_ip_addr(&target) {
2903 address_filter.forgive_ip_addr(ip_addr);
2904 Ok("Address Filter address punishment forgiven\n".to_owned())
2905 } else {
2906 apibail_invalid_argument!("debug_punish_remove", "target", target);
2907 }
2908 }
2909
2910 fn debug_punish(&self, command: Option<DebugPunishSubcommand>) -> VeilidAPIResult<String> {
2911 let Some(command) = command else {
2912 apibail_missing_argument!("debug_punish", "command");
2913 };
2914
2915 match command {
2916 DebugPunishSubcommand::List => self.debug_punish_list(vec![]),
2917 DebugPunishSubcommand::Clear => self.debug_punish_clear(vec![]),
2918 DebugPunishSubcommand::Add { target } => self.debug_punish_add(target),
2919 DebugPunishSubcommand::Remove { target } => self.debug_punish_remove(target),
2920 }
2921 }
2922
2923 pub fn debug_help(&self) -> VeilidAPIResult<String> {
2925 let out = render_parser_help::<DebugCommandParser>();
2926 let out = out
2927 .lines()
2928 .map(|line| line.trim().to_string())
2929 .collect::<Vec<String>>()
2930 .join("\n");
2931 Ok(out)
2932 }
2933
2934 pub async fn debug_uptime(&self) -> VeilidAPIResult<String> {
2936 let mut result = String::new();
2937
2938 writeln!(result, "Uptime...").ok();
2939
2940 let state = self.get_state().await?;
2941
2942 let uptime = state.attachment.uptime;
2943 writeln!(result, " since launch: {uptime:#}").ok();
2944
2945 if let Some(attached_uptime) = state.attachment.attached_uptime {
2946 writeln!(result, " since attachment: {attached_uptime:#}").ok();
2947 }
2948
2949 Ok(result)
2950 }
2951
2952 #[cfg(debug_assertions)]
2954 #[expect(clippy::unused_async)]
2955 pub async fn debug_die(
2956 &self,
2957 mode: Option<String>,
2958 message: Vec<String>,
2959 ) -> VeilidAPIResult<String> {
2960 let arg = mode.as_deref().unwrap_or_default();
2961 let rest = join_args(message);
2962 match arg {
2963 "panic" => {
2964 if rest.is_empty() {
2965 panic!();
2966 } else {
2967 panic!("{}", rest);
2968 }
2969 }
2970 "unwrap" => {
2971 #[expect(clippy::unnecessary_literal_unwrap)]
2972 Option::<()>::None.unwrap();
2973 unreachable!("unwrap must panic");
2974 }
2975 "unwrap_or_log" => {
2976 Option::<()>::None.unwrap_or_log();
2977 unreachable!("unwrap_or_log must panic");
2978 }
2979 "expect" => {
2980 #[expect(clippy::unnecessary_literal_unwrap)]
2981 Option::<()>::None.expect(&rest);
2982 unreachable!("expect must panic");
2983 }
2984 "expect_or_log" => {
2985 Option::<()>::None.expect_or_log(&rest);
2986 unreachable!("expect_or_log must panic");
2987 }
2988 "div0" => {
2989 let x = 0u32;
2990 let y = x / 0u32;
2991 unreachable!("divide by zero must panic: {}", y);
2992 }
2993 "overflow" => {
2994 let x = u32::MAX;
2995 let y = x + 1;
2996 unreachable!("integer overflow must panic: {}", y);
2997 }
2998 "oob" => {
2999 let x = &[3];
3000 #[expect(clippy::out_of_bounds_indexing)]
3001 let y = x[1];
3002 unreachable!("array out of bounds must panic: {}", y);
3003 }
3004 "nullptr" => {
3005 let x: *const i32 = std::ptr::null();
3006 let y = unsafe { *x };
3007 unreachable!("array out of bounds must panic: {}", y);
3008 }
3009 "unreachable" => {
3010 unreachable!("direct call of unreachable macro");
3011 }
3012 _ => {
3013 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())
3014 }
3015 }
3016 }
3017
3018 pub async fn debug(&self, args: String) -> VeilidAPIResult<String> {
3025 let res = {
3026 let argv_parts = shell_words::split(&args)
3027 .map_err(|e| VeilidAPIError::parse_error(e.to_string(), args))?;
3028
3029 if argv_parts.is_empty() || argv_parts.len() == 1 && argv_parts[0] == "help" {
3030 return self.debug_help();
3031 }
3032
3033 let argv: Vec<String> = std::iter::once("debug".to_owned())
3034 .chain(argv_parts.iter().cloned())
3035 .collect();
3036 let nested_family =
3037 get_nested_debug_family_name(argv_parts.first().map(String::as_str));
3038 let known_command =
3039 get_known_debug_command_name(argv_parts.first().map(String::as_str));
3040
3041 let parsed = match DebugCommandParser::try_parse_from(argv) {
3042 Ok(parsed) => parsed,
3043 Err(err) if err.kind() == ErrorKind::DisplayHelp => return Ok(err.to_string()),
3044 Err(err) if err.kind() == ErrorKind::InvalidSubcommand => {
3045 if let Some(family) = nested_family {
3046 return Err(VeilidAPIError::generic(format!(
3047 "Unknown {} subcommand",
3048 family
3049 )));
3050 }
3051 return Err(VeilidAPIError::generic("Unknown debug command"));
3052 }
3053 Err(_) => {
3054 if let Some(family) = known_command {
3055 let arg = argv_parts.get(1).cloned().unwrap_or_default();
3056 apibail_invalid_argument!(format!("debug_{}", family), "arg_1", arg);
3057 } else {
3058 return Err(VeilidAPIError::generic("Unknown debug command"));
3059 }
3060 }
3061 };
3062
3063 let command = parsed.command;
3064
3065 match command {
3066 DebugCommand::Nodeid => self.debug_nodeid(),
3067 DebugCommand::Buckets { min_state } => self.debug_buckets(min_state),
3068 DebugCommand::Dialinfo => self.debug_dialinfo(),
3069 DebugCommand::Peerinfo { args } => self.debug_peerinfo(args),
3070 DebugCommand::Contact { node_ref } => self.debug_contact(node_ref),
3071 DebugCommand::Keypair { cryptokind } => self.debug_keypair(cryptokind),
3072 DebugCommand::Entries { args } => self.debug_entries(args),
3073 DebugCommand::Entry { node } => self.debug_entry(node),
3074 DebugCommand::Punish { command } => self.debug_punish(command),
3075 DebugCommand::Txtrecord { keypairs } => self.debug_txtrecord(keypairs).await,
3076 DebugCommand::Relay { args } => self.debug_relay(args).await,
3077 DebugCommand::Ping { destination } => self.debug_ping(destination).await,
3078 DebugCommand::Appmessage { destination, data } => {
3079 self.debug_app_message(destination, data).await
3080 }
3081 DebugCommand::Appcall { destination, data } => {
3082 self.debug_app_call(destination, data).await
3083 }
3084 DebugCommand::Appreply { id, data } => self.debug_app_reply(id, data).await,
3085 DebugCommand::Resolve { destination } => self.debug_resolve(destination).await,
3086 DebugCommand::Nodeinfo => self.debug_nodeinfo().await,
3087 DebugCommand::Purge { command } => self.debug_purge(command).await,
3088 DebugCommand::Attach => self.debug_attach().await,
3089 DebugCommand::Detach => self.debug_detach().await,
3090 DebugCommand::Config { arg_0, arg_1 } => self.debug_config(arg_0, arg_1).await,
3091 DebugCommand::Network { command } => self.debug_network(command).await,
3092 DebugCommand::Route { command } => self.debug_route(command).await,
3093 DebugCommand::Record { command } => self.debug_record(command).await,
3094 DebugCommand::Table { command } => self.debug_table(command).await,
3095 DebugCommand::Uptime => self.debug_uptime().await,
3096 #[cfg(debug_assertions)]
3097 DebugCommand::Die { mode, message } => self.debug_die(mode, message).await,
3098 }
3099 };
3100 res
3101 }
3102
3103 fn get_destination(
3104 self,
3105 registry: VeilidComponentRegistry,
3106 ) -> impl FnOnce(&str) -> PinBoxFutureStatic<Option<Destination>> {
3107 move |text| {
3108 let text = text.to_owned();
3109 Box::pin(async move {
3110 let (text, ss) = if let Some((first, second)) = text.split_once('+') {
3112 let ss = get_safety_selection(registry.clone())(second)?;
3113 (first, Some(ss))
3114 } else {
3115 (text.as_str(), None)
3116 };
3117 if text.is_empty() {
3118 return None;
3119 }
3120 if &text[0..1] == "#" {
3121 let routing_table = registry.routing_table();
3122 let rss = routing_table.route_spec_store();
3123
3124 let text = &text[1..];
3126
3127 let private_route = if let Some(prid) =
3128 get_route_id(registry.clone(), false, true)(text)
3129 {
3130 rss.best_remote_private_route(&RemoteRouteSetId::from_route_id(prid))?
3131 } else {
3132 let n = get_number(text)?;
3133
3134 self.with_debug_cache(|dc| {
3135 let prid: &RouteId = dc.imported_routes.get(n)?;
3136 let prid = RemoteRouteSetId::from_route_id(prid.clone());
3137 let Some(private_route) = rss.best_remote_private_route(&prid) else {
3138 let _ = dc.imported_routes.remove(n);
3140 veilid_log!(registry info "removed dead imported route {}", n);
3141 return None;
3142 };
3143 Some(private_route)
3144 })?
3145 };
3146
3147 Some(Destination::private_route(
3148 private_route,
3149 ss.unwrap_or(SafetySelection::Unsafe(Sequencing::default())),
3150 ))
3151 } else if let Some((first, second)) = text.split_once('@') {
3152 if ss.is_some() {
3153 return None;
3154 }
3155 let relay_di = DialInfo::from_str(second).ok()?;
3157 let target_nr = get_node_ref(registry.clone())(first)?;
3158
3159 Some(Destination::dial_info(relay_di, target_nr))
3160 } else {
3161 let target_nr = resolve_filtered_node_ref(
3163 registry.clone(),
3164 ss.clone()
3165 .unwrap_or(SafetySelection::Unsafe(Sequencing::PreferUnordered)),
3166 )(text)
3167 .await?;
3168
3169 Some(Destination::direct(target_nr, ss))
3170 }
3171 })
3172 }
3173 }
3174
3175 fn get_opened_dht_record_context(
3176 self,
3177 args: &[String],
3178 context: &str,
3179 key: &str,
3180 arg: usize,
3181 ) -> VeilidAPIResult<(RecordKey, RoutingContext)> {
3182 let key = match get_debug_argument_at(args, arg, context, key, get_dht_key_no_safety)
3183 .ok()
3184 .or_else(|| {
3185 self.with_debug_cache(|dc| dc.opened_record_contexts.back().map(|kv| kv.0).cloned())
3187 }) {
3188 Some(k) => k,
3189 None => {
3190 apibail_missing_argument!("no keys are opened", "key");
3191 }
3192 };
3193
3194 let Some(rc) = self.with_debug_cache(|dc| dc.opened_record_contexts.get(&key).cloned())
3197 else {
3198 apibail_missing_argument!("key is not opened", "key");
3199 };
3200
3201 Ok((key, rc))
3202 }
3203}
3204
3205#[cfg(test)]
3206mod clap_parse_tests {
3207 use super::*;
3208
3209 #[test]
3210 fn parses_top_level_commands() {
3211 let parsed = DebugCommandParser::try_parse_from(["debug", "nodeinfo"]).ok();
3212 assert!(matches!(
3213 parsed,
3214 Some(DebugCommandParser {
3215 command: DebugCommand::Nodeinfo
3216 })
3217 ));
3218
3219 let parsed = DebugCommandParser::try_parse_from(["debug", "nodeid"]).ok();
3220 assert!(matches!(
3221 parsed,
3222 Some(DebugCommandParser {
3223 command: DebugCommand::Nodeid
3224 })
3225 ));
3226
3227 assert!(DebugCommandParser::try_parse_from(["debug", "NoDeId"]).is_err());
3228 }
3229
3230 #[test]
3231 fn rejects_unknown_top_level_command() {
3232 assert!(DebugCommandParser::try_parse_from(["debug", "definitely-unknown"]).is_err());
3233 }
3234
3235 #[test]
3236 fn single_pass_parser_preserves_quoted_payload_tokens() {
3237 let parsed =
3238 DebugCommandParser::try_parse_from(["debug", "appmessage", "destination", "foo bar"])
3239 .ok();
3240 assert!(matches!(
3241 parsed,
3242 Some(DebugCommandParser {
3243 command: DebugCommand::Appmessage { .. }
3244 })
3245 ));
3246 }
3247
3248 #[test]
3249 fn parses_route_subcommands() {
3250 let parsed = DebugCommandParser::try_parse_from(["debug", "route", "allocate", "rel"]).ok();
3251 assert!(matches!(
3252 parsed,
3253 Some(DebugCommandParser {
3254 command: DebugCommand::Route {
3255 command: Some(DebugRouteSubcommand::Allocate { .. })
3256 }
3257 })
3258 ));
3259
3260 let parsed =
3261 DebugCommandParser::try_parse_from(["debug", "route", "test", "route-id"]).ok();
3262 assert!(matches!(
3263 parsed,
3264 Some(DebugCommandParser {
3265 command: DebugCommand::Route {
3266 command: Some(DebugRouteSubcommand::Test { .. })
3267 }
3268 })
3269 ));
3270 }
3271
3272 #[test]
3273 fn rejects_unknown_route_subcommand() {
3274 assert!(DebugCommandParser::try_parse_from(["debug", "route", "unknown"]).is_err());
3275 }
3276
3277 #[test]
3278 fn parses_record_subcommands() {
3279 let parsed = DebugCommandParser::try_parse_from(["debug", "record", "create", "1"]).ok();
3280 assert!(matches!(
3281 parsed,
3282 Some(DebugCommandParser {
3283 command: DebugCommand::Record {
3284 command: Some(DebugRecordSubcommand::Create { .. })
3285 }
3286 })
3287 ));
3288
3289 let parsed =
3290 DebugCommandParser::try_parse_from(["debug", "record", "rehydrate", "some-key"]).ok();
3291 assert!(matches!(
3292 parsed,
3293 Some(DebugCommandParser {
3294 command: DebugCommand::Record {
3295 command: Some(DebugRecordSubcommand::Rehydrate { .. })
3296 }
3297 })
3298 ));
3299 }
3300
3301 #[test]
3302 fn parses_table_punish_and_network_subcommands() {
3303 let parsed = DebugCommandParser::try_parse_from(["debug", "table", "info", "table"]).ok();
3304 assert!(matches!(
3305 parsed,
3306 Some(DebugCommandParser {
3307 command: DebugCommand::Table {
3308 command: Some(DebugTableSubcommand::Info { .. })
3309 }
3310 })
3311 ));
3312
3313 let parsed = DebugCommandParser::try_parse_from(["debug", "punish", "remove", "node"]).ok();
3314 assert!(matches!(
3315 parsed,
3316 Some(DebugCommandParser {
3317 command: DebugCommand::Punish {
3318 command: Some(DebugPunishSubcommand::Remove { .. })
3319 }
3320 })
3321 ));
3322
3323 let parsed = DebugCommandParser::try_parse_from(["debug", "network", "stats"]).ok();
3324 assert!(matches!(
3325 parsed,
3326 Some(DebugCommandParser {
3327 command: DebugCommand::Network {
3328 command: Some(DebugNetworkSubcommand::Stats)
3329 }
3330 })
3331 ));
3332
3333 let parsed = DebugCommandParser::try_parse_from(["debug", "purge", "routes"]).ok();
3334 assert!(matches!(
3335 parsed,
3336 Some(DebugCommandParser {
3337 command: DebugCommand::Purge {
3338 command: Some(DebugPurgeSubcommand::Routes)
3339 }
3340 })
3341 ));
3342 }
3343
3344 #[test]
3345 fn parses_missing_nested_subcommands() {
3346 let parsed = DebugCommandParser::try_parse_from(["debug", "route"]).ok();
3347 assert!(matches!(
3348 parsed,
3349 Some(DebugCommandParser {
3350 command: DebugCommand::Route { command: None }
3351 })
3352 ));
3353
3354 let parsed = DebugCommandParser::try_parse_from(["debug", "record"]).ok();
3355 assert!(matches!(
3356 parsed,
3357 Some(DebugCommandParser {
3358 command: DebugCommand::Record { command: None }
3359 })
3360 ));
3361
3362 let parsed = DebugCommandParser::try_parse_from(["debug", "purge"]).ok();
3363 assert!(matches!(
3364 parsed,
3365 Some(DebugCommandParser {
3366 command: DebugCommand::Purge { command: None }
3367 })
3368 ));
3369 }
3370
3371 #[test]
3372 fn parses_buckets_config_and_die_forms() {
3373 let parsed = DebugCommandParser::try_parse_from(["debug", "buckets", "dead"]).ok();
3374 assert!(matches!(
3375 parsed,
3376 Some(DebugCommandParser {
3377 command: DebugCommand::Buckets { min_state: Some(_) }
3378 })
3379 ));
3380
3381 let parsed = DebugCommandParser::try_parse_from(["debug", "config", "insecure"]).ok();
3382 assert!(matches!(
3383 parsed,
3384 Some(DebugCommandParser {
3385 command: DebugCommand::Config {
3386 arg_0: Some(_),
3387 arg_1: None
3388 }
3389 })
3390 ));
3391
3392 #[cfg(debug_assertions)]
3393 {
3394 let parsed = DebugCommandParser::try_parse_from(["debug", "die", "panic", "msg"]).ok();
3395 assert!(matches!(
3396 parsed,
3397 Some(DebugCommandParser {
3398 command: DebugCommand::Die {
3399 mode: Some(_),
3400 message
3401 }
3402 }) if message.len() == 1
3403 ));
3404 }
3405 }
3406
3407 #[test]
3408 fn parses_record_data_forms() {
3409 assert_eq!(get_data("#666f6f"), Some(b"foo".to_vec()));
3410 assert_eq!(get_data("\"foo\\nbar\""), Some(b"foo\nbar".to_vec()));
3411 assert_eq!(get_data("plain"), Some(b"plain".to_vec()));
3412 assert!(parse_data("#zz").is_err());
3413 }
3414
3415 #[test]
3416 fn parses_context_free_converters() {
3417 assert!(matches!(
3418 parse_bucket_entry_state("dead"),
3419 Ok(BucketEntryState::Dead)
3420 ));
3421 assert!(matches!(
3422 parse_dht_report_scope("syncset"),
3423 Ok(DHTReportScope::SyncSet)
3424 ));
3425 assert!(PublishedState::from_str("published")
3426 .map(|x| x.as_bool())
3427 .unwrap_or(false));
3428 assert!(parse_subkeys("1..=3,5..=8").is_ok());
3429 }
3430
3431 #[test]
3432 fn generated_help_includes_command_groups() {
3433 let help = render_parser_help::<DebugCommandParser>();
3434 assert!(help.contains("nodeid"));
3435 assert!(help.contains("record"));
3436 assert!(help.contains("help"));
3437 }
3438
3439 #[test]
3440 fn top_level_help_subcommand_is_available() {
3441 let err = DebugCommandParser::try_parse_from(["debug", "help", "route"]).unwrap_err();
3442 assert_eq!(err.kind(), ErrorKind::DisplayHelp);
3443 }
3444}