1use std::io::{self, Read, Write};
12use std::net::{IpAddr, TcpListener, TcpStream};
13use std::sync::mpsc;
14use std::sync::{
15 atomic::{AtomicBool, Ordering},
16 Arc,
17};
18use std::thread;
19use std::time::Duration;
20
21use rns_core::msgpack::{self, Value as MsgpackValue};
22use rns_crypto::hmac::hmac_sha256;
23use rns_crypto::sha256::sha256;
24
25use crate::event::{
26 BackboneInterfaceEntry, BackbonePeerStateEntry, BlackholeInfo, DrainStatus, Event, EventSender,
27 HookInfo, InterfaceStatsResponse, KnownDestinationEntry, LifecycleState, PathTableEntry,
28 ProviderBridgeStats, QueryRequest, QueryResponse, RateTableEntry, RuntimeConfigApplyMode,
29 RuntimeConfigEntry, RuntimeConfigError, RuntimeConfigErrorCode, RuntimeConfigSource,
30 RuntimeConfigValue, SingleInterfaceStat,
31};
32use crate::md5::hmac_md5;
33use crate::pickle::{self, PickleValue};
34
35const CHALLENGE_PREFIX: &[u8] = b"#CHALLENGE#";
36const WELCOME: &[u8] = b"#WELCOME#";
37const FAILURE: &[u8] = b"#FAILURE#";
38const CHALLENGE_LEN: usize = 40;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41enum RpcCodec {
42 Msgpack,
43 Pickle,
44}
45
46#[derive(Debug, Clone)]
48pub enum RpcAddr {
49 Tcp(String, u16),
50}
51
52pub struct RpcServer {
54 shutdown: Arc<AtomicBool>,
55 thread: Option<thread::JoinHandle<()>>,
56}
57
58impl RpcServer {
59 pub fn start(addr: &RpcAddr, auth_key: [u8; 32], event_tx: EventSender) -> io::Result<Self> {
61 let shutdown = Arc::new(AtomicBool::new(false));
62 let shutdown2 = shutdown.clone();
63
64 let listener = match addr {
65 RpcAddr::Tcp(host, port) => {
66 let l = TcpListener::bind((host.as_str(), *port))?;
67 l.set_nonblocking(true)?;
69 l
70 }
71 };
72
73 let thread = thread::Builder::new()
74 .name("rpc-server".into())
75 .spawn(move || {
76 rpc_server_loop(listener, auth_key, event_tx, shutdown2);
77 })
78 .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
79
80 Ok(RpcServer {
81 shutdown,
82 thread: Some(thread),
83 })
84 }
85
86 pub fn stop(&mut self) {
88 self.shutdown.store(true, Ordering::Relaxed);
89 if let Some(handle) = self.thread.take() {
90 let _ = handle.join();
91 }
92 }
93}
94
95impl Drop for RpcServer {
96 fn drop(&mut self) {
97 self.stop();
98 }
99}
100
101fn rpc_server_loop(
102 listener: TcpListener,
103 auth_key: [u8; 32],
104 event_tx: EventSender,
105 shutdown: Arc<AtomicBool>,
106) {
107 loop {
108 if shutdown.load(Ordering::Relaxed) {
109 break;
110 }
111
112 match listener.accept() {
113 Ok((stream, _addr)) => {
114 let _ = stream.set_nonblocking(false);
116 let _ = stream.set_read_timeout(Some(std::time::Duration::from_secs(10)));
117 let _ = stream.set_write_timeout(Some(std::time::Duration::from_secs(10)));
118
119 if let Err(e) = handle_connection(stream, &auth_key, &event_tx) {
120 log::debug!("RPC connection error: {}", e);
121 }
122 }
123 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
124 thread::sleep(std::time::Duration::from_millis(100));
126 }
127 Err(e) => {
128 log::error!("RPC accept error: {}", e);
129 thread::sleep(std::time::Duration::from_millis(100));
130 }
131 }
132 }
133}
134
135fn handle_connection(
136 mut stream: TcpStream,
137 auth_key: &[u8; 32],
138 event_tx: &EventSender,
139) -> io::Result<()> {
140 server_auth(&mut stream, auth_key)?;
142
143 let request_bytes = recv_bytes(&mut stream)?;
145 let (request, codec) = decode_rpc_payload(&request_bytes)?;
146
147 let response = handle_rpc_request(&request, event_tx)?;
149
150 let response_bytes = encode_rpc_payload(&response, codec);
152 send_bytes(&mut stream, &response_bytes)?;
153
154 Ok(())
155}
156
157fn decode_rpc_payload(data: &[u8]) -> io::Result<(PickleValue, RpcCodec)> {
158 if let Ok(value) = msgpack::unpack_exact(data) {
159 return Ok((msgpack_to_pickle(&value), RpcCodec::Msgpack));
160 }
161 pickle::decode(data)
162 .map(|value| (value, RpcCodec::Pickle))
163 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))
164}
165
166fn encode_rpc_payload(value: &PickleValue, codec: RpcCodec) -> Vec<u8> {
167 match codec {
168 RpcCodec::Msgpack => msgpack::pack(&pickle_to_msgpack(value)),
169 RpcCodec::Pickle => pickle::encode(value),
170 }
171}
172
173fn pickle_to_msgpack(value: &PickleValue) -> MsgpackValue {
174 match value {
175 PickleValue::None => MsgpackValue::Nil,
176 PickleValue::Bool(v) => MsgpackValue::Bool(*v),
177 PickleValue::Int(v) if *v >= 0 => MsgpackValue::UInt(*v as u64),
178 PickleValue::Int(v) => MsgpackValue::Int(*v),
179 PickleValue::Float(v) => MsgpackValue::Float(*v),
180 PickleValue::String(v) => MsgpackValue::Str(v.clone()),
181 PickleValue::Bytes(v) => MsgpackValue::Bin(v.clone()),
182 PickleValue::List(values) => {
183 MsgpackValue::Array(values.iter().map(pickle_to_msgpack).collect())
184 }
185 PickleValue::Dict(values) => MsgpackValue::Map(
186 values
187 .iter()
188 .map(|(key, value)| (pickle_to_msgpack(key), pickle_to_msgpack(value)))
189 .collect(),
190 ),
191 }
192}
193
194fn msgpack_to_pickle(value: &MsgpackValue) -> PickleValue {
195 match value {
196 MsgpackValue::Nil => PickleValue::None,
197 MsgpackValue::Bool(v) => PickleValue::Bool(*v),
198 MsgpackValue::UInt(v) if *v <= i64::MAX as u64 => PickleValue::Int(*v as i64),
199 MsgpackValue::UInt(v) => PickleValue::Float(*v as f64),
200 MsgpackValue::Int(v) => PickleValue::Int(*v),
201 MsgpackValue::Float(v) => PickleValue::Float(*v),
202 MsgpackValue::Bin(v) => PickleValue::Bytes(v.clone()),
203 MsgpackValue::Str(v) => PickleValue::String(v.clone()),
204 MsgpackValue::Array(values) => {
205 PickleValue::List(values.iter().map(msgpack_to_pickle).collect())
206 }
207 MsgpackValue::Map(values) => PickleValue::Dict(
208 values
209 .iter()
210 .map(|(key, value)| (msgpack_to_pickle(key), msgpack_to_pickle(value)))
211 .collect(),
212 ),
213 }
214}
215
216fn server_auth(stream: &mut TcpStream, auth_key: &[u8; 32]) -> io::Result<()> {
218 let mut random_bytes = [0u8; CHALLENGE_LEN];
220 {
222 let mut f = std::fs::File::open("/dev/urandom")?;
223 f.read_exact(&mut random_bytes)?;
224 }
225
226 let mut challenge_message = Vec::with_capacity(CHALLENGE_PREFIX.len() + 8 + CHALLENGE_LEN);
227 challenge_message.extend_from_slice(CHALLENGE_PREFIX);
228 challenge_message.extend_from_slice(b"{sha256}");
229 challenge_message.extend_from_slice(&random_bytes);
230
231 send_bytes(stream, &challenge_message)?;
232
233 let response = recv_bytes(stream)?;
235
236 let message = &challenge_message[CHALLENGE_PREFIX.len()..];
239
240 if verify_response(auth_key, message, &response) {
241 send_bytes(stream, WELCOME)?;
242 Ok(())
243 } else {
244 send_bytes(stream, FAILURE)?;
245 Err(io::Error::new(
246 io::ErrorKind::PermissionDenied,
247 "auth failed",
248 ))
249 }
250}
251
252fn verify_response(auth_key: &[u8; 32], message: &[u8], response: &[u8]) -> bool {
254 if response.starts_with(b"{sha256}") {
256 let digest = &response[8..];
257 let expected = hmac_sha256(auth_key, message);
258 constant_time_eq(digest, &expected)
259 }
260 else if response.len() == 16 {
262 let expected = hmac_md5(auth_key, message);
263 constant_time_eq(response, &expected)
264 }
265 else if response.starts_with(b"{md5}") {
267 let digest = &response[5..];
268 let expected = hmac_md5(auth_key, message);
269 constant_time_eq(digest, &expected)
270 } else {
271 false
272 }
273}
274
275fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
277 if a.len() != b.len() {
278 return false;
279 }
280 let mut diff = 0u8;
281 for (x, y) in a.iter().zip(b.iter()) {
282 diff |= x ^ y;
283 }
284 diff == 0
285}
286
287fn send_bytes(stream: &mut TcpStream, data: &[u8]) -> io::Result<()> {
289 let len = data.len() as i32;
290 stream.write_all(&len.to_be_bytes())?;
291 stream.write_all(data)?;
292 stream.flush()
293}
294
295fn recv_bytes(stream: &mut TcpStream) -> io::Result<Vec<u8>> {
297 let mut len_buf = [0u8; 4];
298 stream.read_exact(&mut len_buf)?;
299 let len = i32::from_be_bytes(len_buf);
300
301 if len < 0 {
302 let mut len8_buf = [0u8; 8];
304 stream.read_exact(&mut len8_buf)?;
305 let len = u64::from_be_bytes(len8_buf) as usize;
306 if len > 64 * 1024 * 1024 {
307 return Err(io::Error::new(
308 io::ErrorKind::InvalidData,
309 "message too large",
310 ));
311 }
312 let mut buf = vec![0u8; len];
313 stream.read_exact(&mut buf)?;
314 Ok(buf)
315 } else {
316 let len = len as usize;
317 if len > 64 * 1024 * 1024 {
318 return Err(io::Error::new(
319 io::ErrorKind::InvalidData,
320 "message too large",
321 ));
322 }
323 let mut buf = vec![0u8; len];
324 stream.read_exact(&mut buf)?;
325 Ok(buf)
326 }
327}
328
329fn handle_rpc_request(request: &PickleValue, event_tx: &EventSender) -> io::Result<PickleValue> {
331 if let Some(get_val) = request.get("get") {
333 if let Some(path) = get_val.as_str() {
334 return match path {
335 "interface_stats" => {
336 let resp = send_query(event_tx, QueryRequest::InterfaceStats)?;
337 if let QueryResponse::InterfaceStats(stats) = resp {
338 Ok(interface_stats_to_pickle(&stats))
339 } else {
340 Ok(PickleValue::None)
341 }
342 }
343 "path_table" => {
344 let max_hops = request
345 .get("max_hops")
346 .and_then(|v| v.as_int().map(|n| n as u8));
347 let resp = send_query(event_tx, QueryRequest::PathTable { max_hops })?;
348 if let QueryResponse::PathTable(entries) = resp {
349 Ok(path_table_to_pickle(&entries))
350 } else {
351 Ok(PickleValue::None)
352 }
353 }
354 "rate_table" => {
355 let resp = send_query(event_tx, QueryRequest::RateTable)?;
356 if let QueryResponse::RateTable(entries) = resp {
357 Ok(rate_table_to_pickle(&entries))
358 } else {
359 Ok(PickleValue::None)
360 }
361 }
362 "next_hop" => {
363 let hash = extract_dest_hash(request, "destination_hash")?;
364 let resp = send_query(event_tx, QueryRequest::NextHop { dest_hash: hash })?;
365 if let QueryResponse::NextHop(Some(nh)) = resp {
366 Ok(PickleValue::Bytes(nh.next_hop.to_vec()))
367 } else {
368 Ok(PickleValue::None)
369 }
370 }
371 "next_hop_if_name" => {
372 let hash = extract_dest_hash(request, "destination_hash")?;
373 let resp =
374 send_query(event_tx, QueryRequest::NextHopIfName { dest_hash: hash })?;
375 if let QueryResponse::NextHopIfName(Some(name)) = resp {
376 Ok(PickleValue::String(name))
377 } else {
378 Ok(PickleValue::None)
379 }
380 }
381 "link_count" => {
382 let resp = send_query(event_tx, QueryRequest::LinkCount)?;
383 if let QueryResponse::LinkCount(n) = resp {
384 Ok(PickleValue::Int(n as i64))
385 } else {
386 Ok(PickleValue::None)
387 }
388 }
389 "transport_identity" => {
390 let resp = send_query(event_tx, QueryRequest::TransportIdentity)?;
391 if let QueryResponse::TransportIdentity(Some(hash)) = resp {
392 Ok(PickleValue::Bytes(hash.to_vec()))
393 } else {
394 Ok(PickleValue::None)
395 }
396 }
397 "blackholed" => {
398 let resp = send_query(event_tx, QueryRequest::GetBlackholed)?;
399 if let QueryResponse::Blackholed(entries) = resp {
400 Ok(blackholed_to_pickle(&entries))
401 } else {
402 Ok(PickleValue::None)
403 }
404 }
405 "discovered_interfaces" => {
406 let only_available = request
407 .get("only_available")
408 .and_then(|v| v.as_bool())
409 .unwrap_or(false);
410 let only_transport = request
411 .get("only_transport")
412 .and_then(|v| v.as_bool())
413 .unwrap_or(false);
414 let resp = send_query(
415 event_tx,
416 QueryRequest::DiscoveredInterfaces {
417 only_available,
418 only_transport,
419 },
420 )?;
421 if let QueryResponse::DiscoveredInterfaces(interfaces) = resp {
422 Ok(discovered_interfaces_to_pickle(&interfaces))
423 } else {
424 Ok(PickleValue::None)
425 }
426 }
427 "hooks" => {
428 let (response_tx, response_rx) = mpsc::channel();
429 event_tx
430 .send(Event::ListHooks { response_tx })
431 .map_err(|_| {
432 io::Error::new(io::ErrorKind::BrokenPipe, "driver shut down")
433 })?;
434 let hooks = response_rx
435 .recv_timeout(std::time::Duration::from_secs(5))
436 .map_err(|_| {
437 io::Error::new(io::ErrorKind::TimedOut, "list hooks timed out")
438 })?;
439 Ok(hooks_to_pickle(&hooks))
440 }
441 "runtime_config" => {
442 let resp = send_query(event_tx, QueryRequest::ListRuntimeConfig)?;
443 if let QueryResponse::RuntimeConfigList(entries) = resp {
444 Ok(runtime_config_list_to_pickle(&entries))
445 } else {
446 Ok(PickleValue::None)
447 }
448 }
449 "known_destinations" => {
450 let resp = send_query(event_tx, QueryRequest::KnownDestinations)?;
451 if let QueryResponse::KnownDestinations(entries) = resp {
452 Ok(known_destinations_to_pickle(&entries))
453 } else {
454 Ok(PickleValue::None)
455 }
456 }
457 "runtime_config_entry" => {
458 let key = request
459 .get("key")
460 .and_then(|v| v.as_str())
461 .unwrap_or_default()
462 .to_string();
463 let resp = send_query(event_tx, QueryRequest::GetRuntimeConfig { key })?;
464 if let QueryResponse::RuntimeConfigEntry(entry) = resp {
465 Ok(entry
466 .as_ref()
467 .map(runtime_config_entry_to_pickle)
468 .unwrap_or(PickleValue::None))
469 } else {
470 Ok(PickleValue::None)
471 }
472 }
473 "backbone_peer_state" => {
474 let interface_name = request
475 .get("interface")
476 .and_then(|v| v.as_str())
477 .map(|s| s.to_string());
478 let resp =
479 send_query(event_tx, QueryRequest::BackbonePeerState { interface_name })?;
480 if let QueryResponse::BackbonePeerState(entries) = resp {
481 Ok(backbone_peer_state_to_pickle(&entries))
482 } else {
483 Ok(PickleValue::None)
484 }
485 }
486 "backbone_interfaces" => {
487 let resp = send_query(event_tx, QueryRequest::BackboneInterfaces)?;
488 if let QueryResponse::BackboneInterfaces(entries) = resp {
489 Ok(backbone_interfaces_to_pickle(&entries))
490 } else {
491 Ok(PickleValue::None)
492 }
493 }
494 "provider_bridge_stats" => {
495 let resp = send_query(event_tx, QueryRequest::ProviderBridgeStats)?;
496 if let QueryResponse::ProviderBridgeStats(stats) = resp {
497 Ok(stats
498 .as_ref()
499 .map(provider_bridge_stats_to_pickle)
500 .unwrap_or(PickleValue::None))
501 } else {
502 Ok(PickleValue::None)
503 }
504 }
505 "drain_status" => {
506 let resp = send_query(event_tx, QueryRequest::DrainStatus)?;
507 if let QueryResponse::DrainStatus(status) = resp {
508 Ok(drain_status_to_pickle(&status))
509 } else {
510 Ok(PickleValue::None)
511 }
512 }
513 _ => Ok(PickleValue::None),
514 };
515 }
516 }
517
518 if let Some(begin_val) = request.get("begin_drain") {
519 let timeout_secs = begin_val
520 .as_float()
521 .or_else(|| begin_val.as_int().map(|value| value as f64))
522 .unwrap_or(0.0)
523 .max(0.0);
524 let timeout = Duration::from_secs_f64(timeout_secs);
525 let _ = event_tx.send(Event::BeginDrain { timeout });
526 return Ok(PickleValue::Bool(true));
527 }
528
529 if let Some(set_val) = request.get("set").and_then(|v| v.as_str()) {
530 if set_val == "known_destination_retained" {
531 let dest_hash = extract_dest_hash(request, "dest_hash")?;
532 let resp = send_query(event_tx, QueryRequest::RetainKnownDestination { dest_hash })?;
533 return if let QueryResponse::RetainKnownDestination(ok) = resp {
534 Ok(PickleValue::Bool(ok))
535 } else {
536 Ok(PickleValue::None)
537 };
538 }
539 if set_val == "identity_retained" {
540 let identity_hash = extract_dest_hash(request, "identity_hash")?;
541 let resp = send_query(event_tx, QueryRequest::RetainIdentity { identity_hash })?;
542 return if let QueryResponse::RetainIdentity(ok) = resp {
543 Ok(PickleValue::Bool(ok))
544 } else {
545 Ok(PickleValue::None)
546 };
547 }
548 if set_val == "known_destination_used" {
549 let dest_hash = extract_dest_hash(request, "dest_hash")?;
550 let resp = send_query(
551 event_tx,
552 QueryRequest::MarkKnownDestinationUsed { dest_hash },
553 )?;
554 return if let QueryResponse::MarkKnownDestinationUsed(ok) = resp {
555 Ok(PickleValue::Bool(ok))
556 } else {
557 Ok(PickleValue::None)
558 };
559 }
560 if set_val == "runtime_config" {
561 let key = request
562 .get("key")
563 .and_then(|v| v.as_str())
564 .unwrap_or_default()
565 .to_string();
566 let Some(value) = request
567 .get("value")
568 .and_then(runtime_config_value_from_pickle)
569 else {
570 return Ok(runtime_config_error_to_pickle(&RuntimeConfigError {
571 code: RuntimeConfigErrorCode::InvalidType,
572 message: "runtime-config set requires a scalar value".into(),
573 }));
574 };
575 let resp = send_query(event_tx, QueryRequest::SetRuntimeConfig { key, value })?;
576 return if let QueryResponse::RuntimeConfigSet(result) = resp {
577 Ok(runtime_config_result_to_pickle(result))
578 } else {
579 Ok(PickleValue::None)
580 };
581 }
582 }
583
584 if let Some(reset_val) = request.get("reset").and_then(|v| v.as_str()) {
585 if reset_val == "runtime_config" {
586 let key = request
587 .get("key")
588 .and_then(|v| v.as_str())
589 .unwrap_or_default()
590 .to_string();
591 let resp = send_query(event_tx, QueryRequest::ResetRuntimeConfig { key })?;
592 return if let QueryResponse::RuntimeConfigReset(result) = resp {
593 Ok(runtime_config_result_to_pickle(result))
594 } else {
595 Ok(PickleValue::None)
596 };
597 }
598 }
599
600 if let Some(clear_val) = request.get("clear").and_then(|v| v.as_str()) {
601 if clear_val == "known_destination_retained" {
602 let dest_hash = extract_dest_hash(request, "dest_hash")?;
603 let resp = send_query(
604 event_tx,
605 QueryRequest::UnretainKnownDestination { dest_hash },
606 )?;
607 return if let QueryResponse::UnretainKnownDestination(ok) = resp {
608 Ok(PickleValue::Bool(ok))
609 } else {
610 Ok(PickleValue::None)
611 };
612 }
613 if clear_val == "backbone_peer_state" {
614 let interface_name = required_string(request, "interface")?;
615 let peer_ip = required_string(request, "ip")?;
616 let peer_ip = peer_ip
617 .parse()
618 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid peer IP"))?;
619 let resp = send_query(
620 event_tx,
621 QueryRequest::ClearBackbonePeerState {
622 interface_name,
623 peer_ip,
624 },
625 )?;
626 return if let QueryResponse::ClearBackbonePeerState(ok) = resp {
627 Ok(PickleValue::Bool(ok))
628 } else {
629 Ok(PickleValue::None)
630 };
631 }
632 }
633
634 if let Some(set_val) = request.get("set").and_then(|v| v.as_str()) {
635 if set_val == "backbone_peer_blacklist" {
636 let interface_name = required_string(request, "interface")?;
637 let peer_ip = required_string(request, "ip")?;
638 let peer_ip: IpAddr = peer_ip
639 .parse()
640 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid peer IP"))?;
641 let duration_secs = request
642 .get("duration_secs")
643 .and_then(|v| v.as_int())
644 .ok_or_else(|| {
645 io::Error::new(io::ErrorKind::InvalidInput, "missing duration_secs")
646 })?;
647 let reason = request
648 .get("reason")
649 .and_then(|v| v.as_str())
650 .unwrap_or("sentinel blacklist")
651 .to_string();
652 let penalty_level = request
653 .get("penalty_level")
654 .and_then(|v| v.as_int())
655 .unwrap_or(0)
656 .clamp(0, u8::MAX as i64) as u8;
657 let resp = send_query(
658 event_tx,
659 QueryRequest::BlacklistBackbonePeer {
660 interface_name,
661 peer_ip,
662 duration: Duration::from_secs(duration_secs as u64),
663 reason,
664 penalty_level,
665 },
666 )?;
667 return if let QueryResponse::BlacklistBackbonePeer(ok) = resp {
668 Ok(PickleValue::Bool(ok))
669 } else {
670 Ok(PickleValue::None)
671 };
672 }
673 }
674
675 if let Some(hash_val) = request.get("request_path") {
677 if let Some(hash_bytes) = hash_val.as_bytes() {
678 if hash_bytes.len() >= 16 {
679 let mut dest_hash = [0u8; 16];
680 dest_hash.copy_from_slice(&hash_bytes[..16]);
681 let _ = event_tx.send(crate::event::Event::RequestPath { dest_hash });
682 return Ok(PickleValue::Bool(true));
683 }
684 }
685 }
686
687 if let Some(hash_val) = request.get("send_probe") {
689 if let Some(hash_bytes) = hash_val.as_bytes() {
690 if hash_bytes.len() >= 16 {
691 let mut dest_hash = [0u8; 16];
692 dest_hash.copy_from_slice(&hash_bytes[..16]);
693 let payload_size = request
694 .get("size")
695 .and_then(|v| v.as_int())
696 .and_then(|n| {
697 if n > 0 && n <= 400 {
698 Some(n as usize)
699 } else {
700 None
701 }
702 })
703 .unwrap_or(16);
704 let resp = send_query(
705 event_tx,
706 QueryRequest::SendProbe {
707 dest_hash,
708 payload_size,
709 },
710 )?;
711 if let QueryResponse::SendProbe(Some((packet_hash, hops))) = resp {
712 return Ok(PickleValue::Dict(vec![
713 (
714 PickleValue::String("packet_hash".into()),
715 PickleValue::Bytes(packet_hash.to_vec()),
716 ),
717 (
718 PickleValue::String("hops".into()),
719 PickleValue::Int(hops as i64),
720 ),
721 ]));
722 } else {
723 return Ok(PickleValue::None);
724 }
725 }
726 }
727 }
728
729 if let Some(hash_val) = request.get("check_proof") {
731 if let Some(hash_bytes) = hash_val.as_bytes() {
732 if hash_bytes.len() >= 32 {
733 let mut packet_hash = [0u8; 32];
734 packet_hash.copy_from_slice(&hash_bytes[..32]);
735 let resp = send_query(event_tx, QueryRequest::CheckProof { packet_hash })?;
736 if let QueryResponse::CheckProof(Some(rtt)) = resp {
737 return Ok(PickleValue::Float(rtt));
738 } else {
739 return Ok(PickleValue::None);
740 }
741 }
742 }
743 }
744
745 if let Some(hash_val) = request.get("blackhole") {
747 if let Some(hash_bytes) = hash_val.as_bytes() {
748 if hash_bytes.len() >= 16 {
749 let mut identity_hash = [0u8; 16];
750 identity_hash.copy_from_slice(&hash_bytes[..16]);
751 let duration_hours = request.get("duration").and_then(|v| v.as_float());
752 let reason = request
753 .get("reason")
754 .and_then(|v| v.as_str())
755 .map(|s| s.to_string());
756 let resp = send_query(
757 event_tx,
758 QueryRequest::BlackholeIdentity {
759 identity_hash,
760 duration_hours,
761 reason,
762 },
763 )?;
764 return Ok(PickleValue::Bool(matches!(
765 resp,
766 QueryResponse::BlackholeResult(true)
767 )));
768 }
769 }
770 }
771
772 if let Some(hash_val) = request.get("unblackhole") {
774 if let Some(hash_bytes) = hash_val.as_bytes() {
775 if hash_bytes.len() >= 16 {
776 let mut identity_hash = [0u8; 16];
777 identity_hash.copy_from_slice(&hash_bytes[..16]);
778 let resp = send_query(
779 event_tx,
780 QueryRequest::UnblackholeIdentity { identity_hash },
781 )?;
782 return Ok(PickleValue::Bool(matches!(
783 resp,
784 QueryResponse::UnblackholeResult(true)
785 )));
786 }
787 }
788 }
789
790 if let Some(drop_val) = request.get("drop") {
792 if let Some(path) = drop_val.as_str() {
793 return match path {
794 "path" => {
795 let hash = extract_dest_hash(request, "destination_hash")?;
796 let resp = send_query(event_tx, QueryRequest::DropPath { dest_hash: hash })?;
797 if let QueryResponse::DropPath(ok) = resp {
798 Ok(PickleValue::Bool(ok))
799 } else {
800 Ok(PickleValue::None)
801 }
802 }
803 "all_via" => {
804 let hash = extract_dest_hash(request, "destination_hash")?;
805 let resp = send_query(
806 event_tx,
807 QueryRequest::DropAllVia {
808 transport_hash: hash,
809 },
810 )?;
811 if let QueryResponse::DropAllVia(n) = resp {
812 Ok(PickleValue::Int(n as i64))
813 } else {
814 Ok(PickleValue::None)
815 }
816 }
817 "announce_queues" => {
818 let resp = send_query(event_tx, QueryRequest::DropAnnounceQueues)?;
819 if let QueryResponse::DropAnnounceQueues = resp {
820 Ok(PickleValue::Bool(true))
821 } else {
822 Ok(PickleValue::None)
823 }
824 }
825 _ => Ok(PickleValue::None),
826 };
827 }
828 }
829
830 if let Some(hook_val) = request.get("hook").and_then(|v| v.as_str()) {
831 return handle_hook_rpc_request(hook_val, request, event_tx);
832 }
833
834 Ok(PickleValue::None)
835}
836
837fn handle_hook_rpc_request(
838 op: &str,
839 request: &PickleValue,
840 event_tx: &EventSender,
841) -> io::Result<PickleValue> {
842 match op {
843 "load" => {
844 let name = required_string(request, "name")?;
845 let attach_point = required_string(request, "attach_point")?;
846 let priority = request
847 .get("priority")
848 .and_then(|v| v.as_int())
849 .unwrap_or(0) as i32;
850 let wasm = request
851 .get("wasm")
852 .and_then(|v| v.as_bytes())
853 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing wasm"))?
854 .to_vec();
855 let (response_tx, response_rx) = mpsc::channel();
856 event_tx
857 .send(Event::LoadHook {
858 name,
859 wasm_bytes: wasm,
860 attach_point,
861 priority,
862 response_tx,
863 })
864 .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "driver shut down"))?;
865 let response = response_rx
866 .recv_timeout(std::time::Duration::from_secs(5))
867 .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "hook load timed out"))?;
868 Ok(hook_result_to_pickle(response))
869 }
870 "load_builtin" => {
871 let name = required_string(request, "name")?;
872 let attach_point = required_string(request, "attach_point")?;
873 let builtin_id = required_string(request, "builtin_id")?;
874 let priority = request
875 .get("priority")
876 .and_then(|v| v.as_int())
877 .unwrap_or(0) as i32;
878 let (response_tx, response_rx) = mpsc::channel();
879 event_tx
880 .send(Event::LoadBuiltinHook {
881 name,
882 builtin_id,
883 attach_point,
884 priority,
885 response_tx,
886 })
887 .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "driver shut down"))?;
888 let response = response_rx
889 .recv_timeout(std::time::Duration::from_secs(5))
890 .map_err(|_| {
891 io::Error::new(io::ErrorKind::TimedOut, "built-in hook load timed out")
892 })?;
893 Ok(hook_result_to_pickle(response))
894 }
895 "unload" => {
896 let name = required_string(request, "name")?;
897 let attach_point = required_string(request, "attach_point")?;
898 let (response_tx, response_rx) = mpsc::channel();
899 event_tx
900 .send(Event::UnloadHook {
901 name,
902 attach_point,
903 response_tx,
904 })
905 .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "driver shut down"))?;
906 let response = response_rx
907 .recv_timeout(std::time::Duration::from_secs(5))
908 .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "hook unload timed out"))?;
909 Ok(hook_result_to_pickle(response))
910 }
911 "enable" | "disable" => {
912 let name = required_string(request, "name")?;
913 let attach_point = required_string(request, "attach_point")?;
914 let enabled = op == "enable";
915 let (response_tx, response_rx) = mpsc::channel();
916 event_tx
917 .send(Event::SetHookEnabled {
918 name,
919 attach_point,
920 enabled,
921 response_tx,
922 })
923 .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "driver shut down"))?;
924 let response = response_rx
925 .recv_timeout(std::time::Duration::from_secs(5))
926 .map_err(|_| {
927 io::Error::new(io::ErrorKind::TimedOut, "hook enable/disable timed out")
928 })?;
929 Ok(hook_result_to_pickle(response))
930 }
931 "set_priority" => {
932 let name = required_string(request, "name")?;
933 let attach_point = required_string(request, "attach_point")?;
934 let priority = request
935 .get("priority")
936 .and_then(|v| v.as_int())
937 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing priority"))?
938 as i32;
939 let (response_tx, response_rx) = mpsc::channel();
940 event_tx
941 .send(Event::SetHookPriority {
942 name,
943 attach_point,
944 priority,
945 response_tx,
946 })
947 .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "driver shut down"))?;
948 let response = response_rx
949 .recv_timeout(std::time::Duration::from_secs(5))
950 .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "hook priority timed out"))?;
951 Ok(hook_result_to_pickle(response))
952 }
953 _ => Ok(PickleValue::None),
954 }
955}
956
957fn send_query(event_tx: &EventSender, request: QueryRequest) -> io::Result<QueryResponse> {
959 let (resp_tx, resp_rx) = mpsc::channel();
960 event_tx
961 .send(Event::Query(request, resp_tx))
962 .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "driver shut down"))?;
963 resp_rx
964 .recv_timeout(std::time::Duration::from_secs(5))
965 .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "query timed out"))
966}
967
968fn extract_dest_hash(request: &PickleValue, key: &str) -> io::Result<[u8; 16]> {
970 let bytes = request
971 .get(key)
972 .and_then(|v| v.as_bytes())
973 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing destination_hash"))?;
974 if bytes.len() < 16 {
975 return Err(io::Error::new(io::ErrorKind::InvalidData, "hash too short"));
976 }
977 let mut hash = [0u8; 16];
978 hash.copy_from_slice(&bytes[..16]);
979 Ok(hash)
980}
981
982fn required_string(request: &PickleValue, key: &str) -> io::Result<String> {
983 request
984 .get(key)
985 .and_then(|v| v.as_str())
986 .map(|s| s.to_string())
987 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("missing {}", key)))
988}
989
990fn hook_result_to_pickle(result: Result<(), String>) -> PickleValue {
991 match result {
992 Ok(()) => PickleValue::Dict(vec![(
993 PickleValue::String("ok".into()),
994 PickleValue::Bool(true),
995 )]),
996 Err(error) => PickleValue::Dict(vec![
997 (PickleValue::String("ok".into()), PickleValue::Bool(false)),
998 (
999 PickleValue::String("error".into()),
1000 PickleValue::String(error),
1001 ),
1002 ]),
1003 }
1004}
1005
1006fn interface_stats_to_pickle(stats: &InterfaceStatsResponse) -> PickleValue {
1009 let mut ifaces = Vec::new();
1010 for iface in &stats.interfaces {
1011 ifaces.push(single_iface_to_pickle(iface));
1012 }
1013
1014 let mut dict = vec![
1015 (
1016 PickleValue::String("interfaces".into()),
1017 PickleValue::List(ifaces),
1018 ),
1019 (
1020 PickleValue::String("transport_enabled".into()),
1021 PickleValue::Bool(stats.transport_enabled),
1022 ),
1023 (
1024 PickleValue::String("transport_uptime".into()),
1025 PickleValue::Float(stats.transport_uptime),
1026 ),
1027 (
1028 PickleValue::String("rxb".into()),
1029 PickleValue::Int(stats.total_rxb as i64),
1030 ),
1031 (
1032 PickleValue::String("txb".into()),
1033 PickleValue::Int(stats.total_txb as i64),
1034 ),
1035 ];
1036
1037 if let Some(tid) = stats.transport_id {
1038 dict.push((
1039 PickleValue::String("transport_id".into()),
1040 PickleValue::Bytes(tid.to_vec()),
1041 ));
1042 } else {
1043 dict.push((
1044 PickleValue::String("transport_id".into()),
1045 PickleValue::None,
1046 ));
1047 }
1048
1049 if let Some(pr) = stats.probe_responder {
1050 dict.push((
1051 PickleValue::String("probe_responder".into()),
1052 PickleValue::Bytes(pr.to_vec()),
1053 ));
1054 } else {
1055 dict.push((
1056 PickleValue::String("probe_responder".into()),
1057 PickleValue::None,
1058 ));
1059 }
1060
1061 if let Some(pool) = &stats.backbone_peer_pool {
1062 let members = pool
1063 .members
1064 .iter()
1065 .map(|member| {
1066 let mut member_dict = vec![
1067 (
1068 PickleValue::String("name".into()),
1069 PickleValue::String(member.name.clone()),
1070 ),
1071 (
1072 PickleValue::String("remote".into()),
1073 PickleValue::String(member.remote.clone()),
1074 ),
1075 (
1076 PickleValue::String("source".into()),
1077 PickleValue::String(member.source.clone()),
1078 ),
1079 (
1080 PickleValue::String("priority".into()),
1081 PickleValue::Int(member.priority as i64),
1082 ),
1083 (
1084 PickleValue::String("state".into()),
1085 PickleValue::String(member.state.clone()),
1086 ),
1087 (
1088 PickleValue::String("failure_count".into()),
1089 PickleValue::Int(member.failure_count as i64),
1090 ),
1091 ];
1092 member_dict.push((
1093 PickleValue::String("interface_id".into()),
1094 member
1095 .interface_id
1096 .map(|id| PickleValue::Int(id as i64))
1097 .unwrap_or(PickleValue::None),
1098 ));
1099 member_dict.push((
1100 PickleValue::String("last_error".into()),
1101 member
1102 .last_error
1103 .as_ref()
1104 .map(|err| PickleValue::String(err.clone()))
1105 .unwrap_or(PickleValue::None),
1106 ));
1107 member_dict.push((
1108 PickleValue::String("cooldown_remaining_seconds".into()),
1109 member
1110 .cooldown_remaining_seconds
1111 .map(PickleValue::Float)
1112 .unwrap_or(PickleValue::None),
1113 ));
1114 PickleValue::Dict(member_dict)
1115 })
1116 .collect();
1117 dict.push((
1118 PickleValue::String("backbone_peer_pool".into()),
1119 PickleValue::Dict(vec![
1120 (
1121 PickleValue::String("max_connected".into()),
1122 PickleValue::Int(pool.max_connected as i64),
1123 ),
1124 (
1125 PickleValue::String("active_count".into()),
1126 PickleValue::Int(pool.active_count as i64),
1127 ),
1128 (
1129 PickleValue::String("standby_count".into()),
1130 PickleValue::Int(pool.standby_count as i64),
1131 ),
1132 (
1133 PickleValue::String("cooldown_count".into()),
1134 PickleValue::Int(pool.cooldown_count as i64),
1135 ),
1136 (
1137 PickleValue::String("members".into()),
1138 PickleValue::List(members),
1139 ),
1140 ]),
1141 ));
1142 } else {
1143 dict.push((
1144 PickleValue::String("backbone_peer_pool".into()),
1145 PickleValue::None,
1146 ));
1147 }
1148
1149 PickleValue::Dict(dict)
1150}
1151
1152fn single_iface_to_pickle(s: &SingleInterfaceStat) -> PickleValue {
1153 let mut dict = vec![
1154 (
1155 PickleValue::String("id".into()),
1156 PickleValue::Int(s.id as i64),
1157 ),
1158 (
1159 PickleValue::String("name".into()),
1160 PickleValue::String(s.name.clone()),
1161 ),
1162 (
1163 PickleValue::String("status".into()),
1164 PickleValue::Bool(s.status),
1165 ),
1166 (
1167 PickleValue::String("mode".into()),
1168 PickleValue::Int(s.mode as i64),
1169 ),
1170 (
1171 PickleValue::String("gravity".into()),
1172 PickleValue::Int(s.gravity),
1173 ),
1174 (
1175 PickleValue::String("announces_to_internal".into()),
1176 s.announces_to_internal
1177 .map(PickleValue::Bool)
1178 .unwrap_or(PickleValue::None),
1179 ),
1180 (
1181 PickleValue::String("rxb".into()),
1182 PickleValue::Int(s.rxb as i64),
1183 ),
1184 (
1185 PickleValue::String("txb".into()),
1186 PickleValue::Int(s.txb as i64),
1187 ),
1188 (
1189 PickleValue::String("rx_packets".into()),
1190 PickleValue::Int(s.rx_packets as i64),
1191 ),
1192 (
1193 PickleValue::String("tx_packets".into()),
1194 PickleValue::Int(s.tx_packets as i64),
1195 ),
1196 (
1197 PickleValue::String("cpu_load".into()),
1198 s.cpu_load
1199 .map(PickleValue::Float)
1200 .unwrap_or(PickleValue::None),
1201 ),
1202 (
1203 PickleValue::String("mem_load".into()),
1204 s.mem_load
1205 .map(PickleValue::Float)
1206 .unwrap_or(PickleValue::None),
1207 ),
1208 (
1209 PickleValue::String("switch_id".into()),
1210 s.switch_id
1211 .map(|v| PickleValue::Bytes(v.to_vec()))
1212 .unwrap_or(PickleValue::None),
1213 ),
1214 (
1215 PickleValue::String("endpoint_id".into()),
1216 s.endpoint_id
1217 .map(|v| PickleValue::Bytes(v.to_vec()))
1218 .unwrap_or(PickleValue::None),
1219 ),
1220 (
1221 PickleValue::String("via_switch_id".into()),
1222 s.via_switch_id
1223 .map(|v| PickleValue::Bytes(v.to_vec()))
1224 .unwrap_or(PickleValue::None),
1225 ),
1226 (
1227 PickleValue::String("peers".into()),
1228 s.peers
1229 .map(|v| PickleValue::Int(v as i64))
1230 .unwrap_or(PickleValue::None),
1231 ),
1232 (
1233 PickleValue::String("started".into()),
1234 PickleValue::Float(s.started),
1235 ),
1236 (
1237 PickleValue::String("ia_freq".into()),
1238 PickleValue::Float(s.ia_freq),
1239 ),
1240 (
1241 PickleValue::String("oa_freq".into()),
1242 PickleValue::Float(s.oa_freq),
1243 ),
1244 (
1245 PickleValue::String("ip_freq".into()),
1246 PickleValue::Float(s.ip_freq),
1247 ),
1248 (
1249 PickleValue::String("op_freq".into()),
1250 PickleValue::Float(s.op_freq),
1251 ),
1252 (
1253 PickleValue::String("burst_active".into()),
1254 PickleValue::Bool(s.burst_active),
1255 ),
1256 (
1257 PickleValue::String("burst_activated".into()),
1258 PickleValue::Float(s.burst_activated),
1259 ),
1260 (
1261 PickleValue::String("pr_burst_active".into()),
1262 PickleValue::Bool(s.pr_burst_active),
1263 ),
1264 (
1265 PickleValue::String("pr_burst_activated".into()),
1266 PickleValue::Float(s.pr_burst_activated),
1267 ),
1268 (
1269 PickleValue::String("clients".into()),
1270 s.clients
1271 .map(|clients| PickleValue::Int(clients as i64))
1272 .unwrap_or(PickleValue::None),
1273 ),
1274 (
1275 PickleValue::String("blocked_ips".into()),
1276 s.blocked_ips
1277 .map(|blocked| PickleValue::Int(blocked as i64))
1278 .unwrap_or(PickleValue::None),
1279 ),
1280 (
1281 PickleValue::String("blocked_ip_list".into()),
1282 s.blocked_ip_list
1283 .as_ref()
1284 .map(|blocked| {
1285 PickleValue::List(blocked.iter().cloned().map(PickleValue::String).collect())
1286 })
1287 .unwrap_or(PickleValue::None),
1288 ),
1289 (
1290 PickleValue::String("announce_rate_grace".into()),
1291 PickleValue::Int(s.announce_rate_grace as i64),
1292 ),
1293 (
1294 PickleValue::String("announce_rate_penalty".into()),
1295 PickleValue::Float(s.announce_rate_penalty),
1296 ),
1297 ];
1298
1299 match s.announce_rate_target {
1300 Some(target) => dict.push((
1301 PickleValue::String("announce_rate_target".into()),
1302 PickleValue::Float(target),
1303 )),
1304 None => dict.push((
1305 PickleValue::String("announce_rate_target".into()),
1306 PickleValue::None,
1307 )),
1308 }
1309
1310 match s.bitrate {
1311 Some(br) => dict.push((
1312 PickleValue::String("bitrate".into()),
1313 PickleValue::Int(br as i64),
1314 )),
1315 None => dict.push((PickleValue::String("bitrate".into()), PickleValue::None)),
1316 }
1317
1318 match s.ifac_size {
1319 Some(sz) => dict.push((
1320 PickleValue::String("ifac_size".into()),
1321 PickleValue::Int(sz as i64),
1322 )),
1323 None => dict.push((PickleValue::String("ifac_size".into()), PickleValue::None)),
1324 }
1325
1326 PickleValue::Dict(dict)
1327}
1328
1329fn path_table_to_pickle(entries: &[PathTableEntry]) -> PickleValue {
1330 let list: Vec<PickleValue> = entries
1331 .iter()
1332 .map(|e| {
1333 PickleValue::Dict(vec![
1334 (
1335 PickleValue::String("hash".into()),
1336 PickleValue::Bytes(e.hash.to_vec()),
1337 ),
1338 (
1339 PickleValue::String("timestamp".into()),
1340 PickleValue::Float(e.timestamp),
1341 ),
1342 (
1343 PickleValue::String("via".into()),
1344 PickleValue::Bytes(e.via.to_vec()),
1345 ),
1346 (
1347 PickleValue::String("hops".into()),
1348 PickleValue::Int(e.hops as i64),
1349 ),
1350 (
1351 PickleValue::String("expires".into()),
1352 PickleValue::Float(e.expires),
1353 ),
1354 (
1355 PickleValue::String("interface".into()),
1356 PickleValue::String(e.interface_name.clone()),
1357 ),
1358 ])
1359 })
1360 .collect();
1361 PickleValue::List(list)
1362}
1363
1364fn rate_table_to_pickle(entries: &[RateTableEntry]) -> PickleValue {
1365 let list: Vec<PickleValue> = entries
1366 .iter()
1367 .map(|e| {
1368 PickleValue::Dict(vec![
1369 (
1370 PickleValue::String("hash".into()),
1371 PickleValue::Bytes(e.hash.to_vec()),
1372 ),
1373 (
1374 PickleValue::String("last".into()),
1375 PickleValue::Float(e.last),
1376 ),
1377 (
1378 PickleValue::String("rate_violations".into()),
1379 PickleValue::Int(e.rate_violations as i64),
1380 ),
1381 (
1382 PickleValue::String("blocked_until".into()),
1383 PickleValue::Float(e.blocked_until),
1384 ),
1385 (
1386 PickleValue::String("timestamps".into()),
1387 PickleValue::List(
1388 e.timestamps
1389 .iter()
1390 .map(|&t| PickleValue::Float(t))
1391 .collect(),
1392 ),
1393 ),
1394 ])
1395 })
1396 .collect();
1397 PickleValue::List(list)
1398}
1399
1400fn blackholed_to_pickle(entries: &[BlackholeInfo]) -> PickleValue {
1401 let list: Vec<PickleValue> = entries
1402 .iter()
1403 .map(|e| {
1404 let mut dict = vec![
1405 (
1406 PickleValue::String("identity_hash".into()),
1407 PickleValue::Bytes(e.identity_hash.to_vec()),
1408 ),
1409 (
1410 PickleValue::String("created".into()),
1411 PickleValue::Float(e.created),
1412 ),
1413 (
1414 PickleValue::String("expires".into()),
1415 PickleValue::Float(e.expires),
1416 ),
1417 ];
1418 if let Some(ref reason) = e.reason {
1419 dict.push((
1420 PickleValue::String("reason".into()),
1421 PickleValue::String(reason.clone()),
1422 ));
1423 } else {
1424 dict.push((PickleValue::String("reason".into()), PickleValue::None));
1425 }
1426 PickleValue::Dict(dict)
1427 })
1428 .collect();
1429 PickleValue::List(list)
1430}
1431
1432fn discovered_interfaces_to_pickle(
1433 interfaces: &[crate::discovery::DiscoveredInterface],
1434) -> PickleValue {
1435 let list: Vec<PickleValue> = interfaces
1436 .iter()
1437 .map(|iface| {
1438 let mut dict = vec![
1439 (
1440 PickleValue::String("type".into()),
1441 PickleValue::String(iface.interface_type.clone()),
1442 ),
1443 (
1444 PickleValue::String("transport".into()),
1445 PickleValue::Bool(iface.transport),
1446 ),
1447 (
1448 PickleValue::String("name".into()),
1449 PickleValue::String(iface.name.clone()),
1450 ),
1451 (
1452 PickleValue::String("discovered".into()),
1453 PickleValue::Float(iface.discovered),
1454 ),
1455 (
1456 PickleValue::String("last_heard".into()),
1457 PickleValue::Float(iface.last_heard),
1458 ),
1459 (
1460 PickleValue::String("heard_count".into()),
1461 PickleValue::Int(iface.heard_count as i64),
1462 ),
1463 (
1464 PickleValue::String("status".into()),
1465 PickleValue::String(iface.status.as_str().into()),
1466 ),
1467 (
1468 PickleValue::String("stamp".into()),
1469 PickleValue::Bytes(iface.stamp.clone()),
1470 ),
1471 (
1472 PickleValue::String("value".into()),
1473 PickleValue::Int(iface.stamp_value as i64),
1474 ),
1475 (
1476 PickleValue::String("transport_id".into()),
1477 PickleValue::Bytes(iface.transport_id.to_vec()),
1478 ),
1479 (
1480 PickleValue::String("network_id".into()),
1481 PickleValue::Bytes(iface.network_id.to_vec()),
1482 ),
1483 (
1484 PickleValue::String("hops".into()),
1485 PickleValue::Int(iface.hops as i64),
1486 ),
1487 ];
1488
1489 if let Some(v) = iface.latitude {
1491 dict.push((
1492 PickleValue::String("latitude".into()),
1493 PickleValue::Float(v),
1494 ));
1495 } else {
1496 dict.push((PickleValue::String("latitude".into()), PickleValue::None));
1497 }
1498 if let Some(v) = iface.longitude {
1499 dict.push((
1500 PickleValue::String("longitude".into()),
1501 PickleValue::Float(v),
1502 ));
1503 } else {
1504 dict.push((PickleValue::String("longitude".into()), PickleValue::None));
1505 }
1506 if let Some(v) = iface.height {
1507 dict.push((PickleValue::String("height".into()), PickleValue::Float(v)));
1508 } else {
1509 dict.push((PickleValue::String("height".into()), PickleValue::None));
1510 }
1511
1512 if let Some(ref v) = iface.reachable_on {
1514 dict.push((
1515 PickleValue::String("reachable_on".into()),
1516 PickleValue::String(v.clone()),
1517 ));
1518 } else {
1519 dict.push((
1520 PickleValue::String("reachable_on".into()),
1521 PickleValue::None,
1522 ));
1523 }
1524 if let Some(v) = iface.port {
1525 dict.push((
1526 PickleValue::String("port".into()),
1527 PickleValue::Int(v as i64),
1528 ));
1529 } else {
1530 dict.push((PickleValue::String("port".into()), PickleValue::None));
1531 }
1532
1533 if let Some(v) = iface.frequency {
1535 dict.push((
1536 PickleValue::String("frequency".into()),
1537 PickleValue::Int(v as i64),
1538 ));
1539 } else {
1540 dict.push((PickleValue::String("frequency".into()), PickleValue::None));
1541 }
1542 if let Some(v) = iface.bandwidth {
1543 dict.push((
1544 PickleValue::String("bandwidth".into()),
1545 PickleValue::Int(v as i64),
1546 ));
1547 } else {
1548 dict.push((PickleValue::String("bandwidth".into()), PickleValue::None));
1549 }
1550 if let Some(v) = iface.spreading_factor {
1551 dict.push((PickleValue::String("sf".into()), PickleValue::Int(v as i64)));
1552 } else {
1553 dict.push((PickleValue::String("sf".into()), PickleValue::None));
1554 }
1555 if let Some(v) = iface.coding_rate {
1556 dict.push((PickleValue::String("cr".into()), PickleValue::Int(v as i64)));
1557 } else {
1558 dict.push((PickleValue::String("cr".into()), PickleValue::None));
1559 }
1560 if let Some(ref v) = iface.modulation {
1561 dict.push((
1562 PickleValue::String("modulation".into()),
1563 PickleValue::String(v.clone()),
1564 ));
1565 } else {
1566 dict.push((PickleValue::String("modulation".into()), PickleValue::None));
1567 }
1568 if let Some(v) = iface.channel {
1569 dict.push((
1570 PickleValue::String("channel".into()),
1571 PickleValue::Int(v as i64),
1572 ));
1573 } else {
1574 dict.push((PickleValue::String("channel".into()), PickleValue::None));
1575 }
1576
1577 if let Some(ref v) = iface.ifac_netname {
1579 dict.push((
1580 PickleValue::String("ifac_netname".into()),
1581 PickleValue::String(v.clone()),
1582 ));
1583 } else {
1584 dict.push((
1585 PickleValue::String("ifac_netname".into()),
1586 PickleValue::None,
1587 ));
1588 }
1589 if let Some(ref v) = iface.ifac_netkey {
1590 dict.push((
1591 PickleValue::String("ifac_netkey".into()),
1592 PickleValue::String(v.clone()),
1593 ));
1594 } else {
1595 dict.push((PickleValue::String("ifac_netkey".into()), PickleValue::None));
1596 }
1597
1598 if let Some(ref v) = iface.config_entry {
1600 dict.push((
1601 PickleValue::String("config_entry".into()),
1602 PickleValue::String(v.clone()),
1603 ));
1604 } else {
1605 dict.push((
1606 PickleValue::String("config_entry".into()),
1607 PickleValue::None,
1608 ));
1609 }
1610
1611 dict.push((
1612 PickleValue::String("discovery_hash".into()),
1613 PickleValue::Bytes(iface.discovery_hash.to_vec()),
1614 ));
1615
1616 PickleValue::Dict(dict)
1617 })
1618 .collect();
1619 PickleValue::List(list)
1620}
1621
1622fn hooks_to_pickle(hooks: &[HookInfo]) -> PickleValue {
1623 PickleValue::List(
1624 hooks
1625 .iter()
1626 .map(|hook| {
1627 PickleValue::Dict(vec![
1628 (
1629 PickleValue::String("name".into()),
1630 PickleValue::String(hook.name.clone()),
1631 ),
1632 (
1633 PickleValue::String("type".into()),
1634 PickleValue::String(hook.hook_type.clone()),
1635 ),
1636 (
1637 PickleValue::String("attach_point".into()),
1638 PickleValue::String(hook.attach_point.clone()),
1639 ),
1640 (
1641 PickleValue::String("priority".into()),
1642 PickleValue::Int(hook.priority as i64),
1643 ),
1644 (
1645 PickleValue::String("enabled".into()),
1646 PickleValue::Bool(hook.enabled),
1647 ),
1648 (
1649 PickleValue::String("consecutive_traps".into()),
1650 PickleValue::Int(hook.consecutive_traps as i64),
1651 ),
1652 ])
1653 })
1654 .collect(),
1655 )
1656}
1657
1658fn backbone_peer_state_to_pickle(entries: &[BackbonePeerStateEntry]) -> PickleValue {
1659 PickleValue::List(
1660 entries
1661 .iter()
1662 .map(|entry| {
1663 PickleValue::Dict(vec![
1664 (
1665 PickleValue::String("interface".into()),
1666 PickleValue::String(entry.interface_name.clone()),
1667 ),
1668 (
1669 PickleValue::String("ip".into()),
1670 PickleValue::String(entry.peer_ip.to_string()),
1671 ),
1672 (
1673 PickleValue::String("connected_count".into()),
1674 PickleValue::Int(entry.connected_count as i64),
1675 ),
1676 (
1677 PickleValue::String("blacklisted_remaining_secs".into()),
1678 entry
1679 .blacklisted_remaining_secs
1680 .map(PickleValue::Float)
1681 .unwrap_or(PickleValue::None),
1682 ),
1683 (
1684 PickleValue::String("blacklist_reason".into()),
1685 entry
1686 .blacklist_reason
1687 .as_ref()
1688 .map(|v: &String| PickleValue::String(v.clone()))
1689 .unwrap_or(PickleValue::None),
1690 ),
1691 (
1692 PickleValue::String("reject_count".into()),
1693 PickleValue::Int(entry.reject_count as i64),
1694 ),
1695 ])
1696 })
1697 .collect(),
1698 )
1699}
1700
1701fn backbone_interfaces_to_pickle(entries: &[BackboneInterfaceEntry]) -> PickleValue {
1702 PickleValue::List(
1703 entries
1704 .iter()
1705 .map(|entry| {
1706 PickleValue::Dict(vec![
1707 (
1708 PickleValue::String("id".into()),
1709 PickleValue::Int(entry.interface_id.0 as i64),
1710 ),
1711 (
1712 PickleValue::String("name".into()),
1713 PickleValue::String(entry.interface_name.clone()),
1714 ),
1715 ])
1716 })
1717 .collect(),
1718 )
1719}
1720
1721fn provider_bridge_stats_to_pickle(stats: &ProviderBridgeStats) -> PickleValue {
1722 PickleValue::Dict(vec![
1723 (
1724 PickleValue::String("connected".into()),
1725 PickleValue::Bool(stats.connected),
1726 ),
1727 (
1728 PickleValue::String("consumer_count".into()),
1729 PickleValue::Int(stats.consumer_count as i64),
1730 ),
1731 (
1732 PickleValue::String("queue_max_events".into()),
1733 PickleValue::Int(stats.queue_max_events as i64),
1734 ),
1735 (
1736 PickleValue::String("queue_max_bytes".into()),
1737 PickleValue::Int(stats.queue_max_bytes as i64),
1738 ),
1739 (
1740 PickleValue::String("backlog_len".into()),
1741 PickleValue::Int(stats.backlog_len as i64),
1742 ),
1743 (
1744 PickleValue::String("backlog_bytes".into()),
1745 PickleValue::Int(stats.backlog_bytes as i64),
1746 ),
1747 (
1748 PickleValue::String("backlog_dropped_pending".into()),
1749 PickleValue::Int(stats.backlog_dropped_pending as i64),
1750 ),
1751 (
1752 PickleValue::String("backlog_dropped_total".into()),
1753 PickleValue::Int(stats.backlog_dropped_total as i64),
1754 ),
1755 (
1756 PickleValue::String("total_disconnect_count".into()),
1757 PickleValue::Int(stats.total_disconnect_count as i64),
1758 ),
1759 (
1760 PickleValue::String("consumers".into()),
1761 PickleValue::List(
1762 stats
1763 .consumers
1764 .iter()
1765 .map(|consumer| {
1766 PickleValue::Dict(vec![
1767 (
1768 PickleValue::String("id".into()),
1769 PickleValue::Int(consumer.id as i64),
1770 ),
1771 (
1772 PickleValue::String("connected".into()),
1773 PickleValue::Bool(consumer.connected),
1774 ),
1775 (
1776 PickleValue::String("queue_len".into()),
1777 PickleValue::Int(consumer.queue_len as i64),
1778 ),
1779 (
1780 PickleValue::String("queued_bytes".into()),
1781 PickleValue::Int(consumer.queued_bytes as i64),
1782 ),
1783 (
1784 PickleValue::String("dropped_pending".into()),
1785 PickleValue::Int(consumer.dropped_pending as i64),
1786 ),
1787 (
1788 PickleValue::String("dropped_total".into()),
1789 PickleValue::Int(consumer.dropped_total as i64),
1790 ),
1791 (
1792 PickleValue::String("queue_max_events".into()),
1793 PickleValue::Int(consumer.queue_max_events as i64),
1794 ),
1795 (
1796 PickleValue::String("queue_max_bytes".into()),
1797 PickleValue::Int(consumer.queue_max_bytes as i64),
1798 ),
1799 ])
1800 })
1801 .collect(),
1802 ),
1803 ),
1804 ])
1805}
1806
1807fn lifecycle_state_name(state: LifecycleState) -> &'static str {
1808 match state {
1809 LifecycleState::Active => "active",
1810 LifecycleState::Draining => "draining",
1811 LifecycleState::Stopping => "stopping",
1812 LifecycleState::Stopped => "stopped",
1813 }
1814}
1815
1816fn drain_status_to_pickle(status: &DrainStatus) -> PickleValue {
1817 PickleValue::Dict(vec![
1818 (
1819 PickleValue::String("state".into()),
1820 PickleValue::String(lifecycle_state_name(status.state).into()),
1821 ),
1822 (
1823 PickleValue::String("drain_age_seconds".into()),
1824 status
1825 .drain_age_seconds
1826 .map(PickleValue::Float)
1827 .unwrap_or(PickleValue::None),
1828 ),
1829 (
1830 PickleValue::String("deadline_remaining_seconds".into()),
1831 status
1832 .deadline_remaining_seconds
1833 .map(PickleValue::Float)
1834 .unwrap_or(PickleValue::None),
1835 ),
1836 (
1837 PickleValue::String("drain_complete".into()),
1838 PickleValue::Bool(status.drain_complete),
1839 ),
1840 (
1841 PickleValue::String("interface_writer_queued_frames".into()),
1842 PickleValue::Int(status.interface_writer_queued_frames as i64),
1843 ),
1844 (
1845 PickleValue::String("provider_backlog_events".into()),
1846 PickleValue::Int(status.provider_backlog_events as i64),
1847 ),
1848 (
1849 PickleValue::String("provider_consumer_queued_events".into()),
1850 PickleValue::Int(status.provider_consumer_queued_events as i64),
1851 ),
1852 (
1853 PickleValue::String("detail".into()),
1854 status
1855 .detail
1856 .as_ref()
1857 .map(|detail| PickleValue::String(detail.clone()))
1858 .unwrap_or(PickleValue::None),
1859 ),
1860 ])
1861}
1862
1863fn runtime_config_value_to_pickle(value: &RuntimeConfigValue) -> PickleValue {
1864 match value {
1865 RuntimeConfigValue::Int(v) => PickleValue::Int(*v),
1866 RuntimeConfigValue::Float(v) => PickleValue::Float(*v),
1867 RuntimeConfigValue::Bool(v) => PickleValue::Bool(*v),
1868 RuntimeConfigValue::String(v) => PickleValue::String(v.clone()),
1869 RuntimeConfigValue::Null => PickleValue::None,
1870 }
1871}
1872
1873fn runtime_config_value_from_pickle(value: &PickleValue) -> Option<RuntimeConfigValue> {
1874 match value {
1875 PickleValue::Int(v) => Some(RuntimeConfigValue::Int(*v)),
1876 PickleValue::Float(v) => Some(RuntimeConfigValue::Float(*v)),
1877 PickleValue::Bool(v) => Some(RuntimeConfigValue::Bool(*v)),
1878 PickleValue::String(v) => Some(RuntimeConfigValue::String(v.clone())),
1879 PickleValue::None => Some(RuntimeConfigValue::Null),
1880 _ => None,
1881 }
1882}
1883
1884fn runtime_config_entry_to_pickle(entry: &RuntimeConfigEntry) -> PickleValue {
1885 PickleValue::Dict(vec![
1886 (
1887 PickleValue::String("key".into()),
1888 PickleValue::String(entry.key.clone()),
1889 ),
1890 (
1891 PickleValue::String("value".into()),
1892 runtime_config_value_to_pickle(&entry.value),
1893 ),
1894 (
1895 PickleValue::String("default".into()),
1896 runtime_config_value_to_pickle(&entry.default),
1897 ),
1898 (
1899 PickleValue::String("source".into()),
1900 PickleValue::String(match entry.source {
1901 RuntimeConfigSource::Startup => "startup".into(),
1902 RuntimeConfigSource::RuntimeOverride => "runtime_override".into(),
1903 }),
1904 ),
1905 (
1906 PickleValue::String("apply_mode".into()),
1907 PickleValue::String(match entry.apply_mode {
1908 RuntimeConfigApplyMode::Immediate => "immediate".into(),
1909 RuntimeConfigApplyMode::NewConnectionsOnly => "new_connections_only".into(),
1910 RuntimeConfigApplyMode::NextReconnect => "next_reconnect".into(),
1911 RuntimeConfigApplyMode::RestartRequired => "restart_required".into(),
1912 }),
1913 ),
1914 (
1915 PickleValue::String("description".into()),
1916 entry
1917 .description
1918 .as_ref()
1919 .map(|v| PickleValue::String(v.clone()))
1920 .unwrap_or(PickleValue::None),
1921 ),
1922 ])
1923}
1924
1925fn runtime_config_list_to_pickle(entries: &[RuntimeConfigEntry]) -> PickleValue {
1926 PickleValue::List(entries.iter().map(runtime_config_entry_to_pickle).collect())
1927}
1928
1929fn runtime_config_error_to_pickle(error: &RuntimeConfigError) -> PickleValue {
1930 PickleValue::Dict(vec![
1931 (
1932 PickleValue::String("error".into()),
1933 PickleValue::String(match error.code {
1934 RuntimeConfigErrorCode::UnknownKey => "unknown_key".into(),
1935 RuntimeConfigErrorCode::InvalidType => "invalid_type".into(),
1936 RuntimeConfigErrorCode::InvalidValue => "invalid_value".into(),
1937 RuntimeConfigErrorCode::Unsupported => "unsupported".into(),
1938 RuntimeConfigErrorCode::NotFound => "not_found".into(),
1939 RuntimeConfigErrorCode::ApplyFailed => "apply_failed".into(),
1940 }),
1941 ),
1942 (
1943 PickleValue::String("message".into()),
1944 PickleValue::String(error.message.clone()),
1945 ),
1946 ])
1947}
1948
1949fn runtime_config_result_to_pickle(
1950 result: Result<RuntimeConfigEntry, RuntimeConfigError>,
1951) -> PickleValue {
1952 match result {
1953 Ok(entry) => runtime_config_entry_to_pickle(&entry),
1954 Err(error) => runtime_config_error_to_pickle(&error),
1955 }
1956}
1957
1958fn known_destination_entry_to_pickle(entry: &KnownDestinationEntry) -> PickleValue {
1959 PickleValue::Dict(vec![
1960 (
1961 PickleValue::String("dest_hash".into()),
1962 PickleValue::Bytes(entry.dest_hash.to_vec()),
1963 ),
1964 (
1965 PickleValue::String("identity_hash".into()),
1966 PickleValue::Bytes(entry.identity_hash.to_vec()),
1967 ),
1968 (
1969 PickleValue::String("public_key".into()),
1970 PickleValue::Bytes(entry.public_key.to_vec()),
1971 ),
1972 (
1973 PickleValue::String("app_data".into()),
1974 entry
1975 .app_data
1976 .as_ref()
1977 .map(|data: &Vec<u8>| PickleValue::Bytes(data.clone()))
1978 .unwrap_or(PickleValue::None),
1979 ),
1980 (
1981 PickleValue::String("hops".into()),
1982 PickleValue::Int(entry.hops as i64),
1983 ),
1984 (
1985 PickleValue::String("received_at".into()),
1986 PickleValue::Float(entry.received_at),
1987 ),
1988 (
1989 PickleValue::String("receiving_interface".into()),
1990 PickleValue::Int(entry.receiving_interface.0 as i64),
1991 ),
1992 (
1993 PickleValue::String("was_used".into()),
1994 PickleValue::Bool(entry.was_used),
1995 ),
1996 (
1997 PickleValue::String("last_used_at".into()),
1998 entry
1999 .last_used_at
2000 .map(PickleValue::Float)
2001 .unwrap_or(PickleValue::None),
2002 ),
2003 (
2004 PickleValue::String("retained".into()),
2005 PickleValue::Bool(entry.retained),
2006 ),
2007 ])
2008}
2009
2010fn known_destinations_to_pickle(entries: &[KnownDestinationEntry]) -> PickleValue {
2011 PickleValue::List(
2012 entries
2013 .iter()
2014 .map(known_destination_entry_to_pickle)
2015 .collect(),
2016 )
2017}
2018
2019fn parse_known_destination_entry(value: &PickleValue) -> io::Result<KnownDestinationEntry> {
2020 let get_bytes = |key: &str, len: usize| -> io::Result<Vec<u8>> {
2021 let value = value.get(key).ok_or_else(|| {
2022 io::Error::new(io::ErrorKind::InvalidData, format!("missing {}", key))
2023 })?;
2024 let bytes = value.as_bytes().ok_or_else(|| {
2025 io::Error::new(io::ErrorKind::InvalidData, format!("invalid {}", key))
2026 })?;
2027 if bytes.len() != len {
2028 return Err(io::Error::new(
2029 io::ErrorKind::InvalidData,
2030 format!("invalid {} length", key),
2031 ));
2032 }
2033 Ok(bytes.to_vec())
2034 };
2035 let get_int = |key: &str| -> io::Result<i64> {
2036 value
2037 .get(key)
2038 .and_then(|v| v.as_int())
2039 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("invalid {}", key)))
2040 };
2041 let get_float = |key: &str| -> io::Result<f64> {
2042 value
2043 .get(key)
2044 .and_then(|v| v.as_float())
2045 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("invalid {}", key)))
2046 };
2047 let get_bool = |key: &str| -> io::Result<bool> {
2048 value
2049 .get(key)
2050 .and_then(|v| v.as_bool())
2051 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("invalid {}", key)))
2052 };
2053
2054 let mut dest_hash = [0u8; 16];
2055 dest_hash.copy_from_slice(&get_bytes("dest_hash", 16)?);
2056 let mut identity_hash = [0u8; 16];
2057 identity_hash.copy_from_slice(&get_bytes("identity_hash", 16)?);
2058 let mut public_key = [0u8; 64];
2059 public_key.copy_from_slice(&get_bytes("public_key", 64)?);
2060 let app_data = value
2061 .get("app_data")
2062 .and_then(|v| v.as_bytes())
2063 .map(|bytes| bytes.to_vec());
2064 let last_used_at = value.get("last_used_at").and_then(|v| v.as_float());
2065
2066 Ok(KnownDestinationEntry {
2067 dest_hash,
2068 identity_hash,
2069 public_key,
2070 app_data,
2071 hops: get_int("hops")? as u8,
2072 received_at: get_float("received_at")?,
2073 receiving_interface: rns_core::transport::types::InterfaceId(
2074 get_int("receiving_interface")? as u64,
2075 ),
2076 was_used: get_bool("was_used")?,
2077 last_used_at,
2078 retained: get_bool("retained")?,
2079 })
2080}
2081
2082fn parse_known_destination_list(value: &PickleValue) -> io::Result<Vec<KnownDestinationEntry>> {
2083 let list = value
2084 .as_list()
2085 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "expected list"))?;
2086 list.iter().map(parse_known_destination_entry).collect()
2087}
2088
2089pub struct RpcClient {
2093 stream: TcpStream,
2094}
2095
2096impl RpcClient {
2097 pub fn connect(addr: &RpcAddr, auth_key: &[u8; 32]) -> io::Result<Self> {
2099 let mut stream = match addr {
2100 RpcAddr::Tcp(host, port) => TcpStream::connect((host.as_str(), *port))?,
2101 };
2102
2103 stream.set_read_timeout(Some(std::time::Duration::from_secs(10)))?;
2104 stream.set_write_timeout(Some(std::time::Duration::from_secs(10)))?;
2105
2106 client_auth(&mut stream, auth_key)?;
2108
2109 Ok(RpcClient { stream })
2110 }
2111
2112 pub fn call(&mut self, request: &PickleValue) -> io::Result<PickleValue> {
2114 let request_bytes = encode_rpc_payload(request, RpcCodec::Msgpack);
2115 send_bytes(&mut self.stream, &request_bytes)?;
2116
2117 let response_bytes = recv_bytes(&mut self.stream)?;
2118 decode_rpc_payload(&response_bytes).map(|(value, _)| value)
2119 }
2120
2121 pub fn list_hooks(&mut self) -> io::Result<Vec<HookInfo>> {
2122 let response = self.call(&PickleValue::Dict(vec![(
2123 PickleValue::String("get".into()),
2124 PickleValue::String("hooks".into()),
2125 )]))?;
2126 parse_hook_list(&response)
2127 }
2128
2129 pub fn begin_drain(&mut self, timeout: Duration) -> io::Result<bool> {
2130 let response = self.call(&PickleValue::Dict(vec![(
2131 PickleValue::String("begin_drain".into()),
2132 PickleValue::Float(timeout.as_secs_f64()),
2133 )]))?;
2134 Ok(response.as_bool().unwrap_or(false))
2135 }
2136
2137 pub fn drain_status(&mut self) -> io::Result<Option<DrainStatus>> {
2138 let response = self.call(&PickleValue::Dict(vec![(
2139 PickleValue::String("get".into()),
2140 PickleValue::String("drain_status".into()),
2141 )]))?;
2142 parse_drain_status(&response)
2143 }
2144
2145 pub fn provider_bridge_stats(&mut self) -> io::Result<PickleValue> {
2146 self.call(&PickleValue::Dict(vec![(
2147 PickleValue::String("get".into()),
2148 PickleValue::String("provider_bridge_stats".into()),
2149 )]))
2150 }
2151
2152 pub fn load_hook(
2153 &mut self,
2154 name: &str,
2155 attach_point: &str,
2156 priority: i32,
2157 wasm: &[u8],
2158 ) -> io::Result<Result<(), String>> {
2159 let response = self.call(&PickleValue::Dict(vec![
2160 (
2161 PickleValue::String("hook".into()),
2162 PickleValue::String("load".into()),
2163 ),
2164 (
2165 PickleValue::String("name".into()),
2166 PickleValue::String(name.to_string()),
2167 ),
2168 (
2169 PickleValue::String("attach_point".into()),
2170 PickleValue::String(attach_point.to_string()),
2171 ),
2172 (
2173 PickleValue::String("priority".into()),
2174 PickleValue::Int(priority as i64),
2175 ),
2176 (
2177 PickleValue::String("wasm".into()),
2178 PickleValue::Bytes(wasm.to_vec()),
2179 ),
2180 ]))?;
2181 parse_hook_result(&response)
2182 }
2183
2184 pub fn load_builtin_hook(
2185 &mut self,
2186 name: &str,
2187 attach_point: &str,
2188 priority: i32,
2189 builtin_id: &str,
2190 ) -> io::Result<Result<(), String>> {
2191 let response = self.call(&PickleValue::Dict(vec![
2192 (
2193 PickleValue::String("hook".into()),
2194 PickleValue::String("load_builtin".into()),
2195 ),
2196 (
2197 PickleValue::String("name".into()),
2198 PickleValue::String(name.to_string()),
2199 ),
2200 (
2201 PickleValue::String("attach_point".into()),
2202 PickleValue::String(attach_point.to_string()),
2203 ),
2204 (
2205 PickleValue::String("priority".into()),
2206 PickleValue::Int(priority as i64),
2207 ),
2208 (
2209 PickleValue::String("builtin_id".into()),
2210 PickleValue::String(builtin_id.to_string()),
2211 ),
2212 ]))?;
2213 parse_hook_result(&response)
2214 }
2215
2216 pub fn unload_hook(
2217 &mut self,
2218 name: &str,
2219 attach_point: &str,
2220 ) -> io::Result<Result<(), String>> {
2221 let response = self.call(&PickleValue::Dict(vec![
2222 (
2223 PickleValue::String("hook".into()),
2224 PickleValue::String("unload".into()),
2225 ),
2226 (
2227 PickleValue::String("name".into()),
2228 PickleValue::String(name.to_string()),
2229 ),
2230 (
2231 PickleValue::String("attach_point".into()),
2232 PickleValue::String(attach_point.to_string()),
2233 ),
2234 ]))?;
2235 parse_hook_result(&response)
2236 }
2237
2238 pub fn set_hook_enabled(
2239 &mut self,
2240 name: &str,
2241 attach_point: &str,
2242 enabled: bool,
2243 ) -> io::Result<Result<(), String>> {
2244 let op = if enabled { "enable" } else { "disable" };
2245 let response = self.call(&PickleValue::Dict(vec![
2246 (
2247 PickleValue::String("hook".into()),
2248 PickleValue::String(op.into()),
2249 ),
2250 (
2251 PickleValue::String("name".into()),
2252 PickleValue::String(name.to_string()),
2253 ),
2254 (
2255 PickleValue::String("attach_point".into()),
2256 PickleValue::String(attach_point.to_string()),
2257 ),
2258 ]))?;
2259 parse_hook_result(&response)
2260 }
2261
2262 pub fn set_hook_priority(
2263 &mut self,
2264 name: &str,
2265 attach_point: &str,
2266 priority: i32,
2267 ) -> io::Result<Result<(), String>> {
2268 let response = self.call(&PickleValue::Dict(vec![
2269 (
2270 PickleValue::String("hook".into()),
2271 PickleValue::String("set_priority".into()),
2272 ),
2273 (
2274 PickleValue::String("name".into()),
2275 PickleValue::String(name.to_string()),
2276 ),
2277 (
2278 PickleValue::String("attach_point".into()),
2279 PickleValue::String(attach_point.to_string()),
2280 ),
2281 (
2282 PickleValue::String("priority".into()),
2283 PickleValue::Int(priority as i64),
2284 ),
2285 ]))?;
2286 parse_hook_result(&response)
2287 }
2288
2289 pub fn blacklist_backbone_peer(
2290 &mut self,
2291 interface: &str,
2292 ip: &str,
2293 duration_secs: u64,
2294 reason: Option<&str>,
2295 penalty_level: Option<u8>,
2296 ) -> io::Result<bool> {
2297 let mut request = vec![
2298 (
2299 PickleValue::String("set".into()),
2300 PickleValue::String("backbone_peer_blacklist".into()),
2301 ),
2302 (
2303 PickleValue::String("interface".into()),
2304 PickleValue::String(interface.to_string()),
2305 ),
2306 (
2307 PickleValue::String("ip".into()),
2308 PickleValue::String(ip.to_string()),
2309 ),
2310 (
2311 PickleValue::String("duration_secs".into()),
2312 PickleValue::Int(duration_secs as i64),
2313 ),
2314 ];
2315 if let Some(reason) = reason {
2316 request.push((
2317 PickleValue::String("reason".into()),
2318 PickleValue::String(reason.to_string()),
2319 ));
2320 }
2321 if let Some(level) = penalty_level {
2322 request.push((
2323 PickleValue::String("penalty_level".into()),
2324 PickleValue::Int(level as i64),
2325 ));
2326 }
2327 let response = self.call(&PickleValue::Dict(request))?;
2328 Ok(response.as_bool().unwrap_or(false))
2329 }
2330
2331 pub fn known_destinations(&mut self) -> io::Result<Vec<KnownDestinationEntry>> {
2332 let response = self.call(&PickleValue::Dict(vec![(
2333 PickleValue::String("get".into()),
2334 PickleValue::String("known_destinations".into()),
2335 )]))?;
2336 parse_known_destination_list(&response)
2337 }
2338
2339 pub fn retain_known_destination(&mut self, dest_hash: [u8; 16]) -> io::Result<bool> {
2340 let response = self.call(&PickleValue::Dict(vec![
2341 (
2342 PickleValue::String("set".into()),
2343 PickleValue::String("known_destination_retained".into()),
2344 ),
2345 (
2346 PickleValue::String("dest_hash".into()),
2347 PickleValue::Bytes(dest_hash.to_vec()),
2348 ),
2349 ]))?;
2350 Ok(response.as_bool().unwrap_or(false))
2351 }
2352
2353 pub fn retain_identity(&mut self, identity_hash: [u8; 16]) -> io::Result<bool> {
2354 let response = self.call(&PickleValue::Dict(vec![
2355 (
2356 PickleValue::String("set".into()),
2357 PickleValue::String("identity_retained".into()),
2358 ),
2359 (
2360 PickleValue::String("identity_hash".into()),
2361 PickleValue::Bytes(identity_hash.to_vec()),
2362 ),
2363 ]))?;
2364 Ok(response.as_bool().unwrap_or(false))
2365 }
2366
2367 pub fn unretain_known_destination(&mut self, dest_hash: [u8; 16]) -> io::Result<bool> {
2368 let response = self.call(&PickleValue::Dict(vec![
2369 (
2370 PickleValue::String("clear".into()),
2371 PickleValue::String("known_destination_retained".into()),
2372 ),
2373 (
2374 PickleValue::String("dest_hash".into()),
2375 PickleValue::Bytes(dest_hash.to_vec()),
2376 ),
2377 ]))?;
2378 Ok(response.as_bool().unwrap_or(false))
2379 }
2380
2381 pub fn mark_known_destination_used(&mut self, dest_hash: [u8; 16]) -> io::Result<bool> {
2382 let response = self.call(&PickleValue::Dict(vec![
2383 (
2384 PickleValue::String("set".into()),
2385 PickleValue::String("known_destination_used".into()),
2386 ),
2387 (
2388 PickleValue::String("dest_hash".into()),
2389 PickleValue::Bytes(dest_hash.to_vec()),
2390 ),
2391 ]))?;
2392 Ok(response.as_bool().unwrap_or(false))
2393 }
2394}
2395
2396fn parse_lifecycle_state(value: &str) -> Option<LifecycleState> {
2397 match value {
2398 "active" => Some(LifecycleState::Active),
2399 "draining" => Some(LifecycleState::Draining),
2400 "stopping" => Some(LifecycleState::Stopping),
2401 "stopped" => Some(LifecycleState::Stopped),
2402 _ => None,
2403 }
2404}
2405
2406fn parse_drain_status(value: &PickleValue) -> io::Result<Option<DrainStatus>> {
2407 if !matches!(value, PickleValue::Dict(_)) {
2408 return Ok(None);
2409 }
2410 let state = value
2411 .get("state")
2412 .and_then(|entry| entry.as_str())
2413 .and_then(parse_lifecycle_state)
2414 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing drain state"))?;
2415 let drain_age_seconds = value.get("drain_age_seconds").and_then(|entry| {
2416 entry
2417 .as_float()
2418 .or_else(|| entry.as_int().map(|v| v as f64))
2419 });
2420 let deadline_remaining_seconds = value.get("deadline_remaining_seconds").and_then(|entry| {
2421 entry
2422 .as_float()
2423 .or_else(|| entry.as_int().map(|v| v as f64))
2424 });
2425 let drain_complete = value
2426 .get("drain_complete")
2427 .and_then(|entry| entry.as_bool())
2428 .unwrap_or(false);
2429 let interface_writer_queued_frames = value
2430 .get("interface_writer_queued_frames")
2431 .and_then(|entry| entry.as_int())
2432 .unwrap_or(0)
2433 .max(0) as usize;
2434 let provider_backlog_events = value
2435 .get("provider_backlog_events")
2436 .and_then(|entry| entry.as_int())
2437 .unwrap_or(0)
2438 .max(0) as usize;
2439 let provider_consumer_queued_events = value
2440 .get("provider_consumer_queued_events")
2441 .and_then(|entry| entry.as_int())
2442 .unwrap_or(0)
2443 .max(0) as usize;
2444 let detail = value
2445 .get("detail")
2446 .and_then(|entry| entry.as_str().map(|v| v.to_string()));
2447 Ok(Some(DrainStatus {
2448 state,
2449 drain_age_seconds,
2450 deadline_remaining_seconds,
2451 drain_complete,
2452 interface_writer_queued_frames,
2453 provider_backlog_events,
2454 provider_consumer_queued_events,
2455 detail,
2456 }))
2457}
2458
2459fn parse_hook_result(response: &PickleValue) -> io::Result<Result<(), String>> {
2460 let ok = response
2461 .get("ok")
2462 .and_then(|v| v.as_bool())
2463 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid hook response"))?;
2464 if ok {
2465 Ok(Ok(()))
2466 } else {
2467 Ok(Err(response
2468 .get("error")
2469 .and_then(|v| v.as_str())
2470 .unwrap_or("unknown hook error")
2471 .to_string()))
2472 }
2473}
2474
2475fn parse_hook_list(response: &PickleValue) -> io::Result<Vec<HookInfo>> {
2476 let list = response
2477 .as_list()
2478 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid hooks response"))?;
2479 let mut hooks = Vec::with_capacity(list.len());
2480 for item in list {
2481 hooks.push(HookInfo {
2482 name: item
2483 .get("name")
2484 .and_then(|v| v.as_str())
2485 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing hook name"))?
2486 .to_string(),
2487 hook_type: item
2488 .get("type")
2489 .and_then(|v| v.as_str())
2490 .unwrap_or(default_hook_type())
2491 .to_string(),
2492 attach_point: item
2493 .get("attach_point")
2494 .and_then(|v| v.as_str())
2495 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing attach_point"))?
2496 .to_string(),
2497 priority: item
2498 .get("priority")
2499 .and_then(|v| v.as_int())
2500 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing priority"))?
2501 as i32,
2502 enabled: item
2503 .get("enabled")
2504 .and_then(|v| v.as_bool())
2505 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing enabled"))?,
2506 consecutive_traps: item
2507 .get("consecutive_traps")
2508 .and_then(|v| v.as_int())
2509 .ok_or_else(|| {
2510 io::Error::new(io::ErrorKind::InvalidData, "missing consecutive_traps")
2511 })? as u32,
2512 });
2513 }
2514 Ok(hooks)
2515}
2516
2517fn default_hook_type() -> &'static str {
2518 #[cfg(feature = "rns-hooks-native")]
2519 {
2520 return "native";
2521 }
2522 #[cfg(all(not(feature = "rns-hooks-native"), feature = "rns-hooks-wasm"))]
2523 {
2524 return "wasm";
2525 }
2526 #[cfg(all(not(feature = "rns-hooks-native"), not(feature = "rns-hooks-wasm")))]
2527 {
2528 "wasm"
2529 }
2530}
2531
2532fn client_auth(stream: &mut TcpStream, auth_key: &[u8; 32]) -> io::Result<()> {
2534 let challenge = recv_bytes(stream)?;
2536
2537 if !challenge.starts_with(CHALLENGE_PREFIX) {
2538 return Err(io::Error::new(
2539 io::ErrorKind::InvalidData,
2540 "expected challenge",
2541 ));
2542 }
2543
2544 let message = &challenge[CHALLENGE_PREFIX.len()..];
2545
2546 let response = create_response(auth_key, message);
2548 send_bytes(stream, &response)?;
2549
2550 let result = recv_bytes(stream)?;
2552 if result == WELCOME {
2553 Ok(())
2554 } else {
2555 Err(io::Error::new(
2556 io::ErrorKind::PermissionDenied,
2557 "authentication failed",
2558 ))
2559 }
2560}
2561
2562fn create_response(auth_key: &[u8; 32], message: &[u8]) -> Vec<u8> {
2564 if message.starts_with(b"{sha256}") || message.len() > 20 {
2566 let digest = hmac_sha256(auth_key, message);
2568 let mut response = Vec::with_capacity(8 + 32);
2569 response.extend_from_slice(b"{sha256}");
2570 response.extend_from_slice(&digest);
2571 response
2572 } else {
2573 let digest = hmac_md5(auth_key, message);
2575 digest.to_vec()
2576 }
2577}
2578
2579pub fn derive_auth_key(private_key: &[u8]) -> [u8; 32] {
2581 sha256(private_key)
2582}
2583
2584#[cfg(test)]
2585mod tests {
2586 use super::*;
2587
2588 #[test]
2589 fn send_recv_bytes_roundtrip() {
2590 let (mut c1, mut c2) = tcp_pair();
2591 let data = b"hello world";
2592 send_bytes(&mut c1, data).unwrap();
2593 let received = recv_bytes(&mut c2).unwrap();
2594 assert_eq!(&received, data);
2595 }
2596
2597 #[test]
2598 fn send_recv_empty() {
2599 let (mut c1, mut c2) = tcp_pair();
2600 send_bytes(&mut c1, b"").unwrap();
2601 let received = recv_bytes(&mut c2).unwrap();
2602 assert!(received.is_empty());
2603 }
2604
2605 #[test]
2606 fn auth_success() {
2607 let key = derive_auth_key(b"test-private-key");
2608 let (mut server, mut client) = tcp_pair();
2609
2610 let key2 = key;
2611 let t = thread::spawn(move || {
2612 client_auth(&mut client, &key2).unwrap();
2613 });
2614
2615 server_auth(&mut server, &key).unwrap();
2616 t.join().unwrap();
2617 }
2618
2619 #[test]
2620 fn auth_failure_wrong_key() {
2621 let server_key = derive_auth_key(b"server-key");
2622 let client_key = derive_auth_key(b"wrong-key");
2623 let (mut server, mut client) = tcp_pair();
2624
2625 let t = thread::spawn(move || {
2626 let result = client_auth(&mut client, &client_key);
2627 assert!(result.is_err());
2628 });
2629
2630 let result = server_auth(&mut server, &server_key);
2631 assert!(result.is_err());
2632 t.join().unwrap();
2633 }
2634
2635 #[test]
2636 fn verify_sha256_response() {
2637 let key = derive_auth_key(b"mykey");
2638 let message = b"{sha256}abcdefghijklmnopqrstuvwxyz0123456789ABCD";
2639 let response = create_response(&key, message);
2640 assert!(response.starts_with(b"{sha256}"));
2641 assert!(verify_response(&key, message, &response));
2642 }
2643
2644 #[test]
2645 fn verify_legacy_md5_response() {
2646 let key = derive_auth_key(b"mykey");
2647 let message = b"01234567890123456789";
2649 let digest = hmac_md5(&key, message);
2651 assert!(verify_response(&key, message, &digest));
2652 }
2653
2654 #[test]
2655 fn constant_time_eq_works() {
2656 assert!(constant_time_eq(b"hello", b"hello"));
2657 assert!(!constant_time_eq(b"hello", b"world"));
2658 assert!(!constant_time_eq(b"hello", b"hell"));
2659 }
2660
2661 #[test]
2662 fn rpc_payload_accepts_msgpack_and_legacy_pickle() {
2663 let request = PickleValue::Dict(vec![
2664 (
2665 PickleValue::String("get".into()),
2666 PickleValue::String("link_count".into()),
2667 ),
2668 (PickleValue::String("max_hops".into()), PickleValue::Int(3)),
2669 ]);
2670
2671 let msgpack_bytes = encode_rpc_payload(&request, RpcCodec::Msgpack);
2672 let (decoded_msgpack, msgpack_codec) = decode_rpc_payload(&msgpack_bytes).unwrap();
2673 assert_eq!(decoded_msgpack, request);
2674 assert_eq!(msgpack_codec, RpcCodec::Msgpack);
2675
2676 let pickle_bytes = encode_rpc_payload(&request, RpcCodec::Pickle);
2677 let (decoded_pickle, pickle_codec) = decode_rpc_payload(&pickle_bytes).unwrap();
2678 assert_eq!(decoded_pickle, request);
2679 assert_eq!(pickle_codec, RpcCodec::Pickle);
2680 }
2681
2682 #[test]
2683 fn rpc_roundtrip() {
2684 let key = derive_auth_key(b"test-key");
2685 let (event_tx, event_rx) = crate::event::channel();
2686
2687 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
2690 let port = listener.local_addr().unwrap().port();
2691 listener.set_nonblocking(true).unwrap();
2692
2693 let shutdown = Arc::new(AtomicBool::new(false));
2694 let shutdown2 = shutdown.clone();
2695
2696 let driver_thread = thread::spawn(move || loop {
2698 match event_rx.recv_timeout(std::time::Duration::from_secs(5)) {
2699 Ok(Event::Query(QueryRequest::LinkCount, resp_tx)) => {
2700 let _ = resp_tx.send(QueryResponse::LinkCount(42));
2701 }
2702 Ok(Event::Query(QueryRequest::InterfaceStats, resp_tx)) => {
2703 let _ = resp_tx.send(QueryResponse::InterfaceStats(InterfaceStatsResponse {
2704 interfaces: vec![SingleInterfaceStat {
2705 id: 7,
2706 name: "TestInterface".into(),
2707 status: true,
2708 mode: 1,
2709 gravity: -2,
2710 announces_to_internal: Some(true),
2711 rxb: 1000,
2712 txb: 2000,
2713 rx_packets: 10,
2714 tx_packets: 20,
2715 cpu_load: None,
2716 mem_load: None,
2717 switch_id: None,
2718 endpoint_id: None,
2719 via_switch_id: None,
2720 peers: None,
2721 bitrate: Some(10_000_000),
2722 ifac_size: None,
2723 started: 1000.0,
2724 ia_freq: 0.0,
2725 ip_freq: 0.0,
2726 op_freq: 0.0,
2727 op_samples: 0,
2728 burst_active: false,
2729 burst_activated: 0.0,
2730 pr_burst_active: false,
2731 pr_burst_activated: 0.0,
2732 oa_freq: 0.0,
2733 clients: Some(2),
2734 blocked_ips: None,
2735 blocked_ip_list: None,
2736 announce_rate_target: Some(3600.0),
2737 announce_rate_grace: 5,
2738 announce_rate_penalty: 0.0,
2739 interface_type: "TestInterface".into(),
2740 }],
2741 transport_id: None,
2742 transport_enabled: true,
2743 transport_uptime: 3600.0,
2744 total_rxb: 1000,
2745 total_txb: 2000,
2746 probe_responder: None,
2747 backbone_peer_pool: None,
2748 }));
2749 }
2750 _ => break,
2751 }
2752 });
2753
2754 let key2 = key;
2755 let shutdown3 = shutdown2.clone();
2756 let server_thread = thread::spawn(move || {
2757 rpc_server_loop(listener, key2, event_tx, shutdown3);
2758 });
2759
2760 thread::sleep(std::time::Duration::from_millis(50));
2762
2763 let server_addr = RpcAddr::Tcp("127.0.0.1".into(), port);
2765 let mut client = RpcClient::connect(&server_addr, &key).unwrap();
2766 let response = client
2767 .call(&PickleValue::Dict(vec![(
2768 PickleValue::String("get".into()),
2769 PickleValue::String("link_count".into()),
2770 )]))
2771 .unwrap();
2772 assert_eq!(response.as_int().unwrap(), 42);
2773 drop(client);
2774
2775 let mut client2 = RpcClient::connect(&server_addr, &key).unwrap();
2777 let response2 = client2
2778 .call(&PickleValue::Dict(vec![(
2779 PickleValue::String("get".into()),
2780 PickleValue::String("interface_stats".into()),
2781 )]))
2782 .unwrap();
2783 let ifaces = response2.get("interfaces").unwrap().as_list().unwrap();
2784 assert_eq!(ifaces.len(), 1);
2785 let iface = &ifaces[0];
2786 assert_eq!(
2787 iface.get("name").unwrap().as_str().unwrap(),
2788 "TestInterface"
2789 );
2790 assert_eq!(iface.get("rxb").unwrap().as_int().unwrap(), 1000);
2791 drop(client2);
2792
2793 shutdown2.store(true, Ordering::Relaxed);
2795 server_thread.join().unwrap();
2796 driver_thread.join().unwrap();
2797 }
2798
2799 #[test]
2800 fn derive_auth_key_deterministic() {
2801 let key1 = derive_auth_key(b"test");
2802 let key2 = derive_auth_key(b"test");
2803 assert_eq!(key1, key2);
2804 let key3 = derive_auth_key(b"other");
2806 assert_ne!(key1, key3);
2807 }
2808
2809 #[test]
2810 fn pickle_request_handling() {
2811 let (event_tx, event_rx) = crate::event::channel();
2813
2814 let driver = thread::spawn(move || {
2815 if let Ok(Event::Query(QueryRequest::DropPath { dest_hash }, resp_tx)) = event_rx.recv()
2816 {
2817 assert_eq!(dest_hash, [1u8; 16]);
2818 let _ = resp_tx.send(QueryResponse::DropPath(true));
2819 }
2820 });
2821
2822 let request = PickleValue::Dict(vec![
2823 (
2824 PickleValue::String("drop".into()),
2825 PickleValue::String("path".into()),
2826 ),
2827 (
2828 PickleValue::String("destination_hash".into()),
2829 PickleValue::Bytes(vec![1u8; 16]),
2830 ),
2831 ]);
2832
2833 let response = handle_rpc_request(&request, &event_tx).unwrap();
2834 assert_eq!(response, PickleValue::Bool(true));
2835 driver.join().unwrap();
2836 }
2837
2838 #[test]
2839 fn hook_list_request_handling() {
2840 let (event_tx, event_rx) = crate::event::channel();
2841
2842 let driver = thread::spawn(move || {
2843 if let Ok(Event::ListHooks { response_tx }) = event_rx.recv() {
2844 let _ = response_tx.send(vec![HookInfo {
2845 name: "stats".into(),
2846 hook_type: "wasm".into(),
2847 attach_point: "PreIngress".into(),
2848 priority: 7,
2849 enabled: true,
2850 consecutive_traps: 0,
2851 }]);
2852 }
2853 });
2854
2855 let request = PickleValue::Dict(vec![(
2856 PickleValue::String("get".into()),
2857 PickleValue::String("hooks".into()),
2858 )]);
2859 let response = handle_rpc_request(&request, &event_tx).unwrap();
2860 let hooks = parse_hook_list(&response).unwrap();
2861 assert_eq!(hooks.len(), 1);
2862 assert_eq!(hooks[0].name, "stats");
2863 driver.join().unwrap();
2864 }
2865
2866 #[test]
2867 fn hook_load_request_handling() {
2868 let (event_tx, event_rx) = crate::event::channel();
2869
2870 let driver = thread::spawn(move || {
2871 if let Ok(Event::LoadHook {
2872 name,
2873 wasm_bytes,
2874 attach_point,
2875 priority,
2876 response_tx,
2877 }) = event_rx.recv()
2878 {
2879 assert_eq!(name, "stats");
2880 assert_eq!(attach_point, "PreIngress");
2881 assert_eq!(priority, 11);
2882 assert_eq!(wasm_bytes, vec![1, 2, 3]);
2883 let _ = response_tx.send(Ok(()));
2884 }
2885 });
2886
2887 let request = PickleValue::Dict(vec![
2888 (
2889 PickleValue::String("hook".into()),
2890 PickleValue::String("load".into()),
2891 ),
2892 (
2893 PickleValue::String("name".into()),
2894 PickleValue::String("stats".into()),
2895 ),
2896 (
2897 PickleValue::String("attach_point".into()),
2898 PickleValue::String("PreIngress".into()),
2899 ),
2900 (PickleValue::String("priority".into()), PickleValue::Int(11)),
2901 (
2902 PickleValue::String("wasm".into()),
2903 PickleValue::Bytes(vec![1, 2, 3]),
2904 ),
2905 ]);
2906 let response = handle_rpc_request(&request, &event_tx).unwrap();
2907 assert_eq!(parse_hook_result(&response).unwrap(), Ok(()));
2908 driver.join().unwrap();
2909 }
2910
2911 #[test]
2912 fn interface_stats_pickle_format() {
2913 let stats = InterfaceStatsResponse {
2914 interfaces: vec![SingleInterfaceStat {
2915 id: 1,
2916 name: "TCP".into(),
2917 status: true,
2918 mode: 1,
2919 gravity: -2,
2920 announces_to_internal: Some(true),
2921 rxb: 100,
2922 txb: 200,
2923 rx_packets: 5,
2924 tx_packets: 10,
2925 cpu_load: Some(12.0),
2926 mem_load: Some(34.5),
2927 switch_id: Some([1, 2, 3, 4]),
2928 endpoint_id: Some([5; 8]),
2929 via_switch_id: Some([6; 4]),
2930 peers: Some(2),
2931 bitrate: Some(1000000),
2932 ifac_size: Some(16),
2933 started: 1000.0,
2934 ia_freq: 1.0,
2935 ip_freq: 2.0,
2936 op_freq: 3.0,
2937 op_samples: 0,
2938 burst_active: true,
2939 burst_activated: 1200.0,
2940 pr_burst_active: true,
2941 pr_burst_activated: 1300.0,
2942 oa_freq: 4.0,
2943 clients: Some(3),
2944 blocked_ips: Some(2),
2945 blocked_ip_list: Some(vec!["192.0.2.1".into(), "203.0.113.2".into()]),
2946 announce_rate_target: Some(3600.0),
2947 announce_rate_grace: 5,
2948 announce_rate_penalty: 0.0,
2949 interface_type: "TCPClientInterface".into(),
2950 }],
2951 transport_id: Some([0xAB; 16]),
2952 transport_enabled: true,
2953 transport_uptime: 3600.0,
2954 total_rxb: 100,
2955 total_txb: 200,
2956 probe_responder: None,
2957 backbone_peer_pool: None,
2958 };
2959
2960 let pickle = interface_stats_to_pickle(&stats);
2961
2962 let encoded = pickle::encode(&pickle);
2964 let decoded = pickle::decode(&encoded).unwrap();
2965 assert_eq!(
2966 decoded.get("transport_enabled").unwrap().as_bool().unwrap(),
2967 true
2968 );
2969 let ifaces = decoded.get("interfaces").unwrap().as_list().unwrap();
2970 assert_eq!(ifaces[0].get("id").unwrap().as_int().unwrap(), 1);
2971 assert_eq!(ifaces[0].get("name").unwrap().as_str().unwrap(), "TCP");
2972 assert_eq!(ifaces[0].get("gravity").unwrap().as_int().unwrap(), -2);
2973 assert!(ifaces[0]
2974 .get("announces_to_internal")
2975 .unwrap()
2976 .as_bool()
2977 .unwrap());
2978 assert_eq!(ifaces[0].get("ia_freq").unwrap().as_float().unwrap(), 1.0);
2979 assert_eq!(ifaces[0].get("ip_freq").unwrap().as_float().unwrap(), 2.0);
2980 assert_eq!(ifaces[0].get("op_freq").unwrap().as_float().unwrap(), 3.0);
2981 assert_eq!(ifaces[0].get("oa_freq").unwrap().as_float().unwrap(), 4.0);
2982 assert_eq!(
2983 ifaces[0].get("burst_active").unwrap().as_bool().unwrap(),
2984 true
2985 );
2986 assert_eq!(
2987 ifaces[0]
2988 .get("burst_activated")
2989 .unwrap()
2990 .as_float()
2991 .unwrap(),
2992 1200.0
2993 );
2994 assert_eq!(
2995 ifaces[0].get("pr_burst_active").unwrap().as_bool().unwrap(),
2996 true
2997 );
2998 assert_eq!(
2999 ifaces[0]
3000 .get("pr_burst_activated")
3001 .unwrap()
3002 .as_float()
3003 .unwrap(),
3004 1300.0
3005 );
3006 assert_eq!(
3007 ifaces[0]
3008 .get("announce_rate_target")
3009 .unwrap()
3010 .as_float()
3011 .unwrap(),
3012 3600.0
3013 );
3014 assert_eq!(
3015 ifaces[0]
3016 .get("announce_rate_grace")
3017 .unwrap()
3018 .as_int()
3019 .unwrap(),
3020 5
3021 );
3022 assert_eq!(
3023 ifaces[0]
3024 .get("announce_rate_penalty")
3025 .unwrap()
3026 .as_float()
3027 .unwrap(),
3028 0.0
3029 );
3030 assert_eq!(ifaces[0].get("clients").unwrap().as_int().unwrap(), 3);
3031 assert_eq!(ifaces[0].get("blocked_ips").unwrap().as_int().unwrap(), 2);
3032 assert_eq!(
3033 ifaces[0]
3034 .get("blocked_ip_list")
3035 .unwrap()
3036 .as_list()
3037 .unwrap()
3038 .iter()
3039 .map(|value| value.as_str().unwrap())
3040 .collect::<Vec<_>>(),
3041 vec!["192.0.2.1", "203.0.113.2"]
3042 );
3043 }
3044
3045 #[test]
3046 fn interface_stats_pickle_includes_backbone_peer_pool_priority() {
3047 let stats = InterfaceStatsResponse {
3048 interfaces: vec![],
3049 transport_id: None,
3050 transport_enabled: true,
3051 transport_uptime: 1.0,
3052 total_rxb: 0,
3053 total_txb: 0,
3054 probe_responder: None,
3055 backbone_peer_pool: Some(crate::event::BackbonePeerPoolStatus {
3056 max_connected: 1,
3057 active_count: 1,
3058 standby_count: 0,
3059 cooldown_count: 0,
3060 members: vec![crate::event::BackbonePeerPoolMemberStatus {
3061 name: "peer".into(),
3062 remote: "127.0.0.1:4242".into(),
3063 source: "configured".into(),
3064 priority: 77,
3065 state: "active".into(),
3066 interface_id: Some(42),
3067 failure_count: 0,
3068 last_error: None,
3069 cooldown_remaining_seconds: None,
3070 }],
3071 }),
3072 };
3073
3074 let pickle = interface_stats_to_pickle(&stats);
3075 let encoded = pickle::encode(&pickle);
3076 let decoded = pickle::decode(&encoded).unwrap();
3077 let pool = decoded.get("backbone_peer_pool").unwrap();
3078 let members = pool.get("members").unwrap().as_list().unwrap();
3079 assert_eq!(members[0].get("priority").unwrap().as_int().unwrap(), 77);
3080 }
3081
3082 #[test]
3083 fn send_probe_rpc_unknown_dest() {
3084 let (event_tx, event_rx) = crate::event::channel();
3085
3086 let driver = thread::spawn(move || {
3087 if let Ok(Event::Query(
3088 QueryRequest::SendProbe {
3089 dest_hash,
3090 payload_size,
3091 },
3092 resp_tx,
3093 )) = event_rx.recv()
3094 {
3095 assert_eq!(dest_hash, [0xAA; 16]);
3096 assert_eq!(payload_size, 16); let _ = resp_tx.send(QueryResponse::SendProbe(None));
3098 }
3099 });
3100
3101 let request = PickleValue::Dict(vec![(
3102 PickleValue::String("send_probe".into()),
3103 PickleValue::Bytes(vec![0xAA; 16]),
3104 )]);
3105
3106 let response = handle_rpc_request(&request, &event_tx).unwrap();
3107 assert_eq!(response, PickleValue::None);
3108 driver.join().unwrap();
3109 }
3110
3111 #[test]
3112 fn send_probe_rpc_with_result() {
3113 let (event_tx, event_rx) = crate::event::channel();
3114
3115 let packet_hash = [0xBB; 32];
3116 let driver = thread::spawn(move || {
3117 if let Ok(Event::Query(
3118 QueryRequest::SendProbe {
3119 dest_hash,
3120 payload_size,
3121 },
3122 resp_tx,
3123 )) = event_rx.recv()
3124 {
3125 assert_eq!(dest_hash, [0xCC; 16]);
3126 assert_eq!(payload_size, 32);
3127 let _ = resp_tx.send(QueryResponse::SendProbe(Some((packet_hash, 3))));
3128 }
3129 });
3130
3131 let request = PickleValue::Dict(vec![
3132 (
3133 PickleValue::String("send_probe".into()),
3134 PickleValue::Bytes(vec![0xCC; 16]),
3135 ),
3136 (PickleValue::String("size".into()), PickleValue::Int(32)),
3137 ]);
3138
3139 let response = handle_rpc_request(&request, &event_tx).unwrap();
3140 let ph = response.get("packet_hash").unwrap().as_bytes().unwrap();
3141 assert_eq!(ph, &[0xBB; 32]);
3142 assert_eq!(response.get("hops").unwrap().as_int().unwrap(), 3);
3143 driver.join().unwrap();
3144 }
3145
3146 #[test]
3147 fn retain_identity_rpc_sends_identity_hash_query() {
3148 let (event_tx, event_rx) = crate::event::channel();
3149 let identity_hash = [0x44; 16];
3150
3151 let driver = thread::spawn(move || {
3152 if let Ok(Event::Query(
3153 QueryRequest::RetainIdentity {
3154 identity_hash: hash,
3155 },
3156 resp_tx,
3157 )) = event_rx.recv()
3158 {
3159 assert_eq!(hash, identity_hash);
3160 let _ = resp_tx.send(QueryResponse::RetainIdentity(true));
3161 }
3162 });
3163
3164 let request = PickleValue::Dict(vec![
3165 (
3166 PickleValue::String("set".into()),
3167 PickleValue::String("identity_retained".into()),
3168 ),
3169 (
3170 PickleValue::String("identity_hash".into()),
3171 PickleValue::Bytes(identity_hash.to_vec()),
3172 ),
3173 ]);
3174
3175 let response = handle_rpc_request(&request, &event_tx).unwrap();
3176 assert_eq!(response, PickleValue::Bool(true));
3177 driver.join().unwrap();
3178 }
3179
3180 #[test]
3181 fn send_probe_rpc_size_validation() {
3182 let (event_tx, event_rx) = crate::event::channel();
3183
3184 let driver = thread::spawn(move || {
3186 if let Ok(Event::Query(QueryRequest::SendProbe { payload_size, .. }, resp_tx)) =
3187 event_rx.recv()
3188 {
3189 assert_eq!(payload_size, 16); let _ = resp_tx.send(QueryResponse::SendProbe(None));
3191 }
3192 });
3193
3194 let request = PickleValue::Dict(vec![
3195 (
3196 PickleValue::String("send_probe".into()),
3197 PickleValue::Bytes(vec![0xDD; 16]),
3198 ),
3199 (PickleValue::String("size".into()), PickleValue::Int(-1)),
3200 ]);
3201
3202 let response = handle_rpc_request(&request, &event_tx).unwrap();
3203 assert_eq!(response, PickleValue::None);
3204 driver.join().unwrap();
3205 }
3206
3207 #[test]
3208 fn send_probe_rpc_size_too_large() {
3209 let (event_tx, event_rx) = crate::event::channel();
3210
3211 let driver = thread::spawn(move || {
3213 if let Ok(Event::Query(QueryRequest::SendProbe { payload_size, .. }, resp_tx)) =
3214 event_rx.recv()
3215 {
3216 assert_eq!(payload_size, 16); let _ = resp_tx.send(QueryResponse::SendProbe(None));
3218 }
3219 });
3220
3221 let request = PickleValue::Dict(vec![
3222 (
3223 PickleValue::String("send_probe".into()),
3224 PickleValue::Bytes(vec![0xDD; 16]),
3225 ),
3226 (PickleValue::String("size".into()), PickleValue::Int(999)),
3227 ]);
3228
3229 let response = handle_rpc_request(&request, &event_tx).unwrap();
3230 assert_eq!(response, PickleValue::None);
3231 driver.join().unwrap();
3232 }
3233
3234 #[test]
3235 fn check_proof_rpc_not_found() {
3236 let (event_tx, event_rx) = crate::event::channel();
3237
3238 let driver = thread::spawn(move || {
3239 if let Ok(Event::Query(QueryRequest::CheckProof { packet_hash }, resp_tx)) =
3240 event_rx.recv()
3241 {
3242 assert_eq!(packet_hash, [0xEE; 32]);
3243 let _ = resp_tx.send(QueryResponse::CheckProof(None));
3244 }
3245 });
3246
3247 let request = PickleValue::Dict(vec![(
3248 PickleValue::String("check_proof".into()),
3249 PickleValue::Bytes(vec![0xEE; 32]),
3250 )]);
3251
3252 let response = handle_rpc_request(&request, &event_tx).unwrap();
3253 assert_eq!(response, PickleValue::None);
3254 driver.join().unwrap();
3255 }
3256
3257 #[test]
3258 fn check_proof_rpc_found() {
3259 let (event_tx, event_rx) = crate::event::channel();
3260
3261 let driver = thread::spawn(move || {
3262 if let Ok(Event::Query(QueryRequest::CheckProof { packet_hash }, resp_tx)) =
3263 event_rx.recv()
3264 {
3265 assert_eq!(packet_hash, [0xFF; 32]);
3266 let _ = resp_tx.send(QueryResponse::CheckProof(Some(0.352)));
3267 }
3268 });
3269
3270 let request = PickleValue::Dict(vec![(
3271 PickleValue::String("check_proof".into()),
3272 PickleValue::Bytes(vec![0xFF; 32]),
3273 )]);
3274
3275 let response = handle_rpc_request(&request, &event_tx).unwrap();
3276 if let PickleValue::Float(rtt) = response {
3277 assert!((rtt - 0.352).abs() < 0.001);
3278 } else {
3279 panic!("Expected Float, got {:?}", response);
3280 }
3281 driver.join().unwrap();
3282 }
3283
3284 #[test]
3285 fn request_path_rpc() {
3286 let (event_tx, event_rx) = crate::event::channel();
3287
3288 let driver =
3289 thread::spawn(
3290 move || match event_rx.recv_timeout(std::time::Duration::from_secs(5)) {
3291 Ok(Event::RequestPath { dest_hash }) => {
3292 assert_eq!(dest_hash, [0x11; 16]);
3293 }
3294 other => panic!("Expected RequestPath event, got {:?}", other),
3295 },
3296 );
3297
3298 let request = PickleValue::Dict(vec![(
3299 PickleValue::String("request_path".into()),
3300 PickleValue::Bytes(vec![0x11; 16]),
3301 )]);
3302
3303 let response = handle_rpc_request(&request, &event_tx).unwrap();
3304 assert_eq!(response, PickleValue::Bool(true));
3305 driver.join().unwrap();
3306 }
3307
3308 #[test]
3309 fn begin_drain_rpc_emits_event() {
3310 let (event_tx, event_rx) = crate::event::channel();
3311
3312 let driver = thread::spawn(
3313 move || match event_rx.recv_timeout(Duration::from_secs(5)) {
3314 Ok(Event::BeginDrain { timeout }) => {
3315 assert!((timeout.as_secs_f64() - 1.5).abs() < 0.001);
3316 }
3317 other => panic!("Expected BeginDrain event, got {:?}", other),
3318 },
3319 );
3320
3321 let request = PickleValue::Dict(vec![(
3322 PickleValue::String("begin_drain".into()),
3323 PickleValue::Float(1.5),
3324 )]);
3325
3326 let response = handle_rpc_request(&request, &event_tx).unwrap();
3327 assert_eq!(response, PickleValue::Bool(true));
3328 driver.join().unwrap();
3329 }
3330
3331 #[test]
3332 fn drain_status_rpc_roundtrips_fields() {
3333 let (event_tx, event_rx) = crate::event::channel();
3334
3335 let driver = thread::spawn(move || {
3336 if let Ok(Event::Query(QueryRequest::DrainStatus, resp_tx)) = event_rx.recv() {
3337 let _ = resp_tx.send(QueryResponse::DrainStatus(DrainStatus {
3338 state: LifecycleState::Draining,
3339 drain_age_seconds: Some(0.75),
3340 deadline_remaining_seconds: Some(2.25),
3341 drain_complete: false,
3342 interface_writer_queued_frames: 3,
3343 provider_backlog_events: 4,
3344 provider_consumer_queued_events: 5,
3345 detail: Some("node is draining existing work".into()),
3346 }));
3347 }
3348 });
3349
3350 let request = PickleValue::Dict(vec![(
3351 PickleValue::String("get".into()),
3352 PickleValue::String("drain_status".into()),
3353 )]);
3354
3355 let response = handle_rpc_request(&request, &event_tx).unwrap();
3356 assert_eq!(response.get("state").unwrap().as_str(), Some("draining"));
3357 assert_eq!(
3358 response.get("drain_complete").unwrap().as_bool(),
3359 Some(false)
3360 );
3361 assert_eq!(
3362 response
3363 .get("deadline_remaining_seconds")
3364 .unwrap()
3365 .as_float(),
3366 Some(2.25)
3367 );
3368 assert_eq!(
3369 response
3370 .get("interface_writer_queued_frames")
3371 .unwrap()
3372 .as_int(),
3373 Some(3)
3374 );
3375 assert_eq!(
3376 response.get("provider_backlog_events").unwrap().as_int(),
3377 Some(4)
3378 );
3379 assert_eq!(
3380 response
3381 .get("provider_consumer_queued_events")
3382 .unwrap()
3383 .as_int(),
3384 Some(5)
3385 );
3386 assert_eq!(
3387 response.get("detail").unwrap().as_str(),
3388 Some("node is draining existing work")
3389 );
3390 driver.join().unwrap();
3391 }
3392
3393 #[test]
3394 fn interface_stats_with_probe_responder() {
3395 let probe_hash = [0x42; 16];
3396 let stats = InterfaceStatsResponse {
3397 interfaces: vec![],
3398 transport_id: None,
3399 transport_enabled: true,
3400 transport_uptime: 100.0,
3401 total_rxb: 0,
3402 total_txb: 0,
3403 probe_responder: Some(probe_hash),
3404 backbone_peer_pool: None,
3405 };
3406
3407 let pickle = interface_stats_to_pickle(&stats);
3408 let encoded = pickle::encode(&pickle);
3409 let decoded = pickle::decode(&encoded).unwrap();
3410
3411 let pr = decoded.get("probe_responder").unwrap().as_bytes().unwrap();
3412 assert_eq!(pr, &probe_hash);
3413 }
3414
3415 #[test]
3416 fn runtime_config_get_and_set_rpc() {
3417 let (event_tx, event_rx) = crate::event::channel();
3418
3419 let driver = thread::spawn(move || {
3420 if let Ok(Event::Query(QueryRequest::GetRuntimeConfig { key }, resp_tx)) =
3421 event_rx.recv()
3422 {
3423 assert_eq!(key, "global.tick_interval_ms");
3424 let _ = resp_tx.send(QueryResponse::RuntimeConfigEntry(Some(
3425 RuntimeConfigEntry {
3426 key,
3427 value: RuntimeConfigValue::Int(1000),
3428 default: RuntimeConfigValue::Int(1000),
3429 source: RuntimeConfigSource::Startup,
3430 apply_mode: RuntimeConfigApplyMode::Immediate,
3431 description: Some("tick".into()),
3432 },
3433 )));
3434 } else {
3435 panic!("expected GetRuntimeConfig query");
3436 }
3437
3438 if let Ok(Event::Query(QueryRequest::SetRuntimeConfig { key, value }, resp_tx)) =
3439 event_rx.recv()
3440 {
3441 assert_eq!(key, "global.tick_interval_ms");
3442 assert_eq!(value, RuntimeConfigValue::Int(250));
3443 let _ = resp_tx.send(QueryResponse::RuntimeConfigSet(Ok(RuntimeConfigEntry {
3444 key,
3445 value: RuntimeConfigValue::Int(250),
3446 default: RuntimeConfigValue::Int(1000),
3447 source: RuntimeConfigSource::RuntimeOverride,
3448 apply_mode: RuntimeConfigApplyMode::Immediate,
3449 description: Some("tick".into()),
3450 })));
3451 } else {
3452 panic!("expected SetRuntimeConfig query");
3453 }
3454 });
3455
3456 let get_request = PickleValue::Dict(vec![
3457 (
3458 PickleValue::String("get".into()),
3459 PickleValue::String("runtime_config_entry".into()),
3460 ),
3461 (
3462 PickleValue::String("key".into()),
3463 PickleValue::String("global.tick_interval_ms".into()),
3464 ),
3465 ]);
3466 let get_response = handle_rpc_request(&get_request, &event_tx).unwrap();
3467 assert_eq!(
3468 get_response.get("key").and_then(|v| v.as_str()),
3469 Some("global.tick_interval_ms")
3470 );
3471
3472 let set_request = PickleValue::Dict(vec![
3473 (
3474 PickleValue::String("set".into()),
3475 PickleValue::String("runtime_config".into()),
3476 ),
3477 (
3478 PickleValue::String("key".into()),
3479 PickleValue::String("global.tick_interval_ms".into()),
3480 ),
3481 (PickleValue::String("value".into()), PickleValue::Int(250)),
3482 ]);
3483 let set_response = handle_rpc_request(&set_request, &event_tx).unwrap();
3484 assert_eq!(
3485 set_response.get("value").and_then(|v| v.as_int()),
3486 Some(250)
3487 );
3488
3489 driver.join().unwrap();
3490 }
3491
3492 fn tcp_pair() -> (TcpStream, TcpStream) {
3494 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
3495 let port = listener.local_addr().unwrap().port();
3496 let client = TcpStream::connect(("127.0.0.1", port)).unwrap();
3497 let (server, _) = listener.accept().unwrap();
3498 client
3499 .set_read_timeout(Some(std::time::Duration::from_secs(5)))
3500 .unwrap();
3501 server
3502 .set_read_timeout(Some(std::time::Duration::from_secs(5)))
3503 .unwrap();
3504 (server, client)
3505 }
3506}