Skip to main content

veilid_core/veilid_api/
debug.rs

1////////////////////////////////////////////////////////////////
2// Debugging
3
4use 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            // Unsafe
267            let text = &text[1..];
268            let seq = get_sequencing(text).unwrap_or_default();
269            Some(SafetySelection::Unsafe(seq))
270        } else {
271            // Safe
272            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    // Enforce 32 byte node ids
332    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    // Enforce 32 byte node ids
341    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        // Safety selection
374        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    // check if message body is ascii printable
562    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        // Dump routing table bucket info
611        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        // Dump routing table dialinfo
617        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        // Dump routing table peerinfo
622        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        // Dump routing table txt record
654        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        // Generate a keypair
686        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        // Dump routing table entries
713        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        // Dump routing table entry
733        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        // Change routing domain relays
778        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        // Dump routing table entry
799        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        // Dump core state
806        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        // Dump connection table
825        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        // Dump routing table entry
840        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        // One argument is 'config get'
863        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        // let rest = rest.trim_start().to_owned();
877
878        if arg == "restart" {
879            // Must be attached
880            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                // Must be detached
908                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                // Purge connection table
918                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                // Eliminate last_connections from routing table entries
925                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                // Purge route spec store
935                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        // Send a StatusQ
1062        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        // Send an AppMessage
1097        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        // Send an AppCall
1134        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        // Send a AppCall Reply
1181        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        // [ord|*ord] [rel] [<count>] [in|out] [avoid_node_id]
1190
1191        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        // Allocate route
1233        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        // <route id>
1245        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        // Release route
1258        let out = match rss.release_route(route_id.clone()) {
1259            true => {
1260                // release imported
1261                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        // <route id> [full]
1278        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        // Publish route
1304        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                // Convert to blob
1310                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        // <route id>
1330        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        // Unpublish route
1343        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        // <route id>
1352        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        //
1378        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        // <blob>
1401        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        // <route id>
1426        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        // <local|remote>
1480        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        // <local|remote> [bytes]
1522        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        // Get routing context with optional safety
1571        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        // Do a record create
1582        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        // Save routing context for record
1588        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        // Get routing context with optional safety
1614        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        // Do a record open
1625        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        // Save routing context for record
1631        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        // Do a record close
1644        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        // Do a record set
1708        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        // Do a record get
1773        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        // Do a record delete (can use any routing context here)
1800        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        // Do a record watch
1911        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        // Do a record watch cancel
1946        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        // Do a record inspect
2004        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        // Do a record rehydrate
2055        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        //
2104        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            //
2121            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        //
2129        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        //
2171        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        //
2181        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        //
2192        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        //
2213        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    /// Get the help text for 'internal debug' commands.
2252    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    /// Get node uptime info.
2357    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    /// Cause veilid-core to panic via various means
2375    #[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    /// Execute an 'internal debug command'.
2437    pub async fn debug(&self, args: String) -> VeilidAPIResult<String> {
2438        let res = {
2439            let args = args.trim_start();
2440            if args.is_empty() {
2441                // No arguments runs help command
2442                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                // Safety selection
2524                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                    // Private route
2538                    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                                // Remove imported route
2551                                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                    // Relay
2568                    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                    // Direct
2574                    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                // If unspecified, use the most recent key opened or created
2597                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        // Get routing context for record
2606
2607        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 += &sc;
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}