1use deaddrop_core::store::{Store, unix_now};
5use deaddrop_core::{PROTOCOL_LABEL, Result};
6use serde::Serialize;
7use std::net::SocketAddr;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use std::sync::atomic::{AtomicBool, Ordering};
11use tokio::io::{AsyncReadExt, AsyncWriteExt};
12use tokio::net::TcpListener;
13
14pub const CONTROL_FILE: &str = "control.json";
15pub const DAEMON_FILE: &str = "daemon.json";
16
17#[derive(Debug, Clone, Serialize, serde::Deserialize)]
18pub struct ControlInfo {
19 pub listen: String,
20 pub token: String,
21 pub pid: u32,
22 pub started_at: u64,
23 #[serde(default)]
24 pub unix_socket: Option<String>,
25}
26
27pub fn control_path(root: &Path) -> PathBuf {
28 root.join(CONTROL_FILE)
29}
30
31pub fn daemon_path(root: &Path) -> PathBuf {
32 root.join(DAEMON_FILE)
33}
34
35pub fn write_info(root: &Path, info: &ControlInfo) -> Result<()> {
36 let bytes = serde_json::to_vec_pretty(info)
37 .map_err(|e| deaddrop_core::DdError::crypto(e.to_string()))?;
38 std::fs::write(control_path(root), &bytes)?;
39 std::fs::write(daemon_path(root), bytes)?;
40 Ok(())
41}
42
43pub fn read_info(root: &Path) -> Result<Option<ControlInfo>> {
44 let p = control_path(root);
45 if !p.exists() {
46 return Ok(None);
47 }
48 let bytes = std::fs::read(p)?;
49 let info: ControlInfo = serde_json::from_slice(&bytes)
50 .map_err(|e| deaddrop_core::DdError::invalid_frame(format!("control.json: {e}")))?;
51 Ok(Some(info))
52}
53
54pub fn clear(root: &Path) {
55 let _ = std::fs::remove_file(control_path(root));
56 let _ = std::fs::remove_file(daemon_path(root));
57 let _ = std::fs::remove_file(root.join("control.sock"));
58}
59
60pub fn random_token() -> String {
61 use std::time::{SystemTime, UNIX_EPOCH};
62 let n = SystemTime::now()
63 .duration_since(UNIX_EPOCH)
64 .map(|d| d.as_nanos())
65 .unwrap_or(1);
66 format!("{n:x}{:x}", std::process::id())
67}
68
69pub struct ControlServer {
70 pub info: ControlInfo,
71 pub stop: Arc<AtomicBool>,
72}
73
74impl ControlServer {
75 pub async fn bind(root: &Path, ddp_listen: SocketAddr) -> Result<Self> {
76 let listener = TcpListener::bind("127.0.0.1:0").await?;
77 let local = listener.local_addr()?;
78 let token = random_token();
79 #[cfg(unix)]
80 let unix_socket = {
81 let sock = root.join("control.sock");
82 let _ = std::fs::remove_file(&sock);
83 Some(sock.display().to_string())
84 };
85 #[cfg(not(unix))]
86 let unix_socket = None::<String>;
87 let info = ControlInfo {
88 listen: local.to_string(),
89 token: token.clone(),
90 pid: std::process::id(),
91 started_at: unix_now(),
92 unix_socket: unix_socket.clone(),
93 };
94 write_info(root, &info)?;
95 let stop = Arc::new(AtomicBool::new(false));
96 let stop_a = stop.clone();
97 let root_a = root.to_path_buf();
98 let token_a = token.clone();
99 tokio::spawn(async move {
100 loop {
101 if stop_a.load(Ordering::Relaxed) {
102 break;
103 }
104 match listener.accept().await {
105 Ok((stream, _)) => {
106 let stop = stop_a.clone();
107 let root = root_a.clone();
108 let token = token_a.clone();
109 tokio::spawn(async move {
110 let _ = handle_client(stream, &root, &token, &stop).await;
111 });
112 }
113 Err(_) => break,
114 }
115 }
116 });
117 #[cfg(unix)]
118 if let Some(path) = unix_socket {
119 let stop_b = stop.clone();
120 let root_b = root.to_path_buf();
121 let token_b = token;
122 tokio::spawn(async move {
123 if let Ok(ul) = tokio::net::UnixListener::bind(&path) {
124 loop {
125 if stop_b.load(Ordering::Relaxed) {
126 break;
127 }
128 if let Ok((stream, _)) = ul.accept().await {
129 let stop = stop_b.clone();
130 let root = root_b.clone();
131 let token = token_b.clone();
132 tokio::spawn(async move {
133 let _ = handle_unix(stream, &root, &token, &stop).await;
134 });
135 }
136 }
137 }
138 });
139 }
140 let _ = ddp_listen;
141 Ok(Self { info, stop })
142 }
143}
144
145#[cfg(unix)]
146async fn handle_unix(
147 stream: tokio::net::UnixStream,
148 root: &Path,
149 token: &str,
150 stop: &Arc<AtomicBool>,
151) -> Result<()> {
152 handle_generic(stream, root, token, stop).await
153}
154
155async fn handle_client(
156 stream: tokio::net::TcpStream,
157 root: &Path,
158 token: &str,
159 stop: &Arc<AtomicBool>,
160) -> Result<()> {
161 handle_generic(stream, root, token, stop).await
162}
163
164async fn handle_generic<S>(
165 mut stream: S,
166 root: &Path,
167 token: &str,
168 stop: &Arc<AtomicBool>,
169) -> Result<()>
170where
171 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
172{
173 let mut buf = vec![0u8; 8192];
174 let n = stream.read(&mut buf).await?;
175 let req = String::from_utf8_lossy(&buf[..n]);
176 let authed = req.contains(&format!("Bearer {token}")) || req.lines().any(|l| l.trim() == token);
177 if !authed {
178 let body = b"HTTP/1.0 401 Unauthorized\r\nContent-Length: 12\r\n\r\nunauthorized";
179 let _ = stream.write_all(body).await;
180 return Ok(());
181 }
182 let path = req
183 .lines()
184 .next()
185 .unwrap_or("")
186 .split_whitespace()
187 .nth(1)
188 .unwrap_or("/");
189 if path == "/v1/shutdown" || path.starts_with("/v1/shutdown") {
190 stop.store(true, Ordering::Relaxed);
191 let body = br#"{"ok":true}"#;
192 write_http(&mut stream, 200, body).await?;
193 return Ok(());
194 }
195 let payload = match status_json(root, path) {
196 Ok(v) => v,
197 Err(e) => serde_json::json!({"error": e.to_string()}),
198 };
199 let bytes = serde_json::to_vec(&payload).unwrap_or_else(|_| b"{}".to_vec());
200 write_http(&mut stream, 200, &bytes).await?;
201 Ok(())
202}
203
204async fn write_http<S: tokio::io::AsyncWrite + Unpin>(
205 stream: &mut S,
206 code: u16,
207 body: &[u8],
208) -> Result<()> {
209 let head = format!(
210 "HTTP/1.0 {code} OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n",
211 body.len()
212 );
213 stream.write_all(head.as_bytes()).await?;
214 stream.write_all(body).await?;
215 Ok(())
216}
217
218fn status_json(root: &Path, path: &str) -> Result<serde_json::Value> {
219 let store = Store::open(root, Default::default())?;
220 let now = unix_now();
221 match path {
222 "/v1/status" => {
223 let id = store.load_identity()?;
224 let s = store.stats()?;
225 Ok(serde_json::json!({
226 "protocol": PROTOCOL_LABEL,
227 "identity": id.peer_id.to_string(),
228 "objects": s.objects,
229 "storage": s.physical_size,
230 }))
231 }
232 "/v1/peers" => {
233 let book = store.load_contacts()?;
234 let list: Vec<_> = book
235 .all()
236 .map(|(id, c)| {
237 serde_json::json!({
238 "id": id.to_string(),
239 "name": c.card.name,
240 "trust": format!("{:?}", c.trust),
241 })
242 })
243 .collect();
244 Ok(serde_json::json!({ "peers": list }))
245 }
246 "/v1/drops" => {
247 let ids: Vec<_> = store
248 .inventory(now)?
249 .into_iter()
250 .map(|i| i.to_string())
251 .collect();
252 Ok(serde_json::json!({ "drops": ids }))
253 }
254 "/v1/routes" => Ok(serde_json::json!({
255 "note": "route table is computed per Drop; use dd route explain"
256 })),
257 "/v1/transfers" => Ok(transfers_json(&store, now)?),
258 "/v1/spaces" => {
259 let spaces: Vec<_> = store
260 .list_spaces()?
261 .into_iter()
262 .map(|s| serde_json::json!({"name": s.name}))
263 .collect();
264 Ok(serde_json::json!({ "spaces": spaces }))
265 }
266 "/v1/channels" => {
267 let ch: Vec<_> = store
268 .list_channels()?
269 .into_iter()
270 .map(|(n, f, p)| serde_json::json!({"name": n, "filter": f, "policy": p}))
271 .collect();
272 Ok(serde_json::json!({ "channels": ch }))
273 }
274 "/v1/events" => Ok(serde_json::json!({
275 "note": "subscribe via SDK DeadDrop::events(); HTTP is a snapshot API"
276 })),
277 _ => Ok(serde_json::json!({ "error": "not found", "path": path })),
278 }
279}
280
281pub fn transfers_json(store: &Store, now: u64) -> Result<serde_json::Value> {
282 let mut active = 0u64;
283 let mut queued = 0u64;
284 let mut completed = 0u64;
285 let mut items = Vec::new();
286 for id in store.inventory(now)? {
287 let complete = store.complete(&id).unwrap_or(false);
288 let mask = store.present_mask(&id).unwrap_or_default();
289 let present = mask.iter().filter(|x| **x).count();
290 let total = mask.len().max(1);
291 if complete {
292 completed += 1;
293 } else if present > 0 {
294 active += 1;
295 } else {
296 queued += 1;
297 }
298 items.push(serde_json::json!({
299 "id": id.to_string(),
300 "chunks": format!("{present}/{total}"),
301 "complete": complete,
302 }));
303 }
304 Ok(serde_json::json!({
305 "active": active,
306 "queued": queued,
307 "completed": completed,
308 "items": items,
309 }))
310}
311
312pub async fn query(root: &Path, path: &str) -> Result<serde_json::Value> {
313 let Some(info) = read_info(root)? else {
314 return Err(deaddrop_core::DdError::invalid_frame("daemon not running"));
315 };
316 let addr: SocketAddr = info
317 .listen
318 .parse()
319 .map_err(|_| deaddrop_core::DdError::invalid_frame("control listen"))?;
320 let mut s = tokio::net::TcpStream::connect(addr).await?;
321 let req = format!(
322 "GET {path} HTTP/1.0\r\nAuthorization: Bearer {}\r\n\r\n",
323 info.token
324 );
325 s.write_all(req.as_bytes()).await?;
326 let mut buf = Vec::new();
327 s.read_to_end(&mut buf).await?;
328 let text = String::from_utf8_lossy(&buf);
329 let body = text.split("\r\n\r\n").nth(1).unwrap_or("{}");
330 Ok(serde_json::from_str(body).unwrap_or_else(|_| serde_json::json!({"raw": body})))
331}