Skip to main content

prns_runtime/runtime/rns_rpc/
mod.rs

1use alloc::vec::Vec;
2
3use prns_core::identity::IdentityHash;
4use prns_core::interfaces::rns_management::{RnsInterfaceStats, RnsTransportStatus};
5use prns_core::interfaces::shared_instance::rns_rpc::{
6    DestinationDataOperation, LegacyRpcReplyPlan, RnsRpcReply, RnsRpcReplyEncodeError,
7    RnsRpcRequest, RpcOperationOutcome, RpcRequest, RpcVerb,
8};
9use prns_core::routing::{BlackholeExpiry, BlackholedIdentity};
10use prns_core::wire::DestinationHash;
11
12use super::node_introspection::NodeIntrospection;
13use super::rns_management::{announce_rate_table, interface_stats};
14use super::{
15    DestinationIdentityRetentionControl, DestinationIdentityRetentionControlError,
16    IdentityBlackholeControl, IdentityBlackholeControlError, IdentityBlackholeSource,
17    IdentityBlackholeSourceError, RoutingControl, RoutingControlError,
18};
19
20#[cfg(test)]
21mod tests;
22
23pub async fn reply<B>(
24    request: &RpcRequest<'_>,
25    query: &impl NodeIntrospection,
26    control: &impl RoutingControl,
27    retention: &impl DestinationIdentityRetentionControl,
28    blackholes: &B,
29    blackhole_source: IdentityHash,
30    transport_status: Option<RnsTransportStatus>,
31) -> Result<Vec<u8>, RnsRpcReplyEncodeError>
32where
33    B: IdentityBlackholeSource + IdentityBlackholeControl,
34{
35    let reply = match request {
36        RpcRequest::Pickle(_) => {
37            reply_for_pickle(
38                request.verb(),
39                request.legacy_destination_hash(),
40                query,
41                transport_status,
42            )
43            .await
44        }
45        RpcRequest::Msgpack(request) => {
46            reply_for_msgpack(
47                request,
48                query,
49                control,
50                retention,
51                blackholes,
52                blackhole_source,
53                transport_status,
54            )
55            .await
56        }
57    };
58    reply.encode(request.dialect())
59}
60
61/// Execute an already-decoded request and return the canonical MessagePack
62/// reply. Host transports use this after normalizing legacy pickle requests;
63/// the transport then re-encodes the reply in the request's original dialect.
64pub async fn reply_decoded<B>(
65    request: &RnsRpcRequest,
66    query: &impl NodeIntrospection,
67    control: &impl RoutingControl,
68    retention: &impl DestinationIdentityRetentionControl,
69    blackholes: &B,
70    blackhole_source: IdentityHash,
71    transport_status: Option<RnsTransportStatus>,
72) -> Result<Vec<u8>, RnsRpcReplyEncodeError>
73where
74    B: IdentityBlackholeSource + IdentityBlackholeControl,
75{
76    reply_for_msgpack(
77        request,
78        query,
79        control,
80        retention,
81        blackholes,
82        blackhole_source,
83        transport_status,
84    )
85    .await
86    .encode(prns_core::interfaces::shared_instance::rns_rpc::RpcDialect::Msgpack)
87}
88
89async fn reply_for_msgpack<B>(
90    request: &RnsRpcRequest,
91    query: &impl NodeIntrospection,
92    control: &impl RoutingControl,
93    retention: &impl DestinationIdentityRetentionControl,
94    blackholes: &B,
95    blackhole_source: IdentityHash,
96    transport_status: Option<RnsTransportStatus>,
97) -> RnsRpcReply
98where
99    B: IdentityBlackholeSource + IdentityBlackholeControl,
100{
101    match request {
102        RnsRpcRequest::InterfaceStats => {
103            RnsRpcReply::interface_stats(interface_stats_with_transport(query, transport_status))
104        }
105
106        RnsRpcRequest::PathTable { max_hops } => {
107            RnsRpcReply::path_table(query.routes().await, max_hops.as_ref())
108        }
109
110        RnsRpcRequest::RateTable => {
111            RnsRpcReply::announce_rate_table(announce_rate_table(query.announce_rates().await))
112        }
113
114        RnsRpcRequest::NextHopInterface { destination_hash } => {
115            RnsRpcReply::next_hop_interface_name(query.route(*destination_hash).await)
116        }
117
118        RnsRpcRequest::NextHop { destination_hash } => {
119            RnsRpcReply::next_hop(query.route(*destination_hash).await)
120        }
121
122        RnsRpcRequest::FirstHopTimeout { .. } => RnsRpcReply::first_hop_timeout(),
123
124        RnsRpcRequest::LinkCount => RnsRpcReply::integer(i64::from(query.link_count().await)),
125
126        RnsRpcRequest::PacketRssi { packet_hash } => RnsRpcReply::packet_rssi(
127            packet_hash
128                .packet_hash()
129                .and_then(|packet_hash| query.packet_phy(packet_hash)),
130        ),
131
132        RnsRpcRequest::PacketSnr { packet_hash } => RnsRpcReply::packet_snr(
133            packet_hash
134                .packet_hash()
135                .and_then(|packet_hash| query.packet_phy(packet_hash)),
136        ),
137
138        RnsRpcRequest::PacketQuality { packet_hash } => RnsRpcReply::packet_quality(
139            packet_hash
140                .packet_hash()
141                .and_then(|packet_hash| query.packet_phy(packet_hash)),
142        ),
143
144        RnsRpcRequest::BlackholedIdentities => RnsRpcReply::blackholed_identities(
145            blackhole_source_outcome(blackholes.blackholed_identities().await),
146        ),
147
148        RnsRpcRequest::DropPath { destination_hash } => RnsRpcReply::drop_path(
149            routing_control_outcome(control.drop_route(*destination_hash).await),
150        ),
151
152        RnsRpcRequest::DropAllVia { transport_id } => RnsRpcReply::drop_all_via(
153            routing_control_outcome(control.drop_routes_via(*transport_id).await),
154        ),
155
156        RnsRpcRequest::DropAnnounceQueues => {
157            let _ = control.clear_announce_queues().await;
158            RnsRpcReply::drop_announce_queues()
159        }
160
161        RnsRpcRequest::IsBlackholed { identity_hash } => RnsRpcReply::is_blackholed(
162            blackhole_source_outcome(blackholes.is_blackholed(*identity_hash).await),
163        ),
164
165        RnsRpcRequest::BlackholeIdentity {
166            identity_hash,
167            until,
168            reason,
169        } => {
170            let expiry = until.as_ref().map_or(BlackholeExpiry::Indefinite, |until| {
171                until.blackhole_expiry()
172            });
173            RnsRpcReply::blackhole_identity(blackhole_control_outcome(
174                blackholes
175                    .blackhole_identity(BlackholedIdentity {
176                        identity: *identity_hash,
177                        source: blackhole_source,
178                        expiry,
179                        reason: reason.as_deref(),
180                    })
181                    .await,
182            ))
183        }
184
185        RnsRpcRequest::UnblackholeIdentity { identity_hash } => RnsRpcReply::unblackhole_identity(
186            blackhole_control_outcome(blackholes.unblackhole_identity(*identity_hash).await),
187        ),
188
189        RnsRpcRequest::DestinationData {
190            operation,
191            destination_hash,
192        } => match operation {
193            DestinationDataOperation::Used => RnsRpcReply::mark_destination_used(
194                retention_control_outcome(retention.mark_destination_used(*destination_hash).await),
195            ),
196            DestinationDataOperation::Retain => RnsRpcReply::retain_destination(
197                retention_control_outcome(retention.retain_destination(*destination_hash).await),
198            ),
199            DestinationDataOperation::Unretain => RnsRpcReply::release_destination(
200                retention_control_outcome(retention.release_destination(*destination_hash).await),
201            ),
202        },
203
204        RnsRpcRequest::RetainIdentity { identity_hash } => RnsRpcReply::retain_identity(
205            retention_control_outcome(retention.retain_identity(*identity_hash).await),
206        ),
207    }
208}
209
210async fn reply_for_pickle(
211    verb: RpcVerb,
212    destination_hash: Option<DestinationHash>,
213    query: &impl NodeIntrospection,
214    transport_status: Option<RnsTransportStatus>,
215) -> RnsRpcReply {
216    match LegacyRpcReplyPlan::for_request(verb, destination_hash) {
217        LegacyRpcReplyPlan::InterfaceStats => {
218            RnsRpcReply::interface_stats(interface_stats_with_transport(query, transport_status))
219        }
220        LegacyRpcReplyPlan::PathTable => RnsRpcReply::path_table(query.routes().await, None),
221        LegacyRpcReplyPlan::NextHopInterfaceName(destination_hash) => {
222            RnsRpcReply::next_hop_interface_name(query.route(destination_hash).await)
223        }
224        LegacyRpcReplyPlan::NextHop(destination_hash) => {
225            RnsRpcReply::next_hop(query.route(destination_hash).await)
226        }
227        LegacyRpcReplyPlan::LinkCount => RnsRpcReply::integer(i64::from(query.link_count().await)),
228        LegacyRpcReplyPlan::Immediate(reply) => reply,
229    }
230}
231
232fn interface_stats_with_transport(
233    query: &impl NodeIntrospection,
234    transport_status: Option<RnsTransportStatus>,
235) -> RnsInterfaceStats {
236    let stats = interface_stats(query.interface_inventory());
237    match transport_status {
238        Some(transport_status) => stats.with_transport(transport_status),
239        None => stats,
240    }
241}
242
243fn routing_control_outcome<T>(result: Result<T, RoutingControlError>) -> RpcOperationOutcome<T> {
244    match result {
245        Ok(outcome) => RpcOperationOutcome::Completed(outcome),
246        Err(RoutingControlError::NodeStopped | RoutingControlError::Busy) => {
247            RpcOperationOutcome::Unavailable
248        }
249    }
250}
251
252fn retention_control_outcome<T>(
253    result: Result<T, DestinationIdentityRetentionControlError>,
254) -> RpcOperationOutcome<T> {
255    match result {
256        Ok(outcome) => RpcOperationOutcome::Completed(outcome),
257        Err(
258            DestinationIdentityRetentionControlError::NodeStopped
259            | DestinationIdentityRetentionControlError::Busy,
260        ) => RpcOperationOutcome::Unavailable,
261    }
262}
263
264fn blackhole_source_outcome<T>(
265    result: Result<T, IdentityBlackholeSourceError>,
266) -> RpcOperationOutcome<T> {
267    match result {
268        Ok(outcome) => RpcOperationOutcome::Completed(outcome),
269        Err(IdentityBlackholeSourceError::NodeStopped | IdentityBlackholeSourceError::Busy) => {
270            RpcOperationOutcome::Unavailable
271        }
272    }
273}
274
275fn blackhole_control_outcome<T>(
276    result: Result<T, IdentityBlackholeControlError>,
277) -> RpcOperationOutcome<T> {
278    match result {
279        Ok(outcome) => RpcOperationOutcome::Completed(outcome),
280        Err(
281            IdentityBlackholeControlError::NodeStopped
282            | IdentityBlackholeControlError::Busy
283            | IdentityBlackholeControlError::CapacityExhausted
284            | IdentityBlackholeControlError::ReasonTooLong
285            | IdentityBlackholeControlError::DurabilityFailed,
286        ) => RpcOperationOutcome::Unavailable,
287    }
288}