1use crate::proto::{respond, send_msg, Out};
9use crate::store;
10use crate::{bus, exec, fsops, jobs, osops, peer, watch};
11use serde_json::{json, Value};
12use std::collections::HashMap;
13
14pub fn caps() -> Vec<&'static str> {
17 #[cfg_attr(not(any(feature = "sysinfo-caps", feature = "pty")), allow(unused_mut))]
18 let mut c = vec![
19 "hello",
20 "kv",
21 "fs",
22 "exec",
23 "jobs",
24 "bus",
25 "peer",
26 "watch",
27 "open",
28 "clipboard",
29 "notify",
30 "hostinfo",
31 "scheme",
32 "ui",
33 ];
34 #[cfg(feature = "sysinfo-caps")]
35 {
36 c.push("sysinfo");
37 c.push("procs");
38 }
39 #[cfg(feature = "pty")]
40 c.push("pty");
41 c
42}
43
44pub struct Session {
48 #[cfg(feature = "pty")]
49 ptys: HashMap<String, crate::pty::PtySession>,
50 watchers: HashMap<String, watch::Watcher>,
51 #[cfg(feature = "sysinfo-caps")]
52 sysmon: Option<crate::sysmon::Monitor>,
53 sub_id: Option<u64>,
55 peer_id: Option<u64>,
57 authed: bool,
60}
61
62impl Drop for Session {
63 fn drop(&mut self) {
64 if let Some(id) = self.sub_id {
65 bus::unregister(id);
66 }
67 if let Some(id) = self.peer_id {
68 peer::unregister_link(id);
69 }
70 }
71}
72
73impl Default for Session {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl Session {
80 pub fn new() -> Self {
82 Self::guarded(false)
83 }
84
85 pub fn guarded(require_auth: bool) -> Self {
88 Session {
89 #[cfg(feature = "pty")]
90 ptys: HashMap::new(),
91 watchers: HashMap::new(),
92 #[cfg(feature = "sysinfo-caps")]
93 sysmon: None,
94 sub_id: None,
95 peer_id: None,
96 authed: !require_auth,
97 }
98 }
99
100 fn key(req: &Value) -> String {
103 req["id"].as_str().unwrap_or("").to_string()
104 }
105
106 fn app(req: &Value) -> String {
108 req["app"].as_str().unwrap_or("zwire").to_string()
109 }
110
111 pub fn handle(&mut self, out: &Out, msg: &Value) {
114 if let Some(cmd) = msg["cmd"].as_str() {
115 self.handle_cmd(out, msg, cmd);
116 return;
117 }
118 if !msg["ui"].is_null() {
120 let ui = store::write_ui(&store::app_dir("zwire"), &msg["ui"]);
121 bus::publish("ui", &ui);
124 peer::broadcast("ui", &ui);
125 respond(out, msg, json!({"ok": true, "ui": ui}));
126 } else if let Some(s) = msg["scheme"].as_str() {
127 self.set_scheme(out, msg, s);
128 } else {
129 respond(out, msg, json!({"ok": false, "err": "empty"}));
130 }
131 }
132
133 fn handle_cmd(&mut self, out: &Out, msg: &Value, cmd: &str) {
134 if !self.authed && !matches!(cmd, "auth" | "peer_hello" | "hello" | "ping") {
137 respond(out, msg, json!({"ok": false, "err": "unauthorized"}));
138 return;
139 }
140 match cmd {
141 "hello" | "ping" => respond(
142 out,
143 msg,
144 json!({
145 "ok": true, "host": "zwire-host", "version": crate::VERSION,
146 "os": std::env::consts::OS, "arch": std::env::consts::ARCH,
147 "pid": std::process::id(), "caps": caps(),
148 }),
149 ),
150
151 "kv_get" => {
153 let v = store::kv_get(&Self::app(msg), msg["key"].as_str().unwrap_or(""));
154 respond(out, msg, json!({"ok": true, "value": v}));
155 }
156 "kv_set" => {
157 let ok = store::kv_set(
158 &Self::app(msg),
159 msg["key"].as_str().unwrap_or(""),
160 &msg["value"],
161 );
162 respond(out, msg, json!({ "ok": ok }));
163 }
164 "kv_merge" => {
165 let v = store::kv_merge(
166 &Self::app(msg),
167 msg["key"].as_str().unwrap_or(""),
168 &msg["value"],
169 );
170 respond(out, msg, json!({"ok": true, "value": v}));
171 }
172 "kv_del" => {
173 let ok = store::kv_del(&Self::app(msg), msg["key"].as_str().unwrap_or(""));
174 respond(out, msg, json!({ "ok": ok }));
175 }
176 "kv_keys" => {
177 let keys = store::kv_keys(&Self::app(msg));
178 respond(out, msg, json!({"ok": true, "keys": keys}));
179 }
180
181 "get" => {
183 let d = store::app_dir("zwire");
184 respond(
185 out,
186 msg,
187 json!({"ok": true, "scheme": store::current_scheme(&d), "ui": store::current_ui(&d)}),
188 );
189 }
190
191 "fs_watch" | "fs_tail" => {
193 let key = Self::key(msg);
194 let watcher = if cmd == "fs_watch" {
195 watch::Watcher::fs_watch(out, msg, key.clone())
196 } else {
197 watch::Watcher::fs_tail(out, msg, key.clone())
198 };
199 self.watchers.insert(key, watcher);
200 respond(out, msg, json!({"ok": true, "watching": true}));
201 }
202 "watch_stop" => {
203 self.watchers.remove(&Self::key(msg));
204 respond(out, msg, json!({"ok": true}));
205 }
206 "watch_list" => {
207 let mut ids: Vec<&String> = self.watchers.keys().collect();
208 ids.sort();
209 respond(out, msg, json!({"ok": true, "watchers": ids}));
210 }
211
212 c if c.starts_with("fs_") => {
214 let reply = fsops::handle(c, msg);
215 respond(out, msg, reply);
216 }
217
218 "exec" => respond(out, msg, exec::run(msg)),
220
221 c if c.starts_with("job_") => {
223 let reply = jobs::handle(c, msg);
224 respond(out, msg, reply);
225 }
226
227 #[cfg(feature = "sysinfo-caps")]
229 "ps" | "kill" | "which" => respond(out, msg, crate::procs::handle(cmd, msg)),
230
231 "sub" => {
233 let id = *self.sub_id.get_or_insert_with(|| bus::register(out));
234 if let Some(topic) = msg["topic"].as_str() {
235 bus::subscribe(id, topic);
236 respond(out, msg, json!({"ok": true, "topic": topic}));
237 } else {
238 respond(out, msg, json!({"ok": false, "err": "no_topic"}));
239 }
240 }
241 "unsub" => {
242 if let (Some(id), Some(topic)) = (self.sub_id, msg["topic"].as_str()) {
243 bus::unsubscribe(id, topic);
244 }
245 respond(out, msg, json!({"ok": true}));
246 }
247 "pub" => match msg["topic"].as_str() {
248 Some(topic) => {
249 let delivered = bus::publish(topic, &msg["data"]);
250 let forwarded = peer::broadcast(topic, &msg["data"]);
251 respond(
252 out,
253 msg,
254 json!({"ok": true, "delivered": delivered, "forwarded": forwarded}),
255 );
256 }
257 None => respond(out, msg, json!({"ok": false, "err": "no_topic"})),
258 },
259
260 "auth" => {
262 if peer::token_ok(msg["token"].as_str()) {
263 self.authed = true;
264 respond(out, msg, json!({"ok": true}));
265 } else {
266 respond(out, msg, json!({"ok": false, "err": "unauthorized"}));
267 }
268 }
269 "peer_hello" => {
270 if peer::token_ok(msg["token"].as_str()) {
271 self.authed = true;
272 let name = msg["name"].as_str().unwrap_or("peer");
273 self.peer_id = Some(peer::register_link(out, name));
274 respond(out, msg, json!({"ok": true, "name": peer::local_name()}));
275 } else {
276 respond(out, msg, json!({"ok": false, "err": "unauthorized"}));
277 }
278 }
279 "peer_pub" => {
282 if let Some(topic) = msg["topic"].as_str() {
283 bus::publish(topic, &msg["data"]);
284 }
285 }
286 "peers" => respond(
287 out,
288 msg,
289 json!({"ok": true, "self": peer::local_name(), "peers": peer::peer_names()}),
290 ),
291 "peer_connect" => match msg["addr"].as_str() {
292 Some(addr) => {
293 peer::dial(addr.to_string());
294 respond(out, msg, json!({"ok": true, "dialing": addr}));
295 }
296 None => respond(out, msg, json!({"ok": false, "err": "no_addr"})),
297 },
298 "remote" => match msg["peer"].as_str() {
299 Some(addr) => match peer::remote(addr, &msg["request"]) {
300 Ok(reply) => {
301 respond(out, msg, json!({"ok": true, "peer": addr, "reply": reply}))
302 }
303 Err(e) => respond(out, msg, json!({"ok": false, "err": e})),
304 },
305 None => respond(out, msg, json!({"ok": false, "err": "no_peer"})),
306 },
307
308 "open" | "clipboard_get" | "clipboard_set" | "notify" | "hostinfo" => {
310 respond(out, msg, osops::handle(cmd, msg));
311 }
312
313 #[cfg(feature = "sysinfo-caps")]
315 "sysinfo_start" => {
316 let interval = msg["interval_ms"].as_u64().unwrap_or(2000);
317 self.sysmon = Some(crate::sysmon::Monitor::start(out, interval, Self::key(msg)));
318 respond(out, msg, json!({"ok": true, "streaming": true}));
319 }
320 #[cfg(feature = "sysinfo-caps")]
321 "sysinfo_stop" => {
322 self.sysmon = None;
323 respond(out, msg, json!({"ok": true, "streaming": false}));
324 }
325 #[cfg(feature = "sysinfo-caps")]
326 "sysinfo_once" => {
327 use sysinfo::{Disks, Networks, System};
328 let mut sys = System::new();
329 sys.refresh_cpu_usage();
330 sys.refresh_memory();
331 let nets = Networks::new_with_refreshed_list();
332 let mut disks = Disks::new_with_refreshed_list();
337 disks.refresh(true);
338 respond(
339 out,
340 msg,
341 json!({"ok": true, "sys": crate::sysmon::snapshot(1.0, &nets, &disks, &sys)}),
342 );
343 }
344
345 #[cfg(feature = "pty")]
347 "pty_spawn" => {
348 let key = Self::key(msg);
349 match crate::pty::PtySession::spawn(out, msg, key.clone()) {
350 Some(s) => {
351 self.ptys.insert(key, s);
352 respond(out, msg, json!({"ok": true, "spawned": true}));
353 }
354 None => respond(out, msg, json!({"ok": false, "err": "pty_spawn_failed"})),
355 }
356 }
357 #[cfg(feature = "pty")]
358 "pty_write" => {
359 if let Some(s) = self.ptys.get_mut(&Self::key(msg)) {
360 s.write(msg);
361 }
362 }
363 #[cfg(feature = "pty")]
364 "pty_resize" => {
365 if let Some(s) = self.ptys.get(&Self::key(msg)) {
366 s.resize(msg);
367 }
368 }
369 #[cfg(feature = "pty")]
370 "pty_kill" => {
371 self.ptys.remove(&Self::key(msg));
374 }
375
376 _ => {
377 let _ = send_msg(out, &json!({"ok": false, "err": "unknown_cmd", "cmd": cmd}));
378 }
379 }
380 }
381
382 fn set_scheme(&self, out: &Out, msg: &Value, s: &str) {
383 if store::SCHEMES.contains(&s) {
384 store::write_scheme(&store::app_dir("zwire"), s);
385 let data = json!({ "scheme": s });
388 bus::publish("scheme", &data);
389 peer::broadcast("scheme", &data);
390 respond(out, msg, json!({"ok": true, "scheme": s}));
391 } else {
392 respond(out, msg, json!({"ok": false, "err": "bad_scheme"}));
393 }
394 }
395}