1use serde_json::{json, Value};
2
3use rns_crypto::identity::Identity;
4use rns_net::{
5 DestHash, Destination, IdentityHash, ProofStrategy, QueryRequest, QueryResponse, RnsNode,
6};
7
8use crate::auth::check_auth;
9use crate::config::CtlConfig;
10use crate::encode::{from_base64, hex_to_array, to_base64, to_hex};
11use crate::http::{parse_query, HttpRequest, HttpResponse};
12use crate::state::{DestinationEntry, SharedState};
13
14pub type NodeHandle = std::sync::Arc<std::sync::Mutex<Option<RnsNode>>>;
16
17fn with_node<F>(node: &NodeHandle, f: F) -> HttpResponse
19where
20 F: FnOnce(&RnsNode) -> HttpResponse,
21{
22 let guard = node.lock().unwrap();
23 match guard.as_ref() {
24 Some(n) => f(n),
25 None => HttpResponse::internal_error("Node is shutting down"),
26 }
27}
28
29pub fn handle_request(
31 req: &HttpRequest,
32 node: &NodeHandle,
33 state: &SharedState,
34 config: &CtlConfig,
35) -> HttpResponse {
36 if req.method == "GET" && req.path == "/health" {
38 return HttpResponse::ok(json!({"status": "healthy"}));
39 }
40
41 if let Err(resp) = check_auth(req, config) {
43 return resp;
44 }
45
46 match (req.method.as_str(), req.path.as_str()) {
47 ("GET", "/api/info") => handle_info(node, state),
49 ("GET", "/api/interfaces") => handle_interfaces(node),
50 ("GET", "/api/destinations") => handle_destinations(node, state),
51 ("GET", "/api/paths") => handle_paths(req, node),
52 ("GET", "/api/links") => handle_links(node),
53 ("GET", "/api/resources") => handle_resources(node),
54 ("GET", "/api/announces") => handle_event_list(req, state, "announces"),
55 ("GET", "/api/packets") => handle_event_list(req, state, "packets"),
56 ("GET", "/api/proofs") => handle_event_list(req, state, "proofs"),
57 ("GET", "/api/link_events") => handle_event_list(req, state, "link_events"),
58 ("GET", "/api/resource_events") => handle_event_list(req, state, "resource_events"),
59
60 ("GET", path) if path.starts_with("/api/identity/") => {
62 let hash_str = &path["/api/identity/".len()..];
63 handle_recall_identity(hash_str, node)
64 }
65
66 ("POST", "/api/destination") => handle_post_destination(req, node, state),
68 ("POST", "/api/announce") => handle_post_announce(req, node, state),
69 ("POST", "/api/send") => handle_post_send(req, node, state),
70 ("POST", "/api/link") => handle_post_link(req, node),
71 ("POST", "/api/link/send") => handle_post_link_send(req, node),
72 ("POST", "/api/link/close") => handle_post_link_close(req, node),
73 ("POST", "/api/channel") => handle_post_channel(req, node),
74 ("POST", "/api/resource") => handle_post_resource(req, node),
75 ("POST", "/api/path/request") => handle_post_path_request(req, node),
76 ("POST", "/api/direct_connect") => handle_post_direct_connect(req, node),
77
78 ("GET", "/api/hooks") => handle_list_hooks(node),
80 ("POST", "/api/hook/load") => handle_load_hook(req, node),
81 ("POST", "/api/hook/unload") => handle_unload_hook(req, node),
82 ("POST", "/api/hook/reload") => handle_reload_hook(req, node),
83
84 _ => HttpResponse::not_found(),
85 }
86}
87
88fn handle_info(node: &NodeHandle, state: &SharedState) -> HttpResponse {
91 with_node(node, |n| {
92 let transport_id = match n.query(QueryRequest::TransportIdentity) {
93 Ok(QueryResponse::TransportIdentity(id)) => id,
94 _ => None,
95 };
96 let s = state.read().unwrap();
97 HttpResponse::ok(json!({
98 "transport_id": transport_id.map(|h| to_hex(&h)),
99 "identity_hash": s.identity_hash.map(|h| to_hex(&h)),
100 "uptime_seconds": s.uptime_seconds(),
101 }))
102 })
103}
104
105fn handle_interfaces(node: &NodeHandle) -> HttpResponse {
106 with_node(node, |n| match n.query(QueryRequest::InterfaceStats) {
107 Ok(QueryResponse::InterfaceStats(stats)) => {
108 let ifaces: Vec<Value> = stats
109 .interfaces
110 .iter()
111 .map(|i| {
112 json!({
113 "name": i.name,
114 "status": if i.status { "up" } else { "down" },
115 "mode": i.mode,
116 "interface_type": i.interface_type,
117 "rxb": i.rxb,
118 "txb": i.txb,
119 "rx_packets": i.rx_packets,
120 "tx_packets": i.tx_packets,
121 "bitrate": i.bitrate,
122 "started": i.started,
123 "ia_freq": i.ia_freq,
124 "oa_freq": i.oa_freq,
125 })
126 })
127 .collect();
128 HttpResponse::ok(json!({
129 "interfaces": ifaces,
130 "transport_enabled": stats.transport_enabled,
131 "transport_uptime": stats.transport_uptime,
132 "total_rxb": stats.total_rxb,
133 "total_txb": stats.total_txb,
134 }))
135 }
136 _ => HttpResponse::internal_error("Query failed"),
137 })
138}
139
140fn handle_destinations(node: &NodeHandle, state: &SharedState) -> HttpResponse {
141 with_node(node, |n| match n.query(QueryRequest::LocalDestinations) {
142 Ok(QueryResponse::LocalDestinations(dests)) => {
143 let s = state.read().unwrap();
144 let list: Vec<Value> = dests
145 .iter()
146 .map(|d| {
147 let name = s
148 .destinations
149 .get(&d.hash)
150 .map(|e| e.full_name.as_str())
151 .unwrap_or("");
152 json!({
153 "hash": to_hex(&d.hash),
154 "type": d.dest_type,
155 "name": name,
156 })
157 })
158 .collect();
159 HttpResponse::ok(json!({"destinations": list}))
160 }
161 _ => HttpResponse::internal_error("Query failed"),
162 })
163}
164
165fn handle_paths(req: &HttpRequest, node: &NodeHandle) -> HttpResponse {
166 let params = parse_query(&req.query);
167 let filter_hash: Option<[u8; 16]> = params.get("dest_hash").and_then(|s| hex_to_array(s));
168
169 with_node(node, |n| {
170 match n.query(QueryRequest::PathTable { max_hops: None }) {
171 Ok(QueryResponse::PathTable(paths)) => {
172 let list: Vec<Value> = paths
173 .iter()
174 .filter(|p| filter_hash.map_or(true, |h| p.hash == h))
175 .map(|p| {
176 json!({
177 "hash": to_hex(&p.hash),
178 "via": to_hex(&p.via),
179 "hops": p.hops,
180 "expires": p.expires,
181 "interface": p.interface_name,
182 "timestamp": p.timestamp,
183 })
184 })
185 .collect();
186 HttpResponse::ok(json!({"paths": list}))
187 }
188 _ => HttpResponse::internal_error("Query failed"),
189 }
190 })
191}
192
193fn handle_links(node: &NodeHandle) -> HttpResponse {
194 with_node(node, |n| match n.query(QueryRequest::Links) {
195 Ok(QueryResponse::Links(links)) => {
196 let list: Vec<Value> = links
197 .iter()
198 .map(|l| {
199 json!({
200 "link_id": to_hex(&l.link_id),
201 "state": l.state,
202 "is_initiator": l.is_initiator,
203 "dest_hash": to_hex(&l.dest_hash),
204 "remote_identity": l.remote_identity.map(|h| to_hex(&h)),
205 "rtt": l.rtt,
206 })
207 })
208 .collect();
209 HttpResponse::ok(json!({"links": list}))
210 }
211 _ => HttpResponse::internal_error("Query failed"),
212 })
213}
214
215fn handle_resources(node: &NodeHandle) -> HttpResponse {
216 with_node(node, |n| match n.query(QueryRequest::Resources) {
217 Ok(QueryResponse::Resources(resources)) => {
218 let list: Vec<Value> = resources
219 .iter()
220 .map(|r| {
221 json!({
222 "link_id": to_hex(&r.link_id),
223 "direction": r.direction,
224 "total_parts": r.total_parts,
225 "transferred_parts": r.transferred_parts,
226 "complete": r.complete,
227 })
228 })
229 .collect();
230 HttpResponse::ok(json!({"resources": list}))
231 }
232 _ => HttpResponse::internal_error("Query failed"),
233 })
234}
235
236fn handle_event_list(req: &HttpRequest, state: &SharedState, kind: &str) -> HttpResponse {
237 let params = parse_query(&req.query);
238 let clear = params.get("clear").map_or(false, |v| v == "true");
239
240 let mut s = state.write().unwrap();
241 let items: Vec<Value> = match kind {
242 "announces" => {
243 let v: Vec<Value> = s
244 .announces
245 .iter()
246 .map(|r| serde_json::to_value(r).unwrap_or_default())
247 .collect();
248 if clear {
249 s.announces.clear();
250 }
251 v
252 }
253 "packets" => {
254 let v: Vec<Value> = s
255 .packets
256 .iter()
257 .map(|r| serde_json::to_value(r).unwrap_or_default())
258 .collect();
259 if clear {
260 s.packets.clear();
261 }
262 v
263 }
264 "proofs" => {
265 let v: Vec<Value> = s
266 .proofs
267 .iter()
268 .map(|r| serde_json::to_value(r).unwrap_or_default())
269 .collect();
270 if clear {
271 s.proofs.clear();
272 }
273 v
274 }
275 "link_events" => {
276 let v: Vec<Value> = s
277 .link_events
278 .iter()
279 .map(|r| serde_json::to_value(r).unwrap_or_default())
280 .collect();
281 if clear {
282 s.link_events.clear();
283 }
284 v
285 }
286 "resource_events" => {
287 let v: Vec<Value> = s
288 .resource_events
289 .iter()
290 .map(|r| serde_json::to_value(r).unwrap_or_default())
291 .collect();
292 if clear {
293 s.resource_events.clear();
294 }
295 v
296 }
297 _ => Vec::new(),
298 };
299
300 let mut obj = serde_json::Map::new();
301 obj.insert(kind.to_string(), Value::Array(items));
302 HttpResponse::ok(Value::Object(obj))
303}
304
305fn handle_recall_identity(hash_str: &str, node: &NodeHandle) -> HttpResponse {
306 let dest_hash: [u8; 16] = match hex_to_array(hash_str) {
307 Some(h) => h,
308 None => return HttpResponse::bad_request("Invalid dest_hash hex (expected 32 hex chars)"),
309 };
310
311 with_node(node, |n| match n.recall_identity(&DestHash(dest_hash)) {
312 Ok(Some(ai)) => HttpResponse::ok(json!({
313 "dest_hash": to_hex(&ai.dest_hash.0),
314 "identity_hash": to_hex(&ai.identity_hash.0),
315 "public_key": to_hex(&ai.public_key),
316 "app_data": ai.app_data.as_ref().map(|d| to_base64(d)),
317 "hops": ai.hops,
318 "received_at": ai.received_at,
319 })),
320 Ok(None) => HttpResponse::not_found(),
321 Err(_) => HttpResponse::internal_error("Query failed"),
322 })
323}
324
325fn parse_json_body(req: &HttpRequest) -> Result<Value, HttpResponse> {
328 serde_json::from_slice(&req.body)
329 .map_err(|e| HttpResponse::bad_request(&format!("Invalid JSON: {}", e)))
330}
331
332fn handle_post_destination(
333 req: &HttpRequest,
334 node: &NodeHandle,
335 state: &SharedState,
336) -> HttpResponse {
337 let body = match parse_json_body(req) {
338 Ok(v) => v,
339 Err(r) => return r,
340 };
341
342 let dest_type_str = body["type"].as_str().unwrap_or("");
343 let app_name = match body["app_name"].as_str() {
344 Some(s) => s,
345 None => return HttpResponse::bad_request("Missing app_name"),
346 };
347 let aspects: Vec<&str> = body["aspects"]
348 .as_array()
349 .map(|a| a.iter().filter_map(|v| v.as_str()).collect())
350 .unwrap_or_default();
351
352 let (identity_hash, identity_prv_key, identity_pub_key) = {
353 let s = state.read().unwrap();
354 let ih = s.identity_hash;
355 let prv = s.identity.as_ref().and_then(|i| i.get_private_key());
356 let pubk = s.identity.as_ref().and_then(|i| i.get_public_key());
357 (ih, prv, pubk)
358 };
359
360 let (dest, signing_key) = match dest_type_str {
361 "single" => {
362 let direction = body["direction"].as_str().unwrap_or("in");
363 match direction {
364 "in" => {
365 let ih = match identity_hash {
366 Some(h) => IdentityHash(h),
367 None => return HttpResponse::internal_error("No identity loaded"),
368 };
369 let dest = Destination::single_in(app_name, &aspects, ih)
370 .set_proof_strategy(parse_proof_strategy(&body));
371 (dest, identity_prv_key)
372 }
373 "out" => {
374 let dh_str = match body["dest_hash"].as_str() {
375 Some(s) => s,
376 None => {
377 return HttpResponse::bad_request(
378 "OUT single requires dest_hash of remote",
379 )
380 }
381 };
382 let dh: [u8; 16] = match hex_to_array(dh_str) {
383 Some(h) => h,
384 None => return HttpResponse::bad_request("Invalid dest_hash"),
385 };
386 return with_node(node, |n| {
387 match n.recall_identity(&DestHash(dh)) {
388 Ok(Some(recalled)) => {
389 let dest = Destination::single_out(app_name, &aspects, &recalled);
390 let full_name = format_dest_name(app_name, &aspects);
392 let mut s = state.write().unwrap();
393 s.destinations.insert(
394 dest.hash.0,
395 DestinationEntry {
396 destination: dest.clone(),
397 full_name: full_name.clone(),
398 },
399 );
400 HttpResponse::created(json!({
401 "dest_hash": to_hex(&dest.hash.0),
402 "name": full_name,
403 "type": "single",
404 "direction": "out",
405 }))
406 }
407 Ok(None) => {
408 HttpResponse::bad_request("No recalled identity for dest_hash")
409 }
410 Err(_) => HttpResponse::internal_error("Query failed"),
411 }
412 });
413 }
414 _ => return HttpResponse::bad_request("direction must be 'in' or 'out'"),
415 }
416 }
417 "plain" => {
418 let dest = Destination::plain(app_name, &aspects)
419 .set_proof_strategy(parse_proof_strategy(&body));
420 (dest, None)
421 }
422 "group" => {
423 let mut dest = Destination::group(app_name, &aspects)
424 .set_proof_strategy(parse_proof_strategy(&body));
425 if let Some(key_b64) = body["group_key"].as_str() {
426 match from_base64(key_b64) {
427 Some(key) => {
428 if let Err(e) = dest.load_private_key(key) {
429 return HttpResponse::bad_request(&format!("Invalid group key: {}", e));
430 }
431 }
432 None => return HttpResponse::bad_request("Invalid base64 group_key"),
433 }
434 } else {
435 dest.create_keys();
436 }
437 (dest, None)
438 }
439 _ => return HttpResponse::bad_request("type must be 'single', 'plain', or 'group'"),
440 };
441
442 with_node(node, |n| {
443 match n.register_destination_with_proof(&dest, signing_key) {
444 Ok(()) => {
445 if dest_type_str == "single" && body["direction"].as_str().unwrap_or("in") == "in" {
448 if let (Some(prv), Some(pubk)) = (identity_prv_key, identity_pub_key) {
449 let mut sig_prv = [0u8; 32];
450 sig_prv.copy_from_slice(&prv[32..64]);
451 let mut sig_pub = [0u8; 32];
452 sig_pub.copy_from_slice(&pubk[32..64]);
453 let _ = n.register_link_destination(dest.hash.0, sig_prv, sig_pub, 0);
454 }
455 }
456
457 let full_name = format_dest_name(app_name, &aspects);
458 let hash_hex = to_hex(&dest.hash.0);
459 let group_key_b64 = dest.get_private_key().map(to_base64);
460 let mut s = state.write().unwrap();
461 s.destinations.insert(
462 dest.hash.0,
463 DestinationEntry {
464 destination: dest,
465 full_name: full_name.clone(),
466 },
467 );
468 let mut resp = json!({
469 "dest_hash": hash_hex,
470 "name": full_name,
471 "type": dest_type_str,
472 });
473 if let Some(gk) = group_key_b64 {
474 resp["group_key"] = Value::String(gk);
475 }
476 HttpResponse::created(resp)
477 }
478 Err(_) => HttpResponse::internal_error("Failed to register destination"),
479 }
480 })
481}
482
483fn handle_post_announce(req: &HttpRequest, node: &NodeHandle, state: &SharedState) -> HttpResponse {
484 let body = match parse_json_body(req) {
485 Ok(v) => v,
486 Err(r) => return r,
487 };
488
489 let dh_str = match body["dest_hash"].as_str() {
490 Some(s) => s,
491 None => return HttpResponse::bad_request("Missing dest_hash"),
492 };
493 let dh: [u8; 16] = match hex_to_array(dh_str) {
494 Some(h) => h,
495 None => return HttpResponse::bad_request("Invalid dest_hash"),
496 };
497
498 let app_data: Option<Vec<u8>> = body["app_data"].as_str().and_then(from_base64);
499
500 let (dest, identity) = {
501 let s = state.read().unwrap();
502 let dest = match s.destinations.get(&dh) {
503 Some(entry) => entry.destination.clone(),
504 None => return HttpResponse::bad_request("Destination not registered via API"),
505 };
506 let identity = match s.identity.as_ref().and_then(|i| i.get_private_key()) {
507 Some(prv) => Identity::from_private_key(&prv),
508 None => return HttpResponse::internal_error("No identity loaded"),
509 };
510 (dest, identity)
511 };
512
513 with_node(node, |n| {
514 match n.announce(&dest, &identity, app_data.as_deref()) {
515 Ok(()) => HttpResponse::ok(json!({"status": "announced", "dest_hash": dh_str})),
516 Err(_) => HttpResponse::internal_error("Announce failed"),
517 }
518 })
519}
520
521fn handle_post_send(req: &HttpRequest, node: &NodeHandle, state: &SharedState) -> HttpResponse {
522 let body = match parse_json_body(req) {
523 Ok(v) => v,
524 Err(r) => return r,
525 };
526
527 let dh_str = match body["dest_hash"].as_str() {
528 Some(s) => s,
529 None => return HttpResponse::bad_request("Missing dest_hash"),
530 };
531 let dh: [u8; 16] = match hex_to_array(dh_str) {
532 Some(h) => h,
533 None => return HttpResponse::bad_request("Invalid dest_hash"),
534 };
535 let data = match body["data"].as_str().and_then(from_base64) {
536 Some(d) => d,
537 None => return HttpResponse::bad_request("Missing or invalid base64 data"),
538 };
539
540 let s = state.read().unwrap();
541 let dest = match s.destinations.get(&dh) {
542 Some(entry) => entry.destination.clone(),
543 None => return HttpResponse::bad_request("Destination not registered via API"),
544 };
545 drop(s);
546
547 with_node(node, |n| match n.send_packet(&dest, &data) {
548 Ok(ph) => HttpResponse::ok(json!({
549 "status": "sent",
550 "packet_hash": to_hex(&ph.0),
551 })),
552 Err(_) => HttpResponse::internal_error("Send failed"),
553 })
554}
555
556fn handle_post_link(req: &HttpRequest, node: &NodeHandle) -> HttpResponse {
557 let body = match parse_json_body(req) {
558 Ok(v) => v,
559 Err(r) => return r,
560 };
561
562 let dh_str = match body["dest_hash"].as_str() {
563 Some(s) => s,
564 None => return HttpResponse::bad_request("Missing dest_hash"),
565 };
566 let dh: [u8; 16] = match hex_to_array(dh_str) {
567 Some(h) => h,
568 None => return HttpResponse::bad_request("Invalid dest_hash"),
569 };
570
571 with_node(node, |n| {
572 let recalled = match n.recall_identity(&DestHash(dh)) {
574 Ok(Some(ai)) => ai,
575 Ok(None) => return HttpResponse::bad_request("No recalled identity for dest_hash"),
576 Err(_) => return HttpResponse::internal_error("Query failed"),
577 };
578 let mut sig_pub = [0u8; 32];
580 sig_pub.copy_from_slice(&recalled.public_key[32..64]);
581
582 match n.create_link(dh, sig_pub) {
583 Ok(link_id) => HttpResponse::created(json!({
584 "link_id": to_hex(&link_id),
585 })),
586 Err(_) => HttpResponse::internal_error("Create link failed"),
587 }
588 })
589}
590
591fn handle_post_link_send(req: &HttpRequest, node: &NodeHandle) -> HttpResponse {
592 let body = match parse_json_body(req) {
593 Ok(v) => v,
594 Err(r) => return r,
595 };
596
597 let link_id: [u8; 16] = match body["link_id"].as_str().and_then(|s| hex_to_array(s)) {
598 Some(h) => h,
599 None => return HttpResponse::bad_request("Missing or invalid link_id"),
600 };
601 let data = match body["data"].as_str().and_then(from_base64) {
602 Some(d) => d,
603 None => return HttpResponse::bad_request("Missing or invalid base64 data"),
604 };
605 let context = body["context"].as_u64().unwrap_or(0) as u8;
606
607 with_node(node, |n| match n.send_on_link(link_id, data, context) {
608 Ok(()) => HttpResponse::ok(json!({"status": "sent"})),
609 Err(_) => HttpResponse::internal_error("Send on link failed"),
610 })
611}
612
613fn handle_post_link_close(req: &HttpRequest, node: &NodeHandle) -> HttpResponse {
614 let body = match parse_json_body(req) {
615 Ok(v) => v,
616 Err(r) => return r,
617 };
618
619 let link_id: [u8; 16] = match body["link_id"].as_str().and_then(|s| hex_to_array(s)) {
620 Some(h) => h,
621 None => return HttpResponse::bad_request("Missing or invalid link_id"),
622 };
623
624 with_node(node, |n| match n.teardown_link(link_id) {
625 Ok(()) => HttpResponse::ok(json!({"status": "closed"})),
626 Err(_) => HttpResponse::internal_error("Teardown link failed"),
627 })
628}
629
630fn handle_post_channel(req: &HttpRequest, node: &NodeHandle) -> HttpResponse {
631 let body = match parse_json_body(req) {
632 Ok(v) => v,
633 Err(r) => return r,
634 };
635
636 let link_id: [u8; 16] = match body["link_id"].as_str().and_then(|s| hex_to_array(s)) {
637 Some(h) => h,
638 None => return HttpResponse::bad_request("Missing or invalid link_id"),
639 };
640 let msgtype = body["msgtype"].as_u64().unwrap_or(0) as u16;
641 let payload = match body["payload"].as_str().and_then(from_base64) {
642 Some(d) => d,
643 None => return HttpResponse::bad_request("Missing or invalid base64 payload"),
644 };
645
646 with_node(node, |n| {
647 match n.send_channel_message(link_id, msgtype, payload) {
648 Ok(()) => HttpResponse::ok(json!({"status": "sent"})),
649 Err(_) => HttpResponse::internal_error("Channel message failed"),
650 }
651 })
652}
653
654fn handle_post_resource(req: &HttpRequest, node: &NodeHandle) -> HttpResponse {
655 let body = match parse_json_body(req) {
656 Ok(v) => v,
657 Err(r) => return r,
658 };
659
660 let link_id: [u8; 16] = match body["link_id"].as_str().and_then(|s| hex_to_array(s)) {
661 Some(h) => h,
662 None => return HttpResponse::bad_request("Missing or invalid link_id"),
663 };
664 let data = match body["data"].as_str().and_then(from_base64) {
665 Some(d) => d,
666 None => return HttpResponse::bad_request("Missing or invalid base64 data"),
667 };
668 let metadata = body["metadata"].as_str().and_then(from_base64);
669
670 with_node(node, |n| match n.send_resource(link_id, data, metadata) {
671 Ok(()) => HttpResponse::ok(json!({"status": "sent"})),
672 Err(_) => HttpResponse::internal_error("Resource send failed"),
673 })
674}
675
676fn handle_post_path_request(req: &HttpRequest, node: &NodeHandle) -> HttpResponse {
677 let body = match parse_json_body(req) {
678 Ok(v) => v,
679 Err(r) => return r,
680 };
681
682 let dh_str = match body["dest_hash"].as_str() {
683 Some(s) => s,
684 None => return HttpResponse::bad_request("Missing dest_hash"),
685 };
686 let dh: [u8; 16] = match hex_to_array(dh_str) {
687 Some(h) => h,
688 None => return HttpResponse::bad_request("Invalid dest_hash"),
689 };
690
691 with_node(node, |n| match n.request_path(&DestHash(dh)) {
692 Ok(()) => HttpResponse::ok(json!({"status": "requested"})),
693 Err(_) => HttpResponse::internal_error("Path request failed"),
694 })
695}
696
697fn handle_post_direct_connect(req: &HttpRequest, node: &NodeHandle) -> HttpResponse {
698 let body = match parse_json_body(req) {
699 Ok(v) => v,
700 Err(r) => return r,
701 };
702
703 let lid_str = match body["link_id"].as_str() {
704 Some(s) => s,
705 None => return HttpResponse::bad_request("Missing link_id"),
706 };
707 let link_id: [u8; 16] = match hex_to_array(lid_str) {
708 Some(h) => h,
709 None => return HttpResponse::bad_request("Invalid link_id"),
710 };
711
712 with_node(node, |n| match n.propose_direct_connect(link_id) {
713 Ok(()) => HttpResponse::ok(json!({"status": "proposed"})),
714 Err(_) => HttpResponse::internal_error("Direct connect proposal failed"),
715 })
716}
717
718fn handle_list_hooks(node: &NodeHandle) -> HttpResponse {
721 with_node(node, |n| match n.list_hooks() {
722 Ok(hooks) => {
723 let list: Vec<Value> = hooks
724 .iter()
725 .map(|h| {
726 json!({
727 "name": h.name,
728 "attach_point": h.attach_point,
729 "priority": h.priority,
730 "enabled": h.enabled,
731 "consecutive_traps": h.consecutive_traps,
732 })
733 })
734 .collect();
735 HttpResponse::ok(json!({"hooks": list}))
736 }
737 Err(_) => HttpResponse::internal_error("Query failed"),
738 })
739}
740
741fn handle_load_hook(req: &HttpRequest, node: &NodeHandle) -> HttpResponse {
747 let body = match parse_json_body(req) {
748 Ok(v) => v,
749 Err(r) => return r,
750 };
751
752 let name = match body["name"].as_str() {
753 Some(s) => s.to_string(),
754 None => return HttpResponse::bad_request("Missing name"),
755 };
756 let path = match body["path"].as_str() {
757 Some(s) => s,
758 None => return HttpResponse::bad_request("Missing path"),
759 };
760 let attach_point = match body["attach_point"].as_str() {
761 Some(s) => s.to_string(),
762 None => return HttpResponse::bad_request("Missing attach_point"),
763 };
764 let priority = body["priority"].as_i64().unwrap_or(0) as i32;
765
766 let wasm_bytes = match std::fs::read(path) {
768 Ok(b) => b,
769 Err(e) => return HttpResponse::bad_request(&format!("Failed to read WASM file: {}", e)),
770 };
771
772 with_node(node, |n| {
773 match n.load_hook(name, wasm_bytes, attach_point, priority) {
774 Ok(Ok(())) => HttpResponse::ok(json!({"status": "loaded"})),
775 Ok(Err(e)) => HttpResponse::bad_request(&e),
776 Err(_) => HttpResponse::internal_error("Driver unavailable"),
777 }
778 })
779}
780
781fn handle_unload_hook(req: &HttpRequest, node: &NodeHandle) -> HttpResponse {
782 let body = match parse_json_body(req) {
783 Ok(v) => v,
784 Err(r) => return r,
785 };
786
787 let name = match body["name"].as_str() {
788 Some(s) => s.to_string(),
789 None => return HttpResponse::bad_request("Missing name"),
790 };
791 let attach_point = match body["attach_point"].as_str() {
792 Some(s) => s.to_string(),
793 None => return HttpResponse::bad_request("Missing attach_point"),
794 };
795
796 with_node(node, |n| match n.unload_hook(name, attach_point) {
797 Ok(Ok(())) => HttpResponse::ok(json!({"status": "unloaded"})),
798 Ok(Err(e)) => HttpResponse::bad_request(&e),
799 Err(_) => HttpResponse::internal_error("Driver unavailable"),
800 })
801}
802
803fn handle_reload_hook(req: &HttpRequest, node: &NodeHandle) -> HttpResponse {
804 let body = match parse_json_body(req) {
805 Ok(v) => v,
806 Err(r) => return r,
807 };
808
809 let name = match body["name"].as_str() {
810 Some(s) => s.to_string(),
811 None => return HttpResponse::bad_request("Missing name"),
812 };
813 let path = match body["path"].as_str() {
814 Some(s) => s,
815 None => return HttpResponse::bad_request("Missing path"),
816 };
817 let attach_point = match body["attach_point"].as_str() {
818 Some(s) => s.to_string(),
819 None => return HttpResponse::bad_request("Missing attach_point"),
820 };
821
822 let wasm_bytes = match std::fs::read(path) {
823 Ok(b) => b,
824 Err(e) => return HttpResponse::bad_request(&format!("Failed to read WASM file: {}", e)),
825 };
826
827 with_node(node, |n| {
828 match n.reload_hook(name, attach_point, wasm_bytes) {
829 Ok(Ok(())) => HttpResponse::ok(json!({"status": "reloaded"})),
830 Ok(Err(e)) => HttpResponse::bad_request(&e),
831 Err(_) => HttpResponse::internal_error("Driver unavailable"),
832 }
833 })
834}
835
836fn format_dest_name(app_name: &str, aspects: &[&str]) -> String {
839 if aspects.is_empty() {
840 app_name.to_string()
841 } else {
842 format!("{}.{}", app_name, aspects.join("."))
843 }
844}
845
846fn parse_proof_strategy(body: &Value) -> ProofStrategy {
847 match body["proof_strategy"].as_str() {
848 Some("all") => ProofStrategy::ProveAll,
849 Some("app") => ProofStrategy::ProveApp,
850 _ => ProofStrategy::ProveNone,
851 }
852}