fcnetd_client/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3#[cfg(feature = "connection-pool")]
4use std::marker::PhantomData;
5use std::path::Path;
6#[cfg(feature = "deadpool")]
7use std::path::PathBuf;
8
9use fcnet_types::{FirecrackerNetwork, FirecrackerNetworkOperation};
10use serde::Serialize;
11use socket::Socket;
12
13const OK_RESPONSE: &str = "OK";
14
15pub mod socket;
16
17#[derive(Debug)]
18pub enum FcnetdError {
19    RequestWriteError(std::io::Error),
20    RequestSerializeError(serde_json::Error),
21    ResponseReadError(std::io::Error),
22    ConnectionClosed,
23    OperationFailed(String),
24}
25
26impl std::error::Error for FcnetdError {}
27
28impl std::fmt::Display for FcnetdError {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            FcnetdError::RequestWriteError(err) => write!(f, "Writing the request to the socket failed: {err}"),
32            FcnetdError::RequestSerializeError(err) => write!(f, "Serializing the request to JSON failed: {err}"),
33            FcnetdError::ResponseReadError(err) => write!(f, "Reading the response from the connection failed: {err}"),
34            FcnetdError::ConnectionClosed => write!(f, "The connection was closed before a response could be received"),
35            FcnetdError::OperationFailed(detail) => {
36                write!(f, "The daemon returned a failure of the requested operation: {detail}")
37            }
38        }
39    }
40}
41
42#[derive(Debug)]
43pub struct FcnetdConnection<S: Socket>(S);
44
45#[derive(Serialize)]
46struct Request<'net> {
47    operation: FirecrackerNetworkOperation,
48    network: &'net FirecrackerNetwork,
49}
50
51#[derive(Debug)]
52#[cfg(feature = "connection-pool")]
53#[cfg_attr(docsrs, doc(cfg(feature = "connection-pool")))]
54pub struct FcnetdConnectionPool<S: Socket> {
55    path: PathBuf,
56    password: Option<String>,
57    phantom: PhantomData<S>,
58}
59
60#[cfg(feature = "connection-pool")]
61#[cfg_attr(docsrs, doc(cfg(feature = "connection-pool")))]
62impl<S: Socket> FcnetdConnectionPool<S> {
63    pub fn new(path: impl Into<PathBuf>) -> Self {
64        Self {
65            path: path.into(),
66            password: None,
67            phantom: PhantomData,
68        }
69    }
70
71    pub fn new_with_password(path: impl Into<PathBuf>, password: impl Into<String>) -> Self {
72        Self {
73            path: path.into(),
74            password: Some(password.into()),
75            phantom: PhantomData,
76        }
77    }
78}
79
80#[cfg(feature = "deadpool")]
81#[cfg_attr(docsrs, doc(cfg(feature = "deadpool")))]
82impl<S: Socket> deadpool::managed::Manager for FcnetdConnectionPool<S> {
83    type Type = FcnetdConnection<S>;
84
85    type Error = std::io::Error;
86
87    async fn create(&self) -> Result<Self::Type, Self::Error> {
88        match self.password {
89            Some(ref password) => FcnetdConnection::connect_with_password(&self.path, password).await,
90            None => FcnetdConnection::connect(&self.path).await,
91        }
92    }
93
94    async fn recycle(
95        &self,
96        _obj: &mut Self::Type,
97        _metrics: &deadpool::managed::Metrics,
98    ) -> deadpool::managed::RecycleResult<Self::Error> {
99        deadpool::managed::RecycleResult::Ok(())
100    }
101}
102
103impl<S: Socket> FcnetdConnection<S> {
104    pub async fn connect(path: impl AsRef<Path>) -> Result<Self, std::io::Error> {
105        let socket = S::connect(path.as_ref()).await?;
106        Ok(Self(socket))
107    }
108
109    pub async fn connect_with_password(path: impl AsRef<Path>, password: impl Into<String>) -> Result<Self, std::io::Error> {
110        let password = password.into();
111        let mut socket = S::connect(path.as_ref()).await?;
112        socket.write_line(password).await?;
113        Ok(Self(socket))
114    }
115
116    pub async fn run(&mut self, network: &FirecrackerNetwork, operation: FirecrackerNetworkOperation) -> Result<(), FcnetdError> {
117        let request = Request { operation, network };
118        let request_json = serde_json::to_string(&request).map_err(FcnetdError::RequestSerializeError)?;
119        self.0
120            .write_line(request_json)
121            .await
122            .map_err(FcnetdError::RequestWriteError)?;
123
124        let response = match self.0.read_line().await {
125            Ok(Some(response)) => response,
126            Ok(None) => return Err(FcnetdError::ConnectionClosed),
127            Err(err) => return Err(FcnetdError::ResponseReadError(err)),
128        };
129
130        if response != OK_RESPONSE {
131            return Err(FcnetdError::OperationFailed(response));
132        }
133
134        Ok(())
135    }
136}