prns_runtime/runtime/
rns_remote_management.rs1use alloc::string::String;
2use alloc::vec::Vec;
3use core::time::Duration;
4
5use prns_core::engine::RouteSnapshot;
6use prns_core::identity::IdentityHash;
7use prns_core::interfaces::rns_management::{RnsPathTable, RnsTransportStatus};
8
9pub use prns_core::interfaces::rns_management::{
10 decode_remote_path_request as decode_path_request,
11 decode_remote_status_request as decode_status_request,
12 RnsManagementEncodeError as RemoteResponseEncodeError,
13 RnsRemotePathRequest as RemotePathRequest, RnsRemotePathTableRequest as RemotePathTableRequest,
14 RnsRemoteRateTableRequest as RemoteRateTableRequest,
15 RnsRemoteRequestDecodeError as RemoteRequestDecodeError,
16 RnsRemoteStatusRequest as RemoteStatusRequest,
17};
18
19use super::node_introspection::{AnnounceRateSnapshot, InterfaceInventoryEntry};
20use super::rns_management::{announce_rate_table, interface_stats};
21
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub struct RemoteTransportStatus {
24 pub transport_identity: IdentityHash,
25 pub network_identity: Option<IdentityHash>,
26 pub uptime: Duration,
27}
28
29pub fn encode_status_response(
30 request: RemoteStatusRequest,
31 inventory: Vec<InterfaceInventoryEntry<String>>,
32 link_count: u32,
33 transport: Option<RemoteTransportStatus>,
34) -> Result<Vec<u8>, RemoteResponseEncodeError> {
35 let mut stats = interface_stats(inventory);
36 if let Some(transport) = transport {
37 stats = stats.with_transport(RnsTransportStatus::new(
38 transport.transport_identity,
39 transport.network_identity,
40 transport.uptime,
41 ));
42 }
43 let link_count =
44 (request == RemoteStatusRequest::InterfaceStatsAndLinkCount).then_some(link_count);
45 stats.encode_remote_response(link_count)
46}
47
48pub fn encode_path_table_response(
49 selection: RemotePathTableRequest,
50 entries: Vec<RouteSnapshot>,
51) -> Result<Vec<u8>, RemoteResponseEncodeError> {
52 let entries = entries
53 .into_iter()
54 .filter(|entry| selection.includes(entry.destination, entry.hops))
55 .collect();
56 RnsPathTable::new(entries).encode_message_pack()
57}
58
59pub fn encode_rate_table_response(
60 selection: RemoteRateTableRequest,
61 entries: Vec<AnnounceRateSnapshot>,
62) -> Result<Vec<u8>, RemoteResponseEncodeError> {
63 let entries = entries
64 .into_iter()
65 .filter(|entry| selection.includes(entry.destination))
66 .collect();
67 announce_rate_table(entries).encode_message_pack()
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use prns_core::interfaces::{InterfaceId, InterfaceKind};
74 use prns_core::routing::NextHop;
75 use prns_core::units::InstantMillis;
76 use prns_core::wire::DestinationHash;
77
78 #[test]
79 fn table_response_filters_before_using_the_shared_stock_projection() {
80 let selected = DestinationHash::new([0x42; 16]);
81 let entries = vec![
82 route(selected, 2),
83 route(DestinationHash::new([0x43; 16]), 1),
84 route(selected, 4),
85 ];
86 let Ok(RemotePathRequest::Table(selection)) = decode_path_request(&bytes_from_hex(
87 "93a57461626c65c4104242424242424242424242424242424203",
88 )) else {
89 panic!("stock fixture is a path-table request");
90 };
91
92 let encoded = encode_path_table_response(selection, entries).unwrap();
93
94 assert_eq!(
95 encoded,
96 RnsPathTable::new(vec![route(selected, 2)])
97 .encode_message_pack()
98 .unwrap()
99 );
100 }
101
102 #[test]
103 fn status_response_has_the_reference_outer_shape_and_transport_fields() {
104 let encoded = encode_status_response(
105 RemoteStatusRequest::InterfaceStatsAndLinkCount,
106 Vec::new(),
107 2,
108 Some(RemoteTransportStatus {
109 transport_identity: IdentityHash::new([0x11; 16]),
110 network_identity: Some(IdentityHash::new([0x22; 16])),
111 uptime: Duration::from_millis(1_500),
112 }),
113 )
114 .unwrap();
115
116 assert_eq!(
117 encoded,
118 bytes_from_hex(
119 "928aaa696e746572666163657390a372786200a374786200a372787300a374787300a3727373c0ac7472616e73706f72745f6964c41011111111111111111111111111111111aa6e6574776f726b5f6964c41022222222222222222222222222222222b07472616e73706f72745f757074696d65cb3ff8000000000000af70726f62655f726573706f6e646572c002",
120 )
121 );
122 }
123
124 fn route(destination: DestinationHash, hops: u8) -> RouteSnapshot {
125 RouteSnapshot {
126 destination,
127 hops,
128 via: NextHop::Direct,
129 learned_at: InstantMillis(1_000),
130 last_relayed_at: InstantMillis(1_500),
131 expires_at: InstantMillis(2_000),
132 interface: InterfaceId::from_channel_tag(InterfaceKind::TcpClient, b"remote"),
133 }
134 }
135
136 fn bytes_from_hex(value: &str) -> Vec<u8> {
137 value
138 .as_bytes()
139 .chunks_exact(2)
140 .map(|pair| u8::from_str_radix(core::str::from_utf8(pair).unwrap(), 16).unwrap())
141 .collect()
142 }
143}