//! FireCracker REST API client over Unix socket use plasmavmc_types::{Error, Result}; use serde::{Deserialize, Serialize}; use std::path::Path; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::UnixStream; /// FireCracker REST API client pub struct FireCrackerClient { stream: UnixStream, } impl FireCrackerClient { /// Connect to FireCracker API socket pub async fn connect(path: impl AsRef) -> Result { let stream = UnixStream::connect(path.as_ref()) .await .map_err(|e| Error::HypervisorError(format!("Failed to connect FireCracker API: {e}")))?; Ok(Self { stream }) } /// Send HTTP request over Unix socket async fn request( &mut self, method: &str, path: &str, body: Option<&[u8]>, ) -> Result> { let mut request = format!("{} {} HTTP/1.1\r\n", method, path); request.push_str("Host: localhost\r\n"); request.push_str("Content-Type: application/json\r\n"); if let Some(body) = body { request.push_str(&format!("Content-Length: {}\r\n", body.len())); } else { request.push_str("Content-Length: 0\r\n"); } request.push_str("\r\n"); if let Some(body) = body { request.push_str(&String::from_utf8_lossy(body)); } self.stream .write_all(request.as_bytes()) .await .map_err(|e| Error::HypervisorError(format!("Failed to send request: {e}")))?; self.stream .flush() .await .map_err(|e| Error::HypervisorError(format!("Failed to flush request: {e}")))?; // Read response let mut response = Vec::new(); self.stream .read_to_end(&mut response) .await .map_err(|e| Error::HypervisorError(format!("Failed to read response: {e}")))?; // Parse HTTP response let response_str = String::from_utf8_lossy(&response); let mut lines = response_str.lines(); // Parse status line let status_line = lines.next().ok_or_else(|| { Error::HypervisorError("Empty response from FireCracker API".to_string()) })?; if !status_line.contains("200") && !status_line.contains("204") { return Err(Error::HypervisorError(format!( "FireCracker API error: {status_line}" ))); } // Skip headers let mut body_start = 0; for (idx, line) in response_str.lines().enumerate() { if line.is_empty() { body_start = response_str .lines() .take(idx + 1) .map(|l| l.len() + 2) // +2 for \r\n .sum(); break; } } Ok(response[body_start..].to_vec()) } /// PUT /machine-config pub async fn put_machine_config(&mut self, vcpu_count: u32, mem_size_mib: u64) -> Result<()> { #[derive(Serialize)] struct MachineConfig { vcpu_count: u32, mem_size_mib: u64, ht_enabled: bool, } let config = MachineConfig { vcpu_count, mem_size_mib, ht_enabled: false, }; let body = serde_json::to_vec(&config) .map_err(|e| Error::HypervisorError(format!("Failed to serialize config: {e}")))?; self.request("PUT", "/machine-config", Some(&body)) .await?; Ok(()) } /// PUT /boot-source pub async fn put_boot_source( &mut self, kernel_image_path: &str, initrd_path: Option<&str>, boot_args: &str, ) -> Result<()> { #[derive(Serialize)] struct BootSource { kernel_image_path: String, #[serde(skip_serializing_if = "Option::is_none")] initrd_path: Option, boot_args: String, } let boot_source = BootSource { kernel_image_path: kernel_image_path.to_string(), initrd_path: initrd_path.map(|s| s.to_string()), boot_args: boot_args.to_string(), }; let body = serde_json::to_vec(&boot_source) .map_err(|e| Error::HypervisorError(format!("Failed to serialize boot source: {e}")))?; self.request("PUT", "/boot-source", Some(&body)) .await?; Ok(()) } /// PUT /drives/{drive_id} pub async fn put_drive( &mut self, drive_id: &str, path_on_host: &str, is_root_device: bool, is_read_only: bool, ) -> Result<()> { #[derive(Serialize)] struct Drive { path_on_host: String, is_root_device: bool, is_read_only: bool, } let drive = Drive { path_on_host: path_on_host.to_string(), is_root_device, is_read_only, }; let body = serde_json::to_vec(&drive) .map_err(|e| Error::HypervisorError(format!("Failed to serialize drive: {e}")))?; self.request("PUT", &format!("/drives/{}", drive_id), Some(&body)) .await?; Ok(()) } /// PUT /network-interfaces/{iface_id} pub async fn put_network_interface( &mut self, iface_id: &str, guest_mac: &str, host_dev_name: &str, ) -> Result<()> { #[derive(Serialize)] struct NetworkInterface { guest_mac: String, host_dev_name: String, } let iface = NetworkInterface { guest_mac: guest_mac.to_string(), host_dev_name: host_dev_name.to_string(), }; let body = serde_json::to_vec(&iface) .map_err(|e| Error::HypervisorError(format!("Failed to serialize network interface: {e}")))?; self.request( "PUT", &format!("/network-interfaces/{}", iface_id), Some(&body), ) .await?; Ok(()) } /// PUT /actions pub async fn instance_start(&mut self) -> Result<()> { #[derive(Serialize)] struct Action { action_type: String, } let action = Action { action_type: "InstanceStart".to_string(), }; let body = serde_json::to_vec(&action) .map_err(|e| Error::HypervisorError(format!("Failed to serialize action: {e}")))?; self.request("PUT", "/actions", Some(&body)) .await?; Ok(()) } /// PUT /actions (SendCtrlAltDel) pub async fn send_ctrl_alt_del(&mut self) -> Result<()> { #[derive(Serialize)] struct Action { action_type: String, } let action = Action { action_type: "SendCtrlAltDel".to_string(), }; let body = serde_json::to_vec(&action) .map_err(|e| Error::HypervisorError(format!("Failed to serialize action: {e}")))?; self.request("PUT", "/actions", Some(&body)) .await?; Ok(()) } /// GET /vm pub async fn get_vm_info(&mut self) -> Result { let body = self.request("GET", "/vm", None).await?; let info: VmInfo = serde_json::from_slice(&body) .map_err(|e| Error::HypervisorError(format!("Failed to parse VM info: {e}")))?; Ok(info) } } #[derive(Debug, Deserialize)] pub struct VmInfo { #[serde(default)] #[allow(dead_code)] pub state: String, }