1use crate::proto::{read_ndjson, send_msg, Out, Peer};
19use serde_json::{json, Value};
20use std::collections::HashMap;
21use std::io::BufReader;
22use std::net::{TcpListener, TcpStream};
23use std::sync::{Mutex, OnceLock};
24use std::time::Duration;
25
26struct Config {
27 token: Option<String>,
28 name: String,
29}
30
31fn config() -> &'static Mutex<Config> {
32 static C: OnceLock<Mutex<Config>> = OnceLock::new();
33 C.get_or_init(|| {
34 Mutex::new(Config {
35 token: std::env::var("ZWIRE_HOST_TOKEN")
36 .ok()
37 .filter(|t| !t.is_empty()),
38 name: hostname(),
39 })
40 })
41}
42
43fn hostname() -> String {
44 crate::osops::hostname().unwrap_or_else(|| "host".to_string())
45}
46
47pub fn configure(token: Option<String>, name: Option<String>) {
49 let mut c = config().lock().unwrap();
50 if let Some(t) = token.filter(|t| !t.is_empty()) {
51 c.token = Some(t);
52 }
53 if let Some(n) = name {
54 c.name = n;
55 }
56}
57
58fn token() -> Option<String> {
59 config().lock().unwrap().token.clone()
60}
61
62pub fn local_name() -> String {
64 config().lock().unwrap().name.clone()
65}
66
67pub fn auth_required() -> bool {
69 token().is_some()
70}
71
72pub fn token_ok(presented: Option<&str>) -> bool {
74 match token() {
75 None => true,
76 Some(tok) => presented == Some(tok.as_str()),
77 }
78}
79
80struct Link {
83 out: Out,
84 name: String,
85}
86
87fn links() -> &'static Mutex<(u64, HashMap<u64, Link>)> {
88 static L: OnceLock<Mutex<(u64, HashMap<u64, Link>)>> = OnceLock::new();
89 L.get_or_init(|| Mutex::new((0, HashMap::new())))
90}
91
92pub fn register_link(out: &Out, name: &str) -> u64 {
95 let mut g = links().lock().unwrap();
96 let stale: Vec<u64> =
97 g.1.iter()
98 .filter(|(_, l)| l.name == name)
99 .map(|(id, _)| *id)
100 .collect();
101 for id in stale {
102 g.1.remove(&id);
103 }
104 g.0 += 1;
105 let id = g.0;
106 g.1.insert(
107 id,
108 Link {
109 out: out.clone(),
110 name: name.to_string(),
111 },
112 );
113 id
114}
115
116pub fn unregister_link(id: u64) {
118 links().lock().unwrap().1.remove(&id);
119}
120
121pub fn peer_names() -> Vec<String> {
123 let mut names: Vec<String> = links()
124 .lock()
125 .unwrap()
126 .1
127 .values()
128 .map(|l| l.name.clone())
129 .collect();
130 names.sort();
131 names
132}
133
134pub fn broadcast(topic: &str, data: &Value) -> usize {
136 let outs: Vec<Out> = {
137 links()
138 .lock()
139 .unwrap()
140 .1
141 .values()
142 .map(|l| l.out.clone())
143 .collect()
144 };
145 let frame = json!({ "cmd": "peer_pub", "topic": topic, "data": data });
146 outs.iter().filter(|o| send_msg(o, &frame).is_ok()).count()
147}
148
149pub fn listen_tcp(addr: String) {
153 std::thread::spawn(move || {
154 let listener = match TcpListener::bind(&addr) {
155 Ok(l) => l,
156 Err(e) => {
157 eprintln!("zwire-host: tcp bind {addr}: {e}");
158 return;
159 }
160 };
161 eprintln!("zwire-host: peering on tcp {addr}");
162 for conn in listener.incoming().flatten() {
163 std::thread::spawn(move || handle_inbound(conn));
164 }
165 });
166}
167
168fn handle_inbound(stream: TcpStream) {
169 let Ok(rclone) = stream.try_clone() else {
170 return;
171 };
172 let out = Peer::ndjson(Box::new(stream));
173 let sess = crate::session::Session::guarded(auth_required());
175 crate::transport::serve_conn(BufReader::new(rclone), out, sess);
176}
177
178pub fn dial(addr: String) {
182 std::thread::spawn(move || loop {
183 if let Err(e) = try_dial(&addr) {
184 eprintln!("zwire-host: peer {addr}: {e}");
185 }
186 std::thread::sleep(Duration::from_secs(5));
187 });
188}
189
190fn try_dial(addr: &str) -> Result<(), String> {
191 let stream = TcpStream::connect(addr).map_err(|e| e.to_string())?;
192 let rclone = stream.try_clone().map_err(|e| e.to_string())?;
193 let out = Peer::ndjson(Box::new(stream));
194 let mut reader = BufReader::new(rclone);
195
196 send_msg(
198 &out,
199 &json!({ "cmd": "peer_hello", "token": token(), "name": local_name() }),
200 )
201 .map_err(|e| e.to_string())?;
202 let reply = read_ndjson(&mut reader).ok_or("peer closed during handshake")?;
203 if reply["ok"] != json!(true) {
204 return Err(reply["err"]
205 .as_str()
206 .unwrap_or("handshake rejected")
207 .to_string());
208 }
209 let peer_name = reply["name"].as_str().unwrap_or(addr).to_string();
210 let link = register_link(&out, &peer_name);
211 eprintln!("zwire-host: peered with {peer_name} ({addr})");
212
213 let mut sess = crate::session::Session::guarded(false);
216 while let Some(msg) = read_ndjson(&mut reader) {
217 sess.handle(&out, &msg);
218 }
219 unregister_link(link);
220 Ok(())
221}
222
223pub fn remote(addr: &str, request: &Value) -> Result<Value, String> {
229 let stream = TcpStream::connect(addr).map_err(|e| format!("connect {addr}: {e}"))?;
230 let _ = stream.set_read_timeout(Some(Duration::from_secs(15)));
231 let rclone = stream.try_clone().map_err(|e| e.to_string())?;
232 let out = Peer::ndjson(Box::new(stream));
233 let mut reader = BufReader::new(rclone);
234
235 if let Some(tok) = token() {
236 send_msg(&out, &json!({ "cmd": "auth", "token": tok })).map_err(|e| e.to_string())?;
237 let _ = read_ndjson(&mut reader); }
239 send_msg(&out, request).map_err(|e| e.to_string())?;
240 read_ndjson(&mut reader).ok_or_else(|| "no reply from remote".to_string())
241}