1use rns_core::constants;
11use rns_core::destination::destination_hash;
12use rns_core::hash::truncated_hash;
13use rns_core::msgpack::{self, Value};
14use rns_core::transport::types::{InterfaceId, InterfaceInfo};
15use rns_core::transport::TransportEngine;
16
17use super::interface_stats::InterfaceStats;
18use super::time;
19
20pub trait InterfaceStatusView {
22 fn id(&self) -> InterfaceId;
23 fn info(&self) -> &InterfaceInfo;
24 fn online(&self) -> bool;
25 fn stats(&self) -> &InterfaceStats;
26 fn tx_drops(&self) -> u64 {
27 0
28 }
29 fn tx_dropped_bytes(&self) -> u64 {
30 0
31 }
32 fn tx_stalled(&self) -> bool {
33 false
34 }
35 fn tx_buffered(&self) -> usize {
36 0
37 }
38}
39
40pub fn status_path_hash() -> [u8; 16] {
42 truncated_hash(b"/status")
43}
44
45pub fn path_path_hash() -> [u8; 16] {
47 truncated_hash(b"/path")
48}
49
50pub fn list_path_hash() -> [u8; 16] {
52 truncated_hash(b"/list")
53}
54
55pub fn is_management_path(path_hash: &[u8; 16]) -> bool {
57 *path_hash == status_path_hash()
58 || *path_hash == path_path_hash()
59 || *path_hash == list_path_hash()
60}
61
62pub fn management_dest_hash(transport_identity_hash: &[u8; 16]) -> [u8; 16] {
66 destination_hash(
67 "rnstransport",
68 &["remote", "management"],
69 Some(transport_identity_hash),
70 )
71}
72
73pub fn blackhole_dest_hash(transport_identity_hash: &[u8; 16]) -> [u8; 16] {
77 destination_hash(
78 "rnstransport",
79 &["info", "blackhole"],
80 Some(transport_identity_hash),
81 )
82}
83
84pub fn probe_dest_hash(transport_identity_hash: &[u8; 16]) -> [u8; 16] {
88 destination_hash("rnstransport", &["probe"], Some(transport_identity_hash))
89}
90
91pub fn announce_random_blob(rng: &mut dyn rns_crypto::Rng, now: f64) -> [u8; 10] {
97 let mut random_hash = [0u8; 10];
98 rng.fill_bytes(&mut random_hash[..5]);
99 let now_secs = if now.is_finite() && now > 0.0 {
100 now as u64
101 } else {
102 0
103 };
104 random_hash[5..10].copy_from_slice(&now_secs.to_be_bytes()[3..8]);
105 random_hash
106}
107
108fn announce_context(path_response: bool) -> u8 {
111 if path_response {
112 constants::CONTEXT_PATH_RESPONSE
113 } else {
114 constants::CONTEXT_NONE
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum BuiltinDestination {
122 RemoteManagement,
124 Blackhole,
126 Probe,
128}
129
130impl BuiltinDestination {
131 pub const ALL: [BuiltinDestination; 3] = [
132 BuiltinDestination::RemoteManagement,
133 BuiltinDestination::Blackhole,
134 BuiltinDestination::Probe,
135 ];
136
137 pub fn aspects(self) -> &'static [&'static str] {
138 match self {
139 BuiltinDestination::RemoteManagement => &["remote", "management"],
140 BuiltinDestination::Blackhole => &["info", "blackhole"],
141 BuiltinDestination::Probe => &["probe"],
142 }
143 }
144
145 pub fn dest_hash(self, transport_identity_hash: &[u8; 16]) -> [u8; 16] {
146 destination_hash(
147 "rnstransport",
148 self.aspects(),
149 Some(transport_identity_hash),
150 )
151 }
152
153 pub fn name_hash(self) -> [u8; 10] {
154 rns_core::destination::name_hash("rnstransport", self.aspects())
155 }
156
157 pub fn destination(self, transport_identity_hash: [u8; 16]) -> crate::destination::Destination {
159 crate::destination::Destination::single_in(
160 "rnstransport",
161 self.aspects(),
162 rns_core::types::IdentityHash(transport_identity_hash),
163 )
164 }
165
166 pub fn label(self) -> &'static str {
167 match self {
168 BuiltinDestination::RemoteManagement => "management destination",
169 BuiltinDestination::Blackhole => "blackhole info",
170 BuiltinDestination::Probe => "probe responder",
171 }
172 }
173}
174
175pub fn build_builtin_announce(
180 destination: BuiltinDestination,
181 identity: &rns_crypto::identity::Identity,
182 rng: &mut dyn rns_crypto::Rng,
183 now: f64,
184 path_response: bool,
185 app_data: Option<&[u8]>,
186) -> Option<Vec<u8>> {
187 let dest_hash = destination.dest_hash(identity.hash());
188 let name_hash = destination.name_hash();
189 let random_hash = announce_random_blob(rng, now);
190
191 let (announce_data, _has_ratchet) = rns_core::announce::AnnounceData::pack(
192 identity,
193 &dest_hash,
194 &name_hash,
195 &random_hash,
196 None, app_data,
198 )
199 .ok()?;
200
201 let flags = rns_core::packet::PacketFlags {
202 header_type: constants::HEADER_1,
203 context_flag: constants::FLAG_UNSET,
204 transport_type: constants::TRANSPORT_BROADCAST,
205 destination_type: constants::DESTINATION_SINGLE,
206 packet_type: constants::PACKET_TYPE_ANNOUNCE,
207 };
208
209 let packet = rns_core::packet::RawPacket::pack(
210 flags,
211 0,
212 &dest_hash,
213 None,
214 announce_context(path_response),
215 &announce_data,
216 )
217 .ok()?;
218
219 Some(packet.raw)
220}
221
222pub fn build_probe_announce(
224 identity: &rns_crypto::identity::Identity,
225 rng: &mut dyn rns_crypto::Rng,
226 now: f64,
227 path_response: bool,
228) -> Option<Vec<u8>> {
229 build_builtin_announce(
230 BuiltinDestination::Probe,
231 identity,
232 rng,
233 now,
234 path_response,
235 None,
236 )
237}
238
239#[derive(Debug, Clone, Default)]
241pub struct ManagementConfig {
242 pub enable_remote_management: bool,
244 pub remote_management_allowed: Vec<[u8; 16]>,
246 pub publish_blackhole: bool,
248}
249
250pub fn handle_status_request(
256 data: &[u8],
257 engine: &TransportEngine,
258 interfaces: &[&dyn InterfaceStatusView],
259 started: f64,
260 probe_responder_hash: Option<[u8; 16]>,
261) -> Option<Vec<u8>> {
262 let (include_lstats, include_profiling) = match msgpack::unpack_exact(data) {
264 Ok(Value::Array(arr)) if !arr.is_empty() => (
265 arr[0].as_bool().unwrap_or(false),
266 arr.get(1).and_then(Value::as_bool).unwrap_or(false),
267 ),
268 _ => (false, false),
269 };
270
271 let mut iface_list = Vec::new();
273 let mut total_rxb: u64 = 0;
274 let mut total_txb: u64 = 0;
275 let mut total_rxpps = 0.0;
276 let mut total_txpps = 0.0;
277 let mut total_rx_packets: u64 = 0;
278 let mut total_tx_packets: u64 = 0;
279 let mut total_announce_rx: u64 = 0;
280 let mut total_announce_tx: u64 = 0;
281 let mut total_path_request_rx: u64 = 0;
282 let mut total_path_request_tx: u64 = 0;
283 let mut total_arxb: u64 = 0;
284 let mut total_atxb: u64 = 0;
285 let mut total_arxc: u64 = 0;
286 let mut total_atxc: u64 = 0;
287 let mut total_prxb: u64 = 0;
288 let mut total_ptxb: u64 = 0;
289 let mut total_prxc: u64 = 0;
290 let mut total_ptxc: u64 = 0;
291 let mut total_rxs = 0.0;
292 let mut total_txs = 0.0;
293 let mut total_arxs = 0.0;
294 let mut total_atxs = 0.0;
295 let mut total_prxs = 0.0;
296 let mut total_ptxs = 0.0;
297 let mut total_arxf = 0.0;
298 let mut total_atxf = 0.0;
299 let mut total_prxf = 0.0;
300 let mut total_ptxf = 0.0;
301
302 for entry in interfaces {
303 let id = entry.id();
304 let info = entry.info();
305 let stats = entry.stats();
306
307 if !info.is_local_client {
308 total_rxb += stats.rxb;
309 total_txb += stats.txb;
310 total_rxpps += stats.traffic_rates.rxpps;
311 total_txpps += stats.traffic_rates.txpps;
312 total_rx_packets += stats.rx_packets;
313 total_tx_packets += stats.tx_packets;
314 total_announce_rx += stats.arxc;
315 total_announce_tx += stats.atxc;
316 total_path_request_rx += stats.prxc;
317 total_path_request_tx += stats.ptxc;
318 total_arxb += stats.arxb;
319 total_atxb += stats.atxb;
320 total_arxc += stats.arxc;
321 total_atxc += stats.atxc;
322 total_prxb += stats.prxb;
323 total_ptxb += stats.ptxb;
324 total_prxc += stats.prxc;
325 total_ptxc += stats.ptxc;
326 total_rxs += stats.traffic_rates.rxs;
327 total_txs += stats.traffic_rates.txs;
328 total_arxs += stats.traffic_rates.arxs;
329 total_atxs += stats.traffic_rates.atxs;
330 total_prxs += stats.traffic_rates.prxs;
331 total_ptxs += stats.traffic_rates.ptxs;
332 total_arxf += stats.incoming_announce_freq();
333 total_atxf += stats.outgoing_announce_freq();
334 total_prxf += stats.incoming_path_request_freq();
335 total_ptxf += stats.outgoing_path_request_freq();
336 }
337
338 let mut ifstats: Vec<(&str, Value)> = vec![
339 ("name", Value::Str(info.name.clone())),
340 ("short_name", Value::Str(info.name.clone())),
341 ("status", Value::Bool(entry.online())),
342 ("mode", Value::UInt(info.mode as u64)),
343 ("rxb", Value::UInt(stats.rxb)),
344 ("txb", Value::UInt(stats.txb)),
345 ("txdrp", Value::UInt(entry.tx_drops())),
346 ("txdrb", Value::UInt(entry.tx_dropped_bytes())),
347 ("txstalled", Value::Bool(entry.tx_stalled())),
348 ("txbuffered", Value::UInt(entry.tx_buffered() as u64)),
349 ("rxs", Value::Float(stats.traffic_rates.rxs)),
350 ("txs", Value::Float(stats.traffic_rates.txs)),
351 ("arxb", Value::UInt(stats.arxb)),
352 ("atxb", Value::UInt(stats.atxb)),
353 ("arxc", Value::UInt(stats.arxc)),
354 ("atxc", Value::UInt(stats.atxc)),
355 ("arxs", Value::Float(stats.traffic_rates.arxs)),
356 ("atxs", Value::Float(stats.traffic_rates.atxs)),
357 ("prxb", Value::UInt(stats.prxb)),
358 ("ptxb", Value::UInt(stats.ptxb)),
359 ("prxc", Value::UInt(stats.prxc)),
360 ("ptxc", Value::UInt(stats.ptxc)),
361 ("prxs", Value::Float(stats.traffic_rates.prxs)),
362 ("ptxs", Value::Float(stats.traffic_rates.ptxs)),
363 (
364 "protocol_violations",
365 Value::UInt(stats.protocol_violations),
366 ),
367 ("ifac_violations", Value::UInt(stats.ifac_violations)),
368 ("packet_filter_hits", Value::UInt(stats.packet_filter_hits)),
369 ("hop_limiter_hits", Value::UInt(stats.hop_limiter_hits)),
370 (
371 "stamped_path_requests",
372 Value::UInt(stats.stamped_path_requests),
373 ),
374 ];
375 if let Some(br) = info.bitrate {
376 ifstats.push(("bitrate", Value::UInt(br)));
377 } else {
378 ifstats.push(("bitrate", Value::Nil));
379 }
380 ifstats.push((
381 "incoming_announce_freq",
382 Value::Float(stats.incoming_announce_freq()),
383 ));
384 ifstats.push((
385 "outgoing_announce_freq",
386 Value::Float(stats.outgoing_announce_freq()),
387 ));
388 ifstats.push((
389 "held_announces",
390 Value::UInt(engine.held_announce_count(&id) as u64),
391 ));
392 if let Some(rssi) = stats.rssi {
393 ifstats.push(("rssi", Value::Int(i64::from(rssi))));
394 }
395 if let Some(snr) = stats.snr {
396 ifstats.push(("snr", Value::Float(f64::from(snr))));
397 }
398 if let Some(quality) = stats.signal_quality {
399 ifstats.push(("signal_quality", Value::Float(quality)));
400 }
401 if let Some(frequency) = stats.frequency {
402 ifstats.push(("frequency", Value::UInt(u64::from(frequency))));
403 }
404 if let Some(bandwidth) = stats.bandwidth {
405 ifstats.push(("bandwidth", Value::UInt(u64::from(bandwidth))));
406 }
407 if let Some(txpower) = stats.txpower {
408 ifstats.push(("txpower", Value::Int(i64::from(txpower))));
409 }
410 if let Some(sf) = stats.spreading_factor {
411 ifstats.push(("sf", Value::UInt(u64::from(sf))));
412 }
413 if let Some(cr) = stats.coding_rate {
414 ifstats.push(("cr", Value::UInt(u64::from(cr))));
415 }
416 if let Some(channel) = stats.channel {
417 ifstats.push(("channel", Value::UInt(u64::from(channel))));
418 }
419 if let Some(modulation) = stats.modulation.as_ref() {
420 ifstats.push(("modulation", Value::Str(modulation.clone())));
421 }
422
423 ifstats.push(("ifac_signature", Value::Nil));
425 ifstats.push((
426 "ifac_size",
427 if info.bitrate.is_some() {
428 Value::UInt(0)
429 } else {
430 Value::Nil
431 },
432 ));
433 ifstats.push(("ifac_netname", Value::Nil));
434
435 ifstats.push(("clients", Value::Nil));
437 ifstats.push(("announce_queue", Value::Nil));
438
439 let map = ifstats
441 .into_iter()
442 .map(|(k, v)| (Value::Str(k.into()), v))
443 .collect();
444 iface_list.push(Value::Map(map));
445 }
446
447 let mut stats: Vec<(&str, Value)> = vec![
449 ("interfaces", Value::Array(iface_list)),
450 ("rxb", Value::UInt(total_rxb)),
451 ("txb", Value::UInt(total_txb)),
452 ("rxpps", Value::Float(total_rxpps)),
453 ("txpps", Value::Float(total_txpps)),
454 ("rxpkts", Value::UInt(total_rx_packets)),
455 ("txpkts", Value::UInt(total_tx_packets)),
456 ("arxpkts", Value::UInt(total_announce_rx)),
457 ("atxpkts", Value::UInt(total_announce_tx)),
458 ("prxpkts", Value::UInt(total_path_request_rx)),
459 ("ptxpkts", Value::UInt(total_path_request_tx)),
460 ("rxs", Value::Float(total_rxs)),
461 ("txs", Value::Float(total_txs)),
462 ("arxb", Value::UInt(total_arxb)),
463 ("atxb", Value::UInt(total_atxb)),
464 ("arxc", Value::UInt(total_arxc)),
465 ("atxc", Value::UInt(total_atxc)),
466 ("arxs", Value::Float(total_arxs)),
467 ("atxs", Value::Float(total_atxs)),
468 ("prxb", Value::UInt(total_prxb)),
469 ("ptxb", Value::UInt(total_ptxb)),
470 ("prxc", Value::UInt(total_prxc)),
471 ("ptxc", Value::UInt(total_ptxc)),
472 ("prxs", Value::Float(total_prxs)),
473 ("ptxs", Value::Float(total_ptxs)),
474 ("arxf", Value::Float(total_arxf)),
475 ("atxf", Value::Float(total_atxf)),
476 ("prxf", Value::Float(total_prxf)),
477 ("ptxf", Value::Float(total_ptxf)),
478 ];
479
480 let (path_pool_total, path_pool_unused) = engine.path_pool_counts();
481 let total_links = engine.link_table_count();
482 let active_links = engine.active_link_count();
483 stats.push(("tlinks", Value::UInt(total_links as u64)));
484 stats.push(("alinks", Value::UInt(active_links as u64)));
485 stats.push((
486 "plinks",
487 Value::UInt(total_links.saturating_sub(active_links) as u64),
488 ));
489 stats.push(("tpcount", Value::UInt(path_pool_total as u64)));
490 stats.push(("unupcount", Value::UInt(path_pool_unused as u64)));
491 stats.push((
492 "actpcount",
493 Value::UInt(path_pool_total.saturating_sub(path_pool_unused) as u64),
494 ));
495
496 if let Some(identity_hash) = engine.config().identity_hash {
497 stats.push(("transport_id", Value::Bin(identity_hash.to_vec())));
498 stats.push(("transport_uptime", Value::Float(time::now() - started)));
499 }
500 stats.push((
501 "probe_responder",
502 match probe_responder_hash {
503 Some(hash) => Value::Bin(hash.to_vec()),
504 None => Value::Nil,
505 },
506 ));
507 stats.push(("rss", Value::Nil));
508
509 let stats_map = stats
510 .into_iter()
511 .map(|(k, v)| (Value::Str(k.into()), v))
512 .collect();
513
514 let mut response = vec![Value::Map(stats_map)];
516 if include_lstats {
517 let link_count = engine.link_table_count();
518 response.push(Value::UInt(link_count as u64));
519 }
520 if include_profiling {
521 response.push(crate::profiling::results_msgpack());
522 }
523
524 Some(msgpack::pack(&Value::Array(response)))
525}
526
527pub fn handle_path_request(data: &[u8], engine: &TransportEngine) -> Option<Vec<u8>> {
533 let arr = match msgpack::unpack_exact(data) {
534 Ok(Value::Array(arr)) if !arr.is_empty() => arr,
535 _ => return None,
536 };
537
538 let command = match &arr[0] {
539 Value::Str(s) => s.as_str(),
540 _ => return None,
541 };
542
543 let dest_filter: Option<[u8; 16]> = if arr.len() > 1 {
544 match &arr[1] {
545 Value::Bin(b) if b.len() == 16 => {
546 let mut h = [0u8; 16];
547 h.copy_from_slice(b);
548 Some(h)
549 }
550 _ => None,
551 }
552 } else {
553 None
554 };
555
556 let max_hops: Option<u8> = if arr.len() > 2 {
557 arr[2].as_uint().map(|v| v as u8)
558 } else {
559 None
560 };
561
562 match command {
563 "table" => {
564 let paths = engine.get_path_table(max_hops);
565 let mut entries = Vec::new();
566 for p in &paths {
567 if let Some(ref filter) = dest_filter {
568 if p.0 != *filter {
569 continue;
570 }
571 }
572 let entry = vec![
574 (Value::Str("hash".into()), Value::Bin(p.0.to_vec())),
575 (Value::Str("timestamp".into()), Value::Float(p.1)),
576 (Value::Str("via".into()), Value::Bin(p.2.to_vec())),
577 (Value::Str("hops".into()), Value::UInt(p.3 as u64)),
578 (Value::Str("expires".into()), Value::Float(p.4)),
579 (Value::Str("used".into()), Value::Float(p.5)),
580 (Value::Str("interface".into()), Value::Str(p.6.clone())),
581 ];
582 entries.push(Value::Map(entry));
583 }
584 Some(msgpack::pack(&Value::Array(entries)))
585 }
586 "rates" => {
587 let rates = engine.get_rate_table();
588 let mut entries = Vec::new();
589 for r in &rates {
590 if let Some(ref filter) = dest_filter {
591 if r.0 != *filter {
592 continue;
593 }
594 }
595 let timestamps: Vec<Value> = r.4.iter().map(|t| Value::Float(*t)).collect();
597 let entry = vec![
598 (Value::Str("hash".into()), Value::Bin(r.0.to_vec())),
599 (Value::Str("last".into()), Value::Float(r.1)),
600 (
601 Value::Str("rate_violations".into()),
602 Value::UInt(r.2 as u64),
603 ),
604 (Value::Str("blocked_until".into()), Value::Float(r.3)),
605 (Value::Str("timestamps".into()), Value::Array(timestamps)),
606 ];
607 entries.push(Value::Map(entry));
608 }
609 Some(msgpack::pack(&Value::Array(entries)))
610 }
611 _ => None,
612 }
613}
614
615pub fn handle_blackhole_list_request(engine: &TransportEngine) -> Option<Vec<u8>> {
619 let blackholed = engine.get_blackholed();
620 let mut map_entries = Vec::new();
621 for (hash, created, expires, reason) in &blackholed {
622 let mut entry = vec![
623 (Value::Str("created".into()), Value::Float(*created)),
624 (Value::Str("expires".into()), Value::Float(*expires)),
625 ];
626 if let Some(r) = reason {
627 entry.push((Value::Str("reason".into()), Value::Str(r.clone())));
628 }
629 map_entries.push((Value::Bin(hash.to_vec()), Value::Map(entry)));
630 }
631 Some(msgpack::pack(&Value::Map(map_entries)))
632}
633
634pub fn build_management_announce(
636 identity: &rns_crypto::identity::Identity,
637 rng: &mut dyn rns_crypto::Rng,
638 now: f64,
639 path_response: bool,
640) -> Option<Vec<u8>> {
641 build_builtin_announce(
642 BuiltinDestination::RemoteManagement,
643 identity,
644 rng,
645 now,
646 path_response,
647 None,
648 )
649}
650
651pub fn build_blackhole_announce(
653 identity: &rns_crypto::identity::Identity,
654 rng: &mut dyn rns_crypto::Rng,
655 now: f64,
656 path_response: bool,
657) -> Option<Vec<u8>> {
658 build_builtin_announce(
659 BuiltinDestination::Blackhole,
660 identity,
661 rng,
662 now,
663 path_response,
664 None,
665 )
666}