Files
sovereign_browser_rust/src/fips_control.rs
T

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
}