netutils: better HTTP library

This commit is contained in:
Mark Poliakov 2024-11-04 16:29:09 +02:00
parent d1c1360926
commit 968ce321da
12 changed files with 695 additions and 238 deletions

25
userspace/Cargo.lock generated
View File

@ -150,6 +150,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
[[package]]
name = "cc"
version = "1.1.31"
@ -477,6 +483,12 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -577,6 +589,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "http"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "humansize"
version = "2.1.3"
@ -761,6 +784,8 @@ dependencies = [
"bytemuck",
"clap",
"clap-num",
"http",
"log",
"rand 0.9.0-alpha.1",
"serde",
"serde_json",

View File

@ -22,6 +22,7 @@ members = [
exclude = ["dynload-program", "test-kernel-module"]
[workspace.dependencies]
log = "0.4.22"
clap = { version = "4.5.20", features = ["std", "derive", "help", "usage"], default-features = false }
clap-num = "1.1.1"
serde_json = "1.0.132"

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
log.workspace = true
yggdrasil-abi.workspace = true
bytemuck.workspace = true
serde_json.workspace = true
@ -14,6 +15,7 @@ clap-num.workspace = true
rand.workspace = true
url = "2.5.0"
http = "1.1.0"
[lib]
path = "src/lib.rs"

View File

@ -7,7 +7,7 @@ use std::os::{
use std::{io, mem::size_of, process::ExitCode, time::Duration};
use bytemuck::{Pod, Zeroable};
use netutils::{parse_udp_protocol, Error, NetConfig};
use netutils::{netconfig::NetConfig, proto::parse_udp_protocol, Error};
use yggdrasil_abi::net::protocols::{
EtherType, EthernetFrame, InetChecksum, IpProtocol, Ipv4Frame, UdpFrame,
};

View File

@ -1,29 +1,36 @@
use std::{
fs::File,
io::{self, stdout, Read, Stdout, Write},
net::TcpStream,
io::{self, stdout, Stdout, Write},
path::{Path, PathBuf},
process::ExitCode,
str::FromStr,
};
use clap::{Parser, Subcommand};
use url::{Host, Url};
use http::{Method, Uri};
use netutils::proto::http::{Bytes, HttpClient, HttpError};
#[derive(Debug, thiserror::Error)]
enum Error {
#[error("I/O error: {0}")]
IoError(#[from] io::Error),
#[error("HTTP error: {0}")]
HttpError(#[from] HttpError<io::Error>),
}
#[derive(Debug, Parser)]
struct Arguments {
#[clap(short, long)]
output: Option<PathBuf>,
#[clap(subcommand)]
method: Method,
method: RequestMethod,
}
#[derive(Debug, Subcommand)]
enum Method {
enum RequestMethod {
#[clap(arg_required_else_help = true)]
Get {
#[clap(help = "URL to GET")]
url: Url,
url: Uri,
},
}
@ -57,75 +64,18 @@ impl Write for Output {
}
}
fn receive_header<R: Read>(stream: &mut R) -> Result<Option<String>, io::Error> {
let mut buf = [0];
let mut line = Vec::new();
fn get(url: Uri, output: Option<PathBuf>) -> Result<(), Error> {
let mut output = Output::open(output)?;
let mut client = HttpClient::default();
let mut buffer = [0; 4096];
let mut response = client.request(Method::GET, url).call::<Bytes>().unwrap();
loop {
stream.read_exact(&mut buf[..1])?;
if buf[0] == b'\r' {
continue;
}
if buf[0] == b'\n' {
let len = response.read(&mut buffer)?;
if len == 0 {
break;
}
line.push(buf[0]);
}
if line.is_empty() {
return Ok(None);
}
let line = String::from_utf8(line).unwrap();
Ok(Some(line))
}
fn get(url: Url, output: Option<PathBuf>) -> Result<(), io::Error> {
let mut output = Output::open(output)?;
let host = url.host().unwrap();
let request = format!(
"GET {} HTTP/1.1\r\nHost: {}\r\nAccept: */*\r\n\r\n",
url.path(),
host
);
let port = url.port_or_known_default().unwrap();
let mut stream = match host {
Host::Domain(hostname) => TcpStream::connect((hostname, port))?,
Host::Ipv4(address) => TcpStream::connect((address, port))?,
Host::Ipv6(address) => TcpStream::connect((address, port))?,
};
eprintln!("Connecting to {}:{}...", host, port);
//let mut stream = TcpStream::connect(remote)?;
let mut buf = [0; 2048];
for line in request.split('\n') {
eprintln!("> {}", line.trim());
}
stream.write_all(request.as_bytes())?;
let mut content_length = 0;
while let Some(header) = receive_header(&mut stream)? {
let Some((key, value)) = header.split_once(':') else {
continue;
};
let key = key.trim();
let value = value.trim();
if key == "Content-Length" {
content_length = usize::from_str(value).unwrap();
}
eprintln!("< {}", header);
}
while content_length != 0 {
let limit = buf.len().min(content_length);
let amount = stream.read(&mut buf[..limit])?;
output.write_all(&buf[..amount])?;
content_length -= amount;
output.write_all(&buffer[..len])?;
}
Ok(())
@ -135,7 +85,7 @@ fn main() -> ExitCode {
let args = Arguments::parse();
let result = match args.method {
Method::Get { url } => get(url, args.output),
RequestMethod::Get { url } => get(url, args.output),
};
match result {

View File

@ -1,21 +1,11 @@
#![feature(yggdrasil_os, rustc_private)]
#![feature(yggdrasil_os, rustc_private, let_chains)]
use std::{
io,
mem::size_of,
os::yggdrasil::io::message_channel::{
MessageChannel, MessageChannelReceiver, MessageChannelSender, MessageDestination,
MessageReceiver, MessageSender,
},
};
use std::io::{self};
use serde::Deserialize;
use yggdrasil_abi::net::{
netconfig::{InterfaceQuery, NetConfigRequest, NetConfigResult, RoutingInfo},
protocols::{EtherType, EthernetFrame, IpProtocol, Ipv4Frame, UdpFrame},
types::NetValueImpl,
IpAddr, Ipv4Addr, MacAddress, SocketAddr, SubnetAddr,
};
pub mod netconfig;
pub mod proto;
// pub use proto::http::HttpClient;
#[derive(thiserror::Error, Debug)]
pub enum Error {
@ -28,144 +18,3 @@ pub enum Error {
#[error("Timed out")]
TimedOut,
}
pub struct NetConfig {
sender: MessageChannelSender,
receiver: MessageChannelReceiver,
buffer: [u8; 4096],
}
impl NetConfig {
pub fn open() -> Result<Self, Error> {
let channel = MessageChannel::open("@kernel-netconf", true)?;
let (sender, receiver) = channel.split();
Ok(Self {
sender,
receiver,
buffer: [0; 4096],
})
}
pub fn send(&mut self, request: &NetConfigRequest) -> Result<(), Error> {
let bytes = serde_json::to_vec(&request)?;
self.sender
.send_message(&bytes, MessageDestination::Specific(0))?;
Ok(())
}
pub fn request<'de, T: Deserialize<'de>>(
&'de mut self,
request: &NetConfigRequest,
) -> Result<T, Error> {
self.send(request)?;
let (_sender, len) = self.receiver.receive_message(&mut self.buffer)?;
let msg: NetConfigResult<T> = serde_json::from_slice(&self.buffer[..len])?;
match msg {
NetConfigResult::Ok(value) => Ok(value),
NetConfigResult::Err(error) => Err(Error::NetConfError(error)),
}
}
pub fn query_route(&mut self, address: IpAddr) -> Result<RoutingInfo, Error> {
self.request(&NetConfigRequest::QueryRoute(address))
}
pub fn query_arp(
&mut self,
interface_id: u32,
address: IpAddr,
perform_query: bool,
) -> Result<MacAddress, Error> {
self.request(&NetConfigRequest::QueryArp(
interface_id,
address,
perform_query,
))
}
pub fn set_interface_address<Q: Into<InterfaceQuery>>(
&mut self,
interface: Q,
address: IpAddr,
) -> Result<(), Error> {
self.request(&NetConfigRequest::SetNetworkAddress {
interface: interface.into(),
address,
})
}
pub fn add_route<Q: Into<InterfaceQuery>>(
&mut self,
interface: Q,
subnet: SubnetAddr,
gateway: Option<IpAddr>,
) -> Result<(), Error> {
self.request(&NetConfigRequest::AddRoute {
interface: interface.into(),
gateway,
subnet,
})
}
}
pub fn parse_l2_protocol(packet: &[u8]) -> Option<(EthernetFrame, &[u8])> {
if packet.len() < size_of::<EthernetFrame>() {
return None;
}
let l2_frame: &EthernetFrame = bytemuck::from_bytes(&packet[..size_of::<EthernetFrame>()]);
let l2_data = &packet[size_of::<EthernetFrame>()..];
Some((*l2_frame, l2_data))
}
pub fn parse_ip_protocol(packet: &[u8]) -> Option<(IpProtocol, IpAddr, IpAddr, &[u8])> {
let (l2_frame, l2_data) = parse_l2_protocol(packet)?;
match EtherType::from_network_order(l2_frame.ethertype) {
EtherType::IPV4 if l2_data.len() >= size_of::<Ipv4Frame>() => {
let l3_frame: &Ipv4Frame = bytemuck::from_bytes(&l2_data[..size_of::<Ipv4Frame>()]);
let source_addr = IpAddr::V4(Ipv4Addr::from(u32::from_network_order(
l3_frame.source_address,
)));
let destination_addr = IpAddr::V4(Ipv4Addr::from(u32::from_network_order(
l3_frame.destination_address,
)));
Some((
l3_frame.protocol,
source_addr,
destination_addr,
&l2_data[l3_frame.header_length()..l3_frame.total_length()],
))
}
_ => None,
}
}
pub fn parse_udp_protocol(packet: &[u8]) -> Option<(SocketAddr, SocketAddr, &[u8])> {
let (protocol, source_ip, destination_ip, l3_data) = parse_ip_protocol(packet)?;
if protocol != IpProtocol::UDP || l3_data.len() < size_of::<UdpFrame>() {
return None;
}
let l4_frame: &UdpFrame = bytemuck::from_bytes(&l3_data[..size_of::<UdpFrame>()]);
let l4_data_size = core::cmp::min(
l3_data.len() - size_of::<UdpFrame>(),
l4_frame.data_length(),
);
let source_addr = SocketAddr::new(source_ip, u16::from_network_order(l4_frame.source_port));
let destination_addr = SocketAddr::new(
destination_ip,
u16::from_network_order(l4_frame.destination_port),
);
Some((
source_addr,
destination_addr,
&l3_data[size_of::<UdpFrame>()..size_of::<UdpFrame>() + l4_data_size],
))
}

View File

@ -1,7 +1,7 @@
use std::{net::IpAddr, process::ExitCode, str::FromStr};
use clap::{Args, Parser, Subcommand};
use netutils::{Error, NetConfig};
use netutils::{netconfig::NetConfig, Error};
use yggdrasil_abi::net::netconfig::{InterfaceInfo, InterfaceQuery, NetConfigRequest, RouteInfo};
#[derive(Debug, Parser)]

View File

@ -0,0 +1,91 @@
use std::os::yggdrasil::io::message_channel::{
MessageChannel, MessageChannelReceiver, MessageChannelSender, MessageDestination,
MessageReceiver, MessageSender,
};
use serde::Deserialize;
use yggdrasil_abi::net::{
netconfig::{InterfaceQuery, NetConfigRequest, NetConfigResult, RoutingInfo},
IpAddr, MacAddress, SubnetAddr,
};
use crate::Error;
pub struct NetConfig {
sender: MessageChannelSender,
receiver: MessageChannelReceiver,
buffer: [u8; 4096],
}
impl NetConfig {
pub fn open() -> Result<Self, Error> {
let channel = MessageChannel::open("@kernel-netconf", true)?;
let (sender, receiver) = channel.split();
Ok(Self {
sender,
receiver,
buffer: [0; 4096],
})
}
pub fn send(&mut self, request: &NetConfigRequest) -> Result<(), Error> {
let bytes = serde_json::to_vec(&request)?;
self.sender
.send_message(&bytes, MessageDestination::Specific(0))?;
Ok(())
}
pub fn request<'de, T: Deserialize<'de>>(
&'de mut self,
request: &NetConfigRequest,
) -> Result<T, Error> {
self.send(request)?;
let (_sender, len) = self.receiver.receive_message(&mut self.buffer)?;
let msg: NetConfigResult<T> = serde_json::from_slice(&self.buffer[..len])?;
match msg {
NetConfigResult::Ok(value) => Ok(value),
NetConfigResult::Err(error) => Err(Error::NetConfError(error)),
}
}
pub fn query_route(&mut self, address: IpAddr) -> Result<RoutingInfo, Error> {
self.request(&NetConfigRequest::QueryRoute(address))
}
pub fn query_arp(
&mut self,
interface_id: u32,
address: IpAddr,
perform_query: bool,
) -> Result<MacAddress, Error> {
self.request(&NetConfigRequest::QueryArp(
interface_id,
address,
perform_query,
))
}
pub fn set_interface_address<Q: Into<InterfaceQuery>>(
&mut self,
interface: Q,
address: IpAddr,
) -> Result<(), Error> {
self.request(&NetConfigRequest::SetNetworkAddress {
interface: interface.into(),
address,
})
}
pub fn add_route<Q: Into<InterfaceQuery>>(
&mut self,
interface: Q,
subnet: SubnetAddr,
gateway: Option<IpAddr>,
) -> Result<(), Error> {
self.request(&NetConfigRequest::AddRoute {
interface: interface.into(),
gateway,
subnet,
})
}
}

View File

@ -4,9 +4,7 @@ use std::{
mem::size_of,
os::{
fd::AsRawFd,
yggdrasil::{
io::{poll::PollChannel, raw_socket::RawSocket, timer::TimerFd},
},
yggdrasil::io::{poll::PollChannel, raw_socket::RawSocket, timer::TimerFd},
},
process::ExitCode,
sync::atomic::{AtomicBool, Ordering},
@ -15,7 +13,7 @@ use std::{
use bytemuck::Zeroable;
use clap::Parser;
use netutils::{Error, NetConfig};
use netutils::{netconfig::NetConfig, Error};
use yggdrasil_abi::net::{
protocols::{EtherType, EthernetFrame, IcmpV4Frame, InetChecksum, IpProtocol, Ipv4Frame},
types::NetValueImpl,

View File

@ -0,0 +1,471 @@
use std::{
fmt,
io::{self, Read, Write},
marker::PhantomData,
net::{SocketAddr, TcpStream, ToSocketAddrs},
ops::{Deref, DerefMut},
string::FromUtf8Error,
time::Duration,
};
use http::{
header::{self, InvalidHeaderValue},
response,
status::InvalidStatusCode,
HeaderName, HeaderValue, Method, Response, StatusCode, Uri,
};
pub trait HttpConnection {
type Error: std::error::Error + Send + 'static;
fn send(&mut self, buffer: &[u8]) -> Result<usize, Self::Error>;
fn send_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error>;
fn recv(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error>;
fn recv_exact(&mut self, buffer: &mut [u8]) -> Result<(), Self::Error>;
}
pub trait HttpConnector {
type Connection: HttpConnection<Error = Self::Error>;
type Error: std::error::Error + Send + 'static;
fn connect(
&mut self,
remote: &SocketAddr,
scheme: &str,
server_name: &str,
timeout: Option<Duration>,
options: HttpConnectionOptions,
) -> Result<Self::Connection, Self::Error>;
fn supports_scheme(&self, scheme: &str) -> bool;
}
#[derive(Clone)]
pub struct HttpConnectionOptions {
pub read_timeout: Option<Duration>,
pub write_timeout: Option<Duration>,
}
pub struct TcpConnector;
pub trait HttpBody: Sized {
fn from_response(content_length: Option<usize>) -> Option<Self>;
fn write<C: HttpConnection>(&self, connection: &mut C) -> Result<(), C::Error>;
fn content_length(&self) -> Option<usize>;
}
#[derive(Debug)]
pub struct Bytes {
content_length: Option<usize>,
}
pub struct HttpClient<C: HttpConnector> {
connector: C,
options: HttpConnectionOptions,
connect_timeout: Option<Duration>,
}
pub struct HttpRequestBuilder<'c, C: HttpConnector, U: TryInto<Uri>> {
client: &'c mut HttpClient<C>,
url: U,
builder: http::request::Builder,
}
pub struct HttpResponse<C: HttpConnection, T: HttpBody> {
connection: C,
position: usize,
inner: Response<T>,
}
#[derive(Debug, thiserror::Error)]
pub enum HttpError<E: std::error::Error> {
#[error("Malformed URL")]
MalformedUrl,
#[error("Unsupported URL scheme: {0:?}")]
UnsupportedScheme(String),
#[error("Connection error")]
ConnectionError(#[from] E),
#[error("Unexpected end of connection")]
EndOfConnection,
#[error("Malformed response header")]
MalformedHeader(FromUtf8Error),
#[error("Malformed request header: {0}")]
InvalidHeaderValue(InvalidHeaderValue),
#[error("Request error: {0}")]
Request(http::Error),
#[error("Response error: {0}")]
Response(http::Error),
#[error("Hostname error: {0}")]
Hostname(io::Error),
#[error("Invalid status code: {0}")]
InvalidStatusCode(InvalidStatusCode),
#[error("Invalid status line")]
InvalidStatusLine,
#[error("Could not read response body")]
BodyError,
#[error("Request too large")]
RequestTooLarge,
#[error("Could not connect to {0}")]
CouldNotConnect(Uri),
}
impl HttpConnector for TcpConnector {
type Connection = TcpStream;
type Error = io::Error;
fn connect(
&mut self,
remote: &SocketAddr,
_scheme: &str,
_server_name: &str,
timeout: Option<Duration>,
options: HttpConnectionOptions,
) -> Result<Self::Connection, Self::Error> {
let socket = match timeout {
Some(timeout) => TcpStream::connect_timeout(remote, timeout)?,
None => TcpStream::connect(remote)?,
};
socket.set_read_timeout(options.read_timeout)?;
socket.set_write_timeout(options.write_timeout)?;
Ok(socket)
}
fn supports_scheme(&self, scheme: &str) -> bool {
scheme.eq_ignore_ascii_case("http")
}
}
impl HttpConnection for TcpStream {
type Error = io::Error;
fn send(&mut self, buffer: &[u8]) -> Result<usize, Self::Error> {
self.write(buffer)
}
fn send_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> {
self.write_all(buffer)
}
fn recv(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
self.read(buffer)
}
fn recv_exact(&mut self, buffer: &mut [u8]) -> Result<(), Self::Error> {
self.read_exact(buffer)
}
}
impl<C: HttpConnector> HttpClient<C> {
pub fn new_default(connector: C) -> Self {
Self {
connector,
connect_timeout: Some(Duration::from_secs(3)),
options: Default::default(),
}
}
pub fn request<U: TryInto<Uri>>(
&mut self,
method: http::Method,
url: U,
) -> HttpRequestBuilder<C, U> {
HttpRequestBuilder {
client: self,
url,
builder: http::Request::builder().method(method),
}
}
fn send<T: HttpBody, R: HttpBody, U: TryInto<Uri>>(
&mut self,
url: U,
request: http::request::Builder,
body: T,
) -> Result<HttpResponse<C::Connection, R>, HttpError<C::Error>> {
let url: Uri = url.try_into().map_err(|_| HttpError::MalformedUrl)?;
let scheme = url.scheme_str().ok_or(HttpError::MalformedUrl)?;
if !self.connector.supports_scheme(scheme) {
return Err(HttpError::UnsupportedScheme(scheme.into()));
}
let host = url.host().ok_or(HttpError::MalformedUrl)?;
let port = url.port_u16().unwrap_or(80);
let request = if let Some(path) = url.path_and_query() {
request.uri(path.as_str())
} else {
request
};
let request = if let Some(content_length) = body.content_length() {
request.header(header::CONTENT_LENGTH, content_length)
} else {
request
};
let request = if let Some(authority) = url.authority() {
request.header(
header::HOST,
HeaderValue::from_str(authority.as_str()).map_err(HttpError::InvalidHeaderValue)?,
)
} else {
request
};
let request = request.body(body).map_err(HttpError::Request)?;
if request.version() != http::Version::HTTP_11 {
unimplemented!()
}
for socket_addr in
ToSocketAddrs::to_socket_addrs(&(host, port)).map_err(HttpError::Hostname)?
{
let Ok(mut connection) = self.connector.connect(
&socket_addr,
scheme,
host,
self.connect_timeout,
self.options.clone(),
) else {
continue;
};
send_request_http1(&mut connection, &request)?;
return recv_response_http1(connection);
}
Err(HttpError::CouldNotConnect(url))
}
}
impl<C: HttpConnector> From<C> for HttpClient<C> {
fn from(value: C) -> Self {
Self::new_default(value)
}
}
impl Default for HttpClient<TcpConnector> {
fn default() -> Self {
Self::new_default(TcpConnector)
}
}
impl<C: HttpConnector, U: TryInto<Uri>> HttpRequestBuilder<'_, C, U> {
pub fn call<R: HttpBody>(self) -> Result<HttpResponse<C::Connection, R>, HttpError<C::Error>> {
self.client.send(self.url, self.builder, ())
}
}
impl HttpBody for () {
fn from_response(_content_length: Option<usize>) -> Option<Self> {
Some(())
}
fn content_length(&self) -> Option<usize> {
Some(0)
}
fn write<C: HttpConnection>(&self, _connection: &mut C) -> Result<(), C::Error> {
Ok(())
}
}
impl HttpBody for Bytes {
fn from_response(content_length: Option<usize>) -> Option<Self> {
Some(Self { content_length })
}
fn content_length(&self) -> Option<usize> {
self.content_length
}
fn write<C: HttpConnection>(&self, _connection: &mut C) -> Result<(), C::Error> {
todo!()
}
}
impl Default for HttpConnectionOptions {
fn default() -> Self {
Self {
read_timeout: Some(Duration::from_secs(3)),
write_timeout: Some(Duration::from_secs(3)),
}
}
}
impl<C: HttpConnection, T: HttpBody + fmt::Debug> fmt::Debug for HttpResponse<C, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
impl<C: HttpConnection> HttpResponse<C, Bytes> {
pub fn read(&mut self, buffer: &mut [u8]) -> Result<usize, HttpError<C::Error>> {
let amount = match self.inner.body().content_length {
Some(len) => core::cmp::min(len - self.position, buffer.len()),
None => buffer.len(),
};
if amount == 0 {
return Ok(0);
}
let len = self.connection.recv(&mut buffer[..amount])?;
self.position += len;
Ok(len)
}
}
impl<C: HttpConnection, T: HttpBody> Deref for HttpResponse<C, T> {
type Target = Response<T>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<C: HttpConnection, T: HttpBody> DerefMut for HttpResponse<C, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
struct RequestWriter<'b, C: HttpConnection> {
pos: usize,
buffer: &'b mut [u8],
_pd: PhantomData<C>,
}
impl<'b, C: HttpConnection> RequestWriter<'b, C> {
pub fn new(buffer: &'b mut [u8]) -> Self {
Self {
buffer,
pos: 0,
_pd: PhantomData,
}
}
pub fn write(&mut self, bytes: &[u8]) -> Result<(), HttpError<C::Error>> {
if bytes.len() + self.pos > self.buffer.len() {
return Err(HttpError::RequestTooLarge);
}
self.buffer[self.pos..self.pos + bytes.len()].copy_from_slice(bytes);
self.pos += bytes.len();
Ok(())
}
pub fn header(
&mut self,
key: &HeaderName,
value: &HeaderValue,
) -> Result<(), HttpError<C::Error>> {
self.write(key.as_str().as_bytes())?;
self.write(b":")?;
self.write(value.as_bytes())?;
self.write(b"\r\n")?;
Ok(())
}
pub fn request(&mut self, method: &Method, uri: &Uri) -> Result<(), HttpError<C::Error>> {
let line = format!("{} {} HTTP/1.1\r\n", method, uri);
self.write(line.as_bytes())
}
pub fn finish(mut self) -> Result<&'b [u8], HttpError<C::Error>> {
self.write(b"\r\n")?;
Ok(&self.buffer[..self.pos])
}
}
fn send_request_http1<C: HttpConnection, T: HttpBody>(
connection: &mut C,
request: &http::Request<T>,
) -> Result<(), HttpError<C::Error>> {
let mut buffer = [0; 4096];
let mut writer = RequestWriter::<C>::new(&mut buffer);
writer.request(request.method(), request.uri())?;
for (name, value) in request.headers() {
writer.header(name, value)?;
}
let preamble = writer.finish()?;
connection.send_all(preamble)?;
request.body().write(connection)?;
Ok(())
}
fn recv_http1_line<C: HttpConnection>(
connection: &mut C,
buffer: &mut [u8],
) -> Result<usize, HttpError<C::Error>> {
let mut pos = 0;
loop {
if pos == buffer.len() {
todo!()
}
connection.recv_exact(&mut buffer[pos..pos + 1])?;
match buffer[pos] {
b'\r' => continue,
b'\n' => break,
_ => pos += 1,
}
}
Ok(pos)
}
fn recv_response_http1<C: HttpConnection, T: HttpBody>(
mut connection: C,
) -> Result<HttpResponse<C, T>, HttpError<C::Error>> {
let mut buffer = [0; 256];
let len = recv_http1_line(&mut connection, &mut buffer)?;
let mut content_length = None;
let mut builder = response::Builder::new();
let status = parse_status_line_http1::<C>(&buffer[..len])?;
builder = builder.status(status);
loop {
let len = recv_http1_line(&mut connection, &mut buffer)?;
if len == 0 {
break;
}
let Some(eq) = buffer.iter().position(|q| *q == b':') else {
continue;
};
let (name, value) = buffer[..len].split_at(eq);
let name = name.trim_ascii_end();
let value = value[1..].trim_ascii_start();
let Ok(name) = HeaderName::from_bytes(name) else {
continue;
};
if name == header::CONTENT_LENGTH {
if let Some(value) = std::str::from_utf8(value).ok().and_then(|s| s.parse().ok()) {
content_length = Some(value);
}
}
let Ok(value) = HeaderValue::from_bytes(value) else {
continue;
};
builder = builder.header(name, value);
}
let body = T::from_response(content_length).ok_or(HttpError::BodyError)?;
let inner = builder.body(body).map_err(HttpError::Response)?;
Ok(HttpResponse {
connection,
inner,
position: 0,
})
}
fn parse_status_line_http1<C: HttpConnection>(
line: &[u8],
) -> Result<StatusCode, HttpError<C::Error>> {
let mut it = line.split(|c| *c == b' ');
it.next().ok_or(HttpError::InvalidStatusLine)?;
let status = it.next().ok_or(HttpError::InvalidStatusLine)?;
let status = StatusCode::from_bytes(status).map_err(HttpError::InvalidStatusCode)?;
Ok(status)
}

View File

@ -0,0 +1,70 @@
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use yggdrasil_abi::net::{
protocols::{EtherType, EthernetFrame, IpProtocol, Ipv4Frame, UdpFrame},
types::NetValueImpl,
};
pub mod http;
pub fn parse_l2_protocol(packet: &[u8]) -> Option<(EthernetFrame, &[u8])> {
if packet.len() < size_of::<EthernetFrame>() {
return None;
}
let l2_frame: &EthernetFrame = bytemuck::from_bytes(&packet[..size_of::<EthernetFrame>()]);
let l2_data = &packet[size_of::<EthernetFrame>()..];
Some((*l2_frame, l2_data))
}
pub fn parse_ip_protocol(packet: &[u8]) -> Option<(IpProtocol, IpAddr, IpAddr, &[u8])> {
let (l2_frame, l2_data) = parse_l2_protocol(packet)?;
match EtherType::from_network_order(l2_frame.ethertype) {
EtherType::IPV4 if l2_data.len() >= size_of::<Ipv4Frame>() => {
let l3_frame: &Ipv4Frame = bytemuck::from_bytes(&l2_data[..size_of::<Ipv4Frame>()]);
let source_addr = IpAddr::V4(Ipv4Addr::from(u32::from_network_order(
l3_frame.source_address,
)));
let destination_addr = IpAddr::V4(Ipv4Addr::from(u32::from_network_order(
l3_frame.destination_address,
)));
Some((
l3_frame.protocol,
source_addr,
destination_addr,
&l2_data[l3_frame.header_length()..l3_frame.total_length()],
))
}
_ => None,
}
}
pub fn parse_udp_protocol(packet: &[u8]) -> Option<(SocketAddr, SocketAddr, &[u8])> {
let (protocol, source_ip, destination_ip, l3_data) = parse_ip_protocol(packet)?;
if protocol != IpProtocol::UDP || l3_data.len() < size_of::<UdpFrame>() {
return None;
}
let l4_frame: &UdpFrame = bytemuck::from_bytes(&l3_data[..size_of::<UdpFrame>()]);
let l4_data_size = core::cmp::min(
l3_data.len() - size_of::<UdpFrame>(),
l4_frame.data_length(),
);
let source_addr = SocketAddr::new(source_ip, u16::from_network_order(l4_frame.source_port));
let destination_addr = SocketAddr::new(
destination_ip,
u16::from_network_order(l4_frame.destination_port),
);
Some((
source_addr,
destination_addr,
&l3_data[size_of::<UdpFrame>()..size_of::<UdpFrame>() + l4_data_size],
))
}

View File

@ -17,8 +17,8 @@ bytemuck.workspace = true
x25519-dalek.workspace = true
ed25519-dalek = { workspace = true, features = ["rand_core", "pem"] }
sha2.workspace = true
log.workspace = true
rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil-rng_core-0.6.4" }
aes = { version = "0.8.4" }
log = "0.4.22"
env_logger = "0.11.5"