use crate::error::Error;
use crate::record::{self, *};
use bufstream::BufStream;
#[cfg(test)]
use std::collections::VecDeque;
use std::io::{self, Read, Write};
use std::net::TcpStream;
#[derive(Debug)]
pub enum Connection {
Tcp(BufStream<TcpStream>),
#[cfg(test)]
Test(VecDeque<u8>),
}
impl Write for Connection {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
Connection::Tcp(w) => w.write(buf),
#[cfg(test)]
Connection::Test(w) => w.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
Connection::Tcp(w) => w.flush(),
#[cfg(test)]
Connection::Test(w) => w.flush(),
}
}
}
impl Read for Connection {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self {
Connection::Tcp(r) => r.read(buf),
#[cfg(test)]
Connection::Test(r) => r.read(buf),
}
}
}
impl TryFrom<mio::net::TcpStream> for Connection {
type Error = io::Error;
fn try_from(value: mio::net::TcpStream) -> Result<Self, Self::Error> {
let stream = TcpStream::from(value);
stream.set_nonblocking(false)?;
let timeout = std::time::Duration::from_secs(3);
stream.set_read_timeout(Some(timeout))?;
Ok(Connection::Tcp(BufStream::new(stream)))
}
}
#[derive(Debug, Clone)]
pub struct Packet {
pub type_id: u8,
pub content: Vec<u8>,
}
impl Packet {
fn is_discrete(&self) -> bool {
record::DISCRETE_RECORD_TYPES.contains(&self.type_id)
}
fn is_management_record(&self) -> bool {
record::MANAGEMENT_RECORD_TYPES.contains(&self.type_id)
}
fn is_empty(&self) -> bool {
self.content.is_empty()
}
}
impl Connection {
pub fn read_packet(&mut self) -> Result<Packet, Error> {
let mut header = [0u8; 8];
self.read_exact(&mut header)
.map_err(Error::UnexpectedSocketClose)?;
let [version, type_id, req_id_1, req_id_0, length_1, length_0, padding_length, _] = header;
if version != 1 {
return Err(Error::UnsuportedVersion(version));
}
let req_id = u16::from_be_bytes([req_id_1, req_id_0]);
if req_id > 1 {
return Err(Error::MultiplexingUnsupported);
}
let length = u16::from_be_bytes([length_1, length_0]);
let mut content = vec![0u8; length as usize];
let mut padding = vec![0u8; padding_length as usize];
self.read_exact(&mut content)
.map_err(Error::UnexpectedSocketClose)?;
self.read_exact(&mut padding)
.map_err(Error::UnexpectedSocketClose)?;
Ok(Packet { type_id, content })
}
pub fn write_packet(&mut self, packet: &Packet) -> Result<(), io::Error> {
let payload = &packet.content;
let unpadded_len = 8 + payload.len();
let padded_len = unpadded_len.div_ceil(8) * 8;
let padding = (padded_len - unpadded_len) as u8;
let request_id = if packet.is_management_record() {
[0, 0]
} else {
[0, 1]
};
self.write_all(&[1, packet.type_id])?;
self.write_all(&request_id)?;
self.write_all(&(payload.len() as u16).to_be_bytes())?;
self.write_all(&[padding, 0])?;
self.write_all(payload)?;
self.write_all(&vec![0u8; padding as usize])?;
self.flush()
}
pub fn read_record(&mut self) -> Result<Record, Error> {
let first = self.read_packet()?;
let expected_type_id = first.type_id;
if first.is_discrete() || first.is_empty() {
let record = Record::from_bytes(expected_type_id, first.content)?;
return Ok(record);
}
let mut packets = vec![first];
loop {
let packet = self.read_packet()?;
if packet.type_id != expected_type_id {
return Err(Error::MalformedRecordStream);
}
if packet.is_empty() {
break;
}
packets.push(packet);
}
let content = packets
.into_iter()
.flat_map(|r| r.content)
.collect::<Vec<_>>();
let record = Record::from_bytes(expected_type_id, content)?;
Ok(record)
}
pub fn write_record(&mut self, record: &Record) -> Result<(), io::Error> {
let mut payload = vec![];
record.write_bytes(&mut payload)?;
let mut payload_chunks: Vec<Vec<_>> = payload
.chunks(u16::MAX as usize)
.map(<[u8]>::to_vec)
.collect();
payload_chunks.push(vec![]);
for chunk in payload_chunks {
let packet = Packet {
type_id: record.type_id(),
content: chunk,
};
self.write_packet(&packet)?;
if packet.is_discrete() {
break;
}
}
Ok(())
}
impl_expect!(GetValues);
impl_expect!(GetValuesResult);
impl_expect!(UnknownType);
impl_expect!(BeginRequest);
impl_expect!(EndRequest);
impl_expect!(Params);
impl_expect!(AbortRequest);
impl_expect!(Stdin);
impl_expect!(Stdout);
impl_expect!(Stderr);
impl_expect!(Data);
}
macro_rules! impl_expect {
($t:path) => {
paste::paste! {
#[doc =
"Returns the next record if it is a [`" $t "`](crate::record::" $t ") record.\n\n"
"# Errors\n\n"
"Returns `Err(Some(Error))` if reading the connection failed.\n\n"
"Returns `Err(None)` if the next record was something else"
]
pub fn [<expect_ $t:snake>](&mut self) -> Result<$t, Option<Error>> {
match self.read_record() {
Ok(Record::$t(r)) => Ok(r),
Ok(_) => Err(None),
Err(e) => Err(Some(e))
}
}
}
};
}
pub(crate) use impl_expect;
#[cfg(test)]
mod round_trip_tests {
use super::*;
#[track_caller]
fn round_trip(send: impl Into<Record>) {
let mut connection = Connection::Test(VecDeque::new());
let record = send.into();
connection.write_record(&record).unwrap();
let received = connection.read_record().unwrap();
assert_eq!(received, record);
}
#[test]
fn get_values() {
round_trip(GetValues::default());
round_trip(GetValues::default().add("FCGI_MAX_CONNS"));
}
#[test]
fn get_values_result() {
round_trip(GetValuesResult::default());
round_trip(GetValuesResult::default().add("FCGI_MAX_REQS", "1"));
}
#[test]
fn unknown_type() {
round_trip(UnknownType(100));
}
#[test]
fn begin_request() {
round_trip(BeginRequest::new(Role::Responder, true));
}
#[test]
fn params() {
round_trip(Params::default());
round_trip(Params::default().add("PATH", "/home"));
let long_key = "a".repeat(256);
let long_value = "b".repeat(256);
round_trip(Params::default().add(long_key, long_value));
}
#[test]
fn stdin() {
round_trip(Stdin(vec![]));
round_trip(Stdin(b"HELLO".into()));
}
#[test]
fn stdout() {
round_trip(Stdout(vec![]));
round_trip(Stdout(b"HELLO".into()));
}
#[test]
fn stderr() {
round_trip(Stderr(vec![]));
round_trip(Stderr(b"HELLO".into()));
}
#[test]
fn data() {
round_trip(Data(vec![]));
round_trip(Data(b"HELLO".into()));
}
#[test]
fn abort_request() {
round_trip(AbortRequest);
}
#[test]
fn end_request() {
round_trip(EndRequest::new(0, ProtocolStatus::RequestComplete));
round_trip(EndRequest::new(1, ProtocolStatus::UnknownRole));
}
}
#[cfg(test)]
mod stream_parsing_tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn stream_packet_are_concatenated_when_read() {
let mut connection = Connection::Test(VecDeque::new());
let packets = [
Packet {
type_id: record::FCGI_STDOUT,
content: b"HEL".to_vec(),
},
Packet {
type_id: record::FCGI_STDOUT,
content: b"LO".to_vec(),
},
Packet {
type_id: record::FCGI_STDOUT,
content: b"WORLD".to_vec(),
},
Packet {
type_id: record::FCGI_STDOUT,
content: vec![],
},
];
for packet in packets {
connection.write_packet(&packet).unwrap();
}
let actual = connection.read_record().unwrap();
let expected = Record::from(Stdout(b"HELLOWORLD".to_vec()));
assert_eq!(actual, expected);
}
#[test]
fn stream_packets_are_broken_up_when_written() {
let mut connection = Connection::Test(VecDeque::new());
let payload_length = u16::MAX as usize * 5;
let payload = b"A".repeat(payload_length);
let record = Record::from(Stdout(payload.clone()));
connection.write_record(&record).unwrap();
let result = connection.read_record();
assert_matches!(result, Ok(_));
let result = result.unwrap();
assert_eq!(result, Record::from(Stdout(payload)));
}
}