289 lines
6.6 KiB
Rust
289 lines
6.6 KiB
Rust
#![no_std]
|
|
pub mod common;
|
|
pub mod parse;
|
|
pub mod parts;
|
|
pub mod util;
|
|
pub mod write;
|
|
|
|
pub use common::*;
|
|
pub use parse::Parse;
|
|
pub use parts::*;
|
|
pub use util::*;
|
|
pub use write::{Write, WriteCursor, Written};
|
|
|
|
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
pub enum Event<'a> {
|
|
#[default]
|
|
Empty,
|
|
RequestLine(RequestLine<'a>),
|
|
Header(Header<'a>),
|
|
HeadersDone,
|
|
BodyChunk(&'a [u8]),
|
|
RecvDone,
|
|
StatusLine(StatusLine<'a>),
|
|
SendDone,
|
|
}
|
|
impl<'a> core::fmt::Display for Event<'a> {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
match self {
|
|
Event::Empty | Event::HeadersDone | Event::RecvDone | Event::SendDone => Ok(()),
|
|
Event::RequestLine(r) => write!(f, "{} {} {}", r.method, r.target, r.version),
|
|
Event::StatusLine(s) => {
|
|
write!(f, "{} {} {}", s.version, s.status_code, s.status_text)
|
|
},
|
|
Event::Header(h) => match h {
|
|
Header::Other(HeaderOther { name, value }) => {
|
|
write!(
|
|
f,
|
|
"{}: {}",
|
|
name,
|
|
core::str::from_utf8(value).unwrap_or("<binary>")
|
|
)
|
|
},
|
|
Header::Special(h) => match h {
|
|
HeaderSpecial::ContentLength(cl) => write!(f, "Content-Length: {cl}"),
|
|
HeaderSpecial::TransferEncodingChunked => {
|
|
write!(f, "Transfer-Encoding: chunked")
|
|
},
|
|
},
|
|
},
|
|
Event::BodyChunk(b) => {
|
|
write!(f, "{}", core::str::from_utf8(b).unwrap_or("<binary>"))
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub enum Role {
|
|
Client,
|
|
Server,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
enum BodyState {
|
|
ContentLength(usize),
|
|
Chunked,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
enum StateRecv {
|
|
StartLine,
|
|
Headers(Option<BodyState>),
|
|
Body(BodyState),
|
|
ValidateDone,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
enum StateSend {
|
|
StartLine,
|
|
Headers(Option<BodyState>),
|
|
Body(BodyState),
|
|
ValidateDone,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
enum StateConnection {
|
|
Recv(StateRecv),
|
|
Send(StateSend),
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct Connection {
|
|
role: Role,
|
|
state: StateConnection,
|
|
}
|
|
impl Connection {
|
|
pub fn new(role: Role) -> Self {
|
|
let state = match role {
|
|
Role::Server => StateConnection::Recv(StateRecv::StartLine),
|
|
Role::Client => StateConnection::Send(StateSend::StartLine),
|
|
};
|
|
|
|
Self { state, role }
|
|
}
|
|
|
|
pub fn is_sending(&self) -> bool {
|
|
matches!(self.state, StateConnection::Send(_))
|
|
}
|
|
|
|
pub fn is_recving(&self) -> bool {
|
|
matches!(self.state, StateConnection::Recv(_))
|
|
}
|
|
|
|
pub fn handle_recv<'a>(&mut self, bytes: &'a [u8]) -> Parse<'a, Event<'a>> {
|
|
let recv = match &self.state {
|
|
StateConnection::Recv(r) => r,
|
|
_ => {
|
|
return Err((bytes, ErrorKind::InvalidConnectionState.into()));
|
|
},
|
|
};
|
|
|
|
let n = match (self.role, recv) {
|
|
(Role::Server, StateRecv::StartLine) => parse::request_line(bytes).map2(|rl| {
|
|
(
|
|
Event::RequestLine(rl),
|
|
StateConnection::Recv(StateRecv::Headers(None)),
|
|
)
|
|
}),
|
|
|
|
(Role::Client, StateRecv::StartLine) => parse::status_line(bytes).map2(|rl| {
|
|
(
|
|
Event::StatusLine(rl),
|
|
StateConnection::Recv(StateRecv::Headers(None)),
|
|
)
|
|
}),
|
|
|
|
(_, StateRecv::Headers(body_state)) => {
|
|
if bytes.starts_with(b"\r\n") {
|
|
Ok((
|
|
&bytes[2..],
|
|
(
|
|
Event::HeadersDone,
|
|
match body_state {
|
|
None => StateConnection::Recv(StateRecv::ValidateDone),
|
|
Some(b) => StateConnection::Recv(StateRecv::Body(*b)),
|
|
},
|
|
),
|
|
))
|
|
} else {
|
|
parse::header(bytes).map2(|h| {
|
|
let b = match h {
|
|
Header::Special(HeaderSpecial::TransferEncodingChunked) => {
|
|
Some(BodyState::Chunked)
|
|
},
|
|
Header::Special(HeaderSpecial::ContentLength(c)) => {
|
|
Some(BodyState::ContentLength(c))
|
|
},
|
|
_ => *body_state,
|
|
};
|
|
|
|
(
|
|
Event::Header(h),
|
|
StateConnection::Recv(StateRecv::Headers(b)),
|
|
)
|
|
})
|
|
}
|
|
},
|
|
|
|
(_, StateRecv::Body(body_state)) => match body_state {
|
|
BodyState::ContentLength(remaining) => {
|
|
if bytes.len() < *remaining {
|
|
Ok((
|
|
&[] as &[u8],
|
|
(
|
|
Event::BodyChunk(bytes),
|
|
StateConnection::Recv(StateRecv::Body(
|
|
BodyState::ContentLength(remaining - bytes.len()),
|
|
)),
|
|
),
|
|
))
|
|
} else {
|
|
Ok((
|
|
&bytes[*remaining..],
|
|
(
|
|
Event::BodyChunk(&bytes[..*remaining]),
|
|
StateConnection::Recv(StateRecv::ValidateDone),
|
|
),
|
|
))
|
|
}
|
|
},
|
|
_ => todo!(),
|
|
},
|
|
|
|
(_, StateRecv::ValidateDone) => {
|
|
if bytes.is_empty() {
|
|
Ok((
|
|
bytes,
|
|
(Event::RecvDone, StateConnection::Send(StateSend::StartLine)),
|
|
))
|
|
} else {
|
|
fail(ErrorKind::TrailingBytes).tup(bytes)
|
|
}
|
|
},
|
|
};
|
|
|
|
n.map2(move |(ev, next_state)| {
|
|
self.state = next_state;
|
|
|
|
ev
|
|
})
|
|
}
|
|
|
|
pub fn handle_send(&mut self, event: &Event, mut w: impl Write) -> Written {
|
|
let state = match self.state {
|
|
StateConnection::Send(s) => s,
|
|
_ => return fail(ErrorKind::InvalidConnectionState),
|
|
};
|
|
|
|
let next = match (self.role, state, event) {
|
|
(Role::Server, StateSend::StartLine, Event::StatusLine(sl)) => {
|
|
write::status_line(sl, w)
|
|
.map(|_| StateConnection::Send(StateSend::Headers(None)))
|
|
},
|
|
|
|
(Role::Client, StateSend::StartLine, Event::RequestLine(rl)) => {
|
|
write::request_line(rl, w)
|
|
.map(|_| StateConnection::Send(StateSend::Headers(None)))
|
|
},
|
|
|
|
(_, StateSend::Headers(body_state), Event::Header(h)) => {
|
|
write::header(h, w)?;
|
|
|
|
let bs = match h {
|
|
Header::Other(_) => body_state,
|
|
Header::Special(h) => match h {
|
|
HeaderSpecial::TransferEncodingChunked => Some(BodyState::Chunked),
|
|
HeaderSpecial::ContentLength(cl) => {
|
|
Some(BodyState::ContentLength(*cl))
|
|
},
|
|
},
|
|
};
|
|
|
|
Ok(StateConnection::Send(StateSend::Headers(bs)))
|
|
},
|
|
|
|
(_, StateSend::Headers(body_state), Event::HeadersDone) => {
|
|
write!(w, "\r\n")?;
|
|
match body_state {
|
|
Some(bs) => Ok(StateConnection::Send(StateSend::Body(bs))),
|
|
None => Ok(StateConnection::Send(StateSend::ValidateDone)),
|
|
}
|
|
},
|
|
|
|
(_, StateSend::Body(b), Event::BodyChunk(c)) => match b {
|
|
BodyState::ContentLength(cl) => match () {
|
|
_ if c.len() < cl => {
|
|
w.write(c)?;
|
|
|
|
Ok(StateConnection::Send(StateSend::Body(
|
|
BodyState::ContentLength(cl - c.len()),
|
|
)))
|
|
},
|
|
|
|
_ if c.len() == cl => {
|
|
w.write(c)?;
|
|
|
|
Ok(StateConnection::Send(StateSend::ValidateDone))
|
|
},
|
|
|
|
_ => {
|
|
return fail(ErrorKind::BodySizeMismatch);
|
|
},
|
|
},
|
|
BodyState::Chunked => todo!(),
|
|
},
|
|
|
|
(_, StateSend::ValidateDone, Event::SendDone) => {
|
|
Ok(StateConnection::Recv(StateRecv::StartLine))
|
|
},
|
|
|
|
_ => return Err(Error::from(ErrorKind::InvalidEventForConnectionState)),
|
|
}?;
|
|
|
|
self.state = next;
|
|
|
|
Ok(())
|
|
}
|
|
}
|