1use hatchery_build::http;
9use hatchery_enroll::call::capability_for;
10use serde_json::{json, Value};
11use tokio::net::TcpListener;
12
13pub async fn serve(listener: TcpListener, calls: crate::uplink_calls::NodeCalls, wake: Option<crate::server::node_wake::NodeWake>) {
16 loop {
17 let Ok((mut stream, peer)) = listener.accept().await else { continue };
18 if !peer.ip().is_loopback() {
19 continue;
20 }
21 let (calls, wake) = (calls.clone(), wake.clone());
22 tokio::spawn(async move {
23 let Ok(Ok((method, path, _, body))) = tokio::time::timeout(std::time::Duration::from_secs(5), http::read_request(&mut stream, 64 * 1024)).await else {
24 let _ = http::respond(&mut stream, 400, br#"{"error":"bad request"}"#).await;
25 return;
26 };
27 if method != "POST" || path != "/door" {
28 let _ = http::respond(&mut stream, 404, br#"{"error":"not found"}"#).await;
29 return;
30 }
31 let Ok(request) = serde_json::from_slice::<Value>(&body) else {
32 let _ = http::respond(&mut stream, 400, br#"{"error":"malformed"}"#).await;
33 return;
34 };
35 let op = request.get("op").and_then(Value::as_str).unwrap_or_default();
36 if op == "session.wake" {
37 let p = request.get("payload").cloned().unwrap_or_else(|| json!({}));
38 let field = |k: &str| p.get(k).and_then(Value::as_str).unwrap_or_default().to_owned();
39 let (status, reply) = match &wake {
40 None => (404, json!({"error":"letter wake is off on this node"})),
41 Some(w) if !w.authorized(&field("token")) => (403, json!({"error":"refused"})),
42 Some(w) => {
43 let letter = crate::server::node_wake::NodeLetter { nick: field("nick"), from: field("from"), event_id: field("event_id"), body: field("body") };
44 match w.wake(letter).await {
45 Ok(done) => (200, json!({"ok": done})),
46 Err(why) => (409, json!({"error": why})),
47 }
48 }
49 };
50 let _ = http::respond(&mut stream, status, reply.to_string().as_bytes()).await;
51 return;
52 }
53 if capability_for(op).is_none() {
54 let _ = http::respond(&mut stream, 404, br#"{"error":"unknown operation"}"#).await;
55 return;
56 }
57 let payload = request.get("payload").cloned().unwrap_or_else(|| json!({}));
58 let (status, reply) = match calls.call(op, &payload).await {
59 Ok(value) => (200, value.to_string()),
60 Err(crate::uplink_calls::CallError::Refused) => (403, json!({"error":"refused"}).to_string()),
61 Err(crate::uplink_calls::CallError::Unavailable(why)) => {
62 tracing::warn!(%op, reason = %why, "door call unavailable");
63 (502, json!({"error":"unavailable"}).to_string())
64 }
65 };
66 let _ = http::respond(&mut stream, status, reply.as_bytes()).await;
67 });
68 }
69}