1use std::collections::HashMap;
11
12use rns_core::constants;
13use rns_core::destination::destination_hash;
14use rns_core::hash::truncated_hash;
15use rns_core::msgpack::{self, Value};
16use rns_core::transport::TransportEngine;
17
18use crate::interface::InterfaceEntry;
19use crate::time;
20
21pub fn status_path_hash() -> [u8; 16] {
23 truncated_hash(b"/status")
24}
25
26pub fn path_path_hash() -> [u8; 16] {
28 truncated_hash(b"/path")
29}
30
31pub fn list_path_hash() -> [u8; 16] {
33 truncated_hash(b"/list")
34}
35
36pub fn is_management_path(path_hash: &[u8; 16]) -> bool {
38 *path_hash == status_path_hash()
39 || *path_hash == path_path_hash()
40 || *path_hash == list_path_hash()
41}
42
43pub fn management_dest_hash(transport_identity_hash: &[u8; 16]) -> [u8; 16] {
47 destination_hash("rnstransport", &["remote", "management"], Some(transport_identity_hash))
48}
49
50pub fn blackhole_dest_hash(transport_identity_hash: &[u8; 16]) -> [u8; 16] {
54 destination_hash("rnstransport", &["info", "blackhole"], Some(transport_identity_hash))
55}
56
57#[derive(Debug, Clone)]
59pub struct ManagementConfig {
60 pub enable_remote_management: bool,
62 pub remote_management_allowed: Vec<[u8; 16]>,
64 pub publish_blackhole: bool,
66}
67
68impl Default for ManagementConfig {
69 fn default() -> Self {
70 ManagementConfig {
71 enable_remote_management: false,
72 remote_management_allowed: Vec::new(),
73 publish_blackhole: false,
74 }
75 }
76}
77
78pub fn handle_status_request(
83 data: &[u8],
84 engine: &TransportEngine,
85 interfaces: &HashMap<rns_core::transport::types::InterfaceId, InterfaceEntry>,
86 started: f64,
87) -> Option<Vec<u8>> {
88 let include_lstats = match msgpack::unpack_exact(data) {
90 Ok(Value::Array(arr)) if !arr.is_empty() => {
91 arr[0].as_bool().unwrap_or(false)
92 }
93 _ => false,
94 };
95
96 let mut iface_list = Vec::new();
98 let mut total_rxb: u64 = 0;
99 let mut total_txb: u64 = 0;
100
101 for (id, entry) in interfaces {
102 total_rxb += entry.stats.rxb;
103 total_txb += entry.stats.txb;
104
105 let mut ifstats: Vec<(&str, Value)> = Vec::new();
106 ifstats.push(("name", Value::Str(entry.info.name.clone())));
107 ifstats.push(("short_name", Value::Str(entry.info.name.clone())));
108 ifstats.push(("status", Value::Bool(entry.online)));
109 ifstats.push(("mode", Value::UInt(entry.info.mode as u64)));
110 ifstats.push(("rxb", Value::UInt(entry.stats.rxb)));
111 ifstats.push(("txb", Value::UInt(entry.stats.txb)));
112 if let Some(br) = entry.info.bitrate {
113 ifstats.push(("bitrate", Value::UInt(br)));
114 } else {
115 ifstats.push(("bitrate", Value::Nil));
116 }
117 ifstats.push(("incoming_announce_freq", Value::Float(entry.stats.incoming_announce_freq())));
118 ifstats.push(("outgoing_announce_freq", Value::Float(entry.stats.outgoing_announce_freq())));
119 ifstats.push(("held_announces", Value::UInt(engine.held_announce_count(id) as u64)));
120
121 ifstats.push(("ifac_signature", Value::Nil));
123 ifstats.push(("ifac_size", if entry.info.bitrate.is_some() {
124 Value::UInt(0)
125 } else {
126 Value::Nil
127 }));
128 ifstats.push(("ifac_netname", Value::Nil));
129
130 ifstats.push(("clients", Value::Nil));
132 ifstats.push(("announce_queue", Value::Nil));
133 ifstats.push(("rxs", Value::UInt(0)));
134 ifstats.push(("txs", Value::UInt(0)));
135
136 let map = ifstats.into_iter()
138 .map(|(k, v)| (Value::Str(k.into()), v))
139 .collect();
140 iface_list.push(Value::Map(map));
141 }
142
143 let mut stats: Vec<(&str, Value)> = Vec::new();
145 stats.push(("interfaces", Value::Array(iface_list)));
146 stats.push(("rxb", Value::UInt(total_rxb)));
147 stats.push(("txb", Value::UInt(total_txb)));
148 stats.push(("rxs", Value::UInt(0)));
149 stats.push(("txs", Value::UInt(0)));
150
151 if let Some(identity_hash) = engine.config().identity_hash {
152 stats.push(("transport_id", Value::Bin(identity_hash.to_vec())));
153 stats.push(("transport_uptime", Value::Float(time::now() - started)));
154 }
155 stats.push(("probe_responder", Value::Nil));
156 stats.push(("rss", Value::Nil));
157
158 let stats_map = stats.into_iter()
159 .map(|(k, v)| (Value::Str(k.into()), v))
160 .collect();
161
162 let mut response = vec![Value::Map(stats_map)];
164 if include_lstats {
165 let link_count = engine.link_table_count();
166 response.push(Value::UInt(link_count as u64));
167 }
168
169 Some(msgpack::pack(&Value::Array(response)))
170}
171
172pub fn handle_path_request(
178 data: &[u8],
179 engine: &TransportEngine,
180) -> Option<Vec<u8>> {
181 let arr = match msgpack::unpack_exact(data) {
182 Ok(Value::Array(arr)) if !arr.is_empty() => arr,
183 _ => return None,
184 };
185
186 let command = match &arr[0] {
187 Value::Str(s) => s.as_str(),
188 _ => return None,
189 };
190
191 let dest_filter: Option<[u8; 16]> = if arr.len() > 1 {
192 match &arr[1] {
193 Value::Bin(b) if b.len() == 16 => {
194 let mut h = [0u8; 16];
195 h.copy_from_slice(b);
196 Some(h)
197 }
198 _ => None,
199 }
200 } else {
201 None
202 };
203
204 let max_hops: Option<u8> = if arr.len() > 2 {
205 arr[2].as_uint().map(|v| v as u8)
206 } else {
207 None
208 };
209
210 match command {
211 "table" => {
212 let paths = engine.get_path_table(max_hops);
213 let mut entries = Vec::new();
214 for p in &paths {
215 if let Some(ref filter) = dest_filter {
216 if p.0 != *filter {
217 continue;
218 }
219 }
220 let entry = vec![
222 (Value::Str("hash".into()), Value::Bin(p.0.to_vec())),
223 (Value::Str("timestamp".into()), Value::Float(p.1)),
224 (Value::Str("via".into()), Value::Bin(p.2.to_vec())),
225 (Value::Str("hops".into()), Value::UInt(p.3 as u64)),
226 (Value::Str("expires".into()), Value::Float(p.4)),
227 (Value::Str("interface".into()), Value::Str(p.5.clone())),
228 ];
229 entries.push(Value::Map(entry));
230 }
231 Some(msgpack::pack(&Value::Array(entries)))
232 }
233 "rates" => {
234 let rates = engine.get_rate_table();
235 let mut entries = Vec::new();
236 for r in &rates {
237 if let Some(ref filter) = dest_filter {
238 if r.0 != *filter {
239 continue;
240 }
241 }
242 let timestamps: Vec<Value> = r.4.iter().map(|t| Value::Float(*t)).collect();
244 let entry = vec![
245 (Value::Str("hash".into()), Value::Bin(r.0.to_vec())),
246 (Value::Str("last".into()), Value::Float(r.1)),
247 (Value::Str("rate_violations".into()), Value::UInt(r.2 as u64)),
248 (Value::Str("blocked_until".into()), Value::Float(r.3)),
249 (Value::Str("timestamps".into()), Value::Array(timestamps)),
250 ];
251 entries.push(Value::Map(entry));
252 }
253 Some(msgpack::pack(&Value::Array(entries)))
254 }
255 _ => None,
256 }
257}
258
259pub fn handle_blackhole_list_request(
263 engine: &TransportEngine,
264) -> Option<Vec<u8>> {
265 let blackholed = engine.get_blackholed();
266 let mut map_entries = Vec::new();
267 for (hash, created, expires, reason) in &blackholed {
268 let mut entry = vec![
269 (Value::Str("created".into()), Value::Float(*created)),
270 (Value::Str("expires".into()), Value::Float(*expires)),
271 ];
272 if let Some(r) = reason {
273 entry.push((Value::Str("reason".into()), Value::Str(r.clone())));
274 }
275 map_entries.push((Value::Bin(hash.to_vec()), Value::Map(entry)));
276 }
277 Some(msgpack::pack(&Value::Map(map_entries)))
278}
279
280pub fn build_management_announce(
284 identity: &rns_crypto::identity::Identity,
285 rng: &mut dyn rns_crypto::Rng,
286) -> Option<Vec<u8>> {
287 let identity_hash = *identity.hash();
288 let dest_hash = management_dest_hash(&identity_hash);
289 let name_hash = rns_core::destination::name_hash("rnstransport", &["remote", "management"]);
290 let mut random_hash = [0u8; 10];
291 rng.fill_bytes(&mut random_hash);
292
293 let (announce_data, _has_ratchet) = rns_core::announce::AnnounceData::pack(
294 identity,
295 &dest_hash,
296 &name_hash,
297 &random_hash,
298 None, None, )
301 .ok()?;
302
303 let flags = rns_core::packet::PacketFlags {
304 header_type: constants::HEADER_1,
305 context_flag: constants::FLAG_UNSET,
306 transport_type: constants::TRANSPORT_BROADCAST,
307 destination_type: constants::DESTINATION_SINGLE,
308 packet_type: constants::PACKET_TYPE_ANNOUNCE,
309 };
310
311 let packet = rns_core::packet::RawPacket::pack(
312 flags, 0, &dest_hash, None, constants::CONTEXT_NONE, &announce_data,
313 )
314 .ok()?;
315
316 Some(packet.raw)
317}
318
319pub fn build_blackhole_announce(
323 identity: &rns_crypto::identity::Identity,
324 rng: &mut dyn rns_crypto::Rng,
325) -> Option<Vec<u8>> {
326 let identity_hash = *identity.hash();
327 let dest_hash = blackhole_dest_hash(&identity_hash);
328 let name_hash = rns_core::destination::name_hash("rnstransport", &["info", "blackhole"]);
329 let mut random_hash = [0u8; 10];
330 rng.fill_bytes(&mut random_hash);
331
332 let (announce_data, _has_ratchet) = rns_core::announce::AnnounceData::pack(
333 identity,
334 &dest_hash,
335 &name_hash,
336 &random_hash,
337 None,
338 None,
339 )
340 .ok()?;
341
342 let flags = rns_core::packet::PacketFlags {
343 header_type: constants::HEADER_1,
344 context_flag: constants::FLAG_UNSET,
345 transport_type: constants::TRANSPORT_BROADCAST,
346 destination_type: constants::DESTINATION_SINGLE,
347 packet_type: constants::PACKET_TYPE_ANNOUNCE,
348 };
349
350 let packet = rns_core::packet::RawPacket::pack(
351 flags, 0, &dest_hash, None, constants::CONTEXT_NONE, &announce_data,
352 )
353 .ok()?;
354
355 Some(packet.raw)
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use crate::interface::{InterfaceStats, Writer};
362 use crate::ifac::IfacState;
363 use rns_core::transport::types::{InterfaceId, InterfaceInfo, TransportConfig};
364 use std::io;
365
366 struct NullWriter;
367 impl Writer for NullWriter {
368 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
369 Ok(())
370 }
371 }
372
373 fn make_engine() -> TransportEngine {
374 TransportEngine::new(TransportConfig {
375 transport_enabled: true,
376 identity_hash: Some([0xAA; 16]),
377 })
378 }
379
380 fn make_interfaces() -> HashMap<InterfaceId, InterfaceEntry> {
381 let mut map = HashMap::new();
382 let id = InterfaceId(1);
383 let info = InterfaceInfo {
384 id,
385 name: "TestInterface".into(),
386 mode: constants::MODE_FULL,
387 out_capable: true,
388 in_capable: true,
389 bitrate: Some(115200),
390 announce_rate_target: None,
391 announce_rate_grace: 0,
392 announce_rate_penalty: 0.0,
393 announce_cap: constants::ANNOUNCE_CAP,
394 is_local_client: false,
395 wants_tunnel: false,
396 tunnel_id: None,
397 mtu: rns_core::constants::MTU as u32,
398 ia_freq: 0.0,
399 started: 0.0,
400 ingress_control: false,
401 };
402 map.insert(id, InterfaceEntry {
403 id,
404 info,
405 writer: Box::new(NullWriter),
406 online: true,
407 dynamic: false,
408 ifac: None,
409 stats: InterfaceStats {
410 rxb: 1234,
411 txb: 5678,
412 rx_packets: 10,
413 tx_packets: 20,
414 started: 1000.0,
415 ia_timestamps: vec![],
416 oa_timestamps: vec![],
417 },
418 interface_type: "TestInterface".to_string(),
419 });
420 map
421 }
422
423 #[test]
424 fn test_management_dest_hash() {
425 let id_hash = [0x42; 16];
426 let dh = management_dest_hash(&id_hash);
427 assert_eq!(dh, management_dest_hash(&id_hash));
429 assert_ne!(dh, management_dest_hash(&[0x43; 16]));
431 }
432
433 #[test]
434 fn test_blackhole_dest_hash() {
435 let id_hash = [0x42; 16];
436 let dh = blackhole_dest_hash(&id_hash);
437 assert_eq!(dh, blackhole_dest_hash(&id_hash));
438 assert_ne!(dh, management_dest_hash(&id_hash));
440 }
441
442 #[test]
443 fn test_path_hashes_distinct() {
444 let s = status_path_hash();
445 let p = path_path_hash();
446 let l = list_path_hash();
447 assert_ne!(s, p);
448 assert_ne!(s, l);
449 assert_ne!(p, l);
450 assert_ne!(s, [0u8; 16]);
452 }
453
454 #[test]
455 fn test_management_config_default() {
456 let config = ManagementConfig::default();
457 assert!(!config.enable_remote_management);
458 assert!(config.remote_management_allowed.is_empty());
459 assert!(!config.publish_blackhole);
460 }
461
462 #[test]
463 fn test_is_management_path() {
464 assert!(is_management_path(&status_path_hash()));
465 assert!(is_management_path(&path_path_hash()));
466 assert!(is_management_path(&list_path_hash()));
467 assert!(!is_management_path(&[0u8; 16]));
468 }
469
470 #[test]
471 fn test_status_request_basic() {
472 let engine = make_engine();
473 let interfaces = make_interfaces();
474 let started = time::now() - 100.0; let request = msgpack::pack(&Value::Array(vec![Value::Bool(false)]));
478 let response = handle_status_request(&request, &engine, &interfaces, started).unwrap();
479
480 let val = msgpack::unpack_exact(&response).unwrap();
482 match val {
483 Value::Array(arr) => {
484 assert_eq!(arr.len(), 1); match &arr[0] {
486 Value::Map(map) => {
487 let transport_id = map.iter()
489 .find(|(k, _)| *k == Value::Str("transport_id".into()))
490 .map(|(_, v)| v);
491 assert!(transport_id.is_some());
492
493 let rxb = map.iter()
495 .find(|(k, _)| *k == Value::Str("rxb".into()))
496 .map(|(_, v)| v.as_uint().unwrap());
497 assert_eq!(rxb, Some(1234));
498
499 let txb = map.iter()
500 .find(|(k, _)| *k == Value::Str("txb".into()))
501 .map(|(_, v)| v.as_uint().unwrap());
502 assert_eq!(txb, Some(5678));
503
504 let ifaces = map.iter()
506 .find(|(k, _)| *k == Value::Str("interfaces".into()))
507 .map(|(_, v)| v);
508 match ifaces {
509 Some(Value::Array(iface_arr)) => {
510 assert_eq!(iface_arr.len(), 1);
511 }
512 _ => panic!("Expected interfaces array"),
513 }
514
515 let uptime = map.iter()
517 .find(|(k, _)| *k == Value::Str("transport_uptime".into()))
518 .and_then(|(_, v)| v.as_float());
519 assert!(uptime.unwrap() >= 100.0);
520 }
521 _ => panic!("Expected map in response"),
522 }
523 }
524 _ => panic!("Expected array response"),
525 }
526 }
527
528 #[test]
529 fn test_status_request_with_lstats() {
530 let engine = make_engine();
531 let interfaces = make_interfaces();
532 let started = time::now();
533
534 let request = msgpack::pack(&Value::Array(vec![Value::Bool(true)]));
535 let response = handle_status_request(&request, &engine, &interfaces, started).unwrap();
536
537 let val = msgpack::unpack_exact(&response).unwrap();
538 match val {
539 Value::Array(arr) => {
540 assert_eq!(arr.len(), 2); assert_eq!(arr[1].as_uint(), Some(0)); }
543 _ => panic!("Expected array response"),
544 }
545 }
546
547 #[test]
548 fn test_status_request_empty_data() {
549 let engine = make_engine();
550 let interfaces = make_interfaces();
551 let started = time::now();
552
553 let response = handle_status_request(&[], &engine, &interfaces, started).unwrap();
555 let val = msgpack::unpack_exact(&response).unwrap();
556 match val {
557 Value::Array(arr) => assert_eq!(arr.len(), 1),
558 _ => panic!("Expected array response"),
559 }
560 }
561
562 #[test]
563 fn test_path_request_table() {
564 let engine = make_engine();
565
566 let request = msgpack::pack(&Value::Array(vec![Value::Str("table".into())]));
568 let response = handle_path_request(&request, &engine).unwrap();
569 let val = msgpack::unpack_exact(&response).unwrap();
570 match val {
571 Value::Array(arr) => assert_eq!(arr.len(), 0),
572 _ => panic!("Expected array"),
573 }
574 }
575
576 #[test]
577 fn test_path_request_rates() {
578 let engine = make_engine();
579
580 let request = msgpack::pack(&Value::Array(vec![Value::Str("rates".into())]));
581 let response = handle_path_request(&request, &engine).unwrap();
582 let val = msgpack::unpack_exact(&response).unwrap();
583 match val {
584 Value::Array(arr) => assert_eq!(arr.len(), 0),
585 _ => panic!("Expected array"),
586 }
587 }
588
589 #[test]
590 fn test_path_request_unknown_command() {
591 let engine = make_engine();
592
593 let request = msgpack::pack(&Value::Array(vec![Value::Str("unknown".into())]));
594 let response = handle_path_request(&request, &engine);
595 assert!(response.is_none());
596 }
597
598 #[test]
599 fn test_path_request_invalid_data() {
600 let engine = make_engine();
601 let response = handle_path_request(&[], &engine);
602 assert!(response.is_none());
603 }
604
605 #[test]
606 fn test_blackhole_list_empty() {
607 let engine = make_engine();
608 let response = handle_blackhole_list_request(&engine).unwrap();
609 let val = msgpack::unpack_exact(&response).unwrap();
610 match val {
611 Value::Map(entries) => assert_eq!(entries.len(), 0),
612 _ => panic!("Expected map"),
613 }
614 }
615
616 #[test]
619 fn test_build_management_announce() {
620 use rns_crypto::identity::Identity;
621 use rns_crypto::OsRng;
622
623 let identity = Identity::new(&mut OsRng);
624 let raw = build_management_announce(&identity, &mut OsRng);
625 assert!(raw.is_some(), "Should build management announce");
626
627 let raw = raw.unwrap();
628 let pkt = rns_core::packet::RawPacket::unpack(&raw).unwrap();
630 assert_eq!(pkt.flags.packet_type, constants::PACKET_TYPE_ANNOUNCE);
631 assert_eq!(pkt.flags.destination_type, constants::DESTINATION_SINGLE);
632 assert_eq!(pkt.destination_hash, management_dest_hash(identity.hash()));
633 }
634
635 #[test]
636 fn test_build_blackhole_announce() {
637 use rns_crypto::identity::Identity;
638 use rns_crypto::OsRng;
639
640 let identity = Identity::new(&mut OsRng);
641 let raw = build_blackhole_announce(&identity, &mut OsRng);
642 assert!(raw.is_some(), "Should build blackhole announce");
643
644 let raw = raw.unwrap();
645 let pkt = rns_core::packet::RawPacket::unpack(&raw).unwrap();
646 assert_eq!(pkt.flags.packet_type, constants::PACKET_TYPE_ANNOUNCE);
647 assert_eq!(pkt.destination_hash, blackhole_dest_hash(identity.hash()));
648 }
649
650 #[test]
651 fn test_management_announce_validates() {
652 use rns_crypto::identity::Identity;
653 use rns_crypto::OsRng;
654
655 let identity = Identity::new(&mut OsRng);
656 let raw = build_management_announce(&identity, &mut OsRng).unwrap();
657
658 let pkt = rns_core::packet::RawPacket::unpack(&raw).unwrap();
659
660 let validated = rns_core::announce::AnnounceData::unpack(&pkt.data, false);
662 assert!(validated.is_ok(), "Announce data should unpack");
663
664 let ann = validated.unwrap();
665 let result = ann.validate(&pkt.destination_hash);
666 assert!(result.is_ok(), "Announce should validate: {:?}", result.err());
667 }
668
669 #[test]
670 fn test_blackhole_announce_validates() {
671 use rns_crypto::identity::Identity;
672 use rns_crypto::OsRng;
673
674 let identity = Identity::new(&mut OsRng);
675 let raw = build_blackhole_announce(&identity, &mut OsRng).unwrap();
676
677 let pkt = rns_core::packet::RawPacket::unpack(&raw).unwrap();
678 let ann = rns_core::announce::AnnounceData::unpack(&pkt.data, false).unwrap();
679 let result = ann.validate(&pkt.destination_hash);
680 assert!(result.is_ok(), "Blackhole announce should validate: {:?}", result.err());
681 }
682
683 #[test]
684 fn test_management_announce_different_from_blackhole() {
685 use rns_crypto::identity::Identity;
686 use rns_crypto::OsRng;
687
688 let identity = Identity::new(&mut OsRng);
689 let mgmt_raw = build_management_announce(&identity, &mut OsRng).unwrap();
690 let bh_raw = build_blackhole_announce(&identity, &mut OsRng).unwrap();
691
692 let mgmt_pkt = rns_core::packet::RawPacket::unpack(&mgmt_raw).unwrap();
693 let bh_pkt = rns_core::packet::RawPacket::unpack(&bh_raw).unwrap();
694
695 assert_ne!(mgmt_pkt.destination_hash, bh_pkt.destination_hash,
696 "Management and blackhole should have different dest hashes");
697 }
698}