1use rns_core::constants;
11use rns_core::destination::destination_hash;
12use rns_core::hash::truncated_hash;
13use rns_core::msgpack::{self, Value};
14use rns_core::transport::types::{InterfaceId, InterfaceInfo};
15use rns_core::transport::TransportEngine;
16
17use super::interface_stats::InterfaceStats;
18use super::time;
19
20pub trait InterfaceStatusView {
22 fn id(&self) -> InterfaceId;
23 fn info(&self) -> &InterfaceInfo;
24 fn online(&self) -> bool;
25 fn stats(&self) -> &InterfaceStats;
26}
27
28pub fn status_path_hash() -> [u8; 16] {
30 truncated_hash(b"/status")
31}
32
33pub fn path_path_hash() -> [u8; 16] {
35 truncated_hash(b"/path")
36}
37
38pub fn list_path_hash() -> [u8; 16] {
40 truncated_hash(b"/list")
41}
42
43pub fn is_management_path(path_hash: &[u8; 16]) -> bool {
45 *path_hash == status_path_hash()
46 || *path_hash == path_path_hash()
47 || *path_hash == list_path_hash()
48}
49
50pub fn management_dest_hash(transport_identity_hash: &[u8; 16]) -> [u8; 16] {
54 destination_hash(
55 "rnstransport",
56 &["remote", "management"],
57 Some(transport_identity_hash),
58 )
59}
60
61pub fn blackhole_dest_hash(transport_identity_hash: &[u8; 16]) -> [u8; 16] {
65 destination_hash(
66 "rnstransport",
67 &["info", "blackhole"],
68 Some(transport_identity_hash),
69 )
70}
71
72pub fn probe_dest_hash(transport_identity_hash: &[u8; 16]) -> [u8; 16] {
76 destination_hash("rnstransport", &["probe"], Some(transport_identity_hash))
77}
78
79pub fn build_probe_announce(
83 identity: &rns_crypto::identity::Identity,
84 rng: &mut dyn rns_crypto::Rng,
85) -> Option<Vec<u8>> {
86 let identity_hash = *identity.hash();
87 let dest_hash = probe_dest_hash(&identity_hash);
88 let name_hash = rns_core::destination::name_hash("rnstransport", &["probe"]);
89 let mut random_hash = [0u8; 10];
90 rng.fill_bytes(&mut random_hash);
91
92 let (announce_data, _has_ratchet) = rns_core::announce::AnnounceData::pack(
93 identity,
94 &dest_hash,
95 &name_hash,
96 &random_hash,
97 None,
98 None,
99 )
100 .ok()?;
101
102 let flags = rns_core::packet::PacketFlags {
103 header_type: constants::HEADER_1,
104 context_flag: constants::FLAG_UNSET,
105 transport_type: constants::TRANSPORT_BROADCAST,
106 destination_type: constants::DESTINATION_SINGLE,
107 packet_type: constants::PACKET_TYPE_ANNOUNCE,
108 };
109
110 let packet = rns_core::packet::RawPacket::pack(
111 flags,
112 0,
113 &dest_hash,
114 None,
115 constants::CONTEXT_NONE,
116 &announce_data,
117 )
118 .ok()?;
119
120 Some(packet.raw)
121}
122
123#[derive(Debug, Clone)]
125pub struct ManagementConfig {
126 pub enable_remote_management: bool,
128 pub remote_management_allowed: Vec<[u8; 16]>,
130 pub publish_blackhole: bool,
132}
133
134impl Default for ManagementConfig {
135 fn default() -> Self {
136 ManagementConfig {
137 enable_remote_management: false,
138 remote_management_allowed: Vec::new(),
139 publish_blackhole: false,
140 }
141 }
142}
143
144pub fn handle_status_request(
149 data: &[u8],
150 engine: &TransportEngine,
151 interfaces: &[&dyn InterfaceStatusView],
152 started: f64,
153 probe_responder_hash: Option<[u8; 16]>,
154) -> Option<Vec<u8>> {
155 let include_lstats = match msgpack::unpack_exact(data) {
157 Ok(Value::Array(arr)) if !arr.is_empty() => arr[0].as_bool().unwrap_or(false),
158 _ => false,
159 };
160
161 let mut iface_list = Vec::new();
163 let mut total_rxb: u64 = 0;
164 let mut total_txb: u64 = 0;
165
166 for entry in interfaces {
167 let id = entry.id();
168 let info = entry.info();
169 let stats = entry.stats();
170
171 total_rxb += stats.rxb;
172 total_txb += stats.txb;
173
174 let mut ifstats: Vec<(&str, Value)> = Vec::new();
175 ifstats.push(("name", Value::Str(info.name.clone())));
176 ifstats.push(("short_name", Value::Str(info.name.clone())));
177 ifstats.push(("status", Value::Bool(entry.online())));
178 ifstats.push(("mode", Value::UInt(info.mode as u64)));
179 ifstats.push(("rxb", Value::UInt(stats.rxb)));
180 ifstats.push(("txb", Value::UInt(stats.txb)));
181 if let Some(br) = info.bitrate {
182 ifstats.push(("bitrate", Value::UInt(br)));
183 } else {
184 ifstats.push(("bitrate", Value::Nil));
185 }
186 ifstats.push((
187 "incoming_announce_freq",
188 Value::Float(stats.incoming_announce_freq()),
189 ));
190 ifstats.push((
191 "outgoing_announce_freq",
192 Value::Float(stats.outgoing_announce_freq()),
193 ));
194 ifstats.push((
195 "held_announces",
196 Value::UInt(engine.held_announce_count(&id) as u64),
197 ));
198
199 ifstats.push(("ifac_signature", Value::Nil));
201 ifstats.push((
202 "ifac_size",
203 if info.bitrate.is_some() {
204 Value::UInt(0)
205 } else {
206 Value::Nil
207 },
208 ));
209 ifstats.push(("ifac_netname", Value::Nil));
210
211 ifstats.push(("clients", Value::Nil));
213 ifstats.push(("announce_queue", Value::Nil));
214 ifstats.push(("rxs", Value::UInt(0)));
215 ifstats.push(("txs", Value::UInt(0)));
216
217 let map = ifstats
219 .into_iter()
220 .map(|(k, v)| (Value::Str(k.into()), v))
221 .collect();
222 iface_list.push(Value::Map(map));
223 }
224
225 let mut stats: Vec<(&str, Value)> = Vec::new();
227 stats.push(("interfaces", Value::Array(iface_list)));
228 stats.push(("rxb", Value::UInt(total_rxb)));
229 stats.push(("txb", Value::UInt(total_txb)));
230 stats.push(("rxs", Value::UInt(0)));
231 stats.push(("txs", Value::UInt(0)));
232
233 if let Some(identity_hash) = engine.config().identity_hash {
234 stats.push(("transport_id", Value::Bin(identity_hash.to_vec())));
235 stats.push(("transport_uptime", Value::Float(time::now() - started)));
236 }
237 stats.push((
238 "probe_responder",
239 match probe_responder_hash {
240 Some(hash) => Value::Bin(hash.to_vec()),
241 None => Value::Nil,
242 },
243 ));
244 stats.push(("rss", Value::Nil));
245
246 let stats_map = stats
247 .into_iter()
248 .map(|(k, v)| (Value::Str(k.into()), v))
249 .collect();
250
251 let mut response = vec![Value::Map(stats_map)];
253 if include_lstats {
254 let link_count = engine.link_table_count();
255 response.push(Value::UInt(link_count as u64));
256 }
257
258 Some(msgpack::pack(&Value::Array(response)))
259}
260
261pub fn handle_path_request(data: &[u8], engine: &TransportEngine) -> Option<Vec<u8>> {
267 let arr = match msgpack::unpack_exact(data) {
268 Ok(Value::Array(arr)) if !arr.is_empty() => arr,
269 _ => return None,
270 };
271
272 let command = match &arr[0] {
273 Value::Str(s) => s.as_str(),
274 _ => return None,
275 };
276
277 let dest_filter: Option<[u8; 16]> = if arr.len() > 1 {
278 match &arr[1] {
279 Value::Bin(b) if b.len() == 16 => {
280 let mut h = [0u8; 16];
281 h.copy_from_slice(b);
282 Some(h)
283 }
284 _ => None,
285 }
286 } else {
287 None
288 };
289
290 let max_hops: Option<u8> = if arr.len() > 2 {
291 arr[2].as_uint().map(|v| v as u8)
292 } else {
293 None
294 };
295
296 match command {
297 "table" => {
298 let paths = engine.get_path_table(max_hops);
299 let mut entries = Vec::new();
300 for p in &paths {
301 if let Some(ref filter) = dest_filter {
302 if p.0 != *filter {
303 continue;
304 }
305 }
306 let entry = vec![
308 (Value::Str("hash".into()), Value::Bin(p.0.to_vec())),
309 (Value::Str("timestamp".into()), Value::Float(p.1)),
310 (Value::Str("via".into()), Value::Bin(p.2.to_vec())),
311 (Value::Str("hops".into()), Value::UInt(p.3 as u64)),
312 (Value::Str("expires".into()), Value::Float(p.4)),
313 (Value::Str("interface".into()), Value::Str(p.5.clone())),
314 ];
315 entries.push(Value::Map(entry));
316 }
317 Some(msgpack::pack(&Value::Array(entries)))
318 }
319 "rates" => {
320 let rates = engine.get_rate_table();
321 let mut entries = Vec::new();
322 for r in &rates {
323 if let Some(ref filter) = dest_filter {
324 if r.0 != *filter {
325 continue;
326 }
327 }
328 let timestamps: Vec<Value> = r.4.iter().map(|t| Value::Float(*t)).collect();
330 let entry = vec![
331 (Value::Str("hash".into()), Value::Bin(r.0.to_vec())),
332 (Value::Str("last".into()), Value::Float(r.1)),
333 (
334 Value::Str("rate_violations".into()),
335 Value::UInt(r.2 as u64),
336 ),
337 (Value::Str("blocked_until".into()), Value::Float(r.3)),
338 (Value::Str("timestamps".into()), Value::Array(timestamps)),
339 ];
340 entries.push(Value::Map(entry));
341 }
342 Some(msgpack::pack(&Value::Array(entries)))
343 }
344 _ => None,
345 }
346}
347
348pub fn handle_blackhole_list_request(engine: &TransportEngine) -> Option<Vec<u8>> {
352 let blackholed = engine.get_blackholed();
353 let mut map_entries = Vec::new();
354 for (hash, created, expires, reason) in &blackholed {
355 let mut entry = vec![
356 (Value::Str("created".into()), Value::Float(*created)),
357 (Value::Str("expires".into()), Value::Float(*expires)),
358 ];
359 if let Some(r) = reason {
360 entry.push((Value::Str("reason".into()), Value::Str(r.clone())));
361 }
362 map_entries.push((Value::Bin(hash.to_vec()), Value::Map(entry)));
363 }
364 Some(msgpack::pack(&Value::Map(map_entries)))
365}
366
367pub fn build_management_announce(
371 identity: &rns_crypto::identity::Identity,
372 rng: &mut dyn rns_crypto::Rng,
373) -> Option<Vec<u8>> {
374 let identity_hash = *identity.hash();
375 let dest_hash = management_dest_hash(&identity_hash);
376 let name_hash = rns_core::destination::name_hash("rnstransport", &["remote", "management"]);
377 let mut random_hash = [0u8; 10];
378 rng.fill_bytes(&mut random_hash);
379
380 let (announce_data, _has_ratchet) = rns_core::announce::AnnounceData::pack(
381 identity,
382 &dest_hash,
383 &name_hash,
384 &random_hash,
385 None, None, )
388 .ok()?;
389
390 let flags = rns_core::packet::PacketFlags {
391 header_type: constants::HEADER_1,
392 context_flag: constants::FLAG_UNSET,
393 transport_type: constants::TRANSPORT_BROADCAST,
394 destination_type: constants::DESTINATION_SINGLE,
395 packet_type: constants::PACKET_TYPE_ANNOUNCE,
396 };
397
398 let packet = rns_core::packet::RawPacket::pack(
399 flags,
400 0,
401 &dest_hash,
402 None,
403 constants::CONTEXT_NONE,
404 &announce_data,
405 )
406 .ok()?;
407
408 Some(packet.raw)
409}
410
411pub fn build_blackhole_announce(
415 identity: &rns_crypto::identity::Identity,
416 rng: &mut dyn rns_crypto::Rng,
417) -> Option<Vec<u8>> {
418 let identity_hash = *identity.hash();
419 let dest_hash = blackhole_dest_hash(&identity_hash);
420 let name_hash = rns_core::destination::name_hash("rnstransport", &["info", "blackhole"]);
421 let mut random_hash = [0u8; 10];
422 rng.fill_bytes(&mut random_hash);
423
424 let (announce_data, _has_ratchet) = rns_core::announce::AnnounceData::pack(
425 identity,
426 &dest_hash,
427 &name_hash,
428 &random_hash,
429 None,
430 None,
431 )
432 .ok()?;
433
434 let flags = rns_core::packet::PacketFlags {
435 header_type: constants::HEADER_1,
436 context_flag: constants::FLAG_UNSET,
437 transport_type: constants::TRANSPORT_BROADCAST,
438 destination_type: constants::DESTINATION_SINGLE,
439 packet_type: constants::PACKET_TYPE_ANNOUNCE,
440 };
441
442 let packet = rns_core::packet::RawPacket::pack(
443 flags,
444 0,
445 &dest_hash,
446 None,
447 constants::CONTEXT_NONE,
448 &announce_data,
449 )
450 .ok()?;
451
452 Some(packet.raw)
453}