use std::{
fmt::Write as FmtWrite,
io::Write as IoWrite,
net::{TcpStream, ToSocketAddrs},
str,
sync::Mutex,
time::{Duration, Instant},
};
use uuid::Uuid;
use super::{status::CONNECTOR_STATUS_OK, ConnectorStatus, HttpConfig, ResponseReader, USER_AGENT};
use adapter::{
Adapter, AdapterError,
AdapterErrorKind::{AddrInvalid, ConnectionFailed, ResponseError},
};
use serial_number::SerialNumber;
pub struct HttpAdapter {
host: String,
socket: Mutex<TcpStream>,
}
impl Adapter for HttpAdapter {
type Config = HttpConfig;
fn open(config: &Self::Config) -> Result<Self, AdapterError> {
let host = format!("{}:{}", config.addr, config.port);
let timeout = Duration::from_millis(config.timeout_ms);
let socketaddr = &host.to_socket_addrs()?.next().ok_or_else(|| {
err!(
AddrInvalid,
"couldn't resolve DNS for {}",
host.split(':').next().unwrap()
)
})?;
let socket = TcpStream::connect_timeout(socketaddr, timeout)?;
socket.set_read_timeout(Some(timeout))?;
socket.set_write_timeout(Some(timeout))?;
Ok(Self {
host,
socket: Mutex::new(socket),
})
}
fn healthcheck(&self) -> Result<(), AdapterError> {
let status = self.status()?;
if status.message == CONNECTOR_STATUS_OK {
Ok(())
} else {
fail!(
ConnectionFailed,
"bad status message from yubihsm-connector: {}",
&status.message
);
}
}
fn serial_number(&self) -> Result<SerialNumber, AdapterError> {
self.status()?.serial_number.ok_or_else(|| {
err!(
ResponseError,
"no serial available. Launch yubihsm-connector with the --serial option"
)
})
}
fn send_message(&self, uuid: Uuid, cmd: Vec<u8>) -> Result<Vec<u8>, AdapterError> {
self.post("/connector/api", uuid, cmd)
}
}
#[allow(unknown_lints, renamed_and_removed_lints, write_with_newline)]
impl HttpAdapter {
pub fn status(&self) -> Result<ConnectorStatus, AdapterError> {
let http_response = self.get("/connector/status")?;
ConnectorStatus::parse(str::from_utf8(&http_response)?)
}
fn get(&self, path: &str) -> Result<Vec<u8>, AdapterError> {
let mut request = String::new();
write!(request, "GET {} HTTP/1.1\r\n", path)?;
write!(request, "Host: {}\r\n", self.host)?;
write!(request, "User-Agent: {}\r\n", USER_AGENT)?;
write!(request, "Content-Length: 0\r\n\r\n")?;
let mut socket = self.socket.lock().unwrap();
let request_start = Instant::now();
socket.write_all(request.as_bytes())?;
let response = ResponseReader::read(&mut socket)?;
let elapsed_time = Instant::now().duration_since(request_start);
http_debug!(
self,
"method=GET path={} t={}ms",
path,
elapsed_time.as_secs() * 1000 + u64::from(elapsed_time.subsec_millis())
);
Ok(response.into())
}
fn post(&self, path: &str, uuid: Uuid, mut body: Vec<u8>) -> Result<Vec<u8>, AdapterError> {
let mut headers = String::new();
write!(headers, "POST {} HTTP/1.1\r\n", path)?;
write!(headers, "Host: {}\r\n", self.host)?;
write!(headers, "User-Agent: {}\r\n", USER_AGENT)?;
write!(headers, "X-Request-ID: {}\r\n", uuid)?;
write!(headers, "Content-Length: {}\r\n\r\n", body.len())?;
let mut request: Vec<u8> = headers.into();
request.append(&mut body);
let mut socket = self.socket.lock().unwrap();
let request_start = Instant::now();
socket.write_all(&request)?;
let response = ResponseReader::read(&mut socket)?;
let elapsed_time = Instant::now().duration_since(request_start);
http_debug!(
self,
"method=POST path={} uuid={} t={}ms",
path,
uuid,
elapsed_time.as_secs() * 1000 + u64::from(elapsed_time.subsec_millis())
);
Ok(response.into())
}
}