#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(feature = "connection-pool")]
use std::marker::PhantomData;
use std::path::Path;
#[cfg(feature = "deadpool")]
use std::path::PathBuf;
use fcnet_types::{FirecrackerNetwork, FirecrackerNetworkOperation};
use serde::Serialize;
use socket::Socket;
const OK_RESPONSE: &str = "OK";
pub mod socket;
#[derive(Debug)]
pub enum FcnetdError {
RequestWriteError(std::io::Error),
RequestSerializeError(serde_json::Error),
ResponseReadError(std::io::Error),
ConnectionClosed,
OperationFailed(String),
}
impl std::error::Error for FcnetdError {}
impl std::fmt::Display for FcnetdError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FcnetdError::RequestWriteError(err) => write!(f, "Writing the request to the socket failed: {err}"),
FcnetdError::RequestSerializeError(err) => write!(f, "Serializing the request to JSON failed: {err}"),
FcnetdError::ResponseReadError(err) => write!(f, "Reading the response from the connection failed: {err}"),
FcnetdError::ConnectionClosed => write!(f, "The connection was closed before a response could be received"),
FcnetdError::OperationFailed(detail) => {
write!(f, "The daemon returned a failure of the requested operation: {detail}")
}
}
}
}
#[derive(Debug)]
pub struct FcnetdConnection<S: Socket>(S);
#[derive(Serialize)]
struct Request<'net> {
operation: FirecrackerNetworkOperation,
network: &'net FirecrackerNetwork,
}
#[derive(Debug)]
#[cfg(feature = "connection-pool")]
#[cfg_attr(docsrs, doc(cfg(feature = "connection-pool")))]
pub struct FcnetdConnectionPool<S: Socket> {
path: PathBuf,
password: Option<String>,
phantom: PhantomData<S>,
}
#[cfg(feature = "connection-pool")]
#[cfg_attr(docsrs, doc(cfg(feature = "connection-pool")))]
impl<S: Socket> FcnetdConnectionPool<S> {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into(),
password: None,
phantom: PhantomData,
}
}
pub fn new_with_password(path: impl Into<PathBuf>, password: impl Into<String>) -> Self {
Self {
path: path.into(),
password: Some(password.into()),
phantom: PhantomData,
}
}
}
#[cfg(feature = "deadpool")]
#[cfg_attr(docsrs, doc(cfg(feature = "deadpool")))]
impl<S: Socket> deadpool::managed::Manager for FcnetdConnectionPool<S> {
type Type = FcnetdConnection<S>;
type Error = std::io::Error;
async fn create(&self) -> Result<Self::Type, Self::Error> {
match self.password {
Some(ref password) => FcnetdConnection::connect_with_password(&self.path, password).await,
None => FcnetdConnection::connect(&self.path).await,
}
}
async fn recycle(
&self,
_obj: &mut Self::Type,
_metrics: &deadpool::managed::Metrics,
) -> deadpool::managed::RecycleResult<Self::Error> {
deadpool::managed::RecycleResult::Ok(())
}
}
impl<S: Socket> FcnetdConnection<S> {
pub async fn connect(path: impl AsRef<Path>) -> Result<Self, std::io::Error> {
let socket = S::connect(path.as_ref()).await?;
Ok(Self(socket))
}
pub async fn connect_with_password(path: impl AsRef<Path>, password: impl Into<String>) -> Result<Self, std::io::Error> {
let password = password.into();
let mut socket = S::connect(path.as_ref()).await?;
socket.write_line(password).await?;
Ok(Self(socket))
}
pub async fn run(&mut self, network: &FirecrackerNetwork, operation: FirecrackerNetworkOperation) -> Result<(), FcnetdError> {
let request = Request { operation, network };
let request_json = serde_json::to_string(&request).map_err(FcnetdError::RequestSerializeError)?;
self.0
.write_line(request_json)
.await
.map_err(FcnetdError::RequestWriteError)?;
let response = match self.0.read_line().await {
Ok(Some(response)) => response,
Ok(None) => return Err(FcnetdError::ConnectionClosed),
Err(err) => return Err(FcnetdError::ResponseReadError(err)),
};
if response != OK_RESPONSE {
return Err(FcnetdError::OperationFailed(response));
}
Ok(())
}
}