prns_runtime/runtime/rns_rpc/
mod.rs1use 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
61pub 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}