1use std::io::{self, Read, Write};
12use std::net::{IpAddr, TcpListener, TcpStream};
13#[cfg(target_os = "linux")]
14use std::os::linux::net::SocketAddrExt;
15#[cfg(target_os = "linux")]
16use std::os::unix::net::{SocketAddr, UnixListener};
17use std::sync::mpsc;
18use std::sync::{
19 atomic::{AtomicBool, Ordering},
20 Arc,
21};
22use std::thread;
23use std::time::Duration;
24
25use rns_core::msgpack::{self, Value as MsgpackValue};
26use rns_core::transport::types::InterfaceId;
27use rns_crypto::hmac::hmac_sha256;
28use rns_crypto::sha256::sha256;
29
30#[cfg(test)]
31use crate::event::TrafficDetail;
32use crate::event::{
33 BackboneInterfaceEntry, BackbonePeerStateEntry, BlackholeInfo, BurstClassStats, DrainStatus,
34 DynamicBurstStats, Event, EventSender, HookInfo, InterfaceStatsResponse, KnownDestinationEntry,
35 LifecycleState, PathTableEntry, ProviderBridgeStats, QueryRequest, QueryResponse,
36 RateTableEntry, RuntimeConfigApplyMode, RuntimeConfigEntry, RuntimeConfigError,
37 RuntimeConfigErrorCode, RuntimeConfigSource, RuntimeConfigValue, SingleInterfaceStat,
38};
39use crate::md5::hmac_md5;
40use crate::pickle::{self, PickleValue};
41
42const CHALLENGE_PREFIX: &[u8] = b"#CHALLENGE#";
43const WELCOME: &[u8] = b"#WELCOME#";
44const FAILURE: &[u8] = b"#FAILURE#";
45const CHALLENGE_LEN: usize = 40;
46const MAX_CONCURRENT_RPC_CONNECTIONS: usize = 32;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49enum RpcCodec {
50 Msgpack,
51 Pickle,
52}
53
54#[derive(Debug, Clone)]
56pub enum RpcAddr {
57 Tcp(String, u16),
58}
59
60pub struct RpcServer {
62 shutdown: Arc<AtomicBool>,
63 thread: Option<thread::JoinHandle<()>>,
64}
65
66impl RpcServer {
67 pub fn start(addr: &RpcAddr, auth_key: [u8; 32], event_tx: EventSender) -> io::Result<Self> {
69 let shutdown = Arc::new(AtomicBool::new(false));
70 let shutdown2 = shutdown.clone();
71
72 let listener = match addr {
73 RpcAddr::Tcp(host, port) => {
74 let l = TcpListener::bind((host.as_str(), *port))?;
75 l.set_nonblocking(true)?;
77 l
78 }
79 };
80
81 let thread = thread::Builder::new()
82 .name("rpc-server".into())
83 .spawn(move || {
84 rpc_server_loop(listener, auth_key, event_tx, shutdown2);
85 })
86 .map_err(io::Error::other)?;
87
88 Ok(RpcServer {
89 shutdown,
90 thread: Some(thread),
91 })
92 }
93
94 #[cfg(target_os = "linux")]
96 pub fn start_abstract_unix(
97 instance_name: &str,
98 auth_key: [u8; 32],
99 event_tx: EventSender,
100 ) -> io::Result<Self> {
101 let addr = SocketAddr::from_abstract_name(format!("rns/{instance_name}/rpc"))?;
102 let listener = UnixListener::bind_addr(&addr)?;
103 listener.set_nonblocking(true)?;
104 let shutdown = Arc::new(AtomicBool::new(false));
105 let shutdown2 = shutdown.clone();
106 let thread = thread::Builder::new()
107 .name("rpc-unix-server".into())
108 .spawn(move || rpc_server_loop_unix(listener, auth_key, event_tx, shutdown2))
109 .map_err(io::Error::other)?;
110 Ok(Self {
111 shutdown,
112 thread: Some(thread),
113 })
114 }
115
116 pub fn stop(&mut self) {
118 self.shutdown.store(true, Ordering::Relaxed);
119 if let Some(handle) = self.thread.take() {
120 let _ = handle.join();
121 }
122 }
123}
124
125#[cfg(target_os = "linux")]
126fn rpc_server_loop_unix(
127 listener: UnixListener,
128 auth_key: [u8; 32],
129 event_tx: EventSender,
130 shutdown: Arc<AtomicBool>,
131) {
132 let mut workers = Vec::new();
133 loop {
134 reap_finished_rpc_workers(&mut workers);
135 if shutdown.load(Ordering::Relaxed) {
136 break;
137 }
138 match listener.accept() {
139 Ok((stream, _)) => {
140 if workers.len() >= MAX_CONCURRENT_RPC_CONNECTIONS {
141 log::warn!("rejecting Unix RPC connection: {MAX_CONCURRENT_RPC_CONNECTIONS} handlers already active");
142 continue;
143 }
144 let _ = stream.set_read_timeout(Some(Duration::from_secs(10)));
145 let _ = stream.set_write_timeout(Some(Duration::from_secs(10)));
146 let worker_event_tx = event_tx.clone();
147 match thread::Builder::new()
148 .name("rpc-unix-client".into())
149 .spawn(move || {
150 if let Err(e) = handle_connection(stream, &auth_key, &worker_event_tx) {
151 log::debug!("Unix RPC connection error: {e}");
152 }
153 }) {
154 Ok(worker) => workers.push(worker),
155 Err(e) => log::error!("failed to start Unix RPC connection handler: {e}"),
156 }
157 }
158 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
159 thread::sleep(Duration::from_millis(10))
160 }
161 Err(e) => {
162 log::error!("Unix RPC accept error: {e}");
163 thread::sleep(Duration::from_millis(100));
164 }
165 }
166 }
167 for worker in workers {
168 let _ = worker.join();
169 }
170}
171
172impl Drop for RpcServer {
173 fn drop(&mut self) {
174 self.stop();
175 }
176}
177
178fn rpc_server_loop(
179 listener: TcpListener,
180 auth_key: [u8; 32],
181 event_tx: EventSender,
182 shutdown: Arc<AtomicBool>,
183) {
184 let mut workers = Vec::new();
185
186 loop {
187 reap_finished_rpc_workers(&mut workers);
188
189 if shutdown.load(Ordering::Relaxed) {
190 break;
191 }
192
193 match listener.accept() {
194 Ok((stream, addr)) => {
195 if workers.len() >= MAX_CONCURRENT_RPC_CONNECTIONS {
196 log::warn!(
197 "rejecting RPC connection from {addr}: {MAX_CONCURRENT_RPC_CONNECTIONS} handlers already active"
198 );
199 continue;
200 }
201
202 let _ = stream.set_nonblocking(false);
204 let _ = stream.set_read_timeout(Some(std::time::Duration::from_secs(10)));
205 let _ = stream.set_write_timeout(Some(std::time::Duration::from_secs(10)));
206
207 let worker_event_tx = event_tx.clone();
208 match thread::Builder::new()
209 .name("rpc-client".into())
210 .spawn(move || {
211 if let Err(e) = handle_connection(stream, &auth_key, &worker_event_tx) {
212 log::debug!("RPC connection error: {e}");
213 }
214 }) {
215 Ok(worker) => workers.push(worker),
216 Err(e) => log::error!("failed to start RPC connection handler: {e}"),
217 }
218 }
219 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
220 thread::sleep(std::time::Duration::from_millis(10));
222 }
223 Err(e) => {
224 log::error!("RPC accept error: {}", e);
225 thread::sleep(std::time::Duration::from_millis(100));
226 }
227 }
228 }
229
230 for worker in workers {
231 let _ = worker.join();
232 }
233}
234
235fn reap_finished_rpc_workers(workers: &mut Vec<thread::JoinHandle<()>>) {
236 let mut index = 0;
237 while index < workers.len() {
238 if workers[index].is_finished() {
239 let worker = workers.swap_remove(index);
240 let _ = worker.join();
241 } else {
242 index += 1;
243 }
244 }
245}
246
247fn handle_connection<S: Read + Write>(
248 mut stream: S,
249 auth_key: &[u8; 32],
250 event_tx: &EventSender,
251) -> io::Result<()> {
252 server_auth(&mut stream, auth_key)?;
254 answer_challenge(&mut stream, auth_key)?;
256
257 let request_bytes = recv_bytes(&mut stream)?;
259 let (request, codec) = decode_rpc_payload(&request_bytes)?;
260
261 let response = handle_rpc_request(&request, event_tx)?;
263
264 let response_bytes = encode_rpc_payload(&response, codec);
266 send_bytes(&mut stream, &response_bytes)?;
267
268 Ok(())
269}
270
271fn decode_rpc_payload(data: &[u8]) -> io::Result<(PickleValue, RpcCodec)> {
272 if let Ok(value) = msgpack::unpack_exact(data) {
273 return Ok((msgpack_to_pickle(&value), RpcCodec::Msgpack));
274 }
275 pickle::decode(data)
276 .map(|value| (value, RpcCodec::Pickle))
277 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))
278}
279
280fn encode_rpc_payload(value: &PickleValue, codec: RpcCodec) -> Vec<u8> {
281 match codec {
282 RpcCodec::Msgpack => msgpack::pack(&pickle_to_msgpack(value)),
283 RpcCodec::Pickle => pickle::encode(value),
284 }
285}
286
287fn pickle_to_msgpack(value: &PickleValue) -> MsgpackValue {
288 match value {
289 PickleValue::None => MsgpackValue::Nil,
290 PickleValue::Bool(v) => MsgpackValue::Bool(*v),
291 PickleValue::Int(v) if *v >= 0 => MsgpackValue::UInt(*v as u64),
292 PickleValue::Int(v) => MsgpackValue::Int(*v),
293 PickleValue::Float(v) => MsgpackValue::Float(*v),
294 PickleValue::String(v) => MsgpackValue::Str(v.clone()),
295 PickleValue::Bytes(v) => MsgpackValue::Bin(v.clone()),
296 PickleValue::List(values) => {
297 MsgpackValue::Array(values.iter().map(pickle_to_msgpack).collect())
298 }
299 PickleValue::Dict(values) => MsgpackValue::Map(
300 values
301 .iter()
302 .map(|(key, value)| (pickle_to_msgpack(key), pickle_to_msgpack(value)))
303 .collect(),
304 ),
305 }
306}
307
308fn msgpack_to_pickle(value: &MsgpackValue) -> PickleValue {
309 match value {
310 MsgpackValue::Nil => PickleValue::None,
311 MsgpackValue::Bool(v) => PickleValue::Bool(*v),
312 MsgpackValue::UInt(v) if *v <= i64::MAX as u64 => PickleValue::Int(*v as i64),
313 MsgpackValue::UInt(v) => PickleValue::Float(*v as f64),
314 MsgpackValue::Int(v) => PickleValue::Int(*v),
315 MsgpackValue::Float(v) => PickleValue::Float(*v),
316 MsgpackValue::Bin(v) => PickleValue::Bytes(v.clone()),
317 MsgpackValue::Str(v) => PickleValue::String(v.clone()),
318 MsgpackValue::Array(values) => {
319 PickleValue::List(values.iter().map(msgpack_to_pickle).collect())
320 }
321 MsgpackValue::Map(values) => PickleValue::Dict(
322 values
323 .iter()
324 .map(|(key, value)| (msgpack_to_pickle(key), msgpack_to_pickle(value)))
325 .collect(),
326 ),
327 }
328}
329
330fn server_auth<S: Read + Write>(stream: &mut S, auth_key: &[u8; 32]) -> io::Result<()> {
332 let mut random_bytes = [0u8; CHALLENGE_LEN];
334 {
336 let mut f = std::fs::File::open("/dev/urandom")?;
337 f.read_exact(&mut random_bytes)?;
338 }
339
340 let mut challenge_message = Vec::with_capacity(CHALLENGE_PREFIX.len() + 8 + CHALLENGE_LEN);
341 challenge_message.extend_from_slice(CHALLENGE_PREFIX);
342 challenge_message.extend_from_slice(b"{sha256}");
343 challenge_message.extend_from_slice(&random_bytes);
344
345 send_bytes(stream, &challenge_message)?;
346
347 let response = recv_bytes(stream)?;
349
350 let message = &challenge_message[CHALLENGE_PREFIX.len()..];
353
354 if verify_response(auth_key, message, &response) {
355 send_bytes(stream, WELCOME)?;
356 Ok(())
357 } else {
358 send_bytes(stream, FAILURE)?;
359 Err(io::Error::new(
360 io::ErrorKind::PermissionDenied,
361 "auth failed",
362 ))
363 }
364}
365
366fn verify_response(auth_key: &[u8; 32], message: &[u8], response: &[u8]) -> bool {
368 if response.starts_with(b"{sha256}") {
370 let digest = &response[8..];
371 let expected = hmac_sha256(auth_key, message);
372 constant_time_eq(digest, &expected)
373 }
374 else if response.len() == 16 {
376 let expected = hmac_md5(auth_key, message);
377 constant_time_eq(response, &expected)
378 }
379 else if response.starts_with(b"{md5}") {
381 let digest = &response[5..];
382 let expected = hmac_md5(auth_key, message);
383 constant_time_eq(digest, &expected)
384 } else {
385 false
386 }
387}
388
389fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
391 if a.len() != b.len() {
392 return false;
393 }
394 let mut diff = 0u8;
395 for (x, y) in a.iter().zip(b.iter()) {
396 diff |= x ^ y;
397 }
398 diff == 0
399}
400
401fn send_bytes<S: Write>(stream: &mut S, data: &[u8]) -> io::Result<()> {
403 let len = data.len() as i32;
404 stream.write_all(&len.to_be_bytes())?;
405 stream.write_all(data)?;
406 stream.flush()
407}
408
409fn recv_bytes<S: Read>(stream: &mut S) -> io::Result<Vec<u8>> {
411 let mut len_buf = [0u8; 4];
412 stream.read_exact(&mut len_buf)?;
413 let len = i32::from_be_bytes(len_buf);
414
415 if len < 0 {
416 let mut len8_buf = [0u8; 8];
418 stream.read_exact(&mut len8_buf)?;
419 let len = u64::from_be_bytes(len8_buf) as usize;
420 if len > 64 * 1024 * 1024 {
421 return Err(io::Error::new(
422 io::ErrorKind::InvalidData,
423 "message too large",
424 ));
425 }
426 let mut buf = vec![0u8; len];
427 stream.read_exact(&mut buf)?;
428 Ok(buf)
429 } else {
430 let len = len as usize;
431 if len > 64 * 1024 * 1024 {
432 return Err(io::Error::new(
433 io::ErrorKind::InvalidData,
434 "message too large",
435 ));
436 }
437 let mut buf = vec![0u8; len];
438 stream.read_exact(&mut buf)?;
439 Ok(buf)
440 }
441}
442
443const DEFAULT_PROBE_PAYLOAD_SIZE: usize = 16;
445
446fn send_probe_error_pickle(err: crate::event::SendProbeError) -> PickleValue {
451 use crate::event::SendProbeError;
452 let code = match err {
453 SendProbeError::UnknownIdentity => return PickleValue::None,
454 SendProbeError::PayloadTooLarge { .. } => "payload_too_large",
455 SendProbeError::EncryptFailed => "encrypt_failed",
456 SendProbeError::PackFailed => "pack_failed",
457 };
458 let mut entries = vec![(
459 PickleValue::String("error".into()),
460 PickleValue::String(code.into()),
461 )];
462 if let SendProbeError::PayloadTooLarge { max } = err {
463 entries.push((
464 PickleValue::String("max_size".into()),
465 PickleValue::Int(max as i64),
466 ));
467 }
468 PickleValue::Dict(entries)
469}
470
471fn handle_rpc_request(request: &PickleValue, event_tx: &EventSender) -> io::Result<PickleValue> {
473 if let Some(operation) = request.get("manage").and_then(PickleValue::as_str) {
474 let operation = match operation {
475 "attach_interface" => crate::event::InterfaceManagementOperation::Attach,
476 "detach_interface" => crate::event::InterfaceManagementOperation::Detach,
477 "reload_interface" => crate::event::InterfaceManagementOperation::Reload,
478 _ => return Ok(PickleValue::None),
479 };
480 let name = request
481 .get("name")
482 .and_then(PickleValue::as_str)
483 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "missing interface name"))?;
484 let result = send_query(
485 event_tx,
486 QueryRequest::ManageInterface {
487 operation,
488 name: name.to_string(),
489 },
490 )?;
491 return Ok(match result {
492 QueryResponse::InterfaceManagement(Some(value)) => PickleValue::Bool(value),
493 _ => PickleValue::None,
494 });
495 }
496 if let Some(get_val) = request.get("get") {
498 if let Some(path) = get_val.as_str() {
499 return match path {
500 "interface_stats" => {
501 let resp = send_query(event_tx, QueryRequest::InterfaceStats)?;
502 if let QueryResponse::InterfaceStats(stats) = resp {
503 let mut response = interface_stats_to_pickle(&stats);
504 add_inbound_runtime_stats(&mut response, &stats, event_tx);
505 Ok(response)
506 } else {
507 Ok(PickleValue::None)
508 }
509 }
510 "profiling_results" => {
511 Ok(crate::profiling::results_pickle().unwrap_or(PickleValue::None))
512 }
513 "path_table" => {
514 let max_hops = request
515 .get("max_hops")
516 .and_then(|v| v.as_int().map(|n| n as u8));
517 let resp = send_query(event_tx, QueryRequest::PathTable { max_hops })?;
518 if let QueryResponse::PathTable(entries) = resp {
519 Ok(path_table_to_pickle(&entries))
520 } else {
521 Ok(PickleValue::None)
522 }
523 }
524 "rate_table" => {
525 let resp = send_query(event_tx, QueryRequest::RateTable)?;
526 if let QueryResponse::RateTable(entries) = resp {
527 Ok(rate_table_to_pickle(&entries))
528 } else {
529 Ok(PickleValue::None)
530 }
531 }
532 "lowest_interface_bitrate" => {
533 let resp = send_query(event_tx, QueryRequest::LowestInterfaceBitrate)?;
534 if let QueryResponse::LowestInterfaceBitrate(Some(bitrate)) = resp {
535 Ok(PickleValue::Int(bitrate as i64))
536 } else {
537 Ok(PickleValue::None)
538 }
539 }
540 "medium_path_timeout" => {
541 let resp = send_query(event_tx, QueryRequest::MediumPathTimeout)?;
542 if let QueryResponse::MediumPathTimeout(timeout) = resp {
543 Ok(PickleValue::Float(timeout))
544 } else {
545 Ok(PickleValue::Int(0))
546 }
547 }
548 "first_hop_timeout" => {
549 let hash = extract_dest_hash(request, "destination_hash")?;
550 let resp =
551 send_query(event_tx, QueryRequest::FirstHopTimeout { dest_hash: hash })?;
552 if let QueryResponse::FirstHopTimeout(timeout) = resp {
553 Ok(PickleValue::Float(timeout))
554 } else {
555 Ok(PickleValue::Float(
556 rns_core::constants::LINK_ESTABLISHMENT_TIMEOUT_PER_HOP,
557 ))
558 }
559 }
560 "next_hop" => {
561 let hash = extract_dest_hash(request, "destination_hash")?;
562 let resp = send_query(event_tx, QueryRequest::NextHop { dest_hash: hash })?;
563 if let QueryResponse::NextHop(Some(nh)) = resp {
564 Ok(PickleValue::Bytes(nh.next_hop.to_vec()))
565 } else {
566 Ok(PickleValue::None)
567 }
568 }
569 "next_hop_if_name" => {
570 let hash = extract_dest_hash(request, "destination_hash")?;
571 let resp =
572 send_query(event_tx, QueryRequest::NextHopIfName { dest_hash: hash })?;
573 if let QueryResponse::NextHopIfName(Some(name)) = resp {
574 Ok(PickleValue::String(name))
575 } else {
576 Ok(PickleValue::None)
577 }
578 }
579 "link_count" => {
580 let resp = send_query(event_tx, QueryRequest::LinkCount)?;
581 if let QueryResponse::LinkCount(n) = resp {
582 Ok(PickleValue::Int(n as i64))
583 } else {
584 Ok(PickleValue::None)
585 }
586 }
587 "active_link_count" => {
588 let resp = send_query(event_tx, QueryRequest::ActiveLinkCount)?;
589 if let QueryResponse::ActiveLinkCount(n) = resp {
590 Ok(PickleValue::Int(n as i64))
591 } else {
592 Ok(PickleValue::None)
593 }
594 }
595 "transport_identity" => {
596 let resp = send_query(event_tx, QueryRequest::TransportIdentity)?;
597 if let QueryResponse::TransportIdentity(Some(hash)) = resp {
598 Ok(PickleValue::Bytes(hash.to_vec()))
599 } else {
600 Ok(PickleValue::None)
601 }
602 }
603 "blackholed" => {
604 let resp = send_query(event_tx, QueryRequest::GetBlackholed)?;
605 if let QueryResponse::Blackholed(entries) = resp {
606 Ok(blackholed_to_pickle(&entries))
607 } else {
608 Ok(PickleValue::None)
609 }
610 }
611 "discovered_interfaces" => {
612 let only_available = request
613 .get("only_available")
614 .and_then(|v| v.as_bool())
615 .unwrap_or(false);
616 let only_transport = request
617 .get("only_transport")
618 .and_then(|v| v.as_bool())
619 .unwrap_or(false);
620 let resp = send_query(
621 event_tx,
622 QueryRequest::DiscoveredInterfaces {
623 only_available,
624 only_transport,
625 },
626 )?;
627 if let QueryResponse::DiscoveredInterfaces(interfaces) = resp {
628 Ok(discovered_interfaces_to_pickle(&interfaces))
629 } else {
630 Ok(PickleValue::None)
631 }
632 }
633 "hooks" => {
634 let (response_tx, response_rx) = mpsc::channel();
635 try_send_event(event_tx, Event::ListHooks { response_tx })?;
636 let hooks = response_rx
637 .recv_timeout(std::time::Duration::from_secs(5))
638 .map_err(|_| {
639 io::Error::new(io::ErrorKind::TimedOut, "list hooks timed out")
640 })?;
641 Ok(hooks_to_pickle(&hooks))
642 }
643 "runtime_config" => {
644 let resp = send_query(event_tx, QueryRequest::ListRuntimeConfig)?;
645 if let QueryResponse::RuntimeConfigList(entries) = resp {
646 Ok(runtime_config_list_to_pickle(&entries))
647 } else {
648 Ok(PickleValue::None)
649 }
650 }
651 "known_destinations" => {
652 let resp = send_query(event_tx, QueryRequest::KnownDestinations)?;
653 if let QueryResponse::KnownDestinations(entries) = resp {
654 Ok(known_destinations_to_pickle(&entries))
655 } else {
656 Ok(PickleValue::None)
657 }
658 }
659 "runtime_config_entry" => {
660 let key = request
661 .get("key")
662 .and_then(|v| v.as_str())
663 .unwrap_or_default()
664 .to_string();
665 let resp = send_query(event_tx, QueryRequest::GetRuntimeConfig { key })?;
666 if let QueryResponse::RuntimeConfigEntry(entry) = resp {
667 Ok(entry
668 .as_ref()
669 .map(runtime_config_entry_to_pickle)
670 .unwrap_or(PickleValue::None))
671 } else {
672 Ok(PickleValue::None)
673 }
674 }
675 "backbone_peer_state" => {
676 let interface_name = request
677 .get("interface")
678 .and_then(|v| v.as_str())
679 .map(|s| s.to_string());
680 let resp =
681 send_query(event_tx, QueryRequest::BackbonePeerState { interface_name })?;
682 if let QueryResponse::BackbonePeerState(entries) = resp {
683 Ok(backbone_peer_state_to_pickle(&entries))
684 } else {
685 Ok(PickleValue::None)
686 }
687 }
688 "backbone_interfaces" => {
689 let resp = send_query(event_tx, QueryRequest::BackboneInterfaces)?;
690 if let QueryResponse::BackboneInterfaces(entries) = resp {
691 Ok(backbone_interfaces_to_pickle(&entries))
692 } else {
693 Ok(PickleValue::None)
694 }
695 }
696 "provider_bridge_stats" => {
697 let resp = send_query(event_tx, QueryRequest::ProviderBridgeStats)?;
698 if let QueryResponse::ProviderBridgeStats(stats) = resp {
699 Ok(stats
700 .as_ref()
701 .map(provider_bridge_stats_to_pickle)
702 .unwrap_or(PickleValue::None))
703 } else {
704 Ok(PickleValue::None)
705 }
706 }
707 "drain_status" => {
708 let resp = send_query(event_tx, QueryRequest::DrainStatus)?;
709 if let QueryResponse::DrainStatus(status) = resp {
710 Ok(drain_status_to_pickle(&status))
711 } else {
712 Ok(PickleValue::None)
713 }
714 }
715 _ => Ok(PickleValue::None),
716 };
717 }
718 }
719
720 if let Some(begin_val) = request.get("begin_drain") {
721 let timeout_secs = begin_val
722 .as_float()
723 .or_else(|| begin_val.as_int().map(|value| value as f64))
724 .unwrap_or(0.0)
725 .max(0.0);
726 let timeout = Duration::from_secs_f64(timeout_secs);
727 try_send_event(event_tx, Event::BeginDrain { timeout })?;
728 return Ok(PickleValue::Bool(true));
729 }
730
731 if let Some(set_val) = request.get("set").and_then(|v| v.as_str()) {
732 if set_val == "known_destination_retained" {
733 let dest_hash = extract_dest_hash(request, "dest_hash")?;
734 let resp = send_query(event_tx, QueryRequest::RetainKnownDestination { dest_hash })?;
735 return if let QueryResponse::RetainKnownDestination(ok) = resp {
736 Ok(PickleValue::Bool(ok))
737 } else {
738 Ok(PickleValue::None)
739 };
740 }
741 if set_val == "identity_retained" {
742 let identity_hash = extract_dest_hash(request, "identity_hash")?;
743 let resp = send_query(event_tx, QueryRequest::RetainIdentity { identity_hash })?;
744 return if let QueryResponse::RetainIdentity(ok) = resp {
745 Ok(PickleValue::Bool(ok))
746 } else {
747 Ok(PickleValue::None)
748 };
749 }
750 if set_val == "known_destination_used" {
751 let dest_hash = extract_dest_hash(request, "dest_hash")?;
752 let resp = send_query(
753 event_tx,
754 QueryRequest::MarkKnownDestinationUsed { dest_hash },
755 )?;
756 return if let QueryResponse::MarkKnownDestinationUsed(ok) = resp {
757 Ok(PickleValue::Bool(ok))
758 } else {
759 Ok(PickleValue::None)
760 };
761 }
762 if set_val == "runtime_config" {
763 let key = request
764 .get("key")
765 .and_then(|v| v.as_str())
766 .unwrap_or_default()
767 .to_string();
768 let Some(value) = request
769 .get("value")
770 .and_then(runtime_config_value_from_pickle)
771 else {
772 return Ok(runtime_config_error_to_pickle(&RuntimeConfigError {
773 code: RuntimeConfigErrorCode::InvalidType,
774 message: "runtime-config set requires a scalar value".into(),
775 }));
776 };
777 let resp = send_query(event_tx, QueryRequest::SetRuntimeConfig { key, value })?;
778 return if let QueryResponse::RuntimeConfigSet(result) = resp {
779 Ok(runtime_config_result_to_pickle(result))
780 } else {
781 Ok(PickleValue::None)
782 };
783 }
784 }
785
786 if let Some(reset_val) = request.get("reset").and_then(|v| v.as_str()) {
787 if reset_val == "runtime_config" {
788 let key = request
789 .get("key")
790 .and_then(|v| v.as_str())
791 .unwrap_or_default()
792 .to_string();
793 let resp = send_query(event_tx, QueryRequest::ResetRuntimeConfig { key })?;
794 return if let QueryResponse::RuntimeConfigReset(result) = resp {
795 Ok(runtime_config_result_to_pickle(result))
796 } else {
797 Ok(PickleValue::None)
798 };
799 }
800 }
801
802 if let Some(clear_val) = request.get("clear").and_then(|v| v.as_str()) {
803 if clear_val == "known_destination_retained" {
804 let dest_hash = extract_dest_hash(request, "dest_hash")?;
805 let resp = send_query(
806 event_tx,
807 QueryRequest::UnretainKnownDestination { dest_hash },
808 )?;
809 return if let QueryResponse::UnretainKnownDestination(ok) = resp {
810 Ok(PickleValue::Bool(ok))
811 } else {
812 Ok(PickleValue::None)
813 };
814 }
815 if clear_val == "backbone_peer_state" {
816 let interface_name = required_string(request, "interface")?;
817 let peer_ip = required_string(request, "ip")?;
818 let peer_ip = peer_ip
819 .parse()
820 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid peer IP"))?;
821 let resp = send_query(
822 event_tx,
823 QueryRequest::ClearBackbonePeerState {
824 interface_name,
825 peer_ip,
826 },
827 )?;
828 return if let QueryResponse::ClearBackbonePeerState(ok) = resp {
829 Ok(PickleValue::Bool(ok))
830 } else {
831 Ok(PickleValue::None)
832 };
833 }
834 }
835
836 if let Some(set_val) = request.get("set").and_then(|v| v.as_str()) {
837 if set_val == "backbone_peer_blacklist" {
838 let interface_name = required_string(request, "interface")?;
839 let peer_ip = required_string(request, "ip")?;
840 let peer_ip: IpAddr = peer_ip
841 .parse()
842 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid peer IP"))?;
843 let duration_secs = request
844 .get("duration_secs")
845 .and_then(|v| v.as_int())
846 .ok_or_else(|| {
847 io::Error::new(io::ErrorKind::InvalidInput, "missing duration_secs")
848 })?;
849 let reason = request
850 .get("reason")
851 .and_then(|v| v.as_str())
852 .unwrap_or("sentinel blacklist")
853 .to_string();
854 let penalty_level = request
855 .get("penalty_level")
856 .and_then(|v| v.as_int())
857 .unwrap_or(0)
858 .clamp(0, u8::MAX as i64) as u8;
859 let resp = send_query(
860 event_tx,
861 QueryRequest::BlacklistBackbonePeer {
862 interface_name,
863 peer_ip,
864 duration: Duration::from_secs(duration_secs as u64),
865 reason,
866 penalty_level,
867 },
868 )?;
869 return if let QueryResponse::BlacklistBackbonePeer(ok) = resp {
870 Ok(PickleValue::Bool(ok))
871 } else {
872 Ok(PickleValue::None)
873 };
874 }
875 }
876
877 if let Some(hash_val) = request.get("request_path") {
879 if let Some(hash_bytes) = hash_val.as_bytes() {
880 if hash_bytes.len() >= 16 {
881 let mut dest_hash = [0u8; 16];
882 dest_hash.copy_from_slice(&hash_bytes[..16]);
883 try_send_event(event_tx, crate::event::Event::RequestPath { dest_hash })?;
884 return Ok(PickleValue::Bool(true));
885 }
886 }
887 }
888
889 if let Some(hash_val) = request.get("send_probe") {
891 if let Some(hash_bytes) = hash_val.as_bytes() {
892 if hash_bytes.len() >= 16 {
893 let mut dest_hash = [0u8; 16];
894 dest_hash.copy_from_slice(&hash_bytes[..16]);
895 let payload_size = match request.get("size").and_then(|v| v.as_int()) {
899 Some(n) if n > 0 => usize::try_from(n).unwrap_or(usize::MAX),
900 _ => DEFAULT_PROBE_PAYLOAD_SIZE,
901 };
902 if payload_size > crate::event::MAX_PROBE_PAYLOAD_SIZE {
903 return Ok(send_probe_error_pickle(
904 crate::event::SendProbeError::PayloadTooLarge {
905 max: crate::event::MAX_PROBE_PAYLOAD_SIZE,
906 },
907 ));
908 }
909 let resp = send_query(
910 event_tx,
911 QueryRequest::SendProbe {
912 dest_hash,
913 payload_size,
914 },
915 )?;
916 return Ok(match resp {
917 QueryResponse::SendProbe(Ok((packet_hash, hops))) => PickleValue::Dict(vec![
918 (
919 PickleValue::String("packet_hash".into()),
920 PickleValue::Bytes(packet_hash.to_vec()),
921 ),
922 (
923 PickleValue::String("hops".into()),
924 PickleValue::Int(hops as i64),
925 ),
926 ]),
927 QueryResponse::SendProbe(Err(err)) => send_probe_error_pickle(err),
928 _ => PickleValue::None,
929 });
930 }
931 }
932 }
933
934 if let Some(hash_val) = request.get("check_proof") {
936 if let Some(hash_bytes) = hash_val.as_bytes() {
937 if hash_bytes.len() >= 32 {
938 let mut packet_hash = [0u8; 32];
939 packet_hash.copy_from_slice(&hash_bytes[..32]);
940 let resp = send_query(event_tx, QueryRequest::CheckProof { packet_hash })?;
941 if let QueryResponse::CheckProof(Some(rtt)) = resp {
942 return Ok(PickleValue::Float(rtt));
943 } else {
944 return Ok(PickleValue::None);
945 }
946 }
947 }
948 }
949
950 if let Some(hash_val) = request.get("blackhole") {
952 if let Some(hash_bytes) = hash_val.as_bytes() {
953 if hash_bytes.len() >= 16 {
954 let mut identity_hash = [0u8; 16];
955 identity_hash.copy_from_slice(&hash_bytes[..16]);
956 let duration_hours = request.get("duration").and_then(|v| v.as_float());
957 let reason = request
958 .get("reason")
959 .and_then(|v| v.as_str())
960 .map(|s| s.to_string());
961 let resp = send_query(
962 event_tx,
963 QueryRequest::BlackholeIdentity {
964 identity_hash,
965 duration_hours,
966 reason,
967 },
968 )?;
969 return Ok(PickleValue::Bool(matches!(
970 resp,
971 QueryResponse::BlackholeResult(true)
972 )));
973 }
974 }
975 }
976
977 if let Some(hash_val) = request.get("unblackhole") {
979 if let Some(hash_bytes) = hash_val.as_bytes() {
980 if hash_bytes.len() >= 16 {
981 let mut identity_hash = [0u8; 16];
982 identity_hash.copy_from_slice(&hash_bytes[..16]);
983 let resp = send_query(
984 event_tx,
985 QueryRequest::UnblackholeIdentity { identity_hash },
986 )?;
987 return Ok(PickleValue::Bool(matches!(
988 resp,
989 QueryResponse::UnblackholeResult(true)
990 )));
991 }
992 }
993 }
994
995 if let Some(drop_val) = request.get("drop") {
997 if let Some(path) = drop_val.as_str() {
998 return match path {
999 "path" => {
1000 let hash = extract_dest_hash(request, "destination_hash")?;
1001 let resp = send_query(event_tx, QueryRequest::DropPath { dest_hash: hash })?;
1002 if let QueryResponse::DropPath(ok) = resp {
1003 Ok(PickleValue::Bool(ok))
1004 } else {
1005 Ok(PickleValue::None)
1006 }
1007 }
1008 "all_via" => {
1009 let hash = extract_dest_hash(request, "destination_hash")?;
1010 let resp = send_query(
1011 event_tx,
1012 QueryRequest::DropAllVia {
1013 transport_hash: hash,
1014 },
1015 )?;
1016 if let QueryResponse::DropAllVia(n) = resp {
1017 Ok(PickleValue::Int(n as i64))
1018 } else {
1019 Ok(PickleValue::None)
1020 }
1021 }
1022 "announce_queues" => {
1023 let resp = send_query(event_tx, QueryRequest::DropAnnounceQueues)?;
1024 if let QueryResponse::DropAnnounceQueues = resp {
1025 Ok(PickleValue::Bool(true))
1026 } else {
1027 Ok(PickleValue::None)
1028 }
1029 }
1030 _ => Ok(PickleValue::None),
1031 };
1032 }
1033 }
1034
1035 if let Some(hook_val) = request.get("hook").and_then(|v| v.as_str()) {
1036 return handle_hook_rpc_request(hook_val, request, event_tx);
1037 }
1038
1039 Ok(PickleValue::None)
1040}
1041
1042fn handle_hook_rpc_request(
1043 op: &str,
1044 request: &PickleValue,
1045 event_tx: &EventSender,
1046) -> io::Result<PickleValue> {
1047 match op {
1048 "load" => {
1049 let name = required_string(request, "name")?;
1050 let attach_point = required_string(request, "attach_point")?;
1051 let priority = request
1052 .get("priority")
1053 .and_then(|v| v.as_int())
1054 .unwrap_or(0) as i32;
1055 let wasm = request
1056 .get("wasm")
1057 .and_then(|v| v.as_bytes())
1058 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing wasm"))?
1059 .to_vec();
1060 let (response_tx, response_rx) = mpsc::channel();
1061 try_send_event(
1062 event_tx,
1063 Event::LoadHook {
1064 name,
1065 wasm_bytes: wasm,
1066 attach_point,
1067 priority,
1068 response_tx,
1069 },
1070 )?;
1071 let response = response_rx
1072 .recv_timeout(std::time::Duration::from_secs(5))
1073 .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "hook load timed out"))?;
1074 Ok(hook_result_to_pickle(response))
1075 }
1076 "load_builtin" => {
1077 let name = required_string(request, "name")?;
1078 let attach_point = required_string(request, "attach_point")?;
1079 let builtin_id = required_string(request, "builtin_id")?;
1080 let priority = request
1081 .get("priority")
1082 .and_then(|v| v.as_int())
1083 .unwrap_or(0) as i32;
1084 let (response_tx, response_rx) = mpsc::channel();
1085 try_send_event(
1086 event_tx,
1087 Event::LoadBuiltinHook {
1088 name,
1089 builtin_id,
1090 attach_point,
1091 priority,
1092 response_tx,
1093 },
1094 )?;
1095 let response = response_rx
1096 .recv_timeout(std::time::Duration::from_secs(5))
1097 .map_err(|_| {
1098 io::Error::new(io::ErrorKind::TimedOut, "built-in hook load timed out")
1099 })?;
1100 Ok(hook_result_to_pickle(response))
1101 }
1102 "unload" => {
1103 let name = required_string(request, "name")?;
1104 let attach_point = required_string(request, "attach_point")?;
1105 let (response_tx, response_rx) = mpsc::channel();
1106 try_send_event(
1107 event_tx,
1108 Event::UnloadHook {
1109 name,
1110 attach_point,
1111 response_tx,
1112 },
1113 )?;
1114 let response = response_rx
1115 .recv_timeout(std::time::Duration::from_secs(5))
1116 .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "hook unload timed out"))?;
1117 Ok(hook_result_to_pickle(response))
1118 }
1119 "enable" | "disable" => {
1120 let name = required_string(request, "name")?;
1121 let attach_point = required_string(request, "attach_point")?;
1122 let enabled = op == "enable";
1123 let (response_tx, response_rx) = mpsc::channel();
1124 try_send_event(
1125 event_tx,
1126 Event::SetHookEnabled {
1127 name,
1128 attach_point,
1129 enabled,
1130 response_tx,
1131 },
1132 )?;
1133 let response = response_rx
1134 .recv_timeout(std::time::Duration::from_secs(5))
1135 .map_err(|_| {
1136 io::Error::new(io::ErrorKind::TimedOut, "hook enable/disable timed out")
1137 })?;
1138 Ok(hook_result_to_pickle(response))
1139 }
1140 "set_priority" => {
1141 let name = required_string(request, "name")?;
1142 let attach_point = required_string(request, "attach_point")?;
1143 let priority = request
1144 .get("priority")
1145 .and_then(|v| v.as_int())
1146 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing priority"))?
1147 as i32;
1148 let (response_tx, response_rx) = mpsc::channel();
1149 try_send_event(
1150 event_tx,
1151 Event::SetHookPriority {
1152 name,
1153 attach_point,
1154 priority,
1155 response_tx,
1156 },
1157 )?;
1158 let response = response_rx
1159 .recv_timeout(std::time::Duration::from_secs(5))
1160 .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "hook priority timed out"))?;
1161 Ok(hook_result_to_pickle(response))
1162 }
1163 _ => Ok(PickleValue::None),
1164 }
1165}
1166
1167fn send_query(event_tx: &EventSender, request: QueryRequest) -> io::Result<QueryResponse> {
1169 let (resp_tx, resp_rx) = mpsc::channel();
1170 try_send_event(event_tx, Event::query(request, resp_tx))?;
1171 resp_rx
1172 .recv_timeout(std::time::Duration::from_secs(5))
1173 .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "query timed out"))
1174}
1175
1176fn try_send_event(event_tx: &EventSender, event: Event) -> io::Result<()> {
1177 event_tx.try_send(event).map_err(|error| match error {
1178 mpsc::TrySendError::Full(_) => {
1179 io::Error::new(io::ErrorKind::WouldBlock, "driver event queue is full")
1180 }
1181 mpsc::TrySendError::Disconnected(_) => {
1182 io::Error::new(io::ErrorKind::BrokenPipe, "driver shut down")
1183 }
1184 })
1185}
1186
1187fn extract_dest_hash(request: &PickleValue, key: &str) -> io::Result<[u8; 16]> {
1189 let bytes = request
1190 .get(key)
1191 .and_then(|v| v.as_bytes())
1192 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing destination_hash"))?;
1193 if bytes.len() < 16 {
1194 return Err(io::Error::new(io::ErrorKind::InvalidData, "hash too short"));
1195 }
1196 let mut hash = [0u8; 16];
1197 hash.copy_from_slice(&bytes[..16]);
1198 Ok(hash)
1199}
1200
1201fn required_string(request: &PickleValue, key: &str) -> io::Result<String> {
1202 request
1203 .get(key)
1204 .and_then(|v| v.as_str())
1205 .map(|s| s.to_string())
1206 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("missing {}", key)))
1207}
1208
1209fn hook_result_to_pickle(result: Result<(), String>) -> PickleValue {
1210 match result {
1211 Ok(()) => PickleValue::Dict(vec![(
1212 PickleValue::String("ok".into()),
1213 PickleValue::Bool(true),
1214 )]),
1215 Err(error) => PickleValue::Dict(vec![
1216 (PickleValue::String("ok".into()), PickleValue::Bool(false)),
1217 (
1218 PickleValue::String("error".into()),
1219 PickleValue::String(error),
1220 ),
1221 ]),
1222 }
1223}
1224
1225fn interface_stats_to_pickle(stats: &InterfaceStatsResponse) -> PickleValue {
1228 let mut ifaces = Vec::new();
1229 for iface in &stats.interfaces {
1230 ifaces.push(single_iface_to_pickle(iface));
1231 }
1232
1233 let mut dict = vec![
1234 (
1235 PickleValue::String("interfaces".into()),
1236 PickleValue::List(ifaces),
1237 ),
1238 (
1239 PickleValue::String("transport_enabled".into()),
1240 PickleValue::Bool(stats.transport_enabled),
1241 ),
1242 (
1243 PickleValue::String("transport_uptime".into()),
1244 PickleValue::Float(stats.transport_uptime),
1245 ),
1246 (
1247 PickleValue::String("rxb".into()),
1248 PickleValue::Int(stats.total_rxb as i64),
1249 ),
1250 (
1251 PickleValue::String("txb".into()),
1252 PickleValue::Int(stats.total_txb as i64),
1253 ),
1254 (
1255 PickleValue::String("rxpps".into()),
1256 PickleValue::Float(stats.rx_pps),
1257 ),
1258 (
1259 PickleValue::String("txpps".into()),
1260 PickleValue::Float(stats.tx_pps),
1261 ),
1262 (
1263 PickleValue::String("rxs".into()),
1264 PickleValue::Float(stats.traffic.rxs),
1265 ),
1266 (
1267 PickleValue::String("txs".into()),
1268 PickleValue::Float(stats.traffic.txs),
1269 ),
1270 (
1271 PickleValue::String("arxb".into()),
1272 PickleValue::Int(stats.traffic.arxb as i64),
1273 ),
1274 (
1275 PickleValue::String("atxb".into()),
1276 PickleValue::Int(stats.traffic.atxb as i64),
1277 ),
1278 (
1279 PickleValue::String("arxc".into()),
1280 PickleValue::Int(stats.traffic.arxc as i64),
1281 ),
1282 (
1283 PickleValue::String("atxc".into()),
1284 PickleValue::Int(stats.traffic.atxc as i64),
1285 ),
1286 (
1287 PickleValue::String("arxs".into()),
1288 PickleValue::Float(stats.traffic.arxs),
1289 ),
1290 (
1291 PickleValue::String("atxs".into()),
1292 PickleValue::Float(stats.traffic.atxs),
1293 ),
1294 (
1295 PickleValue::String("prxb".into()),
1296 PickleValue::Int(stats.traffic.prxb as i64),
1297 ),
1298 (
1299 PickleValue::String("ptxb".into()),
1300 PickleValue::Int(stats.traffic.ptxb as i64),
1301 ),
1302 (
1303 PickleValue::String("prxc".into()),
1304 PickleValue::Int(stats.traffic.prxc as i64),
1305 ),
1306 (
1307 PickleValue::String("ptxc".into()),
1308 PickleValue::Int(stats.traffic.ptxc as i64),
1309 ),
1310 (
1311 PickleValue::String("prxs".into()),
1312 PickleValue::Float(stats.traffic.prxs),
1313 ),
1314 (
1315 PickleValue::String("ptxs".into()),
1316 PickleValue::Float(stats.traffic.ptxs),
1317 ),
1318 (
1319 PickleValue::String("arxf".into()),
1320 PickleValue::Float(stats.traffic.arxf),
1321 ),
1322 (
1323 PickleValue::String("atxf".into()),
1324 PickleValue::Float(stats.traffic.atxf),
1325 ),
1326 (
1327 PickleValue::String("prxf".into()),
1328 PickleValue::Float(stats.traffic.prxf),
1329 ),
1330 (
1331 PickleValue::String("ptxf".into()),
1332 PickleValue::Float(stats.traffic.ptxf),
1333 ),
1334 ];
1335
1336 if let Some(tid) = stats.transport_id {
1337 dict.push((
1338 PickleValue::String("transport_id".into()),
1339 PickleValue::Bytes(tid.to_vec()),
1340 ));
1341 } else {
1342 dict.push((
1343 PickleValue::String("transport_id".into()),
1344 PickleValue::None,
1345 ));
1346 }
1347
1348 if let Some(pr) = stats.probe_responder {
1349 dict.push((
1350 PickleValue::String("probe_responder".into()),
1351 PickleValue::Bytes(pr.to_vec()),
1352 ));
1353 } else {
1354 dict.push((
1355 PickleValue::String("probe_responder".into()),
1356 PickleValue::None,
1357 ));
1358 }
1359
1360 if let Some(pool) = &stats.backbone_peer_pool {
1361 let members = pool
1362 .members
1363 .iter()
1364 .map(|member| {
1365 let mut member_dict = vec![
1366 (
1367 PickleValue::String("name".into()),
1368 PickleValue::String(member.name.clone()),
1369 ),
1370 (
1371 PickleValue::String("remote".into()),
1372 PickleValue::String(member.remote.clone()),
1373 ),
1374 (
1375 PickleValue::String("source".into()),
1376 PickleValue::String(member.source.clone()),
1377 ),
1378 (
1379 PickleValue::String("priority".into()),
1380 PickleValue::Int(member.priority as i64),
1381 ),
1382 (
1383 PickleValue::String("state".into()),
1384 PickleValue::String(member.state.clone()),
1385 ),
1386 (
1387 PickleValue::String("failure_count".into()),
1388 PickleValue::Int(member.failure_count as i64),
1389 ),
1390 ];
1391 member_dict.push((
1392 PickleValue::String("interface_id".into()),
1393 member
1394 .interface_id
1395 .map(|id| PickleValue::Int(id as i64))
1396 .unwrap_or(PickleValue::None),
1397 ));
1398 member_dict.push((
1399 PickleValue::String("last_error".into()),
1400 member
1401 .last_error
1402 .as_ref()
1403 .map(|err| PickleValue::String(err.clone()))
1404 .unwrap_or(PickleValue::None),
1405 ));
1406 member_dict.push((
1407 PickleValue::String("cooldown_remaining_seconds".into()),
1408 member
1409 .cooldown_remaining_seconds
1410 .map(PickleValue::Float)
1411 .unwrap_or(PickleValue::None),
1412 ));
1413 PickleValue::Dict(member_dict)
1414 })
1415 .collect();
1416 dict.push((
1417 PickleValue::String("backbone_peer_pool".into()),
1418 PickleValue::Dict(vec![
1419 (
1420 PickleValue::String("max_connected".into()),
1421 PickleValue::Int(pool.max_connected as i64),
1422 ),
1423 (
1424 PickleValue::String("active_count".into()),
1425 PickleValue::Int(pool.active_count as i64),
1426 ),
1427 (
1428 PickleValue::String("standby_count".into()),
1429 PickleValue::Int(pool.standby_count as i64),
1430 ),
1431 (
1432 PickleValue::String("cooldown_count".into()),
1433 PickleValue::Int(pool.cooldown_count as i64),
1434 ),
1435 (
1436 PickleValue::String("members".into()),
1437 PickleValue::List(members),
1438 ),
1439 ]),
1440 ));
1441 } else {
1442 dict.push((
1443 PickleValue::String("backbone_peer_pool".into()),
1444 PickleValue::None,
1445 ));
1446 }
1447
1448 PickleValue::Dict(dict)
1449}
1450
1451fn add_inbound_runtime_stats(
1452 response: &mut PickleValue,
1453 stats: &InterfaceStatsResponse,
1454 event_tx: &EventSender,
1455) {
1456 let PickleValue::Dict(dict) = response else {
1457 return;
1458 };
1459 if let Some((_, PickleValue::List(ifaces))) = dict
1460 .iter_mut()
1461 .find(|(key, _)| key.as_str() == Some("interfaces"))
1462 {
1463 for (iface, stat) in ifaces.iter_mut().zip(&stats.interfaces) {
1464 let PickleValue::Dict(iface_dict) = iface else {
1465 continue;
1466 };
1467 let burst_stats = if stat.interface_type == "BackboneInterface" {
1468 event_tx
1469 .dynamic_burst_stats(InterfaceId(stat.id))
1470 .or(Some(DynamicBurstStats {
1471 announce: BurstClassStats {
1472 count: 0,
1473 activated: None,
1474 },
1475 path_request: BurstClassStats {
1476 count: 0,
1477 activated: None,
1478 },
1479 }))
1480 } else {
1481 None
1482 };
1483 iface_dict.push((
1484 PickleValue::String("burst_count".into()),
1485 burst_stats
1486 .map(|stats| PickleValue::Int(stats.announce.count as i64))
1487 .unwrap_or(PickleValue::None),
1488 ));
1489 iface_dict.push((
1490 PickleValue::String("pr_burst_count".into()),
1491 burst_stats
1492 .map(|stats| PickleValue::Int(stats.path_request.count as i64))
1493 .unwrap_or(PickleValue::None),
1494 ));
1495 if let Some(stats) = burst_stats {
1496 set_pickle_dict_entry(
1497 iface_dict,
1498 "burst_active",
1499 PickleValue::Bool(stats.announce.count > 0),
1500 );
1501 set_pickle_dict_entry(
1502 iface_dict,
1503 "burst_activated",
1504 stats
1505 .announce
1506 .activated
1507 .map(PickleValue::Float)
1508 .unwrap_or(PickleValue::None),
1509 );
1510 set_pickle_dict_entry(
1511 iface_dict,
1512 "pr_burst_active",
1513 PickleValue::Bool(stats.path_request.count > 0),
1514 );
1515 set_pickle_dict_entry(
1516 iface_dict,
1517 "pr_burst_activated",
1518 stats
1519 .path_request
1520 .activated
1521 .map(PickleValue::Float)
1522 .unwrap_or(PickleValue::None),
1523 );
1524 }
1525 }
1526 }
1527
1528 let snapshot = event_tx.inbound_queue_snapshot();
1529 let total_height = snapshot.total_height();
1530 let total_capacity = snapshot.total_capacity();
1531 let total_dropped = snapshot.total_dropped();
1532 let pressure = |height: usize, capacity: usize| {
1533 if height == 0 {
1534 0.0
1535 } else {
1536 height as f64 / capacity as f64
1537 }
1538 };
1539 dict.extend([
1540 (
1541 PickleValue::String("rxqt".into()),
1542 PickleValue::Int(total_height as i64),
1543 ),
1544 (
1545 PickleValue::String("rxqd".into()),
1546 PickleValue::Int(snapshot.heights[0] as i64),
1547 ),
1548 (
1549 PickleValue::String("rxqa".into()),
1550 PickleValue::Int(snapshot.heights[1] as i64),
1551 ),
1552 (
1553 PickleValue::String("rxqp".into()),
1554 PickleValue::Int(snapshot.heights[2] as i64),
1555 ),
1556 (
1557 PickleValue::String("rxqil".into()),
1558 PickleValue::Int(snapshot.heights[3] as i64),
1559 ),
1560 (
1561 PickleValue::String("rxqtd".into()),
1562 PickleValue::Int(total_dropped as i64),
1563 ),
1564 (
1565 PickleValue::String("rxqdd".into()),
1566 PickleValue::Int(snapshot.dropped[0] as i64),
1567 ),
1568 (
1569 PickleValue::String("rxqad".into()),
1570 PickleValue::Int(snapshot.dropped[1] as i64),
1571 ),
1572 (
1573 PickleValue::String("rxqpd".into()),
1574 PickleValue::Int(snapshot.dropped[2] as i64),
1575 ),
1576 (
1577 PickleValue::String("rxqild".into()),
1578 PickleValue::Int(snapshot.dropped[3] as i64),
1579 ),
1580 (
1581 PickleValue::String("tqpressure".into()),
1582 PickleValue::Float(pressure(total_height, total_capacity)),
1583 ),
1584 (
1585 PickleValue::String("dqpressure".into()),
1586 PickleValue::Float(pressure(snapshot.heights[0], snapshot.capacities[0])),
1587 ),
1588 (
1589 PickleValue::String("aqpressure".into()),
1590 PickleValue::Float(pressure(snapshot.heights[1], snapshot.capacities[1])),
1591 ),
1592 (
1593 PickleValue::String("pqpressure".into()),
1594 PickleValue::Float(pressure(snapshot.heights[2], snapshot.capacities[2])),
1595 ),
1596 (
1597 PickleValue::String("ilqpressure".into()),
1598 PickleValue::Float(pressure(snapshot.heights[3], snapshot.capacities[3])),
1599 ),
1600 (PickleValue::String("txq".into()), PickleValue::None),
1601 ]);
1602}
1603
1604fn set_pickle_dict_entry(
1605 dict: &mut Vec<(PickleValue, PickleValue)>,
1606 key: &str,
1607 value: PickleValue,
1608) {
1609 if let Some((_, existing)) = dict
1610 .iter_mut()
1611 .find(|(candidate, _)| candidate.as_str() == Some(key))
1612 {
1613 *existing = value;
1614 } else {
1615 dict.push((PickleValue::String(key.into()), value));
1616 }
1617}
1618
1619fn single_iface_to_pickle(s: &SingleInterfaceStat) -> PickleValue {
1620 let mut dict = vec![
1621 (
1622 PickleValue::String("id".into()),
1623 PickleValue::Int(s.id as i64),
1624 ),
1625 (
1626 PickleValue::String("name".into()),
1627 PickleValue::String(s.name.clone()),
1628 ),
1629 (
1630 PickleValue::String("status".into()),
1631 PickleValue::Bool(s.status),
1632 ),
1633 (
1634 PickleValue::String("mode".into()),
1635 PickleValue::Int(s.mode as i64),
1636 ),
1637 (
1638 PickleValue::String("gravity".into()),
1639 PickleValue::Int(s.gravity),
1640 ),
1641 (
1642 PickleValue::String("announces_to_internal".into()),
1643 s.announces_to_internal
1644 .map(PickleValue::Bool)
1645 .unwrap_or(PickleValue::None),
1646 ),
1647 (
1648 PickleValue::String("rxb".into()),
1649 PickleValue::Int(s.rxb as i64),
1650 ),
1651 (
1652 PickleValue::String("txb".into()),
1653 PickleValue::Int(s.txb as i64),
1654 ),
1655 (
1656 PickleValue::String("txdrp".into()),
1657 PickleValue::Int(s.tx_drops as i64),
1658 ),
1659 (
1660 PickleValue::String("txdrb".into()),
1661 PickleValue::Int(s.tx_dropped_bytes as i64),
1662 ),
1663 (
1664 PickleValue::String("txstalled".into()),
1665 PickleValue::Bool(s.tx_stalled),
1666 ),
1667 (
1668 PickleValue::String("txbuffered".into()),
1669 PickleValue::Int(s.tx_buffered as i64),
1670 ),
1671 (
1672 PickleValue::String("rxs".into()),
1673 PickleValue::Float(s.traffic.rxs),
1674 ),
1675 (
1676 PickleValue::String("txs".into()),
1677 PickleValue::Float(s.traffic.txs),
1678 ),
1679 (
1680 PickleValue::String("arxb".into()),
1681 PickleValue::Int(s.traffic.arxb as i64),
1682 ),
1683 (
1684 PickleValue::String("atxb".into()),
1685 PickleValue::Int(s.traffic.atxb as i64),
1686 ),
1687 (
1688 PickleValue::String("arxc".into()),
1689 PickleValue::Int(s.traffic.arxc as i64),
1690 ),
1691 (
1692 PickleValue::String("atxc".into()),
1693 PickleValue::Int(s.traffic.atxc as i64),
1694 ),
1695 (
1696 PickleValue::String("arxs".into()),
1697 PickleValue::Float(s.traffic.arxs),
1698 ),
1699 (
1700 PickleValue::String("atxs".into()),
1701 PickleValue::Float(s.traffic.atxs),
1702 ),
1703 (
1704 PickleValue::String("prxb".into()),
1705 PickleValue::Int(s.traffic.prxb as i64),
1706 ),
1707 (
1708 PickleValue::String("ptxb".into()),
1709 PickleValue::Int(s.traffic.ptxb as i64),
1710 ),
1711 (
1712 PickleValue::String("prxc".into()),
1713 PickleValue::Int(s.traffic.prxc as i64),
1714 ),
1715 (
1716 PickleValue::String("ptxc".into()),
1717 PickleValue::Int(s.traffic.ptxc as i64),
1718 ),
1719 (
1720 PickleValue::String("prxs".into()),
1721 PickleValue::Float(s.traffic.prxs),
1722 ),
1723 (
1724 PickleValue::String("ptxs".into()),
1725 PickleValue::Float(s.traffic.ptxs),
1726 ),
1727 (
1728 PickleValue::String("protocol_violations".into()),
1729 PickleValue::Int(s.protocol_violations as i64),
1730 ),
1731 (
1732 PickleValue::String("ifac_violations".into()),
1733 PickleValue::Int(s.ifac_violations as i64),
1734 ),
1735 (
1736 PickleValue::String("packet_filter_hits".into()),
1737 PickleValue::Int(s.packet_filter_hits as i64),
1738 ),
1739 (
1740 PickleValue::String("rx_packets".into()),
1741 PickleValue::Int(s.rx_packets as i64),
1742 ),
1743 (
1744 PickleValue::String("tx_packets".into()),
1745 PickleValue::Int(s.tx_packets as i64),
1746 ),
1747 (
1748 PickleValue::String("cpu_load".into()),
1749 s.cpu_load
1750 .map(PickleValue::Float)
1751 .unwrap_or(PickleValue::None),
1752 ),
1753 (
1754 PickleValue::String("mem_load".into()),
1755 s.mem_load
1756 .map(PickleValue::Float)
1757 .unwrap_or(PickleValue::None),
1758 ),
1759 (
1760 PickleValue::String("switch_id".into()),
1761 s.switch_id
1762 .map(|v| PickleValue::Bytes(v.to_vec()))
1763 .unwrap_or(PickleValue::None),
1764 ),
1765 (
1766 PickleValue::String("endpoint_id".into()),
1767 s.endpoint_id
1768 .map(|v| PickleValue::Bytes(v.to_vec()))
1769 .unwrap_or(PickleValue::None),
1770 ),
1771 (
1772 PickleValue::String("via_switch_id".into()),
1773 s.via_switch_id
1774 .map(|v| PickleValue::Bytes(v.to_vec()))
1775 .unwrap_or(PickleValue::None),
1776 ),
1777 (
1778 PickleValue::String("peers".into()),
1779 s.peers
1780 .map(|v| PickleValue::Int(v as i64))
1781 .unwrap_or(PickleValue::None),
1782 ),
1783 (
1784 PickleValue::String("started".into()),
1785 PickleValue::Float(s.started),
1786 ),
1787 (
1788 PickleValue::String("ia_freq".into()),
1789 PickleValue::Float(s.ia_freq),
1790 ),
1791 (
1792 PickleValue::String("oa_freq".into()),
1793 PickleValue::Float(s.oa_freq),
1794 ),
1795 (
1796 PickleValue::String("ip_freq".into()),
1797 PickleValue::Float(s.ip_freq),
1798 ),
1799 (
1800 PickleValue::String("op_freq".into()),
1801 PickleValue::Float(s.op_freq),
1802 ),
1803 (
1804 PickleValue::String("burst_active".into()),
1805 PickleValue::Bool(s.burst_active),
1806 ),
1807 (
1808 PickleValue::String("burst_activated".into()),
1809 PickleValue::Float(s.burst_activated),
1810 ),
1811 (
1812 PickleValue::String("pr_burst_active".into()),
1813 PickleValue::Bool(s.pr_burst_active),
1814 ),
1815 (
1816 PickleValue::String("pr_burst_activated".into()),
1817 PickleValue::Float(s.pr_burst_activated),
1818 ),
1819 (
1820 PickleValue::String("clients".into()),
1821 s.clients
1822 .map(|clients| PickleValue::Int(clients as i64))
1823 .unwrap_or(PickleValue::None),
1824 ),
1825 (
1826 PickleValue::String("blocked_ips".into()),
1827 s.blocked_ips
1828 .map(|blocked| PickleValue::Int(blocked as i64))
1829 .unwrap_or(PickleValue::None),
1830 ),
1831 (
1832 PickleValue::String("blocked_ip_list".into()),
1833 s.blocked_ip_list
1834 .as_ref()
1835 .map(|blocked| {
1836 PickleValue::List(blocked.iter().cloned().map(PickleValue::String).collect())
1837 })
1838 .unwrap_or(PickleValue::None),
1839 ),
1840 (
1841 PickleValue::String("announce_rate_grace".into()),
1842 PickleValue::Int(s.announce_rate_grace as i64),
1843 ),
1844 (
1845 PickleValue::String("announce_rate_penalty".into()),
1846 PickleValue::Float(s.announce_rate_penalty),
1847 ),
1848 ];
1849
1850 match s.announce_rate_target {
1851 Some(target) => dict.push((
1852 PickleValue::String("announce_rate_target".into()),
1853 PickleValue::Float(target),
1854 )),
1855 None => dict.push((
1856 PickleValue::String("announce_rate_target".into()),
1857 PickleValue::None,
1858 )),
1859 }
1860
1861 match s.bitrate {
1862 Some(br) => dict.push((
1863 PickleValue::String("bitrate".into()),
1864 PickleValue::Int(br as i64),
1865 )),
1866 None => dict.push((PickleValue::String("bitrate".into()), PickleValue::None)),
1867 }
1868
1869 match s.mtu {
1870 Some(mtu) => dict.push((
1871 PickleValue::String("mtu".into()),
1872 PickleValue::Int(mtu as i64),
1873 )),
1874 None => dict.push((PickleValue::String("mtu".into()), PickleValue::None)),
1875 }
1876
1877 match s.ifac_size {
1878 Some(sz) => dict.push((
1879 PickleValue::String("ifac_size".into()),
1880 PickleValue::Int(sz as i64),
1881 )),
1882 None => dict.push((PickleValue::String("ifac_size".into()), PickleValue::None)),
1883 }
1884
1885 PickleValue::Dict(dict)
1886}
1887
1888fn path_table_to_pickle(entries: &[PathTableEntry]) -> PickleValue {
1889 let list: Vec<PickleValue> = entries
1890 .iter()
1891 .map(|e| {
1892 PickleValue::Dict(vec![
1893 (
1894 PickleValue::String("hash".into()),
1895 PickleValue::Bytes(e.hash.to_vec()),
1896 ),
1897 (
1898 PickleValue::String("timestamp".into()),
1899 PickleValue::Float(e.timestamp),
1900 ),
1901 (
1902 PickleValue::String("via".into()),
1903 PickleValue::Bytes(e.via.to_vec()),
1904 ),
1905 (
1906 PickleValue::String("hops".into()),
1907 PickleValue::Int(e.hops as i64),
1908 ),
1909 (
1910 PickleValue::String("expires".into()),
1911 PickleValue::Float(e.expires),
1912 ),
1913 (
1914 PickleValue::String("interface".into()),
1915 PickleValue::String(e.interface_name.clone()),
1916 ),
1917 ])
1918 })
1919 .collect();
1920 PickleValue::List(list)
1921}
1922
1923fn rate_table_to_pickle(entries: &[RateTableEntry]) -> PickleValue {
1924 let list: Vec<PickleValue> = entries
1925 .iter()
1926 .map(|e| {
1927 PickleValue::Dict(vec![
1928 (
1929 PickleValue::String("hash".into()),
1930 PickleValue::Bytes(e.hash.to_vec()),
1931 ),
1932 (
1933 PickleValue::String("last".into()),
1934 PickleValue::Float(e.last),
1935 ),
1936 (
1937 PickleValue::String("rate_violations".into()),
1938 PickleValue::Int(e.rate_violations as i64),
1939 ),
1940 (
1941 PickleValue::String("blocked_until".into()),
1942 PickleValue::Float(e.blocked_until),
1943 ),
1944 (
1945 PickleValue::String("timestamps".into()),
1946 PickleValue::List(
1947 e.timestamps
1948 .iter()
1949 .map(|&t| PickleValue::Float(t))
1950 .collect(),
1951 ),
1952 ),
1953 ])
1954 })
1955 .collect();
1956 PickleValue::List(list)
1957}
1958
1959fn blackholed_to_pickle(entries: &[BlackholeInfo]) -> PickleValue {
1960 let list: Vec<PickleValue> = entries
1961 .iter()
1962 .map(|e| {
1963 let mut dict = vec![
1964 (
1965 PickleValue::String("identity_hash".into()),
1966 PickleValue::Bytes(e.identity_hash.to_vec()),
1967 ),
1968 (
1969 PickleValue::String("created".into()),
1970 PickleValue::Float(e.created),
1971 ),
1972 (
1973 PickleValue::String("expires".into()),
1974 PickleValue::Float(e.expires),
1975 ),
1976 ];
1977 if let Some(ref reason) = e.reason {
1978 dict.push((
1979 PickleValue::String("reason".into()),
1980 PickleValue::String(reason.clone()),
1981 ));
1982 } else {
1983 dict.push((PickleValue::String("reason".into()), PickleValue::None));
1984 }
1985 PickleValue::Dict(dict)
1986 })
1987 .collect();
1988 PickleValue::List(list)
1989}
1990
1991fn discovered_interfaces_to_pickle(
1992 interfaces: &[crate::discovery::DiscoveredInterface],
1993) -> PickleValue {
1994 let list: Vec<PickleValue> = interfaces
1995 .iter()
1996 .map(|iface| {
1997 let mut dict = vec![
1998 (
1999 PickleValue::String("type".into()),
2000 PickleValue::String(iface.interface_type.clone()),
2001 ),
2002 (
2003 PickleValue::String("impl_name".into()),
2004 iface
2005 .impl_name
2006 .clone()
2007 .map(PickleValue::String)
2008 .unwrap_or(PickleValue::None),
2009 ),
2010 (
2011 PickleValue::String("version".into()),
2012 iface
2013 .impl_version
2014 .clone()
2015 .map(PickleValue::String)
2016 .unwrap_or(PickleValue::None),
2017 ),
2018 (
2019 PickleValue::String("transport".into()),
2020 PickleValue::Bool(iface.transport),
2021 ),
2022 (
2023 PickleValue::String("name".into()),
2024 PickleValue::String(iface.name.clone()),
2025 ),
2026 (
2027 PickleValue::String("discovered".into()),
2028 PickleValue::Float(iface.discovered),
2029 ),
2030 (
2031 PickleValue::String("last_heard".into()),
2032 PickleValue::Float(iface.last_heard),
2033 ),
2034 (
2035 PickleValue::String("heard_count".into()),
2036 PickleValue::Int(iface.heard_count as i64),
2037 ),
2038 (
2039 PickleValue::String("status".into()),
2040 PickleValue::String(iface.status.as_str().into()),
2041 ),
2042 (
2043 PickleValue::String("stamp".into()),
2044 PickleValue::Bytes(iface.stamp.clone()),
2045 ),
2046 (
2047 PickleValue::String("value".into()),
2048 PickleValue::Int(iface.stamp_value as i64),
2049 ),
2050 (
2051 PickleValue::String("transport_id".into()),
2052 PickleValue::Bytes(iface.transport_id.to_vec()),
2053 ),
2054 (
2055 PickleValue::String("network_id".into()),
2056 PickleValue::Bytes(iface.network_id.to_vec()),
2057 ),
2058 (
2059 PickleValue::String("hops".into()),
2060 PickleValue::Int(iface.hops as i64),
2061 ),
2062 ];
2063
2064 if let Some(v) = iface.latitude {
2066 dict.push((
2067 PickleValue::String("latitude".into()),
2068 PickleValue::Float(v),
2069 ));
2070 } else {
2071 dict.push((PickleValue::String("latitude".into()), PickleValue::None));
2072 }
2073 if let Some(v) = iface.longitude {
2074 dict.push((
2075 PickleValue::String("longitude".into()),
2076 PickleValue::Float(v),
2077 ));
2078 } else {
2079 dict.push((PickleValue::String("longitude".into()), PickleValue::None));
2080 }
2081 if let Some(v) = iface.height {
2082 dict.push((PickleValue::String("height".into()), PickleValue::Float(v)));
2083 } else {
2084 dict.push((PickleValue::String("height".into()), PickleValue::None));
2085 }
2086 if let Some(v) = iface.operator_lxmf_address {
2087 dict.push((
2088 PickleValue::String("operator_lxmf_address".into()),
2089 PickleValue::String(crate::discovery::hex_encode(&v)),
2090 ));
2091 }
2092
2093 if let Some(ref v) = iface.reachable_on {
2095 dict.push((
2096 PickleValue::String("reachable_on".into()),
2097 PickleValue::String(v.clone()),
2098 ));
2099 } else {
2100 dict.push((
2101 PickleValue::String("reachable_on".into()),
2102 PickleValue::None,
2103 ));
2104 }
2105 if let Some(v) = iface.port {
2106 dict.push((
2107 PickleValue::String("port".into()),
2108 PickleValue::Int(v as i64),
2109 ));
2110 } else {
2111 dict.push((PickleValue::String("port".into()), PickleValue::None));
2112 }
2113
2114 if let Some(v) = iface.frequency {
2116 dict.push((
2117 PickleValue::String("frequency".into()),
2118 PickleValue::Int(v as i64),
2119 ));
2120 } else {
2121 dict.push((PickleValue::String("frequency".into()), PickleValue::None));
2122 }
2123 if let Some(v) = iface.bandwidth {
2124 dict.push((
2125 PickleValue::String("bandwidth".into()),
2126 PickleValue::Int(v as i64),
2127 ));
2128 } else {
2129 dict.push((PickleValue::String("bandwidth".into()), PickleValue::None));
2130 }
2131 if let Some(v) = iface.spreading_factor {
2132 dict.push((PickleValue::String("sf".into()), PickleValue::Int(v as i64)));
2133 } else {
2134 dict.push((PickleValue::String("sf".into()), PickleValue::None));
2135 }
2136 if let Some(v) = iface.coding_rate {
2137 dict.push((PickleValue::String("cr".into()), PickleValue::Int(v as i64)));
2138 } else {
2139 dict.push((PickleValue::String("cr".into()), PickleValue::None));
2140 }
2141 if let Some(ref v) = iface.modulation {
2142 dict.push((
2143 PickleValue::String("modulation".into()),
2144 PickleValue::String(v.clone()),
2145 ));
2146 } else {
2147 dict.push((PickleValue::String("modulation".into()), PickleValue::None));
2148 }
2149 if let Some(v) = iface.channel {
2150 dict.push((
2151 PickleValue::String("channel".into()),
2152 PickleValue::Int(v as i64),
2153 ));
2154 } else {
2155 dict.push((PickleValue::String("channel".into()), PickleValue::None));
2156 }
2157
2158 if let Some(ref v) = iface.ifac_netname {
2160 dict.push((
2161 PickleValue::String("ifac_netname".into()),
2162 PickleValue::String(v.clone()),
2163 ));
2164 } else {
2165 dict.push((
2166 PickleValue::String("ifac_netname".into()),
2167 PickleValue::None,
2168 ));
2169 }
2170 if let Some(ref v) = iface.ifac_netkey {
2171 dict.push((
2172 PickleValue::String("ifac_netkey".into()),
2173 PickleValue::String(v.clone()),
2174 ));
2175 } else {
2176 dict.push((PickleValue::String("ifac_netkey".into()), PickleValue::None));
2177 }
2178
2179 if let Some(ref v) = iface.config_entry {
2181 dict.push((
2182 PickleValue::String("config_entry".into()),
2183 PickleValue::String(v.clone()),
2184 ));
2185 } else {
2186 dict.push((
2187 PickleValue::String("config_entry".into()),
2188 PickleValue::None,
2189 ));
2190 }
2191
2192 dict.push((
2193 PickleValue::String("discovery_hash".into()),
2194 PickleValue::Bytes(iface.discovery_hash.to_vec()),
2195 ));
2196
2197 PickleValue::Dict(dict)
2198 })
2199 .collect();
2200 PickleValue::List(list)
2201}
2202
2203fn hooks_to_pickle(hooks: &[HookInfo]) -> PickleValue {
2204 PickleValue::List(
2205 hooks
2206 .iter()
2207 .map(|hook| {
2208 PickleValue::Dict(vec![
2209 (
2210 PickleValue::String("name".into()),
2211 PickleValue::String(hook.name.clone()),
2212 ),
2213 (
2214 PickleValue::String("type".into()),
2215 PickleValue::String(hook.hook_type.clone()),
2216 ),
2217 (
2218 PickleValue::String("attach_point".into()),
2219 PickleValue::String(hook.attach_point.clone()),
2220 ),
2221 (
2222 PickleValue::String("priority".into()),
2223 PickleValue::Int(hook.priority as i64),
2224 ),
2225 (
2226 PickleValue::String("enabled".into()),
2227 PickleValue::Bool(hook.enabled),
2228 ),
2229 (
2230 PickleValue::String("consecutive_traps".into()),
2231 PickleValue::Int(hook.consecutive_traps as i64),
2232 ),
2233 ])
2234 })
2235 .collect(),
2236 )
2237}
2238
2239fn backbone_peer_state_to_pickle(entries: &[BackbonePeerStateEntry]) -> PickleValue {
2240 PickleValue::List(
2241 entries
2242 .iter()
2243 .map(|entry| {
2244 PickleValue::Dict(vec![
2245 (
2246 PickleValue::String("interface".into()),
2247 PickleValue::String(entry.interface_name.clone()),
2248 ),
2249 (
2250 PickleValue::String("ip".into()),
2251 PickleValue::String(entry.peer_ip.to_string()),
2252 ),
2253 (
2254 PickleValue::String("connected_count".into()),
2255 PickleValue::Int(entry.connected_count as i64),
2256 ),
2257 (
2258 PickleValue::String("blacklisted_remaining_secs".into()),
2259 entry
2260 .blacklisted_remaining_secs
2261 .map(PickleValue::Float)
2262 .unwrap_or(PickleValue::None),
2263 ),
2264 (
2265 PickleValue::String("blacklist_reason".into()),
2266 entry
2267 .blacklist_reason
2268 .as_ref()
2269 .map(|v: &String| PickleValue::String(v.clone()))
2270 .unwrap_or(PickleValue::None),
2271 ),
2272 (
2273 PickleValue::String("reject_count".into()),
2274 PickleValue::Int(entry.reject_count as i64),
2275 ),
2276 ])
2277 })
2278 .collect(),
2279 )
2280}
2281
2282fn backbone_interfaces_to_pickle(entries: &[BackboneInterfaceEntry]) -> PickleValue {
2283 PickleValue::List(
2284 entries
2285 .iter()
2286 .map(|entry| {
2287 PickleValue::Dict(vec![
2288 (
2289 PickleValue::String("id".into()),
2290 PickleValue::Int(entry.interface_id.0 as i64),
2291 ),
2292 (
2293 PickleValue::String("name".into()),
2294 PickleValue::String(entry.interface_name.clone()),
2295 ),
2296 ])
2297 })
2298 .collect(),
2299 )
2300}
2301
2302fn provider_bridge_stats_to_pickle(stats: &ProviderBridgeStats) -> PickleValue {
2303 PickleValue::Dict(vec![
2304 (
2305 PickleValue::String("connected".into()),
2306 PickleValue::Bool(stats.connected),
2307 ),
2308 (
2309 PickleValue::String("consumer_count".into()),
2310 PickleValue::Int(stats.consumer_count as i64),
2311 ),
2312 (
2313 PickleValue::String("queue_max_events".into()),
2314 PickleValue::Int(stats.queue_max_events as i64),
2315 ),
2316 (
2317 PickleValue::String("queue_max_bytes".into()),
2318 PickleValue::Int(stats.queue_max_bytes as i64),
2319 ),
2320 (
2321 PickleValue::String("backlog_len".into()),
2322 PickleValue::Int(stats.backlog_len as i64),
2323 ),
2324 (
2325 PickleValue::String("backlog_bytes".into()),
2326 PickleValue::Int(stats.backlog_bytes as i64),
2327 ),
2328 (
2329 PickleValue::String("backlog_dropped_pending".into()),
2330 PickleValue::Int(stats.backlog_dropped_pending as i64),
2331 ),
2332 (
2333 PickleValue::String("backlog_dropped_total".into()),
2334 PickleValue::Int(stats.backlog_dropped_total as i64),
2335 ),
2336 (
2337 PickleValue::String("total_disconnect_count".into()),
2338 PickleValue::Int(stats.total_disconnect_count as i64),
2339 ),
2340 (
2341 PickleValue::String("consumers".into()),
2342 PickleValue::List(
2343 stats
2344 .consumers
2345 .iter()
2346 .map(|consumer| {
2347 PickleValue::Dict(vec![
2348 (
2349 PickleValue::String("id".into()),
2350 PickleValue::Int(consumer.id as i64),
2351 ),
2352 (
2353 PickleValue::String("connected".into()),
2354 PickleValue::Bool(consumer.connected),
2355 ),
2356 (
2357 PickleValue::String("queue_len".into()),
2358 PickleValue::Int(consumer.queue_len as i64),
2359 ),
2360 (
2361 PickleValue::String("queued_bytes".into()),
2362 PickleValue::Int(consumer.queued_bytes as i64),
2363 ),
2364 (
2365 PickleValue::String("dropped_pending".into()),
2366 PickleValue::Int(consumer.dropped_pending as i64),
2367 ),
2368 (
2369 PickleValue::String("dropped_total".into()),
2370 PickleValue::Int(consumer.dropped_total as i64),
2371 ),
2372 (
2373 PickleValue::String("queue_max_events".into()),
2374 PickleValue::Int(consumer.queue_max_events as i64),
2375 ),
2376 (
2377 PickleValue::String("queue_max_bytes".into()),
2378 PickleValue::Int(consumer.queue_max_bytes as i64),
2379 ),
2380 ])
2381 })
2382 .collect(),
2383 ),
2384 ),
2385 ])
2386}
2387
2388fn lifecycle_state_name(state: LifecycleState) -> &'static str {
2389 match state {
2390 LifecycleState::Active => "active",
2391 LifecycleState::Draining => "draining",
2392 LifecycleState::Stopping => "stopping",
2393 LifecycleState::Stopped => "stopped",
2394 }
2395}
2396
2397fn drain_status_to_pickle(status: &DrainStatus) -> PickleValue {
2398 PickleValue::Dict(vec![
2399 (
2400 PickleValue::String("state".into()),
2401 PickleValue::String(lifecycle_state_name(status.state).into()),
2402 ),
2403 (
2404 PickleValue::String("drain_age_seconds".into()),
2405 status
2406 .drain_age_seconds
2407 .map(PickleValue::Float)
2408 .unwrap_or(PickleValue::None),
2409 ),
2410 (
2411 PickleValue::String("deadline_remaining_seconds".into()),
2412 status
2413 .deadline_remaining_seconds
2414 .map(PickleValue::Float)
2415 .unwrap_or(PickleValue::None),
2416 ),
2417 (
2418 PickleValue::String("drain_complete".into()),
2419 PickleValue::Bool(status.drain_complete),
2420 ),
2421 (
2422 PickleValue::String("interface_writer_queued_frames".into()),
2423 PickleValue::Int(status.interface_writer_queued_frames as i64),
2424 ),
2425 (
2426 PickleValue::String("provider_backlog_events".into()),
2427 PickleValue::Int(status.provider_backlog_events as i64),
2428 ),
2429 (
2430 PickleValue::String("provider_consumer_queued_events".into()),
2431 PickleValue::Int(status.provider_consumer_queued_events as i64),
2432 ),
2433 (
2434 PickleValue::String("detail".into()),
2435 status
2436 .detail
2437 .as_ref()
2438 .map(|detail| PickleValue::String(detail.clone()))
2439 .unwrap_or(PickleValue::None),
2440 ),
2441 ])
2442}
2443
2444fn runtime_config_value_to_pickle(value: &RuntimeConfigValue) -> PickleValue {
2445 match value {
2446 RuntimeConfigValue::Int(v) => PickleValue::Int(*v),
2447 RuntimeConfigValue::Float(v) => PickleValue::Float(*v),
2448 RuntimeConfigValue::Bool(v) => PickleValue::Bool(*v),
2449 RuntimeConfigValue::String(v) => PickleValue::String(v.clone()),
2450 RuntimeConfigValue::Null => PickleValue::None,
2451 }
2452}
2453
2454fn runtime_config_value_from_pickle(value: &PickleValue) -> Option<RuntimeConfigValue> {
2455 match value {
2456 PickleValue::Int(v) => Some(RuntimeConfigValue::Int(*v)),
2457 PickleValue::Float(v) => Some(RuntimeConfigValue::Float(*v)),
2458 PickleValue::Bool(v) => Some(RuntimeConfigValue::Bool(*v)),
2459 PickleValue::String(v) => Some(RuntimeConfigValue::String(v.clone())),
2460 PickleValue::None => Some(RuntimeConfigValue::Null),
2461 _ => None,
2462 }
2463}
2464
2465fn runtime_config_entry_to_pickle(entry: &RuntimeConfigEntry) -> PickleValue {
2466 PickleValue::Dict(vec![
2467 (
2468 PickleValue::String("key".into()),
2469 PickleValue::String(entry.key.clone()),
2470 ),
2471 (
2472 PickleValue::String("value".into()),
2473 runtime_config_value_to_pickle(&entry.value),
2474 ),
2475 (
2476 PickleValue::String("default".into()),
2477 runtime_config_value_to_pickle(&entry.default),
2478 ),
2479 (
2480 PickleValue::String("source".into()),
2481 PickleValue::String(match entry.source {
2482 RuntimeConfigSource::Startup => "startup".into(),
2483 RuntimeConfigSource::RuntimeOverride => "runtime_override".into(),
2484 }),
2485 ),
2486 (
2487 PickleValue::String("apply_mode".into()),
2488 PickleValue::String(match entry.apply_mode {
2489 RuntimeConfigApplyMode::Immediate => "immediate".into(),
2490 RuntimeConfigApplyMode::NewConnectionsOnly => "new_connections_only".into(),
2491 RuntimeConfigApplyMode::NextReconnect => "next_reconnect".into(),
2492 RuntimeConfigApplyMode::RestartRequired => "restart_required".into(),
2493 }),
2494 ),
2495 (
2496 PickleValue::String("description".into()),
2497 entry
2498 .description
2499 .as_ref()
2500 .map(|v| PickleValue::String(v.clone()))
2501 .unwrap_or(PickleValue::None),
2502 ),
2503 ])
2504}
2505
2506fn runtime_config_list_to_pickle(entries: &[RuntimeConfigEntry]) -> PickleValue {
2507 PickleValue::List(entries.iter().map(runtime_config_entry_to_pickle).collect())
2508}
2509
2510fn runtime_config_error_to_pickle(error: &RuntimeConfigError) -> PickleValue {
2511 PickleValue::Dict(vec![
2512 (
2513 PickleValue::String("error".into()),
2514 PickleValue::String(match error.code {
2515 RuntimeConfigErrorCode::UnknownKey => "unknown_key".into(),
2516 RuntimeConfigErrorCode::InvalidType => "invalid_type".into(),
2517 RuntimeConfigErrorCode::InvalidValue => "invalid_value".into(),
2518 RuntimeConfigErrorCode::Unsupported => "unsupported".into(),
2519 RuntimeConfigErrorCode::NotFound => "not_found".into(),
2520 RuntimeConfigErrorCode::ApplyFailed => "apply_failed".into(),
2521 }),
2522 ),
2523 (
2524 PickleValue::String("message".into()),
2525 PickleValue::String(error.message.clone()),
2526 ),
2527 ])
2528}
2529
2530fn runtime_config_result_to_pickle(
2531 result: Result<RuntimeConfigEntry, RuntimeConfigError>,
2532) -> PickleValue {
2533 match result {
2534 Ok(entry) => runtime_config_entry_to_pickle(&entry),
2535 Err(error) => runtime_config_error_to_pickle(&error),
2536 }
2537}
2538
2539fn known_destination_entry_to_pickle(entry: &KnownDestinationEntry) -> PickleValue {
2540 PickleValue::Dict(vec![
2541 (
2542 PickleValue::String("dest_hash".into()),
2543 PickleValue::Bytes(entry.dest_hash.to_vec()),
2544 ),
2545 (
2546 PickleValue::String("identity_hash".into()),
2547 PickleValue::Bytes(entry.identity_hash.to_vec()),
2548 ),
2549 (
2550 PickleValue::String("public_key".into()),
2551 PickleValue::Bytes(entry.public_key.to_vec()),
2552 ),
2553 (
2554 PickleValue::String("app_data".into()),
2555 entry
2556 .app_data
2557 .as_ref()
2558 .map(|data: &Vec<u8>| PickleValue::Bytes(data.clone()))
2559 .unwrap_or(PickleValue::None),
2560 ),
2561 (
2562 PickleValue::String("hops".into()),
2563 PickleValue::Int(entry.hops as i64),
2564 ),
2565 (
2566 PickleValue::String("received_at".into()),
2567 PickleValue::Float(entry.received_at),
2568 ),
2569 (
2570 PickleValue::String("receiving_interface".into()),
2571 PickleValue::Int(entry.receiving_interface.0 as i64),
2572 ),
2573 (
2574 PickleValue::String("was_used".into()),
2575 PickleValue::Bool(entry.was_used),
2576 ),
2577 (
2578 PickleValue::String("last_used_at".into()),
2579 entry
2580 .last_used_at
2581 .map(PickleValue::Float)
2582 .unwrap_or(PickleValue::None),
2583 ),
2584 (
2585 PickleValue::String("retained".into()),
2586 PickleValue::Bool(entry.retained),
2587 ),
2588 ])
2589}
2590
2591fn known_destinations_to_pickle(entries: &[KnownDestinationEntry]) -> PickleValue {
2592 PickleValue::List(
2593 entries
2594 .iter()
2595 .map(known_destination_entry_to_pickle)
2596 .collect(),
2597 )
2598}
2599
2600fn parse_known_destination_entry(value: &PickleValue) -> io::Result<KnownDestinationEntry> {
2601 let get_bytes = |key: &str, len: usize| -> io::Result<Vec<u8>> {
2602 let value = value.get(key).ok_or_else(|| {
2603 io::Error::new(io::ErrorKind::InvalidData, format!("missing {}", key))
2604 })?;
2605 let bytes = value.as_bytes().ok_or_else(|| {
2606 io::Error::new(io::ErrorKind::InvalidData, format!("invalid {}", key))
2607 })?;
2608 if bytes.len() != len {
2609 return Err(io::Error::new(
2610 io::ErrorKind::InvalidData,
2611 format!("invalid {} length", key),
2612 ));
2613 }
2614 Ok(bytes.to_vec())
2615 };
2616 let get_int = |key: &str| -> io::Result<i64> {
2617 value
2618 .get(key)
2619 .and_then(|v| v.as_int())
2620 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("invalid {}", key)))
2621 };
2622 let get_float = |key: &str| -> io::Result<f64> {
2623 value
2624 .get(key)
2625 .and_then(|v| v.as_float())
2626 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("invalid {}", key)))
2627 };
2628 let get_bool = |key: &str| -> io::Result<bool> {
2629 value
2630 .get(key)
2631 .and_then(|v| v.as_bool())
2632 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("invalid {}", key)))
2633 };
2634
2635 let mut dest_hash = [0u8; 16];
2636 dest_hash.copy_from_slice(&get_bytes("dest_hash", 16)?);
2637 let mut identity_hash = [0u8; 16];
2638 identity_hash.copy_from_slice(&get_bytes("identity_hash", 16)?);
2639 let mut public_key = [0u8; 64];
2640 public_key.copy_from_slice(&get_bytes("public_key", 64)?);
2641 let app_data = value
2642 .get("app_data")
2643 .and_then(|v| v.as_bytes())
2644 .map(|bytes| bytes.to_vec());
2645 let last_used_at = value.get("last_used_at").and_then(|v| v.as_float());
2646
2647 Ok(KnownDestinationEntry {
2648 dest_hash,
2649 identity_hash,
2650 public_key,
2651 app_data,
2652 hops: get_int("hops")? as u8,
2653 received_at: get_float("received_at")?,
2654 receiving_interface: rns_core::transport::types::InterfaceId(
2655 get_int("receiving_interface")? as u64,
2656 ),
2657 was_used: get_bool("was_used")?,
2658 last_used_at,
2659 retained: get_bool("retained")?,
2660 })
2661}
2662
2663fn parse_known_destination_list(value: &PickleValue) -> io::Result<Vec<KnownDestinationEntry>> {
2664 let list = value
2665 .as_list()
2666 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "expected list"))?;
2667 list.iter().map(parse_known_destination_entry).collect()
2668}
2669
2670pub struct RpcClient {
2674 stream: TcpStream,
2675}
2676
2677impl RpcClient {
2678 pub fn connect(addr: &RpcAddr, auth_key: &[u8; 32]) -> io::Result<Self> {
2680 let mut stream = match addr {
2681 RpcAddr::Tcp(host, port) => TcpStream::connect((host.as_str(), *port))?,
2682 };
2683
2684 stream.set_read_timeout(Some(std::time::Duration::from_secs(10)))?;
2685 stream.set_write_timeout(Some(std::time::Duration::from_secs(10)))?;
2686
2687 client_auth(&mut stream, auth_key)?;
2689
2690 Ok(RpcClient { stream })
2691 }
2692
2693 pub fn call(&mut self, request: &PickleValue) -> io::Result<PickleValue> {
2695 let request_bytes = encode_rpc_payload(request, RpcCodec::Msgpack);
2696 send_bytes(&mut self.stream, &request_bytes)?;
2697
2698 let response_bytes = recv_bytes(&mut self.stream)?;
2699 decode_rpc_payload(&response_bytes).map(|(value, _)| value)
2700 }
2701
2702 pub fn list_hooks(&mut self) -> io::Result<Vec<HookInfo>> {
2703 let response = self.call(&PickleValue::Dict(vec![(
2704 PickleValue::String("get".into()),
2705 PickleValue::String("hooks".into()),
2706 )]))?;
2707 parse_hook_list(&response)
2708 }
2709
2710 pub fn begin_drain(&mut self, timeout: Duration) -> io::Result<bool> {
2711 let response = self.call(&PickleValue::Dict(vec![(
2712 PickleValue::String("begin_drain".into()),
2713 PickleValue::Float(timeout.as_secs_f64()),
2714 )]))?;
2715 Ok(response.as_bool().unwrap_or(false))
2716 }
2717
2718 pub fn drain_status(&mut self) -> io::Result<Option<DrainStatus>> {
2719 let response = self.call(&PickleValue::Dict(vec![(
2720 PickleValue::String("get".into()),
2721 PickleValue::String("drain_status".into()),
2722 )]))?;
2723 parse_drain_status(&response)
2724 }
2725
2726 pub fn provider_bridge_stats(&mut self) -> io::Result<PickleValue> {
2727 self.call(&PickleValue::Dict(vec![(
2728 PickleValue::String("get".into()),
2729 PickleValue::String("provider_bridge_stats".into()),
2730 )]))
2731 }
2732
2733 pub fn load_hook(
2734 &mut self,
2735 name: &str,
2736 attach_point: &str,
2737 priority: i32,
2738 wasm: &[u8],
2739 ) -> io::Result<Result<(), String>> {
2740 let response = self.call(&PickleValue::Dict(vec![
2741 (
2742 PickleValue::String("hook".into()),
2743 PickleValue::String("load".into()),
2744 ),
2745 (
2746 PickleValue::String("name".into()),
2747 PickleValue::String(name.to_string()),
2748 ),
2749 (
2750 PickleValue::String("attach_point".into()),
2751 PickleValue::String(attach_point.to_string()),
2752 ),
2753 (
2754 PickleValue::String("priority".into()),
2755 PickleValue::Int(priority as i64),
2756 ),
2757 (
2758 PickleValue::String("wasm".into()),
2759 PickleValue::Bytes(wasm.to_vec()),
2760 ),
2761 ]))?;
2762 parse_hook_result(&response)
2763 }
2764
2765 pub fn load_builtin_hook(
2766 &mut self,
2767 name: &str,
2768 attach_point: &str,
2769 priority: i32,
2770 builtin_id: &str,
2771 ) -> io::Result<Result<(), String>> {
2772 let response = self.call(&PickleValue::Dict(vec![
2773 (
2774 PickleValue::String("hook".into()),
2775 PickleValue::String("load_builtin".into()),
2776 ),
2777 (
2778 PickleValue::String("name".into()),
2779 PickleValue::String(name.to_string()),
2780 ),
2781 (
2782 PickleValue::String("attach_point".into()),
2783 PickleValue::String(attach_point.to_string()),
2784 ),
2785 (
2786 PickleValue::String("priority".into()),
2787 PickleValue::Int(priority as i64),
2788 ),
2789 (
2790 PickleValue::String("builtin_id".into()),
2791 PickleValue::String(builtin_id.to_string()),
2792 ),
2793 ]))?;
2794 parse_hook_result(&response)
2795 }
2796
2797 pub fn unload_hook(
2798 &mut self,
2799 name: &str,
2800 attach_point: &str,
2801 ) -> io::Result<Result<(), String>> {
2802 let response = self.call(&PickleValue::Dict(vec![
2803 (
2804 PickleValue::String("hook".into()),
2805 PickleValue::String("unload".into()),
2806 ),
2807 (
2808 PickleValue::String("name".into()),
2809 PickleValue::String(name.to_string()),
2810 ),
2811 (
2812 PickleValue::String("attach_point".into()),
2813 PickleValue::String(attach_point.to_string()),
2814 ),
2815 ]))?;
2816 parse_hook_result(&response)
2817 }
2818
2819 pub fn set_hook_enabled(
2820 &mut self,
2821 name: &str,
2822 attach_point: &str,
2823 enabled: bool,
2824 ) -> io::Result<Result<(), String>> {
2825 let op = if enabled { "enable" } else { "disable" };
2826 let response = self.call(&PickleValue::Dict(vec![
2827 (
2828 PickleValue::String("hook".into()),
2829 PickleValue::String(op.into()),
2830 ),
2831 (
2832 PickleValue::String("name".into()),
2833 PickleValue::String(name.to_string()),
2834 ),
2835 (
2836 PickleValue::String("attach_point".into()),
2837 PickleValue::String(attach_point.to_string()),
2838 ),
2839 ]))?;
2840 parse_hook_result(&response)
2841 }
2842
2843 pub fn set_hook_priority(
2844 &mut self,
2845 name: &str,
2846 attach_point: &str,
2847 priority: i32,
2848 ) -> io::Result<Result<(), String>> {
2849 let response = self.call(&PickleValue::Dict(vec![
2850 (
2851 PickleValue::String("hook".into()),
2852 PickleValue::String("set_priority".into()),
2853 ),
2854 (
2855 PickleValue::String("name".into()),
2856 PickleValue::String(name.to_string()),
2857 ),
2858 (
2859 PickleValue::String("attach_point".into()),
2860 PickleValue::String(attach_point.to_string()),
2861 ),
2862 (
2863 PickleValue::String("priority".into()),
2864 PickleValue::Int(priority as i64),
2865 ),
2866 ]))?;
2867 parse_hook_result(&response)
2868 }
2869
2870 pub fn blacklist_backbone_peer(
2871 &mut self,
2872 interface: &str,
2873 ip: &str,
2874 duration_secs: u64,
2875 reason: Option<&str>,
2876 penalty_level: Option<u8>,
2877 ) -> io::Result<bool> {
2878 let mut request = vec![
2879 (
2880 PickleValue::String("set".into()),
2881 PickleValue::String("backbone_peer_blacklist".into()),
2882 ),
2883 (
2884 PickleValue::String("interface".into()),
2885 PickleValue::String(interface.to_string()),
2886 ),
2887 (
2888 PickleValue::String("ip".into()),
2889 PickleValue::String(ip.to_string()),
2890 ),
2891 (
2892 PickleValue::String("duration_secs".into()),
2893 PickleValue::Int(duration_secs as i64),
2894 ),
2895 ];
2896 if let Some(reason) = reason {
2897 request.push((
2898 PickleValue::String("reason".into()),
2899 PickleValue::String(reason.to_string()),
2900 ));
2901 }
2902 if let Some(level) = penalty_level {
2903 request.push((
2904 PickleValue::String("penalty_level".into()),
2905 PickleValue::Int(level as i64),
2906 ));
2907 }
2908 let response = self.call(&PickleValue::Dict(request))?;
2909 Ok(response.as_bool().unwrap_or(false))
2910 }
2911
2912 pub fn known_destinations(&mut self) -> io::Result<Vec<KnownDestinationEntry>> {
2913 let response = self.call(&PickleValue::Dict(vec![(
2914 PickleValue::String("get".into()),
2915 PickleValue::String("known_destinations".into()),
2916 )]))?;
2917 parse_known_destination_list(&response)
2918 }
2919
2920 pub fn retain_known_destination(&mut self, dest_hash: [u8; 16]) -> io::Result<bool> {
2921 let response = self.call(&PickleValue::Dict(vec![
2922 (
2923 PickleValue::String("set".into()),
2924 PickleValue::String("known_destination_retained".into()),
2925 ),
2926 (
2927 PickleValue::String("dest_hash".into()),
2928 PickleValue::Bytes(dest_hash.to_vec()),
2929 ),
2930 ]))?;
2931 Ok(response.as_bool().unwrap_or(false))
2932 }
2933
2934 pub fn retain_identity(&mut self, identity_hash: [u8; 16]) -> io::Result<bool> {
2935 let response = self.call(&PickleValue::Dict(vec![
2936 (
2937 PickleValue::String("set".into()),
2938 PickleValue::String("identity_retained".into()),
2939 ),
2940 (
2941 PickleValue::String("identity_hash".into()),
2942 PickleValue::Bytes(identity_hash.to_vec()),
2943 ),
2944 ]))?;
2945 Ok(response.as_bool().unwrap_or(false))
2946 }
2947
2948 pub fn unretain_known_destination(&mut self, dest_hash: [u8; 16]) -> io::Result<bool> {
2949 let response = self.call(&PickleValue::Dict(vec![
2950 (
2951 PickleValue::String("clear".into()),
2952 PickleValue::String("known_destination_retained".into()),
2953 ),
2954 (
2955 PickleValue::String("dest_hash".into()),
2956 PickleValue::Bytes(dest_hash.to_vec()),
2957 ),
2958 ]))?;
2959 Ok(response.as_bool().unwrap_or(false))
2960 }
2961
2962 pub fn mark_known_destination_used(&mut self, dest_hash: [u8; 16]) -> io::Result<bool> {
2963 let response = self.call(&PickleValue::Dict(vec![
2964 (
2965 PickleValue::String("set".into()),
2966 PickleValue::String("known_destination_used".into()),
2967 ),
2968 (
2969 PickleValue::String("dest_hash".into()),
2970 PickleValue::Bytes(dest_hash.to_vec()),
2971 ),
2972 ]))?;
2973 Ok(response.as_bool().unwrap_or(false))
2974 }
2975}
2976
2977fn parse_lifecycle_state(value: &str) -> Option<LifecycleState> {
2978 match value {
2979 "active" => Some(LifecycleState::Active),
2980 "draining" => Some(LifecycleState::Draining),
2981 "stopping" => Some(LifecycleState::Stopping),
2982 "stopped" => Some(LifecycleState::Stopped),
2983 _ => None,
2984 }
2985}
2986
2987fn parse_drain_status(value: &PickleValue) -> io::Result<Option<DrainStatus>> {
2988 if !matches!(value, PickleValue::Dict(_)) {
2989 return Ok(None);
2990 }
2991 let state = value
2992 .get("state")
2993 .and_then(|entry| entry.as_str())
2994 .and_then(parse_lifecycle_state)
2995 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing drain state"))?;
2996 let drain_age_seconds = value.get("drain_age_seconds").and_then(|entry| {
2997 entry
2998 .as_float()
2999 .or_else(|| entry.as_int().map(|v| v as f64))
3000 });
3001 let deadline_remaining_seconds = value.get("deadline_remaining_seconds").and_then(|entry| {
3002 entry
3003 .as_float()
3004 .or_else(|| entry.as_int().map(|v| v as f64))
3005 });
3006 let drain_complete = value
3007 .get("drain_complete")
3008 .and_then(|entry| entry.as_bool())
3009 .unwrap_or(false);
3010 let interface_writer_queued_frames = value
3011 .get("interface_writer_queued_frames")
3012 .and_then(|entry| entry.as_int())
3013 .unwrap_or(0)
3014 .max(0) as usize;
3015 let provider_backlog_events = value
3016 .get("provider_backlog_events")
3017 .and_then(|entry| entry.as_int())
3018 .unwrap_or(0)
3019 .max(0) as usize;
3020 let provider_consumer_queued_events = value
3021 .get("provider_consumer_queued_events")
3022 .and_then(|entry| entry.as_int())
3023 .unwrap_or(0)
3024 .max(0) as usize;
3025 let detail = value
3026 .get("detail")
3027 .and_then(|entry| entry.as_str().map(|v| v.to_string()));
3028 Ok(Some(DrainStatus {
3029 state,
3030 drain_age_seconds,
3031 deadline_remaining_seconds,
3032 drain_complete,
3033 interface_writer_queued_frames,
3034 provider_backlog_events,
3035 provider_consumer_queued_events,
3036 detail,
3037 }))
3038}
3039
3040fn parse_hook_result(response: &PickleValue) -> io::Result<Result<(), String>> {
3041 let ok = response
3042 .get("ok")
3043 .and_then(|v| v.as_bool())
3044 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid hook response"))?;
3045 if ok {
3046 Ok(Ok(()))
3047 } else {
3048 Ok(Err(response
3049 .get("error")
3050 .and_then(|v| v.as_str())
3051 .unwrap_or("unknown hook error")
3052 .to_string()))
3053 }
3054}
3055
3056fn parse_hook_list(response: &PickleValue) -> io::Result<Vec<HookInfo>> {
3057 let list = response
3058 .as_list()
3059 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid hooks response"))?;
3060 let mut hooks = Vec::with_capacity(list.len());
3061 for item in list {
3062 hooks.push(HookInfo {
3063 name: item
3064 .get("name")
3065 .and_then(|v| v.as_str())
3066 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing hook name"))?
3067 .to_string(),
3068 hook_type: item
3069 .get("type")
3070 .and_then(|v| v.as_str())
3071 .unwrap_or(default_hook_type())
3072 .to_string(),
3073 attach_point: item
3074 .get("attach_point")
3075 .and_then(|v| v.as_str())
3076 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing attach_point"))?
3077 .to_string(),
3078 priority: item
3079 .get("priority")
3080 .and_then(|v| v.as_int())
3081 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing priority"))?
3082 as i32,
3083 enabled: item
3084 .get("enabled")
3085 .and_then(|v| v.as_bool())
3086 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing enabled"))?,
3087 consecutive_traps: item
3088 .get("consecutive_traps")
3089 .and_then(|v| v.as_int())
3090 .ok_or_else(|| {
3091 io::Error::new(io::ErrorKind::InvalidData, "missing consecutive_traps")
3092 })? as u32,
3093 });
3094 }
3095 Ok(hooks)
3096}
3097
3098fn default_hook_type() -> &'static str {
3099 #[cfg(feature = "rns-hooks-native")]
3100 {
3101 "native"
3102 }
3103 #[cfg(all(not(feature = "rns-hooks-native"), feature = "rns-hooks-wasm"))]
3104 {
3105 return "wasm";
3106 }
3107 #[cfg(all(not(feature = "rns-hooks-native"), not(feature = "rns-hooks-wasm")))]
3108 {
3109 "wasm"
3110 }
3111}
3112
3113fn client_auth(stream: &mut TcpStream, auth_key: &[u8; 32]) -> io::Result<()> {
3115 answer_challenge(stream, auth_key)?;
3116 server_auth(stream, auth_key)
3117}
3118
3119fn answer_challenge<S: Read + Write>(stream: &mut S, auth_key: &[u8; 32]) -> io::Result<()> {
3120 let challenge = recv_bytes(stream)?;
3122
3123 if !challenge.starts_with(CHALLENGE_PREFIX) {
3124 return Err(io::Error::new(
3125 io::ErrorKind::InvalidData,
3126 "expected challenge",
3127 ));
3128 }
3129
3130 let message = &challenge[CHALLENGE_PREFIX.len()..];
3131
3132 let response = create_response(auth_key, message);
3134 send_bytes(stream, &response)?;
3135
3136 let result = recv_bytes(stream)?;
3138 if result == WELCOME {
3139 Ok(())
3140 } else {
3141 Err(io::Error::new(
3142 io::ErrorKind::PermissionDenied,
3143 "authentication failed",
3144 ))
3145 }
3146}
3147
3148fn create_response(auth_key: &[u8; 32], message: &[u8]) -> Vec<u8> {
3150 if message.starts_with(b"{sha256}") || message.len() > 20 {
3152 let digest = hmac_sha256(auth_key, message);
3154 let mut response = Vec::with_capacity(8 + 32);
3155 response.extend_from_slice(b"{sha256}");
3156 response.extend_from_slice(&digest);
3157 response
3158 } else {
3159 let digest = hmac_md5(auth_key, message);
3161 digest.to_vec()
3162 }
3163}
3164
3165pub fn derive_auth_key(private_key: &[u8]) -> [u8; 32] {
3167 sha256(private_key)
3168}
3169
3170#[cfg(test)]
3171mod tests {
3172 use super::*;
3173
3174 #[test]
3175 fn discovered_interface_rpc_includes_operator_lxmf_address() {
3176 let interface = crate::discovery::DiscoveredInterface {
3177 interface_type: "BackboneInterface".into(),
3178 impl_name: Some("Reticulum".into()),
3179 impl_version: Some("1.5.5".into()),
3180 transport: true,
3181 name: "Operator Test".into(),
3182 discovered: 1.0,
3183 last_heard: 2.0,
3184 heard_count: 1,
3185 status: crate::discovery::DiscoveredStatus::Available,
3186 stamp: vec![0; crate::discovery::STAMP_SIZE],
3187 stamp_value: 16,
3188 transport_id: [0x11; 16],
3189 network_id: [0x22; 16],
3190 hops: 1,
3191 latitude: None,
3192 longitude: None,
3193 height: None,
3194 operator_lxmf_address: Some([0xa5; 16]),
3195 reachable_on: Some("example.com".into()),
3196 port: Some(4242),
3197 frequency: None,
3198 bandwidth: None,
3199 spreading_factor: None,
3200 coding_rate: None,
3201 modulation: None,
3202 channel: None,
3203 ifac_netname: None,
3204 ifac_netkey: None,
3205 config_entry: None,
3206 discovery_hash: [0x33; 32],
3207 };
3208
3209 let encoded = discovered_interfaces_to_pickle(&[interface]);
3210 let first = &encoded.as_list().unwrap()[0];
3211
3212 assert_eq!(
3213 first.get("impl_name").and_then(PickleValue::as_str),
3214 Some("Reticulum")
3215 );
3216 assert_eq!(
3217 first.get("version").and_then(PickleValue::as_str),
3218 Some("1.5.5")
3219 );
3220
3221 assert_eq!(
3222 first
3223 .get("operator_lxmf_address")
3224 .and_then(PickleValue::as_str),
3225 Some("a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5")
3226 );
3227 }
3228
3229 #[test]
3230 fn send_recv_bytes_roundtrip() {
3231 let (mut c1, mut c2) = tcp_pair();
3232 let data = b"hello world";
3233 send_bytes(&mut c1, data).unwrap();
3234 let received = recv_bytes(&mut c2).unwrap();
3235 assert_eq!(&received, data);
3236 }
3237
3238 #[test]
3239 fn send_recv_empty() {
3240 let (mut c1, mut c2) = tcp_pair();
3241 send_bytes(&mut c1, b"").unwrap();
3242 let received = recv_bytes(&mut c2).unwrap();
3243 assert!(received.is_empty());
3244 }
3245
3246 #[test]
3247 fn auth_success() {
3248 let key = derive_auth_key(b"test-private-key");
3249 let (mut server, mut client) = tcp_pair();
3250
3251 let key2 = key;
3252 let t = thread::spawn(move || {
3253 client_auth(&mut client, &key2).unwrap();
3254 });
3255
3256 server_auth(&mut server, &key).unwrap();
3257 answer_challenge(&mut server, &key).unwrap();
3258 t.join().unwrap();
3259 }
3260
3261 #[test]
3262 fn auth_failure_wrong_key() {
3263 let server_key = derive_auth_key(b"server-key");
3264 let client_key = derive_auth_key(b"wrong-key");
3265 let (mut server, mut client) = tcp_pair();
3266
3267 let t = thread::spawn(move || {
3268 let result = client_auth(&mut client, &client_key);
3269 assert!(result.is_err());
3270 });
3271
3272 let result = server_auth(&mut server, &server_key);
3273 assert!(result.is_err());
3274 t.join().unwrap();
3275 }
3276
3277 #[test]
3278 fn verify_sha256_response() {
3279 let key = derive_auth_key(b"mykey");
3280 let message = b"{sha256}abcdefghijklmnopqrstuvwxyz0123456789ABCD";
3281 let response = create_response(&key, message);
3282 assert!(response.starts_with(b"{sha256}"));
3283 assert!(verify_response(&key, message, &response));
3284 }
3285
3286 #[test]
3287 fn verify_legacy_md5_response() {
3288 let key = derive_auth_key(b"mykey");
3289 let message = b"01234567890123456789";
3291 let digest = hmac_md5(&key, message);
3293 assert!(verify_response(&key, message, &digest));
3294 }
3295
3296 #[test]
3297 fn constant_time_eq_works() {
3298 assert!(constant_time_eq(b"hello", b"hello"));
3299 assert!(!constant_time_eq(b"hello", b"world"));
3300 assert!(!constant_time_eq(b"hello", b"hell"));
3301 }
3302
3303 #[test]
3304 fn rpc_payload_accepts_msgpack_and_legacy_pickle() {
3305 let request = PickleValue::Dict(vec![
3306 (
3307 PickleValue::String("get".into()),
3308 PickleValue::String("link_count".into()),
3309 ),
3310 (PickleValue::String("max_hops".into()), PickleValue::Int(3)),
3311 ]);
3312
3313 let msgpack_bytes = encode_rpc_payload(&request, RpcCodec::Msgpack);
3314 let (decoded_msgpack, msgpack_codec) = decode_rpc_payload(&msgpack_bytes).unwrap();
3315 assert_eq!(decoded_msgpack, request);
3316 assert_eq!(msgpack_codec, RpcCodec::Msgpack);
3317
3318 let pickle_bytes = encode_rpc_payload(&request, RpcCodec::Pickle);
3319 let (decoded_pickle, pickle_codec) = decode_rpc_payload(&pickle_bytes).unwrap();
3320 assert_eq!(decoded_pickle, request);
3321 assert_eq!(pickle_codec, RpcCodec::Pickle);
3322 }
3323
3324 #[test]
3325 fn rpc_roundtrip() {
3326 let key = derive_auth_key(b"test-key");
3327 let (event_tx, event_rx) = crate::event::channel();
3328
3329 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
3332 let port = listener.local_addr().unwrap().port();
3333 listener.set_nonblocking(true).unwrap();
3334
3335 let shutdown = Arc::new(AtomicBool::new(false));
3336 let shutdown2 = shutdown.clone();
3337
3338 let driver_thread = thread::spawn(move || loop {
3340 match event_rx.recv_timeout(std::time::Duration::from_secs(5)) {
3341 Ok(Event::Query(request, resp_tx))
3342 if matches!(*request, QueryRequest::LinkCount) =>
3343 {
3344 let _ = resp_tx.send(QueryResponse::LinkCount(42));
3345 }
3346 Ok(Event::Query(request, resp_tx))
3347 if matches!(*request, QueryRequest::ActiveLinkCount) =>
3348 {
3349 let _ = resp_tx.send(QueryResponse::ActiveLinkCount(7));
3350 }
3351 Ok(Event::Query(request, resp_tx))
3352 if matches!(*request, QueryRequest::LowestInterfaceBitrate) =>
3353 {
3354 let _ = resp_tx.send(QueryResponse::LowestInterfaceBitrate(Some(400)));
3355 }
3356 Ok(Event::Query(request, resp_tx))
3357 if matches!(*request, QueryRequest::MediumPathTimeout) =>
3358 {
3359 let _ = resp_tx.send(QueryResponse::MediumPathTimeout(26.0));
3360 }
3361 Ok(Event::Query(request, resp_tx))
3362 if matches!(*request, QueryRequest::FirstHopTimeout { .. }) =>
3363 {
3364 let QueryRequest::FirstHopTimeout { dest_hash } = *request else {
3365 unreachable!()
3366 };
3367 assert_eq!(dest_hash, [0x77; 16]);
3368 let _ = resp_tx.send(QueryResponse::FirstHopTimeout(6.4));
3369 }
3370 Ok(Event::Query(request, resp_tx))
3371 if matches!(*request, QueryRequest::InterfaceStats) =>
3372 {
3373 let _ = resp_tx.send(QueryResponse::InterfaceStats(InterfaceStatsResponse {
3374 interfaces: vec![SingleInterfaceStat {
3375 id: 7,
3376 name: "TestInterface".into(),
3377 status: true,
3378 mode: 1,
3379 gravity: -2,
3380 announces_to_internal: Some(true),
3381 rxb: 1000,
3382 txb: 2000,
3383 tx_drops: 0,
3384 tx_dropped_bytes: 0,
3385 tx_stalled: false,
3386 tx_buffered: 0,
3387 traffic: TrafficDetail::default(),
3388 protocol_violations: 0,
3389 ifac_violations: 0,
3390 packet_filter_hits: 0,
3391 rx_packets: 10,
3392 tx_packets: 20,
3393 cpu_load: None,
3394 mem_load: None,
3395 switch_id: None,
3396 endpoint_id: None,
3397 via_switch_id: None,
3398 peers: None,
3399 bitrate: Some(10_000_000),
3400 mtu: Some(262_144),
3401 ifac_size: None,
3402 started: 1000.0,
3403 ia_freq: 0.0,
3404 ip_freq: 0.0,
3405 op_freq: 0.0,
3406 op_samples: 0,
3407 burst_active: false,
3408 burst_activated: 0.0,
3409 pr_burst_active: false,
3410 pr_burst_activated: 0.0,
3411 oa_freq: 0.0,
3412 clients: Some(2),
3413 blocked_ips: None,
3414 blocked_ip_list: None,
3415 announce_rate_target: Some(3600.0),
3416 announce_rate_grace: 5,
3417 announce_rate_penalty: 0.0,
3418 interface_type: "TestInterface".into(),
3419 }],
3420 transport_id: None,
3421 transport_enabled: true,
3422 transport_uptime: 3600.0,
3423 total_rxb: 1000,
3424 total_txb: 2000,
3425 rx_pps: 3.0,
3426 tx_pps: 4.0,
3427 traffic: TrafficDetail::default(),
3428 probe_responder: None,
3429 backbone_peer_pool: None,
3430 }));
3431 }
3432 _ => break,
3433 }
3434 });
3435
3436 let key2 = key;
3437 let shutdown3 = shutdown2.clone();
3438 let server_thread = thread::spawn(move || {
3439 rpc_server_loop(listener, key2, event_tx, shutdown3);
3440 });
3441
3442 thread::sleep(std::time::Duration::from_millis(50));
3444
3445 let server_addr = RpcAddr::Tcp("127.0.0.1".into(), port);
3447 let mut client = RpcClient::connect(&server_addr, &key).unwrap();
3448 let response = client
3449 .call(&PickleValue::Dict(vec![(
3450 PickleValue::String("get".into()),
3451 PickleValue::String("link_count".into()),
3452 )]))
3453 .unwrap();
3454 assert_eq!(response.as_int().unwrap(), 42);
3455 drop(client);
3456
3457 let mut active_client = RpcClient::connect(&server_addr, &key).unwrap();
3458 let active_response = active_client
3459 .call(&PickleValue::Dict(vec![(
3460 PickleValue::String("get".into()),
3461 PickleValue::String("active_link_count".into()),
3462 )]))
3463 .unwrap();
3464 assert_eq!(active_response.as_int().unwrap(), 7);
3465 drop(active_client);
3466
3467 let mut timeout_client = RpcClient::connect(&server_addr, &key).unwrap();
3468 let bitrate = timeout_client
3469 .call(&PickleValue::Dict(vec![(
3470 PickleValue::String("get".into()),
3471 PickleValue::String("lowest_interface_bitrate".into()),
3472 )]))
3473 .unwrap();
3474 assert_eq!(bitrate.as_int(), Some(400));
3475 drop(timeout_client);
3476 let mut timeout_client = RpcClient::connect(&server_addr, &key).unwrap();
3477 let timeout = timeout_client
3478 .call(&PickleValue::Dict(vec![(
3479 PickleValue::String("get".into()),
3480 PickleValue::String("medium_path_timeout".into()),
3481 )]))
3482 .unwrap();
3483 assert_eq!(timeout.as_float(), Some(26.0));
3484 drop(timeout_client);
3485 let mut timeout_client = RpcClient::connect(&server_addr, &key).unwrap();
3486 let first_hop = timeout_client
3487 .call(&PickleValue::Dict(vec![
3488 (
3489 PickleValue::String("get".into()),
3490 PickleValue::String("first_hop_timeout".into()),
3491 ),
3492 (
3493 PickleValue::String("destination_hash".into()),
3494 PickleValue::Bytes(vec![0x77; 16]),
3495 ),
3496 ]))
3497 .unwrap();
3498 assert_eq!(first_hop.as_float(), Some(6.4));
3499 drop(timeout_client);
3500
3501 let mut client2 = RpcClient::connect(&server_addr, &key).unwrap();
3503 let response2 = client2
3504 .call(&PickleValue::Dict(vec![(
3505 PickleValue::String("get".into()),
3506 PickleValue::String("interface_stats".into()),
3507 )]))
3508 .unwrap();
3509 let ifaces = response2.get("interfaces").unwrap().as_list().unwrap();
3510 assert_eq!(ifaces.len(), 1);
3511 let iface = &ifaces[0];
3512 assert_eq!(
3513 iface.get("name").unwrap().as_str().unwrap(),
3514 "TestInterface"
3515 );
3516 assert_eq!(iface.get("rxb").unwrap().as_int().unwrap(), 1000);
3517 assert_eq!(iface.get("mtu").unwrap().as_int(), Some(262_144));
3518 drop(client2);
3519
3520 shutdown2.store(true, Ordering::Relaxed);
3522 server_thread.join().unwrap();
3523 driver_thread.join().unwrap();
3524 }
3525
3526 #[test]
3527 fn full_event_queue_fails_rpc_query_without_blocking() {
3528 let (event_tx, event_rx) = crate::event::channel_with_capacity(1);
3529 event_tx.send(Event::Tick).unwrap();
3530
3531 let (result_tx, result_rx) = mpsc::channel();
3532 let query_thread = thread::spawn(move || {
3533 let _ = result_tx.send(send_query(&event_tx, QueryRequest::LinkCount));
3534 });
3535
3536 let completed_promptly = match result_rx.recv_timeout(Duration::from_millis(250)) {
3537 Ok(result) => {
3538 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WouldBlock);
3539 true
3540 }
3541 Err(mpsc::RecvTimeoutError::Timeout) => false,
3542 Err(mpsc::RecvTimeoutError::Disconnected) => {
3543 panic!("RPC query thread disconnected without a result")
3544 }
3545 };
3546
3547 assert!(matches!(event_rx.recv().unwrap(), Event::Tick));
3550 if !completed_promptly {
3551 match event_rx.recv_timeout(Duration::from_secs(1)).unwrap() {
3552 Event::Query(request, response_tx)
3553 if matches!(*request, QueryRequest::LinkCount) =>
3554 {
3555 response_tx.send(QueryResponse::LinkCount(1)).unwrap();
3556 }
3557 other => panic!("unexpected event after releasing queue: {other:?}"),
3558 }
3559 let _ = result_rx.recv_timeout(Duration::from_secs(1)).unwrap();
3560 }
3561 query_thread.join().unwrap();
3562
3563 assert!(
3564 completed_promptly,
3565 "RPC query blocked while the driver event queue was full"
3566 );
3567 }
3568
3569 #[test]
3570 fn stalled_rpc_request_does_not_block_later_clients() {
3571 let key = derive_auth_key(b"concurrent-rpc-test");
3572 let (event_tx, event_rx) = crate::event::channel();
3573
3574 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
3575 let port = listener.local_addr().unwrap().port();
3576 listener.set_nonblocking(true).unwrap();
3577
3578 let shutdown = Arc::new(AtomicBool::new(false));
3579 let server_shutdown = shutdown.clone();
3580 let server_thread = thread::spawn(move || {
3581 rpc_server_loop(listener, key, event_tx, server_shutdown);
3582 });
3583
3584 let (first_seen_tx, first_seen_rx) = mpsc::channel();
3585 let (concurrent_tx, concurrent_rx) = mpsc::channel();
3586 let driver_thread = thread::spawn(move || {
3587 let first_response = match event_rx.recv_timeout(Duration::from_secs(1)).unwrap() {
3588 Event::Query(request, response_tx)
3589 if matches!(*request, QueryRequest::LinkCount) =>
3590 {
3591 response_tx
3592 }
3593 other => panic!("unexpected first RPC event: {other:?}"),
3594 };
3595 first_seen_tx.send(()).unwrap();
3596
3597 let second = event_rx.recv_timeout(Duration::from_millis(500));
3598 let handled_concurrently = match second {
3599 Ok(Event::Query(request, response_tx))
3600 if matches!(*request, QueryRequest::TransportIdentity) =>
3601 {
3602 response_tx
3603 .send(QueryResponse::TransportIdentity(Some([7; 16])))
3604 .unwrap();
3605 true
3606 }
3607 Ok(other) => panic!("unexpected second RPC event: {other:?}"),
3608 Err(mpsc::RecvTimeoutError::Timeout) => false,
3609 Err(mpsc::RecvTimeoutError::Disconnected) => {
3610 panic!("RPC event channel disconnected")
3611 }
3612 };
3613
3614 first_response.send(QueryResponse::LinkCount(1)).unwrap();
3615
3616 if !handled_concurrently {
3617 match event_rx.recv_timeout(Duration::from_secs(1)).unwrap() {
3618 Event::Query(request, response_tx)
3619 if matches!(*request, QueryRequest::TransportIdentity) =>
3620 {
3621 response_tx
3622 .send(QueryResponse::TransportIdentity(Some([7; 16])))
3623 .unwrap();
3624 }
3625 other => panic!("unexpected delayed RPC event: {other:?}"),
3626 }
3627 }
3628 concurrent_tx.send(handled_concurrently).unwrap();
3629 });
3630
3631 let first_addr = RpcAddr::Tcp("127.0.0.1".into(), port);
3632 let first_client = thread::spawn(move || {
3633 let mut client = RpcClient::connect(&first_addr, &key).unwrap();
3634 client
3635 .call(&PickleValue::Dict(vec![(
3636 PickleValue::String("get".into()),
3637 PickleValue::String("link_count".into()),
3638 )]))
3639 .unwrap()
3640 });
3641
3642 first_seen_rx.recv_timeout(Duration::from_secs(1)).unwrap();
3643
3644 let second_addr = RpcAddr::Tcp("127.0.0.1".into(), port);
3645 let second_client = thread::spawn(move || {
3646 let mut client = RpcClient::connect(&second_addr, &key).unwrap();
3647 client
3648 .call(&PickleValue::Dict(vec![(
3649 PickleValue::String("get".into()),
3650 PickleValue::String("transport_identity".into()),
3651 )]))
3652 .unwrap()
3653 });
3654
3655 let handled_concurrently = concurrent_rx.recv_timeout(Duration::from_secs(2)).unwrap();
3656 assert_eq!(first_client.join().unwrap().as_int(), Some(1));
3657 assert_eq!(
3658 second_client.join().unwrap().as_bytes(),
3659 Some([7; 16].as_slice())
3660 );
3661
3662 shutdown.store(true, Ordering::Relaxed);
3663 server_thread.join().unwrap();
3664 driver_thread.join().unwrap();
3665
3666 assert!(
3667 handled_concurrently,
3668 "a stalled RPC request prevented the server from handling a later client"
3669 );
3670 }
3671
3672 #[test]
3673 fn derive_auth_key_deterministic() {
3674 let key1 = derive_auth_key(b"test");
3675 let key2 = derive_auth_key(b"test");
3676 assert_eq!(key1, key2);
3677 let key3 = derive_auth_key(b"other");
3679 assert_ne!(key1, key3);
3680 }
3681
3682 #[test]
3683 fn pickle_request_handling() {
3684 let (event_tx, event_rx) = crate::event::channel();
3686
3687 let driver = thread::spawn(move || {
3688 if let Some((QueryRequest::DropPath { dest_hash }, resp_tx)) = event_rx
3689 .recv()
3690 .ok()
3691 .and_then(|event| event.into_query().ok())
3692 {
3693 assert_eq!(dest_hash, [1u8; 16]);
3694 let _ = resp_tx.send(QueryResponse::DropPath(true));
3695 }
3696 });
3697
3698 let request = PickleValue::Dict(vec![
3699 (
3700 PickleValue::String("drop".into()),
3701 PickleValue::String("path".into()),
3702 ),
3703 (
3704 PickleValue::String("destination_hash".into()),
3705 PickleValue::Bytes(vec![1u8; 16]),
3706 ),
3707 ]);
3708
3709 let response = handle_rpc_request(&request, &event_tx).unwrap();
3710 assert_eq!(response, PickleValue::Bool(true));
3711 driver.join().unwrap();
3712 }
3713
3714 #[test]
3715 fn hook_list_request_handling() {
3716 let (event_tx, event_rx) = crate::event::channel();
3717
3718 let driver = thread::spawn(move || {
3719 if let Ok(Event::ListHooks { response_tx }) = event_rx.recv() {
3720 let _ = response_tx.send(vec![HookInfo {
3721 name: "stats".into(),
3722 hook_type: "wasm".into(),
3723 attach_point: "PreIngress".into(),
3724 priority: 7,
3725 enabled: true,
3726 consecutive_traps: 0,
3727 }]);
3728 }
3729 });
3730
3731 let request = PickleValue::Dict(vec![(
3732 PickleValue::String("get".into()),
3733 PickleValue::String("hooks".into()),
3734 )]);
3735 let response = handle_rpc_request(&request, &event_tx).unwrap();
3736 let hooks = parse_hook_list(&response).unwrap();
3737 assert_eq!(hooks.len(), 1);
3738 assert_eq!(hooks[0].name, "stats");
3739 driver.join().unwrap();
3740 }
3741
3742 #[test]
3743 fn hook_load_request_handling() {
3744 let (event_tx, event_rx) = crate::event::channel();
3745
3746 let driver = thread::spawn(move || {
3747 if let Ok(Event::LoadHook {
3748 name,
3749 wasm_bytes,
3750 attach_point,
3751 priority,
3752 response_tx,
3753 }) = event_rx.recv()
3754 {
3755 assert_eq!(name, "stats");
3756 assert_eq!(attach_point, "PreIngress");
3757 assert_eq!(priority, 11);
3758 assert_eq!(wasm_bytes, vec![1, 2, 3]);
3759 let _ = response_tx.send(Ok(()));
3760 }
3761 });
3762
3763 let request = PickleValue::Dict(vec![
3764 (
3765 PickleValue::String("hook".into()),
3766 PickleValue::String("load".into()),
3767 ),
3768 (
3769 PickleValue::String("name".into()),
3770 PickleValue::String("stats".into()),
3771 ),
3772 (
3773 PickleValue::String("attach_point".into()),
3774 PickleValue::String("PreIngress".into()),
3775 ),
3776 (PickleValue::String("priority".into()), PickleValue::Int(11)),
3777 (
3778 PickleValue::String("wasm".into()),
3779 PickleValue::Bytes(vec![1, 2, 3]),
3780 ),
3781 ]);
3782 let response = handle_rpc_request(&request, &event_tx).unwrap();
3783 assert_eq!(parse_hook_result(&response).unwrap(), Ok(()));
3784 driver.join().unwrap();
3785 }
3786
3787 #[test]
3788 fn interface_stats_pickle_format() {
3789 let traffic = TrafficDetail {
3790 rxs: 800.0,
3791 txs: 1600.0,
3792 arxb: 11,
3793 atxb: 22,
3794 arxc: 1,
3795 atxc: 2,
3796 arxs: 88.0,
3797 atxs: 176.0,
3798 prxb: 33,
3799 ptxb: 44,
3800 prxc: 3,
3801 ptxc: 4,
3802 prxs: 264.0,
3803 ptxs: 352.0,
3804 arxf: 1.0,
3805 atxf: 2.0,
3806 prxf: 3.0,
3807 ptxf: 4.0,
3808 };
3809 let mut stats = InterfaceStatsResponse {
3810 interfaces: vec![SingleInterfaceStat {
3811 id: 1,
3812 name: "TCP".into(),
3813 status: true,
3814 mode: 1,
3815 gravity: -2,
3816 announces_to_internal: Some(true),
3817 rxb: 100,
3818 txb: 200,
3819 tx_drops: 3,
3820 tx_dropped_bytes: 4096,
3821 tx_stalled: true,
3822 tx_buffered: 8192,
3823 traffic,
3824 protocol_violations: 11,
3825 ifac_violations: 12,
3826 packet_filter_hits: 13,
3827 rx_packets: 5,
3828 tx_packets: 10,
3829 cpu_load: Some(12.0),
3830 mem_load: Some(34.5),
3831 switch_id: Some([1, 2, 3, 4]),
3832 endpoint_id: Some([5; 8]),
3833 via_switch_id: Some([6; 4]),
3834 peers: Some(2),
3835 bitrate: Some(1000000),
3836 mtu: Some(131_072),
3837 ifac_size: Some(16),
3838 started: 1000.0,
3839 ia_freq: 1.0,
3840 ip_freq: 2.0,
3841 op_freq: 3.0,
3842 op_samples: 0,
3843 burst_active: true,
3844 burst_activated: 1200.0,
3845 pr_burst_active: true,
3846 pr_burst_activated: 1300.0,
3847 oa_freq: 4.0,
3848 clients: Some(3),
3849 blocked_ips: Some(2),
3850 blocked_ip_list: Some(vec!["192.0.2.1".into(), "203.0.113.2".into()]),
3851 announce_rate_target: Some(3600.0),
3852 announce_rate_grace: 5,
3853 announce_rate_penalty: 0.0,
3854 interface_type: "TCPClientInterface".into(),
3855 }],
3856 transport_id: Some([0xAB; 16]),
3857 transport_enabled: true,
3858 transport_uptime: 3600.0,
3859 total_rxb: 100,
3860 total_txb: 200,
3861 rx_pps: 3.0,
3862 tx_pps: 4.0,
3863 traffic,
3864 probe_responder: None,
3865 backbone_peer_pool: None,
3866 };
3867
3868 let pickle = interface_stats_to_pickle(&stats);
3869
3870 let encoded = pickle::encode(&pickle);
3872 let decoded = pickle::decode(&encoded).unwrap();
3873 assert!(decoded.get("transport_enabled").unwrap().as_bool().unwrap());
3874 let ifaces = decoded.get("interfaces").unwrap().as_list().unwrap();
3875 assert_eq!(decoded.get("arxf").unwrap().as_float(), Some(1.0));
3876 assert_eq!(decoded.get("atxf").unwrap().as_float(), Some(2.0));
3877 assert_eq!(decoded.get("prxf").unwrap().as_float(), Some(3.0));
3878 assert_eq!(decoded.get("ptxf").unwrap().as_float(), Some(4.0));
3879 assert_eq!(decoded.get("rxpps").unwrap().as_float(), Some(3.0));
3880 assert_eq!(decoded.get("txpps").unwrap().as_float(), Some(4.0));
3881 assert_eq!(ifaces[0].get("id").unwrap().as_int().unwrap(), 1);
3882 assert_eq!(ifaces[0].get("name").unwrap().as_str().unwrap(), "TCP");
3883 assert_eq!(ifaces[0].get("mtu").unwrap().as_int(), Some(131_072));
3884 assert_eq!(ifaces[0].get("gravity").unwrap().as_int().unwrap(), -2);
3885 assert_eq!(ifaces[0].get("txdrp").unwrap().as_int(), Some(3));
3886 assert_eq!(ifaces[0].get("txdrb").unwrap().as_int(), Some(4096));
3887 assert_eq!(ifaces[0].get("txstalled").unwrap().as_bool(), Some(true));
3888 assert_eq!(ifaces[0].get("txbuffered").unwrap().as_int(), Some(8192));
3889 assert!(ifaces[0]
3890 .get("announces_to_internal")
3891 .unwrap()
3892 .as_bool()
3893 .unwrap());
3894 assert_eq!(ifaces[0].get("ia_freq").unwrap().as_float().unwrap(), 1.0);
3895 assert_eq!(ifaces[0].get("ip_freq").unwrap().as_float().unwrap(), 2.0);
3896 assert_eq!(ifaces[0].get("op_freq").unwrap().as_float().unwrap(), 3.0);
3897 assert_eq!(ifaces[0].get("oa_freq").unwrap().as_float().unwrap(), 4.0);
3898 assert_eq!(ifaces[0].get("arxb").unwrap().as_int().unwrap(), 11);
3899 assert_eq!(ifaces[0].get("atxb").unwrap().as_int().unwrap(), 22);
3900 assert_eq!(ifaces[0].get("prxb").unwrap().as_int().unwrap(), 33);
3901 assert_eq!(ifaces[0].get("ptxb").unwrap().as_int().unwrap(), 44);
3902 assert_eq!(ifaces[0].get("arxs").unwrap().as_float().unwrap(), 88.0);
3903 assert_eq!(ifaces[0].get("ptxs").unwrap().as_float().unwrap(), 352.0);
3904 assert_eq!(
3905 ifaces[0]
3906 .get("protocol_violations")
3907 .unwrap()
3908 .as_int()
3909 .unwrap(),
3910 11
3911 );
3912 assert_eq!(ifaces[0].get("ifac_violations").unwrap().as_int(), Some(12));
3913 assert_eq!(
3914 ifaces[0].get("packet_filter_hits").unwrap().as_int(),
3915 Some(13)
3916 );
3917 assert_eq!(decoded.get("arxb").unwrap().as_int().unwrap(), 11);
3918 assert_eq!(decoded.get("prxb").unwrap().as_int().unwrap(), 33);
3919 assert!(ifaces[0].get("burst_active").unwrap().as_bool().unwrap());
3920 assert_eq!(
3921 ifaces[0]
3922 .get("burst_activated")
3923 .unwrap()
3924 .as_float()
3925 .unwrap(),
3926 1200.0
3927 );
3928 assert!(ifaces[0].get("pr_burst_active").unwrap().as_bool().unwrap());
3929 assert_eq!(
3930 ifaces[0]
3931 .get("pr_burst_activated")
3932 .unwrap()
3933 .as_float()
3934 .unwrap(),
3935 1300.0
3936 );
3937 assert_eq!(
3938 ifaces[0]
3939 .get("announce_rate_target")
3940 .unwrap()
3941 .as_float()
3942 .unwrap(),
3943 3600.0
3944 );
3945 assert_eq!(
3946 ifaces[0]
3947 .get("announce_rate_grace")
3948 .unwrap()
3949 .as_int()
3950 .unwrap(),
3951 5
3952 );
3953 assert_eq!(
3954 ifaces[0]
3955 .get("announce_rate_penalty")
3956 .unwrap()
3957 .as_float()
3958 .unwrap(),
3959 0.0
3960 );
3961 assert_eq!(ifaces[0].get("clients").unwrap().as_int().unwrap(), 3);
3962 assert_eq!(ifaces[0].get("blocked_ips").unwrap().as_int().unwrap(), 2);
3963 assert_eq!(
3964 ifaces[0]
3965 .get("blocked_ip_list")
3966 .unwrap()
3967 .as_list()
3968 .unwrap()
3969 .iter()
3970 .map(|value| value.as_str().unwrap())
3971 .collect::<Vec<_>>(),
3972 vec!["192.0.2.1", "203.0.113.2"]
3973 );
3974
3975 stats.interfaces[0].interface_type = "BackboneInterface".into();
3976 let (event_tx, _event_rx) = crate::event::channel_with_capacity(1);
3977 event_tx.register_dynamic_parent(InterfaceId(2), InterfaceId(1));
3978 event_tx.set_ingress_bursts(InterfaceId(2), Some(10.0), Some(20.0));
3979 event_tx
3980 .try_send(Event::Frame {
3981 interface_id: InterfaceId(2),
3982 data: vec![0],
3983 rssi: None,
3984 snr: None,
3985 })
3986 .unwrap();
3987 assert!(event_tx
3988 .try_send(Event::Frame {
3989 interface_id: InterfaceId(2),
3990 data: vec![0],
3991 rssi: None,
3992 snr: None,
3993 })
3994 .is_err());
3995 assert!(event_tx
3998 .try_send(Event::Frame {
3999 interface_id: InterfaceId(2),
4000 data: vec![0],
4001 rssi: None,
4002 snr: None,
4003 })
4004 .is_err());
4005 let mut runtime_pickle = interface_stats_to_pickle(&stats);
4006 add_inbound_runtime_stats(&mut runtime_pickle, &stats, &event_tx);
4007 let runtime_ifaces = runtime_pickle.get("interfaces").unwrap().as_list().unwrap();
4008 assert_eq!(
4009 runtime_ifaces[0]
4010 .get("burst_count")
4011 .unwrap()
4012 .as_int()
4013 .unwrap(),
4014 1
4015 );
4016 assert_eq!(
4017 runtime_ifaces[0]
4018 .get("pr_burst_count")
4019 .unwrap()
4020 .as_int()
4021 .unwrap(),
4022 1
4023 );
4024 assert!(runtime_ifaces[0]
4025 .get("burst_active")
4026 .unwrap()
4027 .as_bool()
4028 .unwrap());
4029 assert_eq!(
4030 runtime_ifaces[0]
4031 .get("burst_activated")
4032 .unwrap()
4033 .as_float()
4034 .unwrap(),
4035 10.0
4036 );
4037 assert!(runtime_ifaces[0]
4038 .get("pr_burst_active")
4039 .unwrap()
4040 .as_bool()
4041 .unwrap());
4042 assert_eq!(
4043 runtime_ifaces[0]
4044 .get("pr_burst_activated")
4045 .unwrap()
4046 .as_float()
4047 .unwrap(),
4048 20.0
4049 );
4050 assert_eq!(runtime_pickle.get("rxqt").unwrap().as_int().unwrap(), 1);
4051 assert_eq!(runtime_pickle.get("rxqd").unwrap().as_int().unwrap(), 1);
4052 assert_eq!(runtime_pickle.get("rxqtd").unwrap().as_int().unwrap(), 2);
4053 assert_eq!(runtime_pickle.get("rxqdd").unwrap().as_int().unwrap(), 2);
4054 assert_eq!(runtime_pickle.get("rxqad").unwrap().as_int().unwrap(), 0);
4055 assert_eq!(runtime_pickle.get("rxqpd").unwrap().as_int().unwrap(), 0);
4056 assert_eq!(runtime_pickle.get("rxqild").unwrap().as_int().unwrap(), 0);
4057 assert_eq!(
4058 runtime_pickle
4059 .get("dqpressure")
4060 .unwrap()
4061 .as_float()
4062 .unwrap(),
4063 1.0
4064 );
4065 assert_eq!(
4066 runtime_pickle
4067 .get("tqpressure")
4068 .unwrap()
4069 .as_float()
4070 .unwrap(),
4071 0.25
4072 );
4073 }
4074
4075 #[test]
4076 fn profiling_results_rpc_exposes_completed_samples() {
4077 {
4078 let _sample = crate::profiling::profile("entry110.rpc");
4079 }
4080 let (event_tx, _event_rx) = crate::event::channel();
4081 let request = PickleValue::Dict(vec![(
4082 PickleValue::String("get".into()),
4083 PickleValue::String("profiling_results".into()),
4084 )]);
4085 let response = handle_rpc_request(&request, &event_tx).unwrap();
4086 let sample = response.get("entry110.rpc").unwrap();
4087 assert_eq!(
4088 sample.get("name").and_then(PickleValue::as_str),
4089 Some("entry110.rpc")
4090 );
4091 assert_eq!(
4092 sample
4093 .get("stats_all")
4094 .and_then(|stats| stats.get("count"))
4095 .and_then(PickleValue::as_int),
4096 Some(1)
4097 );
4098 }
4099
4100 #[test]
4101 fn interface_stats_pickle_includes_backbone_peer_pool_priority() {
4102 let stats = InterfaceStatsResponse {
4103 interfaces: vec![],
4104 transport_id: None,
4105 transport_enabled: true,
4106 transport_uptime: 1.0,
4107 total_rxb: 0,
4108 total_txb: 0,
4109 rx_pps: 0.0,
4110 tx_pps: 0.0,
4111 traffic: TrafficDetail::default(),
4112 probe_responder: None,
4113 backbone_peer_pool: Some(crate::event::BackbonePeerPoolStatus {
4114 max_connected: 1,
4115 active_count: 1,
4116 standby_count: 0,
4117 cooldown_count: 0,
4118 members: vec![crate::event::BackbonePeerPoolMemberStatus {
4119 name: "peer".into(),
4120 remote: "127.0.0.1:4242".into(),
4121 source: "configured".into(),
4122 priority: 77,
4123 state: "active".into(),
4124 interface_id: Some(42),
4125 failure_count: 0,
4126 last_error: None,
4127 cooldown_remaining_seconds: None,
4128 }],
4129 }),
4130 };
4131
4132 let pickle = interface_stats_to_pickle(&stats);
4133 let encoded = pickle::encode(&pickle);
4134 let decoded = pickle::decode(&encoded).unwrap();
4135 let pool = decoded.get("backbone_peer_pool").unwrap();
4136 let members = pool.get("members").unwrap().as_list().unwrap();
4137 assert_eq!(members[0].get("priority").unwrap().as_int().unwrap(), 77);
4138 }
4139
4140 #[test]
4141 fn send_probe_rpc_unknown_dest() {
4142 let (event_tx, event_rx) = crate::event::channel();
4143
4144 let driver = thread::spawn(move || {
4145 if let Some((
4146 QueryRequest::SendProbe {
4147 dest_hash,
4148 payload_size,
4149 },
4150 resp_tx,
4151 )) = event_rx
4152 .recv()
4153 .ok()
4154 .and_then(|event| event.into_query().ok())
4155 {
4156 assert_eq!(dest_hash, [0xAA; 16]);
4157 assert_eq!(payload_size, 16); let _ = resp_tx.send(QueryResponse::SendProbe(Err(
4159 crate::event::SendProbeError::UnknownIdentity,
4160 )));
4161 }
4162 });
4163
4164 let request = PickleValue::Dict(vec![(
4165 PickleValue::String("send_probe".into()),
4166 PickleValue::Bytes(vec![0xAA; 16]),
4167 )]);
4168
4169 let response = handle_rpc_request(&request, &event_tx).unwrap();
4170 assert_eq!(response, PickleValue::None);
4171 driver.join().unwrap();
4172 }
4173
4174 #[test]
4175 fn send_probe_rpc_with_result() {
4176 let (event_tx, event_rx) = crate::event::channel();
4177
4178 let packet_hash = [0xBB; 32];
4179 let driver = thread::spawn(move || {
4180 if let Some((
4181 QueryRequest::SendProbe {
4182 dest_hash,
4183 payload_size,
4184 },
4185 resp_tx,
4186 )) = event_rx
4187 .recv()
4188 .ok()
4189 .and_then(|event| event.into_query().ok())
4190 {
4191 assert_eq!(dest_hash, [0xCC; 16]);
4192 assert_eq!(payload_size, 32);
4193 let _ = resp_tx.send(QueryResponse::SendProbe(Ok((packet_hash, 3))));
4194 }
4195 });
4196
4197 let request = PickleValue::Dict(vec![
4198 (
4199 PickleValue::String("send_probe".into()),
4200 PickleValue::Bytes(vec![0xCC; 16]),
4201 ),
4202 (PickleValue::String("size".into()), PickleValue::Int(32)),
4203 ]);
4204
4205 let response = handle_rpc_request(&request, &event_tx).unwrap();
4206 let ph = response.get("packet_hash").unwrap().as_bytes().unwrap();
4207 assert_eq!(ph, &[0xBB; 32]);
4208 assert_eq!(response.get("hops").unwrap().as_int().unwrap(), 3);
4209 driver.join().unwrap();
4210 }
4211
4212 #[test]
4213 fn retain_identity_rpc_sends_identity_hash_query() {
4214 let (event_tx, event_rx) = crate::event::channel();
4215 let identity_hash = [0x44; 16];
4216
4217 let driver = thread::spawn(move || {
4218 if let Some((
4219 QueryRequest::RetainIdentity {
4220 identity_hash: hash,
4221 },
4222 resp_tx,
4223 )) = event_rx
4224 .recv()
4225 .ok()
4226 .and_then(|event| event.into_query().ok())
4227 {
4228 assert_eq!(hash, identity_hash);
4229 let _ = resp_tx.send(QueryResponse::RetainIdentity(true));
4230 }
4231 });
4232
4233 let request = PickleValue::Dict(vec![
4234 (
4235 PickleValue::String("set".into()),
4236 PickleValue::String("identity_retained".into()),
4237 ),
4238 (
4239 PickleValue::String("identity_hash".into()),
4240 PickleValue::Bytes(identity_hash.to_vec()),
4241 ),
4242 ]);
4243
4244 let response = handle_rpc_request(&request, &event_tx).unwrap();
4245 assert_eq!(response, PickleValue::Bool(true));
4246 driver.join().unwrap();
4247 }
4248
4249 #[test]
4250 fn send_probe_rpc_size_validation() {
4251 let (event_tx, event_rx) = crate::event::channel();
4252
4253 let driver = thread::spawn(move || {
4255 if let Some((QueryRequest::SendProbe { payload_size, .. }, resp_tx)) = event_rx
4256 .recv()
4257 .ok()
4258 .and_then(|event| event.into_query().ok())
4259 {
4260 assert_eq!(payload_size, 16); let _ = resp_tx.send(QueryResponse::SendProbe(Err(
4262 crate::event::SendProbeError::UnknownIdentity,
4263 )));
4264 }
4265 });
4266
4267 let request = PickleValue::Dict(vec![
4268 (
4269 PickleValue::String("send_probe".into()),
4270 PickleValue::Bytes(vec![0xDD; 16]),
4271 ),
4272 (PickleValue::String("size".into()), PickleValue::Int(-1)),
4273 ]);
4274
4275 let response = handle_rpc_request(&request, &event_tx).unwrap();
4276 assert_eq!(response, PickleValue::None);
4277 driver.join().unwrap();
4278 }
4279
4280 #[test]
4281 fn send_probe_rpc_size_too_large() {
4282 let (event_tx, _event_rx) = crate::event::channel();
4283
4284 let request = PickleValue::Dict(vec![
4287 (
4288 PickleValue::String("send_probe".into()),
4289 PickleValue::Bytes(vec![0xDD; 16]),
4290 ),
4291 (
4292 PickleValue::String("size".into()),
4293 PickleValue::Int(crate::event::MAX_PROBE_PAYLOAD_SIZE as i64 + 1),
4294 ),
4295 ]);
4296
4297 let response = handle_rpc_request(&request, &event_tx).unwrap();
4298 assert_eq!(
4299 response.get("error").and_then(|v| v.as_str()),
4300 Some("payload_too_large")
4301 );
4302 assert_eq!(
4303 response.get("max_size").and_then(|v| v.as_int()),
4304 Some(crate::event::MAX_PROBE_PAYLOAD_SIZE as i64)
4305 );
4306 }
4307
4308 #[test]
4309 fn send_probe_rpc_max_size_reaches_driver() {
4310 let (event_tx, event_rx) = crate::event::channel();
4311
4312 let driver = thread::spawn(move || {
4313 if let Some((QueryRequest::SendProbe { payload_size, .. }, resp_tx)) = event_rx
4314 .recv()
4315 .ok()
4316 .and_then(|event| event.into_query().ok())
4317 {
4318 assert_eq!(payload_size, crate::event::MAX_PROBE_PAYLOAD_SIZE);
4319 let _ = resp_tx.send(QueryResponse::SendProbe(Ok(([0xEE; 32], 2))));
4320 }
4321 });
4322
4323 let request = PickleValue::Dict(vec![
4324 (
4325 PickleValue::String("send_probe".into()),
4326 PickleValue::Bytes(vec![0xDD; 16]),
4327 ),
4328 (
4329 PickleValue::String("size".into()),
4330 PickleValue::Int(crate::event::MAX_PROBE_PAYLOAD_SIZE as i64),
4331 ),
4332 ]);
4333
4334 let response = handle_rpc_request(&request, &event_tx).unwrap();
4335 assert_eq!(response.get("hops").unwrap().as_int().unwrap(), 2);
4336 driver.join().unwrap();
4337 }
4338
4339 #[test]
4340 fn send_probe_rpc_driver_payload_too_large_is_reported() {
4341 let (event_tx, event_rx) = crate::event::channel();
4342
4343 let driver = thread::spawn(move || {
4344 if let Some((QueryRequest::SendProbe { .. }, resp_tx)) = event_rx
4345 .recv()
4346 .ok()
4347 .and_then(|event| event.into_query().ok())
4348 {
4349 let _ = resp_tx.send(QueryResponse::SendProbe(Err(
4350 crate::event::SendProbeError::PayloadTooLarge { max: 123 },
4351 )));
4352 }
4353 });
4354
4355 let request = PickleValue::Dict(vec![(
4356 PickleValue::String("send_probe".into()),
4357 PickleValue::Bytes(vec![0xDD; 16]),
4358 )]);
4359
4360 let response = handle_rpc_request(&request, &event_tx).unwrap();
4361 assert_eq!(
4362 response.get("error").and_then(|v| v.as_str()),
4363 Some("payload_too_large")
4364 );
4365 assert_eq!(response.get("max_size").and_then(|v| v.as_int()), Some(123));
4366 driver.join().unwrap();
4367 }
4368
4369 #[test]
4370 fn check_proof_rpc_not_found() {
4371 let (event_tx, event_rx) = crate::event::channel();
4372
4373 let driver = thread::spawn(move || {
4374 if let Some((QueryRequest::CheckProof { packet_hash }, resp_tx)) = event_rx
4375 .recv()
4376 .ok()
4377 .and_then(|event| event.into_query().ok())
4378 {
4379 assert_eq!(packet_hash, [0xEE; 32]);
4380 let _ = resp_tx.send(QueryResponse::CheckProof(None));
4381 }
4382 });
4383
4384 let request = PickleValue::Dict(vec![(
4385 PickleValue::String("check_proof".into()),
4386 PickleValue::Bytes(vec![0xEE; 32]),
4387 )]);
4388
4389 let response = handle_rpc_request(&request, &event_tx).unwrap();
4390 assert_eq!(response, PickleValue::None);
4391 driver.join().unwrap();
4392 }
4393
4394 #[test]
4395 fn check_proof_rpc_found() {
4396 let (event_tx, event_rx) = crate::event::channel();
4397
4398 let driver = thread::spawn(move || {
4399 if let Some((QueryRequest::CheckProof { packet_hash }, resp_tx)) = event_rx
4400 .recv()
4401 .ok()
4402 .and_then(|event| event.into_query().ok())
4403 {
4404 assert_eq!(packet_hash, [0xFF; 32]);
4405 let _ = resp_tx.send(QueryResponse::CheckProof(Some(0.352)));
4406 }
4407 });
4408
4409 let request = PickleValue::Dict(vec![(
4410 PickleValue::String("check_proof".into()),
4411 PickleValue::Bytes(vec![0xFF; 32]),
4412 )]);
4413
4414 let response = handle_rpc_request(&request, &event_tx).unwrap();
4415 if let PickleValue::Float(rtt) = response {
4416 assert!((rtt - 0.352).abs() < 0.001);
4417 } else {
4418 panic!("Expected Float, got {:?}", response);
4419 }
4420 driver.join().unwrap();
4421 }
4422
4423 #[test]
4424 fn request_path_rpc() {
4425 let (event_tx, event_rx) = crate::event::channel();
4426
4427 let driver =
4428 thread::spawn(
4429 move || match event_rx.recv_timeout(std::time::Duration::from_secs(5)) {
4430 Ok(Event::RequestPath { dest_hash }) => {
4431 assert_eq!(dest_hash, [0x11; 16]);
4432 }
4433 other => panic!("Expected RequestPath event, got {:?}", other),
4434 },
4435 );
4436
4437 let request = PickleValue::Dict(vec![(
4438 PickleValue::String("request_path".into()),
4439 PickleValue::Bytes(vec![0x11; 16]),
4440 )]);
4441
4442 let response = handle_rpc_request(&request, &event_tx).unwrap();
4443 assert_eq!(response, PickleValue::Bool(true));
4444 driver.join().unwrap();
4445 }
4446
4447 #[test]
4448 fn begin_drain_rpc_emits_event() {
4449 let (event_tx, event_rx) = crate::event::channel();
4450
4451 let driver = thread::spawn(
4452 move || match event_rx.recv_timeout(Duration::from_secs(5)) {
4453 Ok(Event::BeginDrain { timeout }) => {
4454 assert!((timeout.as_secs_f64() - 1.5).abs() < 0.001);
4455 }
4456 other => panic!("Expected BeginDrain event, got {:?}", other),
4457 },
4458 );
4459
4460 let request = PickleValue::Dict(vec![(
4461 PickleValue::String("begin_drain".into()),
4462 PickleValue::Float(1.5),
4463 )]);
4464
4465 let response = handle_rpc_request(&request, &event_tx).unwrap();
4466 assert_eq!(response, PickleValue::Bool(true));
4467 driver.join().unwrap();
4468 }
4469
4470 #[test]
4471 fn drain_status_rpc_roundtrips_fields() {
4472 let (event_tx, event_rx) = crate::event::channel();
4473
4474 let driver = thread::spawn(move || {
4475 if let Some((QueryRequest::DrainStatus, resp_tx)) = event_rx
4476 .recv()
4477 .ok()
4478 .and_then(|event| event.into_query().ok())
4479 {
4480 let _ = resp_tx.send(QueryResponse::DrainStatus(DrainStatus {
4481 state: LifecycleState::Draining,
4482 drain_age_seconds: Some(0.75),
4483 deadline_remaining_seconds: Some(2.25),
4484 drain_complete: false,
4485 interface_writer_queued_frames: 3,
4486 provider_backlog_events: 4,
4487 provider_consumer_queued_events: 5,
4488 detail: Some("node is draining existing work".into()),
4489 }));
4490 }
4491 });
4492
4493 let request = PickleValue::Dict(vec![(
4494 PickleValue::String("get".into()),
4495 PickleValue::String("drain_status".into()),
4496 )]);
4497
4498 let response = handle_rpc_request(&request, &event_tx).unwrap();
4499 assert_eq!(response.get("state").unwrap().as_str(), Some("draining"));
4500 assert_eq!(
4501 response.get("drain_complete").unwrap().as_bool(),
4502 Some(false)
4503 );
4504 assert_eq!(
4505 response
4506 .get("deadline_remaining_seconds")
4507 .unwrap()
4508 .as_float(),
4509 Some(2.25)
4510 );
4511 assert_eq!(
4512 response
4513 .get("interface_writer_queued_frames")
4514 .unwrap()
4515 .as_int(),
4516 Some(3)
4517 );
4518 assert_eq!(
4519 response.get("provider_backlog_events").unwrap().as_int(),
4520 Some(4)
4521 );
4522 assert_eq!(
4523 response
4524 .get("provider_consumer_queued_events")
4525 .unwrap()
4526 .as_int(),
4527 Some(5)
4528 );
4529 assert_eq!(
4530 response.get("detail").unwrap().as_str(),
4531 Some("node is draining existing work")
4532 );
4533 driver.join().unwrap();
4534 }
4535
4536 #[test]
4537 fn interface_stats_with_probe_responder() {
4538 let probe_hash = [0x42; 16];
4539 let stats = InterfaceStatsResponse {
4540 interfaces: vec![],
4541 transport_id: None,
4542 transport_enabled: true,
4543 transport_uptime: 100.0,
4544 total_rxb: 0,
4545 total_txb: 0,
4546 rx_pps: 0.0,
4547 tx_pps: 0.0,
4548 traffic: TrafficDetail::default(),
4549 probe_responder: Some(probe_hash),
4550 backbone_peer_pool: None,
4551 };
4552
4553 let pickle = interface_stats_to_pickle(&stats);
4554 let encoded = pickle::encode(&pickle);
4555 let decoded = pickle::decode(&encoded).unwrap();
4556
4557 let pr = decoded.get("probe_responder").unwrap().as_bytes().unwrap();
4558 assert_eq!(pr, &probe_hash);
4559 }
4560
4561 #[test]
4562 fn runtime_config_get_and_set_rpc() {
4563 let (event_tx, event_rx) = crate::event::channel();
4564
4565 let driver = thread::spawn(move || {
4566 if let Some((QueryRequest::GetRuntimeConfig { key }, resp_tx)) = event_rx
4567 .recv()
4568 .ok()
4569 .and_then(|event| event.into_query().ok())
4570 {
4571 assert_eq!(key, "global.tick_interval_ms");
4572 let _ = resp_tx.send(QueryResponse::RuntimeConfigEntry(Some(
4573 RuntimeConfigEntry {
4574 key,
4575 value: RuntimeConfigValue::Int(1000),
4576 default: RuntimeConfigValue::Int(1000),
4577 source: RuntimeConfigSource::Startup,
4578 apply_mode: RuntimeConfigApplyMode::Immediate,
4579 description: Some("tick".into()),
4580 },
4581 )));
4582 } else {
4583 panic!("expected GetRuntimeConfig query");
4584 }
4585
4586 if let Some((QueryRequest::SetRuntimeConfig { key, value }, resp_tx)) = event_rx
4587 .recv()
4588 .ok()
4589 .and_then(|event| event.into_query().ok())
4590 {
4591 assert_eq!(key, "global.tick_interval_ms");
4592 assert_eq!(value, RuntimeConfigValue::Int(250));
4593 let _ = resp_tx.send(QueryResponse::RuntimeConfigSet(Ok(RuntimeConfigEntry {
4594 key,
4595 value: RuntimeConfigValue::Int(250),
4596 default: RuntimeConfigValue::Int(1000),
4597 source: RuntimeConfigSource::RuntimeOverride,
4598 apply_mode: RuntimeConfigApplyMode::Immediate,
4599 description: Some("tick".into()),
4600 })));
4601 } else {
4602 panic!("expected SetRuntimeConfig query");
4603 }
4604 });
4605
4606 let get_request = PickleValue::Dict(vec![
4607 (
4608 PickleValue::String("get".into()),
4609 PickleValue::String("runtime_config_entry".into()),
4610 ),
4611 (
4612 PickleValue::String("key".into()),
4613 PickleValue::String("global.tick_interval_ms".into()),
4614 ),
4615 ]);
4616 let get_response = handle_rpc_request(&get_request, &event_tx).unwrap();
4617 assert_eq!(
4618 get_response.get("key").and_then(|v| v.as_str()),
4619 Some("global.tick_interval_ms")
4620 );
4621
4622 let set_request = PickleValue::Dict(vec![
4623 (
4624 PickleValue::String("set".into()),
4625 PickleValue::String("runtime_config".into()),
4626 ),
4627 (
4628 PickleValue::String("key".into()),
4629 PickleValue::String("global.tick_interval_ms".into()),
4630 ),
4631 (PickleValue::String("value".into()), PickleValue::Int(250)),
4632 ]);
4633 let set_response = handle_rpc_request(&set_request, &event_tx).unwrap();
4634 assert_eq!(
4635 set_response.get("value").and_then(|v| v.as_int()),
4636 Some(250)
4637 );
4638
4639 driver.join().unwrap();
4640 }
4641
4642 fn tcp_pair() -> (TcpStream, TcpStream) {
4644 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
4645 let port = listener.local_addr().unwrap().port();
4646 let client = TcpStream::connect(("127.0.0.1", port)).unwrap();
4647 let (server, _) = listener.accept().unwrap();
4648 client
4649 .set_read_timeout(Some(std::time::Duration::from_secs(5)))
4650 .unwrap();
4651 server
4652 .set_read_timeout(Some(std::time::Duration::from_secs(5)))
4653 .unwrap();
4654 (server, client)
4655 }
4656}