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(0)));
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 };
398 map.insert(id, InterfaceEntry {
399 id,
400 info,
401 writer: Box::new(NullWriter),
402 online: true,
403 dynamic: false,
404 ifac: None,
405 stats: InterfaceStats {
406 rxb: 1234,
407 txb: 5678,
408 rx_packets: 10,
409 tx_packets: 20,
410 started: 1000.0,
411 ia_timestamps: vec![],
412 oa_timestamps: vec![],
413 },
414 interface_type: "TestInterface".to_string(),
415 });
416 map
417 }
418
419 #[test]
420 fn test_management_dest_hash() {
421 let id_hash = [0x42; 16];
422 let dh = management_dest_hash(&id_hash);
423 assert_eq!(dh, management_dest_hash(&id_hash));
425 assert_ne!(dh, management_dest_hash(&[0x43; 16]));
427 }
428
429 #[test]
430 fn test_blackhole_dest_hash() {
431 let id_hash = [0x42; 16];
432 let dh = blackhole_dest_hash(&id_hash);
433 assert_eq!(dh, blackhole_dest_hash(&id_hash));
434 assert_ne!(dh, management_dest_hash(&id_hash));
436 }
437
438 #[test]
439 fn test_path_hashes_distinct() {
440 let s = status_path_hash();
441 let p = path_path_hash();
442 let l = list_path_hash();
443 assert_ne!(s, p);
444 assert_ne!(s, l);
445 assert_ne!(p, l);
446 assert_ne!(s, [0u8; 16]);
448 }
449
450 #[test]
451 fn test_management_config_default() {
452 let config = ManagementConfig::default();
453 assert!(!config.enable_remote_management);
454 assert!(config.remote_management_allowed.is_empty());
455 assert!(!config.publish_blackhole);
456 }
457
458 #[test]
459 fn test_is_management_path() {
460 assert!(is_management_path(&status_path_hash()));
461 assert!(is_management_path(&path_path_hash()));
462 assert!(is_management_path(&list_path_hash()));
463 assert!(!is_management_path(&[0u8; 16]));
464 }
465
466 #[test]
467 fn test_status_request_basic() {
468 let engine = make_engine();
469 let interfaces = make_interfaces();
470 let started = time::now() - 100.0; let request = msgpack::pack(&Value::Array(vec![Value::Bool(false)]));
474 let response = handle_status_request(&request, &engine, &interfaces, started).unwrap();
475
476 let val = msgpack::unpack_exact(&response).unwrap();
478 match val {
479 Value::Array(arr) => {
480 assert_eq!(arr.len(), 1); match &arr[0] {
482 Value::Map(map) => {
483 let transport_id = map.iter()
485 .find(|(k, _)| *k == Value::Str("transport_id".into()))
486 .map(|(_, v)| v);
487 assert!(transport_id.is_some());
488
489 let rxb = map.iter()
491 .find(|(k, _)| *k == Value::Str("rxb".into()))
492 .map(|(_, v)| v.as_uint().unwrap());
493 assert_eq!(rxb, Some(1234));
494
495 let txb = map.iter()
496 .find(|(k, _)| *k == Value::Str("txb".into()))
497 .map(|(_, v)| v.as_uint().unwrap());
498 assert_eq!(txb, Some(5678));
499
500 let ifaces = map.iter()
502 .find(|(k, _)| *k == Value::Str("interfaces".into()))
503 .map(|(_, v)| v);
504 match ifaces {
505 Some(Value::Array(iface_arr)) => {
506 assert_eq!(iface_arr.len(), 1);
507 }
508 _ => panic!("Expected interfaces array"),
509 }
510
511 let uptime = map.iter()
513 .find(|(k, _)| *k == Value::Str("transport_uptime".into()))
514 .and_then(|(_, v)| v.as_float());
515 assert!(uptime.unwrap() >= 100.0);
516 }
517 _ => panic!("Expected map in response"),
518 }
519 }
520 _ => panic!("Expected array response"),
521 }
522 }
523
524 #[test]
525 fn test_status_request_with_lstats() {
526 let engine = make_engine();
527 let interfaces = make_interfaces();
528 let started = time::now();
529
530 let request = msgpack::pack(&Value::Array(vec![Value::Bool(true)]));
531 let response = handle_status_request(&request, &engine, &interfaces, started).unwrap();
532
533 let val = msgpack::unpack_exact(&response).unwrap();
534 match val {
535 Value::Array(arr) => {
536 assert_eq!(arr.len(), 2); assert_eq!(arr[1].as_uint(), Some(0)); }
539 _ => panic!("Expected array response"),
540 }
541 }
542
543 #[test]
544 fn test_status_request_empty_data() {
545 let engine = make_engine();
546 let interfaces = make_interfaces();
547 let started = time::now();
548
549 let response = handle_status_request(&[], &engine, &interfaces, started).unwrap();
551 let val = msgpack::unpack_exact(&response).unwrap();
552 match val {
553 Value::Array(arr) => assert_eq!(arr.len(), 1),
554 _ => panic!("Expected array response"),
555 }
556 }
557
558 #[test]
559 fn test_path_request_table() {
560 let engine = make_engine();
561
562 let request = msgpack::pack(&Value::Array(vec![Value::Str("table".into())]));
564 let response = handle_path_request(&request, &engine).unwrap();
565 let val = msgpack::unpack_exact(&response).unwrap();
566 match val {
567 Value::Array(arr) => assert_eq!(arr.len(), 0),
568 _ => panic!("Expected array"),
569 }
570 }
571
572 #[test]
573 fn test_path_request_rates() {
574 let engine = make_engine();
575
576 let request = msgpack::pack(&Value::Array(vec![Value::Str("rates".into())]));
577 let response = handle_path_request(&request, &engine).unwrap();
578 let val = msgpack::unpack_exact(&response).unwrap();
579 match val {
580 Value::Array(arr) => assert_eq!(arr.len(), 0),
581 _ => panic!("Expected array"),
582 }
583 }
584
585 #[test]
586 fn test_path_request_unknown_command() {
587 let engine = make_engine();
588
589 let request = msgpack::pack(&Value::Array(vec![Value::Str("unknown".into())]));
590 let response = handle_path_request(&request, &engine);
591 assert!(response.is_none());
592 }
593
594 #[test]
595 fn test_path_request_invalid_data() {
596 let engine = make_engine();
597 let response = handle_path_request(&[], &engine);
598 assert!(response.is_none());
599 }
600
601 #[test]
602 fn test_blackhole_list_empty() {
603 let engine = make_engine();
604 let response = handle_blackhole_list_request(&engine).unwrap();
605 let val = msgpack::unpack_exact(&response).unwrap();
606 match val {
607 Value::Map(entries) => assert_eq!(entries.len(), 0),
608 _ => panic!("Expected map"),
609 }
610 }
611
612 #[test]
615 fn test_build_management_announce() {
616 use rns_crypto::identity::Identity;
617 use rns_crypto::OsRng;
618
619 let identity = Identity::new(&mut OsRng);
620 let raw = build_management_announce(&identity, &mut OsRng);
621 assert!(raw.is_some(), "Should build management announce");
622
623 let raw = raw.unwrap();
624 let pkt = rns_core::packet::RawPacket::unpack(&raw).unwrap();
626 assert_eq!(pkt.flags.packet_type, constants::PACKET_TYPE_ANNOUNCE);
627 assert_eq!(pkt.flags.destination_type, constants::DESTINATION_SINGLE);
628 assert_eq!(pkt.destination_hash, management_dest_hash(identity.hash()));
629 }
630
631 #[test]
632 fn test_build_blackhole_announce() {
633 use rns_crypto::identity::Identity;
634 use rns_crypto::OsRng;
635
636 let identity = Identity::new(&mut OsRng);
637 let raw = build_blackhole_announce(&identity, &mut OsRng);
638 assert!(raw.is_some(), "Should build blackhole announce");
639
640 let raw = raw.unwrap();
641 let pkt = rns_core::packet::RawPacket::unpack(&raw).unwrap();
642 assert_eq!(pkt.flags.packet_type, constants::PACKET_TYPE_ANNOUNCE);
643 assert_eq!(pkt.destination_hash, blackhole_dest_hash(identity.hash()));
644 }
645
646 #[test]
647 fn test_management_announce_validates() {
648 use rns_crypto::identity::Identity;
649 use rns_crypto::OsRng;
650
651 let identity = Identity::new(&mut OsRng);
652 let raw = build_management_announce(&identity, &mut OsRng).unwrap();
653
654 let pkt = rns_core::packet::RawPacket::unpack(&raw).unwrap();
655
656 let validated = rns_core::announce::AnnounceData::unpack(&pkt.data, false);
658 assert!(validated.is_ok(), "Announce data should unpack");
659
660 let ann = validated.unwrap();
661 let result = ann.validate(&pkt.destination_hash);
662 assert!(result.is_ok(), "Announce should validate: {:?}", result.err());
663 }
664
665 #[test]
666 fn test_blackhole_announce_validates() {
667 use rns_crypto::identity::Identity;
668 use rns_crypto::OsRng;
669
670 let identity = Identity::new(&mut OsRng);
671 let raw = build_blackhole_announce(&identity, &mut OsRng).unwrap();
672
673 let pkt = rns_core::packet::RawPacket::unpack(&raw).unwrap();
674 let ann = rns_core::announce::AnnounceData::unpack(&pkt.data, false).unwrap();
675 let result = ann.validate(&pkt.destination_hash);
676 assert!(result.is_ok(), "Blackhole announce should validate: {:?}", result.err());
677 }
678
679 #[test]
680 fn test_management_announce_different_from_blackhole() {
681 use rns_crypto::identity::Identity;
682 use rns_crypto::OsRng;
683
684 let identity = Identity::new(&mut OsRng);
685 let mgmt_raw = build_management_announce(&identity, &mut OsRng).unwrap();
686 let bh_raw = build_blackhole_announce(&identity, &mut OsRng).unwrap();
687
688 let mgmt_pkt = rns_core::packet::RawPacket::unpack(&mgmt_raw).unwrap();
689 let bh_pkt = rns_core::packet::RawPacket::unpack(&bh_raw).unwrap();
690
691 assert_ne!(mgmt_pkt.destination_hash, bh_pkt.destination_hash,
692 "Management and blackhole should have different dest hashes");
693 }
694}