195 lines
6.8 KiB
Rust
195 lines
6.8 KiB
Rust
//! FIPS mesh network control
|
|
//!
|
|
//! Port of `fips_control.c` / `fips_control.h` from the C project.
|
|
//! Implements a synchronous FIPS JSON-line control client over a Unix
|
|
//! domain socket. Requests are `{"command": "..."}` JSON lines; responses
|
|
//! are `{"status": "ok", "data": {...}}` JSON lines.
|
|
|
|
use std::io::{BufRead, BufReader, Write};
|
|
use std::os::unix::net::UnixStream;
|
|
use std::sync::Mutex;
|
|
use once_cell::sync::Lazy;
|
|
|
|
const FIPS_RESPONSE_MAX: usize = 1024 * 1024;
|
|
const FIPS_TIMEOUT_MS: u64 = 1500;
|
|
|
|
/// FIPS control state.
|
|
static G_FIPS_CONTROL: Lazy<Mutex<FipsControlState>> = Lazy::new(|| Mutex::new(FipsControlState::default()));
|
|
|
|
struct FipsControlState {
|
|
running: bool,
|
|
fips_path: String,
|
|
socket_path: String,
|
|
}
|
|
|
|
impl Default for FipsControlState {
|
|
fn default() -> Self {
|
|
FipsControlState {
|
|
running: false,
|
|
fips_path: String::new(),
|
|
socket_path: "/tmp/fips_control.sock".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Initialize FIPS control.
|
|
pub fn fips_control_init() {
|
|
let mut state = G_FIPS_CONTROL.lock().unwrap();
|
|
state.running = false;
|
|
}
|
|
|
|
/// Start the FIPS mesh node.
|
|
pub fn fips_control_start() -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut state = G_FIPS_CONTROL.lock().unwrap();
|
|
if state.running {
|
|
return Ok(());
|
|
}
|
|
// Try to connect to the FIPS control socket to verify it's running.
|
|
let socket_path = state.socket_path.clone();
|
|
if UnixStream::connect(&socket_path).is_ok() {
|
|
state.running = true;
|
|
println!("[fips] FIPS mesh node connected via {}", socket_path);
|
|
Ok(())
|
|
} else {
|
|
// FIPS daemon not running — mark as not running.
|
|
println!("[fips] FIPS control socket {} not available", socket_path);
|
|
Err(format!("FIPS control socket {} not available", socket_path).into())
|
|
}
|
|
}
|
|
|
|
/// Stop the FIPS mesh node.
|
|
pub fn fips_control_stop() {
|
|
let mut state = G_FIPS_CONTROL.lock().unwrap();
|
|
state.running = false;
|
|
println!("[fips] FIPS mesh node stopped");
|
|
}
|
|
|
|
/// Check if FIPS is running.
|
|
pub fn fips_control_is_running() -> bool {
|
|
let state = G_FIPS_CONTROL.lock().unwrap();
|
|
state.running
|
|
}
|
|
|
|
/// Send a JSON-line command to the FIPS control socket and return the
|
|
/// parsed response JSON.
|
|
fn fips_request(command: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
|
let state = G_FIPS_CONTROL.lock().unwrap();
|
|
let socket_path = state.socket_path.clone();
|
|
drop(state);
|
|
|
|
let mut stream = UnixStream::connect(&socket_path)?;
|
|
stream.set_read_timeout(Some(std::time::Duration::from_millis(FIPS_TIMEOUT_MS)))?;
|
|
stream.set_write_timeout(Some(std::time::Duration::from_millis(FIPS_TIMEOUT_MS)))?;
|
|
|
|
// Send the command as a JSON line.
|
|
let request = serde_json::json!({ "command": command });
|
|
let line = format!("{}\n", serde_json::to_string(&request)?);
|
|
stream.write_all(line.as_bytes())?;
|
|
|
|
// Read the response (until newline).
|
|
let mut reader = BufReader::new(stream);
|
|
let mut response = String::new();
|
|
let n = reader.read_line(&mut response)?;
|
|
if n == 0 {
|
|
return Err("FIPS closed control connection".into());
|
|
}
|
|
|
|
let root: serde_json::Value = serde_json::from_str(&response)?;
|
|
if root.get("status").and_then(|s| s.as_str()) != Some("ok") {
|
|
let msg = root.get("message").and_then(|m| m.as_str()).unwrap_or("request failed");
|
|
return Err(format!("FIPS control error: {}", msg).into());
|
|
}
|
|
Ok(root)
|
|
}
|
|
|
|
/// Get the FIPS status.
|
|
pub fn fips_control_show_status() -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
|
let root = fips_request("show_status")?;
|
|
Ok(root.get("data").cloned().unwrap_or(serde_json::json!({})))
|
|
}
|
|
|
|
/// Get the FIPS peer list.
|
|
pub fn fips_control_show_peers() -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
|
let root = fips_request("show_peers")?;
|
|
Ok(root.get("data").cloned().unwrap_or(serde_json::json!({})))
|
|
}
|
|
|
|
/// Get the FIPS spanning tree.
|
|
pub fn fips_control_show_tree() -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
|
let root = fips_request("show_tree")?;
|
|
Ok(root.get("data").cloned().unwrap_or(serde_json::json!({})))
|
|
}
|
|
|
|
/// Get the FIPS identity cache.
|
|
pub fn fips_control_show_identity_cache() -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
|
let root = fips_request("show_identity_cache")?;
|
|
Ok(root.get("data").cloned().unwrap_or(serde_json::json!({})))
|
|
}
|
|
|
|
/// Connect to a FIPS peer.
|
|
pub fn fips_control_connect_peer(npub: &str, address: &str, transport: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
|
let state = G_FIPS_CONTROL.lock().unwrap();
|
|
let socket_path = state.socket_path.clone();
|
|
drop(state);
|
|
|
|
let mut stream = UnixStream::connect(&socket_path)?;
|
|
stream.set_read_timeout(Some(std::time::Duration::from_millis(FIPS_TIMEOUT_MS)))?;
|
|
stream.set_write_timeout(Some(std::time::Duration::from_millis(FIPS_TIMEOUT_MS)))?;
|
|
|
|
let request = serde_json::json!({
|
|
"command": "connect_peer",
|
|
"npub": npub,
|
|
"address": address,
|
|
"transport": transport,
|
|
});
|
|
let line = format!("{}\n", serde_json::to_string(&request)?);
|
|
stream.write_all(line.as_bytes())?;
|
|
|
|
let mut reader = BufReader::new(stream);
|
|
let mut response = String::new();
|
|
reader.read_line(&mut response)?;
|
|
let root: serde_json::Value = serde_json::from_str(&response)?;
|
|
Ok(root)
|
|
}
|
|
|
|
/// Disconnect from a FIPS peer.
|
|
pub fn fips_control_disconnect_peer(npub: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
|
let state = G_FIPS_CONTROL.lock().unwrap();
|
|
let socket_path = state.socket_path.clone();
|
|
drop(state);
|
|
|
|
let mut stream = UnixStream::connect(&socket_path)?;
|
|
stream.set_read_timeout(Some(std::time::Duration::from_millis(FIPS_TIMEOUT_MS)))?;
|
|
stream.set_write_timeout(Some(std::time::Duration::from_millis(FIPS_TIMEOUT_MS)))?;
|
|
|
|
let request = serde_json::json!({
|
|
"command": "disconnect_peer",
|
|
"npub": npub,
|
|
});
|
|
let line = format!("{}\n", serde_json::to_string(&request)?);
|
|
stream.write_all(line.as_bytes())?;
|
|
|
|
let mut reader = BufReader::new(stream);
|
|
let mut response = String::new();
|
|
reader.read_line(&mut response)?;
|
|
let root: serde_json::Value = serde_json::from_str(&response)?;
|
|
Ok(root)
|
|
}
|
|
|
|
/// Resolve a FIPS address (npub) to a reachable endpoint.
|
|
pub fn fips_control_resolve(npub: &str) -> Option<String> {
|
|
// Query the identity cache for the npub's address.
|
|
if let Ok(cache) = fips_control_show_identity_cache() {
|
|
if let Some(nodes) = cache.get("nodes").and_then(|n| n.as_array()) {
|
|
for node in nodes {
|
|
if node.get("npub").and_then(|n| n.as_str()) == Some(npub) {
|
|
if let Some(addr) = node.get("address").and_then(|a| a.as_str()) {
|
|
return Some(addr.to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|