1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-08-24 00:02:50 +00:00

Sender SDK

This commit is contained in:
Marcus Hanestad 2025-08-21 14:49:52 +00:00
parent fdbefc63e0
commit afc46f3022
147 changed files with 17638 additions and 114 deletions

View file

@ -0,0 +1,8 @@
[package]
name = "http"
version = "0.1.0"
edition = "2021"
license.workspace = true
[dependencies]
thiserror.workspace = true

196
sdk/sender/http/src/lib.rs Normal file
View file

@ -0,0 +1,196 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Protocol {
Http1,
Http11,
Http2,
}
impl Protocol {
pub fn as_static_str(&self) -> &'static str {
match self {
Protocol::Http1 => "HTTP/1.0",
Protocol::Http11 => "HTTP/1.1",
Protocol::Http2 => "HTTP/2",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Method {
Get,
Post,
Head,
Put,
Options,
}
#[derive(Debug, Clone)]
pub enum StatusCode {
Ok,
ParitalContent,
BadRequest,
NotFound,
MethodNotAllowed,
InternalServerError,
}
impl StatusCode {
pub fn as_static_str(&self) -> &'static str {
match self {
StatusCode::Ok => "200 OK",
StatusCode::ParitalContent => "206 Partial Content",
StatusCode::BadRequest => "400 Bad Request",
StatusCode::NotFound => "404 Not Found",
StatusCode::MethodNotAllowed => "405 Method Not Allowed",
StatusCode::InternalServerError => "500 Internal Server Error",
}
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, Copy)]
pub enum ParseStartLineError {
#[error("invalid method")]
InvalidMethod,
#[error("path is missing")]
MissingPath,
#[error("missing protocol")]
MissingProtocol,
#[error("invalid protocol")]
InvalidProtocol,
}
pub fn parse_request_start_line(
line: &[u8],
) -> Result<(Method, &[u8], Protocol), ParseStartLineError> {
let methods: [(&[u8], Method); 5] = [
(b"GET", Method::Get),
(b"POST", Method::Post),
(b"HEAD", Method::Head),
(b"PUT", Method::Put),
(b"OPTIONS", Method::Options),
];
let (method, method_end_idx) = 'out: {
for method in methods {
let method_len = method.0.len();
if method_len <= line.len() && method.0 == &line[0..method_len] {
break 'out (method.1, method_len);
}
}
return Err(ParseStartLineError::InvalidMethod);
};
if method_end_idx >= line.len() || !line[method_end_idx].is_ascii_whitespace() {
return Err(ParseStartLineError::MissingPath);
}
let path_start_idx = method_end_idx + 1;
let path_end_idx = {
let mut i = path_start_idx;
while i < line.len() && line[i].is_ascii() && !line[i].is_ascii_whitespace() {
i += 1;
}
i
};
let protocol_start_idx = path_end_idx + 1;
if protocol_start_idx >= line.len()
|| path_end_idx - path_start_idx == 0
|| !line[path_end_idx].is_ascii_whitespace()
{
return Err(ParseStartLineError::MissingProtocol);
}
let path = &line[path_start_idx..path_end_idx];
let protocols: [(&[u8], Protocol); 3] = [
(b"HTTP/1.0\r\n", Protocol::Http1),
(b"HTTP/1.1\r\n", Protocol::Http11),
(b"HTTP/2\r\n", Protocol::Http2),
];
let protocol = 'out: {
for protocol in protocols {
if protocol.0 == &line[protocol_start_idx..] {
break 'out protocol.1;
}
}
return Err(ParseStartLineError::InvalidProtocol);
};
Ok((method, path, protocol))
}
pub struct KnownHeaderNames;
impl KnownHeaderNames {
pub const CONTENT_LENGTH: &str = "Content-Length";
pub const CONTENT_RANGE: &str = "Content-Range";
pub const CONTENT_TYPE: &str = "Content-Type";
pub const RANGE: &str = "Range";
}
#[derive(Debug, Clone)]
pub struct ResponseStartLine {
pub protocol: Protocol,
pub status_code: StatusCode,
}
impl ResponseStartLine {
pub fn serialize(&self) -> Vec<u8> {
let mut buf = Vec::new();
let slices = [
self.protocol.as_static_str().as_bytes(),
b" ",
self.status_code.as_static_str().as_bytes(),
b"\r\n",
];
for slice in slices {
buf.extend_from_slice(slice);
}
buf
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! m {
($method:ident) => {
Method::$method
};
}
macro_rules! p {
($protocol:ident) => {
Protocol::$protocol
};
}
#[test]
fn valid_parse_request_start_line() {
let cases: &[(&[u8], (Method, &[u8], Protocol))] = &[
(b"GET / HTTP/1.0\r\n", (m!(Get), b"/", p!(Http1))),
(
b"POST /index HTTP/1.1\r\n",
(m!(Post), b"/index", p!(Http11)),
),
];
for case in cases {
assert_eq!(parse_request_start_line(case.0).unwrap(), case.1,);
}
}
#[test]
fn invalid_parse_request_start_line() {
let cases: &[(&[u8], ParseStartLineError)] = &[
(b"GeT", ParseStartLineError::InvalidMethod),
(b"POST", ParseStartLineError::MissingPath),
(b"POST / FTP\r\n", ParseStartLineError::InvalidProtocol),
];
for case in cases {
assert_eq!(parse_request_start_line(case.0), Err(case.1), "{case:?}");
}
}
}