18de3bd72SRob Bradford // Copyright © 2020 Intel Corporation
28de3bd72SRob Bradford //
38de3bd72SRob Bradford // SPDX-License-Identifier: Apache-2.0
48de3bd72SRob Bradford //
58de3bd72SRob Bradford
68de3bd72SRob Bradford use std::io::{Read, Write};
79af2968aSSebastien Boeuf use std::os::unix::io::RawFd;
888a9f799SRob Bradford
95d0d56f5SSamrutGadde use thiserror::Error;
109af2968aSSebastien Boeuf use vmm_sys_util::sock_ctrl_msg::ScmSocket;
118de3bd72SRob Bradford
125d0d56f5SSamrutGadde #[derive(Debug, Error)]
138de3bd72SRob Bradford pub enum Error {
143f3489e3SPhilipp Schuster #[error("Error writing to or reading from HTTP socket")]
1538380198SPhilipp Schuster Socket(#[source] std::io::Error),
163f3489e3SPhilipp Schuster #[error("Error sending file descriptors")]
1738380198SPhilipp Schuster SocketSendFds(#[source] vmm_sys_util::errno::Error),
183f3489e3SPhilipp Schuster #[error("Error parsing HTTP status code")]
1938380198SPhilipp Schuster StatusCodeParsing(#[source] std::num::ParseIntError),
205d0d56f5SSamrutGadde #[error("HTTP output is missing protocol statement")]
218de3bd72SRob Bradford MissingProtocol,
223f3489e3SPhilipp Schuster #[error("Error parsing HTTP Content-Length field")]
2338380198SPhilipp Schuster ContentLengthParsing(#[source] std::num::ParseIntError),
24*190a11f2SPhilipp Schuster #[error("Server responded with error {0:?}: {1:?}")]
25*190a11f2SPhilipp Schuster ServerResponse(
26*190a11f2SPhilipp Schuster StatusCode,
27*190a11f2SPhilipp Schuster // TODO: Move `api` module from `vmm` to dedicated crate and use a common type definition
28*190a11f2SPhilipp Schuster Option<
29*190a11f2SPhilipp Schuster String, /* Untyped: Currently Vec<String> of error messages from top to root cause */
30*190a11f2SPhilipp Schuster >,
31*190a11f2SPhilipp Schuster ),
328de3bd72SRob Bradford }
338de3bd72SRob Bradford
348de3bd72SRob Bradford #[derive(Clone, Copy, Debug)]
358de3bd72SRob Bradford pub enum StatusCode {
368de3bd72SRob Bradford Continue,
37030d6046SRob Bradford Ok,
388de3bd72SRob Bradford NoContent,
398de3bd72SRob Bradford BadRequest,
408de3bd72SRob Bradford NotFound,
411968805bSFabiano Fidêncio TooManyRequests,
428de3bd72SRob Bradford InternalServerError,
438de3bd72SRob Bradford NotImplemented,
448de3bd72SRob Bradford Unknown,
458de3bd72SRob Bradford }
468de3bd72SRob Bradford
478de3bd72SRob Bradford impl StatusCode {
from_raw(code: usize) -> StatusCode488de3bd72SRob Bradford fn from_raw(code: usize) -> StatusCode {
498de3bd72SRob Bradford match code {
508de3bd72SRob Bradford 100 => StatusCode::Continue,
51030d6046SRob Bradford 200 => StatusCode::Ok,
528de3bd72SRob Bradford 204 => StatusCode::NoContent,
538de3bd72SRob Bradford 400 => StatusCode::BadRequest,
548de3bd72SRob Bradford 404 => StatusCode::NotFound,
551968805bSFabiano Fidêncio 429 => StatusCode::TooManyRequests,
568de3bd72SRob Bradford 500 => StatusCode::InternalServerError,
578de3bd72SRob Bradford 501 => StatusCode::NotImplemented,
588de3bd72SRob Bradford _ => StatusCode::Unknown,
598de3bd72SRob Bradford }
608de3bd72SRob Bradford }
618de3bd72SRob Bradford
parse(code: &str) -> Result<StatusCode, Error>628de3bd72SRob Bradford fn parse(code: &str) -> Result<StatusCode, Error> {
638de3bd72SRob Bradford Ok(StatusCode::from_raw(
648de3bd72SRob Bradford code.trim().parse().map_err(Error::StatusCodeParsing)?,
658de3bd72SRob Bradford ))
668de3bd72SRob Bradford }
678de3bd72SRob Bradford
is_server_error(self) -> bool688de3bd72SRob Bradford fn is_server_error(self) -> bool {
698de3bd72SRob Bradford !matches!(
708de3bd72SRob Bradford self,
71030d6046SRob Bradford StatusCode::Ok | StatusCode::Continue | StatusCode::NoContent
728de3bd72SRob Bradford )
738de3bd72SRob Bradford }
748de3bd72SRob Bradford }
758de3bd72SRob Bradford
get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str>768de3bd72SRob Bradford fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
775e527294SRob Bradford let header_str = format!("{header}: ");
787a18e247SGaelan Steele res.find(&header_str)
797a18e247SGaelan Steele .map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
808de3bd72SRob Bradford }
818de3bd72SRob Bradford
get_status_code(res: &str) -> Result<StatusCode, Error>828de3bd72SRob Bradford fn get_status_code(res: &str) -> Result<StatusCode, Error> {
838de3bd72SRob Bradford if let Some(o) = res.find("HTTP/1.1") {
848de3bd72SRob Bradford Ok(StatusCode::parse(
858de3bd72SRob Bradford &res[o + "HTTP/1.1 ".len()..res[o..].find('\r').unwrap()],
868de3bd72SRob Bradford )?)
878de3bd72SRob Bradford } else {
888de3bd72SRob Bradford Err(Error::MissingProtocol)
898de3bd72SRob Bradford }
908de3bd72SRob Bradford }
918de3bd72SRob Bradford
parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error>928de3bd72SRob Bradford fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
938de3bd72SRob Bradford let mut res = String::new();
948de3bd72SRob Bradford let mut body_offset = None;
958de3bd72SRob Bradford let mut content_length: Option<usize> = None;
968de3bd72SRob Bradford loop {
978de3bd72SRob Bradford let mut bytes = vec![0; 256];
988de3bd72SRob Bradford let count = socket.read(&mut bytes).map_err(Error::Socket)?;
99686e6d50SMaximilian Nitsch // If the return value is 0, the peer has performed an orderly shutdown.
100686e6d50SMaximilian Nitsch if count == 0 {
101686e6d50SMaximilian Nitsch break;
102686e6d50SMaximilian Nitsch }
1038de3bd72SRob Bradford res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
1048de3bd72SRob Bradford
1058de3bd72SRob Bradford // End of headers
1068de3bd72SRob Bradford if let Some(o) = res.find("\r\n\r\n") {
1078de3bd72SRob Bradford body_offset = Some(o + "\r\n\r\n".len());
1088de3bd72SRob Bradford
1098de3bd72SRob Bradford // With all headers available we can see if there is any body
1108de3bd72SRob Bradford content_length = if let Some(length) = get_header(&res, "Content-Length") {
1118de3bd72SRob Bradford Some(length.trim().parse().map_err(Error::ContentLengthParsing)?)
1128de3bd72SRob Bradford } else {
1138de3bd72SRob Bradford None
1148de3bd72SRob Bradford };
1158de3bd72SRob Bradford
1168de3bd72SRob Bradford if content_length.is_none() {
1178de3bd72SRob Bradford break;
1188de3bd72SRob Bradford }
1198de3bd72SRob Bradford }
1208de3bd72SRob Bradford
1218de3bd72SRob Bradford if let Some(body_offset) = body_offset {
1228de3bd72SRob Bradford if let Some(content_length) = content_length {
1238de3bd72SRob Bradford if res.len() >= content_length + body_offset {
1248de3bd72SRob Bradford break;
1258de3bd72SRob Bradford }
1268de3bd72SRob Bradford }
1278de3bd72SRob Bradford }
1288de3bd72SRob Bradford }
12987c0791dSMaximilian Nitsch let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
1308de3bd72SRob Bradford let status_code = get_status_code(&res)?;
1318de3bd72SRob Bradford
1328de3bd72SRob Bradford if status_code.is_server_error() {
1338de3bd72SRob Bradford Err(Error::ServerResponse(status_code, body_string))
1348de3bd72SRob Bradford } else {
1358de3bd72SRob Bradford Ok(body_string)
1368de3bd72SRob Bradford }
1378de3bd72SRob Bradford }
1388de3bd72SRob Bradford
139eea9bceaSJames O. D. Hunt /// Make an API request using the fully qualified command name.
140eea9bceaSJames O. D. Hunt /// For example, full_command could be "vm.create" or "vmm.ping".
simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, full_command: &str, request_body: Option<&str>, request_fds: Vec<RawFd>, ) -> Result<Option<String>, Error>141cd0208feSJames O. D. Hunt pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket>(
1428de3bd72SRob Bradford socket: &mut T,
1438de3bd72SRob Bradford method: &str,
144eea9bceaSJames O. D. Hunt full_command: &str,
1458de3bd72SRob Bradford request_body: Option<&str>,
1469af2968aSSebastien Boeuf request_fds: Vec<RawFd>,
147cd0208feSJames O. D. Hunt ) -> Result<Option<String>, Error> {
1488de3bd72SRob Bradford socket
1499af2968aSSebastien Boeuf .send_with_fds(
1509af2968aSSebastien Boeuf &[format!(
1515e527294SRob Bradford "{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
1528de3bd72SRob Bradford )
1539af2968aSSebastien Boeuf .as_bytes()],
1549af2968aSSebastien Boeuf &request_fds,
1558de3bd72SRob Bradford )
1569af2968aSSebastien Boeuf .map_err(Error::SocketSendFds)?;
1578de3bd72SRob Bradford
1588de3bd72SRob Bradford if let Some(request_body) = request_body {
1598de3bd72SRob Bradford socket
1608de3bd72SRob Bradford .write_all(format!("Content-Length: {}\r\n", request_body.len()).as_bytes())
1618de3bd72SRob Bradford .map_err(Error::Socket)?;
1628de3bd72SRob Bradford }
1638de3bd72SRob Bradford
1648de3bd72SRob Bradford socket.write_all(b"\r\n").map_err(Error::Socket)?;
1658de3bd72SRob Bradford
1668de3bd72SRob Bradford if let Some(request_body) = request_body {
1678de3bd72SRob Bradford socket
1688de3bd72SRob Bradford .write_all(request_body.as_bytes())
1698de3bd72SRob Bradford .map_err(Error::Socket)?;
1708de3bd72SRob Bradford }
1718de3bd72SRob Bradford
1728de3bd72SRob Bradford socket.flush().map_err(Error::Socket)?;
1738de3bd72SRob Bradford
174cd0208feSJames O. D. Hunt parse_http_response(socket)
1758de3bd72SRob Bradford }
176cd0208feSJames O. D. Hunt
simple_api_full_command_with_fds<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, full_command: &str, request_body: Option<&str>, request_fds: Vec<RawFd>, ) -> Result<(), Error>177cd0208feSJames O. D. Hunt pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
178cd0208feSJames O. D. Hunt socket: &mut T,
179cd0208feSJames O. D. Hunt method: &str,
180cd0208feSJames O. D. Hunt full_command: &str,
181cd0208feSJames O. D. Hunt request_body: Option<&str>,
182cd0208feSJames O. D. Hunt request_fds: Vec<RawFd>,
183cd0208feSJames O. D. Hunt ) -> Result<(), Error> {
184cd0208feSJames O. D. Hunt let response = simple_api_full_command_with_fds_and_response(
185cd0208feSJames O. D. Hunt socket,
186cd0208feSJames O. D. Hunt method,
187cd0208feSJames O. D. Hunt full_command,
188cd0208feSJames O. D. Hunt request_body,
189cd0208feSJames O. D. Hunt request_fds,
190cd0208feSJames O. D. Hunt )?;
191cd0208feSJames O. D. Hunt
192cd0208feSJames O. D. Hunt if response.is_some() {
193cd0208feSJames O. D. Hunt println!("{}", response.unwrap());
194cd0208feSJames O. D. Hunt }
195cd0208feSJames O. D. Hunt
1968de3bd72SRob Bradford Ok(())
1978de3bd72SRob Bradford }
1989af2968aSSebastien Boeuf
simple_api_full_command<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, full_command: &str, request_body: Option<&str>, ) -> Result<(), Error>199eea9bceaSJames O. D. Hunt pub fn simple_api_full_command<T: Read + Write + ScmSocket>(
200eea9bceaSJames O. D. Hunt socket: &mut T,
201eea9bceaSJames O. D. Hunt method: &str,
202eea9bceaSJames O. D. Hunt full_command: &str,
203eea9bceaSJames O. D. Hunt request_body: Option<&str>,
204eea9bceaSJames O. D. Hunt ) -> Result<(), Error> {
205eea9bceaSJames O. D. Hunt simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new())
206eea9bceaSJames O. D. Hunt }
207eea9bceaSJames O. D. Hunt
simple_api_full_command_and_response<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, full_command: &str, request_body: Option<&str>, ) -> Result<Option<String>, Error>208cd0208feSJames O. D. Hunt pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
209cd0208feSJames O. D. Hunt socket: &mut T,
210cd0208feSJames O. D. Hunt method: &str,
211cd0208feSJames O. D. Hunt full_command: &str,
212cd0208feSJames O. D. Hunt request_body: Option<&str>,
213cd0208feSJames O. D. Hunt ) -> Result<Option<String>, Error> {
214cd0208feSJames O. D. Hunt simple_api_full_command_with_fds_and_response(
215cd0208feSJames O. D. Hunt socket,
216cd0208feSJames O. D. Hunt method,
217cd0208feSJames O. D. Hunt full_command,
218cd0208feSJames O. D. Hunt request_body,
219cd0208feSJames O. D. Hunt Vec::new(),
220cd0208feSJames O. D. Hunt )
221cd0208feSJames O. D. Hunt }
222cd0208feSJames O. D. Hunt
simple_api_command_with_fds<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, c: &str, request_body: Option<&str>, request_fds: Vec<RawFd>, ) -> Result<(), Error>223eea9bceaSJames O. D. Hunt pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
224eea9bceaSJames O. D. Hunt socket: &mut T,
225eea9bceaSJames O. D. Hunt method: &str,
226eea9bceaSJames O. D. Hunt c: &str,
227eea9bceaSJames O. D. Hunt request_body: Option<&str>,
228eea9bceaSJames O. D. Hunt request_fds: Vec<RawFd>,
229eea9bceaSJames O. D. Hunt ) -> Result<(), Error> {
230eea9bceaSJames O. D. Hunt // Create the full VM command. For VMM commands, use
231eea9bceaSJames O. D. Hunt // simple_api_full_command().
2325e527294SRob Bradford let full_command = format!("vm.{c}");
233eea9bceaSJames O. D. Hunt
234eea9bceaSJames O. D. Hunt simple_api_full_command_with_fds(socket, method, &full_command, request_body, request_fds)
235eea9bceaSJames O. D. Hunt }
236eea9bceaSJames O. D. Hunt
simple_api_command<T: Read + Write + ScmSocket>( socket: &mut T, method: &str, c: &str, request_body: Option<&str>, ) -> Result<(), Error>2379af2968aSSebastien Boeuf pub fn simple_api_command<T: Read + Write + ScmSocket>(
2389af2968aSSebastien Boeuf socket: &mut T,
2399af2968aSSebastien Boeuf method: &str,
2409af2968aSSebastien Boeuf c: &str,
2419af2968aSSebastien Boeuf request_body: Option<&str>,
2429af2968aSSebastien Boeuf ) -> Result<(), Error> {
2439af2968aSSebastien Boeuf simple_api_command_with_fds(socket, method, c, request_body, Vec::new())
2449af2968aSSebastien Boeuf }
245