Skip to main content

prns_runtime/runtime/
rns_remote_management.rs

1use 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}