From acebdaca4cbaf78db8da2ea9cb7bb8504939bdaf Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 28 Oct 2024 15:43:29 +0000 Subject: [PATCH 01/44] feat: add macros feature to crate --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 4973928..bbaba30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ rust-version = "1.81" [features] default = [] +macros = [] [dependencies] psp = { version = "0.3.10" } From da21310ed3d75001810e93a641bb7403f4ce9782 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 28 Oct 2024 15:43:58 +0000 Subject: [PATCH 02/44] feat: implement tls socket macros --- src/lib.rs | 1 + src/socket/macros.rs | 203 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 src/socket/macros.rs diff --git a/src/lib.rs b/src/lib.rs index 8da3582..557fcc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ #![allow(clippy::cast_sign_loss)] #![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_possible_wrap)] +#![feature(slice_pattern)] extern crate alloc; diff --git a/src/socket/macros.rs b/src/socket/macros.rs new file mode 100644 index 0000000..4f417a4 --- /dev/null +++ b/src/socket/macros.rs @@ -0,0 +1,203 @@ +#![allow(unused)] +macro_rules! some_or_none { + () => { + None + }; + ($entity:expr) => { + Some($entity) + }; +} + +#[macro_export] +/// Get the current timestamp +macro_rules! timestamp { + () => {{ + let mut seed = 0; + unsafe { + psp::sys::sceRtcGetCurrentTick(&mut seed); + } + seed + }}; +} + +#[macro_export] +/// Create a new TLS socket +/// +/// The macro will try to open a new TLS connection to the provided remote address. +/// The socket will be stored in a variable named as provided. +/// Please note that the variable will contain a `Result, SocketError>`, +/// not a `TlsSocket<'_, Ready>` directly. +/// +/// # Parameters +/// - `name`: The name of the variable where the socket will be stored +/// - `host`: The hostname and address to connect to +/// - `seed`: (Optional) The seed to use for the RNG, if not provided, the current timestamp is used +/// - `cert`: (Optional) The certificate to use +/// - `ca`: (Optional) The CA to use +/// - `enable_rsa_signatures`: (Optional, default `true`) Whether to enable RSA signatures +/// - `reset_max_fragment_length`: (Optional, default `false`) Whether to reset the max fragment length +/// +/// # Example +/// ```no_run +/// tls_socket! { +/// name: tls_socket, +/// host "myhost.com" => "1.2.3.4", +/// } +/// let mut tls_socket = tls_socket?; +/// tls_socket.write_all("hello world".as_bytes()); +/// ``` +macro_rules! tls_socket { + ( + name: $name:ident, + host $host:expr => $remote:expr, + seed $seed:expr, + cert $cert:expr, + ca $ca:expr, + enable_rsa_signatures $enable_rsa_signatures:expr, + reset_max_fragment_length $mfl:expr, + ) => { + use core::net::Ipv4Addr; + use $crate::socket::state::Ready; + use $crate::types::TlsSocketOptions; + use $crate::socket::tcp::TcpSocket; + use $crate::socket::tls::TlsSocket; + use $crate::socket::{error::SocketError, SocketAddr, SocketAddrV4}; + + let mut $name: Result, SocketError> = Err(SocketError::Other); + + let ip = Ipv4Addr::from_str($remote).unwrap(); + let addr = SocketAddr::V4(SocketAddrV4::new(ip, 443)); + let s = TcpSocket::new(); + if let Ok(s) = s { + let s = s.connect(addr); + if let Ok(s) = s { + let mut read_buf = TlsSocket::new_buffer(); + let mut write_buf = TlsSocket::new_buffer(); + let $name = TlsSocket::new(s, &mut read_buf, &mut write_buf); + let mut options = TlsSocketOptions::new($seed, $host.to_string()); + options.set_cert($cert); + options.set_ca($ca); + options.set_enable_rsa_signatures($enable_rsa_signatures); + options.set_reset_max_fragment_length($mfl); + let $name = $name.open(&options); + } else { + $name = Err(s.err().unwrap()); + } + } else { + $name = Err(s.err().unwrap()); + } + }; + ( + name: $name:ident, + host $host:expr => $remote:expr, + $(seed $seed:expr,)? + $(cert $cert:expr,)? + $(ca $ca:expr,)? + $(enable_rsa_signatures $enable_rsa_signatures:expr,)? + $(reset_max_fragment_length $mfl:expr,)? + ) => { + let seed = some_or_none!($($seed)?); + let seed = seed.unwrap_or(timestamp!()); + let cert = some_or_none!($($cert)?); + let ca = some_or_none!($($ca)?); + let enable_rsa_signatures = some_or_none!($($enable_rsa_signatures)?); + let enable_rsa_signatures = enable_rsa_signatures.unwrap_or(true); + let reset_max_fragment_length = some_or_none!($($mfl)?); + let reset_max_fragment_length = reset_max_fragment_length.unwrap_or(false); + + tls_socket! { + name: $name, + host $host => $remote, + seed seed, + cert cert, + ca ca, + enable_rsa_signatures enable_rsa_signatures, + reset_max_fragment_length reset_max_fragment_length, + } + }; +} + +/// Read from a TLS socket +/// +/// The macro need a `&mut TlsSocket<'_, Ready>` as input. +/// +/// The macro supports the following syntaxes: +/// ```no_run +/// // syntax 1 +/// read!(from socket); +/// // syntax 2 +/// read!(from socket => buf); +/// // syntax 3 +/// read!(string from socket); +/// ``` +/// +/// # Example +/// Read a string from the socket +/// ```no_run +/// if let Ok(s) = read!(string from socket) { +/// println!("{}", s); +/// } +/// ``` +#[macro_export] +macro_rules! read { + (from $socket:ident) => {{ + let mut buf = [0; $crate::socket::tls::MAX_FRAGMENT_LENGTH as usize]; + $socket.read(&mut buf) + }}; + (from $socket:ident => $buf:ident) => {{ + $socket.read(&mut $buf) + }}; + (string from $socket:ident) => { + $socket.read_string() + }; +} + +/// Write to a TLS socket +/// +/// The macro need a `&mut TlsSocket<'_, Ready>` as input. +/// +/// # Example +/// ```no_run +/// write!(buf => socket)?; +/// ``` +#[macro_export] +macro_rules! write { + ($buf:ident => $socket:ident) => {{ + $socket.write_all($buf.as_slice()) + }}; +} + +// fn test() -> Result<(), SocketError> { +// tls_socket! { +// name: _s, +// host "google.com" => "127.0.0.1", +// } +// let mut socket = _s?; + +// read!(from socket).map_err(|_| SocketError::Other)?; +// read!(string from socket).map_err(|_| SocketError::Other)?; +// let mut buf = TlsSocket::new_buffer(); +// read!(from socket => buf).map_err(|_| SocketError::Other)?; +// write!(buf => socket).map_err(|_| SocketError::Other)?; +// let buf = &buf.as_slice()[..]; +// let res = write! {buf => socket}; +// if res.is_err() { +// return Err(SocketError::Other); +// } + +// if let Ok(s) = read!(string from socket) { +// psp::dprintln!("{}", s); +// } + +// let buf = buf.as_slice().as_slice().as_slice(); +// _ = socket.write_all(buf); + +// let _ = socket.write_all("sacw".as_bytes()); + +// Ok(()) +// } + +// #[allow(unused)] +// fn test2() { +// test().unwrap(); +// } From f3d849641be103c33dc521bc8260822ab36dff48 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 28 Oct 2024 15:44:19 +0000 Subject: [PATCH 03/44] feat: add macro sub-module to socket --- src/socket/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/socket/mod.rs b/src/socket/mod.rs index e093bf5..62e5f07 100644 --- a/src/socket/mod.rs +++ b/src/socket/mod.rs @@ -12,6 +12,8 @@ use psp::sys::{in_addr, sockaddr}; use super::netc; pub mod error; +#[cfg(feature = "macros")] +pub mod macros; mod sce; pub mod state; pub mod tcp; From 2baed97796d33412366bd4cefdb89fbbd86dbc75 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 28 Oct 2024 15:44:48 +0000 Subject: [PATCH 04/44] refactor: make tls socket options easier to use --- src/socket/state.rs | 7 +++++++ src/socket/tls.rs | 7 +++++-- src/types/socket_options.rs | 12 ++++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/socket/state.rs b/src/socket/state.rs index df76c4b..3489402 100644 --- a/src/socket/state.rs +++ b/src/socket/state.rs @@ -1,3 +1,10 @@ +//! Socket states +//! +//! psp-net sockets implement the Type State Pattern, and here are defined +//! the different possible states of this crate's sockets. +//! +//! Note that not all sockets may implement all of these states. + use core::fmt::Debug; /// Trait describing the state of a socket diff --git a/src/socket/tls.rs b/src/socket/tls.rs index ca533d8..2b24ca6 100644 --- a/src/socket/tls.rs +++ b/src/socket/tls.rs @@ -22,6 +22,9 @@ lazy_static::lazy_static! { static ref REGEX: Regex = Regex::new("\r|\0").unwrap(); } +/// TLS maximum fragment length, equivalent to 2^14 bytes (`16_384` bytes) +pub const MAX_FRAGMENT_LENGTH: u16 = 16_384; + /// A TLS socket. /// This is a wrapper around a [`TcpSocket`] that provides a TLS connection. pub struct TlsSocket<'a, S: SocketState = NotReady> { @@ -76,7 +79,7 @@ impl<'a> TlsSocket<'_> { /// It is a utility function to create the read/write buffer to pass to [`Self::new()`]. /// /// # Returns - /// A new buffer of `16_384` bytes. + /// A new buffer of [`MAX_FRAGMENT_LENGTH`] (`16_384`) bytes. /// /// # Example /// ```no_run @@ -85,7 +88,7 @@ impl<'a> TlsSocket<'_> { /// let tls_socket = TlsSocket::new(tcp_socket, &mut read_buf, &mut write_buf); /// ``` #[must_use] - pub fn new_buffer() -> [u8; 16_384] { + pub fn new_buffer() -> [u8; MAX_FRAGMENT_LENGTH as usize] { [0; 16_384] } } diff --git a/src/types/socket_options.rs b/src/types/socket_options.rs index 19e1e1d..894429d 100644 --- a/src/types/socket_options.rs +++ b/src/types/socket_options.rs @@ -92,8 +92,8 @@ impl<'a> TlsSocketOptions<'a> { /// /// # Arguments /// - `cert`: The certificate - pub fn set_cert(&mut self, cert: Certificate<'a>) { - self.cert = Some(cert); + pub fn set_cert(&mut self, cert: Option>) { + self.cert = cert; } /// Get the seed @@ -146,4 +146,12 @@ impl<'a> TlsSocketOptions<'a> { pub fn set_ca(&mut self, ca: Option>) { self.ca = ca; } + + /// Set whether RSA signatures should be enabled + /// + /// # Arguments + /// - `enable_rsa_signatures`: Whether RSA signatures should be enabled + pub fn set_enable_rsa_signatures(&mut self, enable_rsa_signatures: bool) { + self.enable_rsa_signatures = enable_rsa_signatures; + } } From efb4de3b9057916043fd2b7559cfcb569ae262ed Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 28 Oct 2024 15:53:49 +0000 Subject: [PATCH 05/44] feat: tls_scoket macro also accept options directly --- src/socket/macros.rs | 50 +++++++++++++------------------------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/src/socket/macros.rs b/src/socket/macros.rs index 4f417a4..8548b29 100644 --- a/src/socket/macros.rs +++ b/src/socket/macros.rs @@ -115,6 +115,21 @@ macro_rules! tls_socket { reset_max_fragment_length reset_max_fragment_length, } }; + ( + name: $name:ident, + host $host:expr => $remote:expr, + opts $opts:expr, + ) => { + tls_socket! { + name: $name, + host $host => $remote, + seed $opts.seed(), + cert $opts.cert(), + ca $opts.ca(), + enable_rsa_signatures $opts.enable_rsa_signatures(), + reset_max_fragment_length $opts.reset_max_fragment_length(), + } + } } /// Read from a TLS socket @@ -166,38 +181,3 @@ macro_rules! write { $socket.write_all($buf.as_slice()) }}; } - -// fn test() -> Result<(), SocketError> { -// tls_socket! { -// name: _s, -// host "google.com" => "127.0.0.1", -// } -// let mut socket = _s?; - -// read!(from socket).map_err(|_| SocketError::Other)?; -// read!(string from socket).map_err(|_| SocketError::Other)?; -// let mut buf = TlsSocket::new_buffer(); -// read!(from socket => buf).map_err(|_| SocketError::Other)?; -// write!(buf => socket).map_err(|_| SocketError::Other)?; -// let buf = &buf.as_slice()[..]; -// let res = write! {buf => socket}; -// if res.is_err() { -// return Err(SocketError::Other); -// } - -// if let Ok(s) = read!(string from socket) { -// psp::dprintln!("{}", s); -// } - -// let buf = buf.as_slice().as_slice().as_slice(); -// _ = socket.write_all(buf); - -// let _ = socket.write_all("sacw".as_bytes()); - -// Ok(()) -// } - -// #[allow(unused)] -// fn test2() { -// test().unwrap(); -// } From ab3ae85c77e92a7edca43c60422669f432d4f7a6 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 28 Oct 2024 18:31:02 +0000 Subject: [PATCH 06/44] feat: add http request utilities module --- Cargo.lock | 7 +++ Cargo.toml | 5 +- src/http/macros.rs | 107 +++++++++++++++++++++++++++++++++++++++++++ src/http/mod.rs | 31 +++++++++++++ src/http/request.rs | 52 +++++++++++++++++++++ src/lib.rs | 2 + src/socket/macros.rs | 2 +- 7 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 src/http/macros.rs create mode 100644 src/http/mod.rs create mode 100644 src/http/request.rs diff --git a/Cargo.lock b/Cargo.lock index c5cf539..1f813c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,6 +357,12 @@ dependencies = [ "digest", ] +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + [[package]] name = "inout" version = "0.1.3" @@ -493,6 +499,7 @@ dependencies = [ "dns-protocol", "embedded-io", "embedded-tls", + "httparse", "lazy_static", "psp", "rand", diff --git a/Cargo.toml b/Cargo.toml index bbaba30..55e3dc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,9 @@ rust-version = "1.81" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = [] +default = ["http"] macros = [] +http = [] [dependencies] psp = { version = "0.3.10" } @@ -26,3 +27,5 @@ lazy_static = { version = "1.5.0", default-features = false, features = [ "spin_no_std", ] } bitflags = { version = "2.6.0", default-features = false } +httparse = { version = "1.9.5", default-features = false } +# reqwless = {version = "0.13.0", features = ["alloc"]} diff --git a/src/http/macros.rs b/src/http/macros.rs new file mode 100644 index 0000000..8d91186 --- /dev/null +++ b/src/http/macros.rs @@ -0,0 +1,107 @@ +#[allow(unused)] +macro_rules! some_or_none { + () => { + None + }; + ($entity:expr) => { + Some($entity) + }; +} + +/// +/// # Example +/// Example GET request +/// ```no_run +/// request! { +/// host "www.example.com", +/// GET > "/", +/// "User-Agent" => "Mozilla/5.0", +/// } +/// ``` +/// +/// Example POST request +/// ```no_run +/// request! { +/// host "www.example.com", +/// POST > "/users/create", +/// content_type "application/json", +/// body body, +#[macro_export] +macro_rules! request { + ( + host $host:expr, + GET $uri:expr, + $($header:expr => $value:expr,)* + ) => { + request! { + host $host, + $crate::http::Method::Get => $uri, + $($header => $value,)* + } + }; + + ( + host $host:expr, + POST $uri:expr, + $(content_type $content_type:expr,)? + $($header:expr => $value:expr,)* + $(body $body:expr)? + ) => { + request! { + host $host, + $crate::http::Method::Post => $uri, + $(content_type $content_type,)? + $($header => $value,)* + $(body $body)? + } + }; + + ( + host $host:expr, + PUT $uri:expr, + $(content_type $content_type:expr,)? + $($header:expr => $value:expr,)* + $(body $body:expr)? + ) => { + request! { + host $host, + $crate::http::Method::Put => $uri, + $(content_type $content_type,)? + $($header => $value,)* + $(body $body)? + } + }; + + ( + host $host:expr, + DELETE $uri:expr, + $(content_type $content_type:expr,)? + $($header:expr => $value:expr,)* + ) => { + request! { + host $host, + $crate::http::Method::Delete => $uri, + $(content_type $content_type,)? + $($header => $value,)* + $(body $body)? + } + }; + + ( + host $host:expr, + $method:expr => $uri:expr, + $(content_type $content_type:expr,)? + $($header:expr => $value:expr,)* + $(body $body:expr)? + ) => {{ + use alloc::string::ToString; + use alloc::vec as a_vec; + $crate::http::Request { + method: $method, + uri: $uri.to_string(), + headers: a_vec![$(($header.to_string(), $value.to_string()),)*], + content_type: some_or_none!($($content_type)?), + body: some_or_none!($($body)?).unwrap_or(Vec::new()), + } + }}; +} diff --git a/src/http/mod.rs b/src/http/mod.rs new file mode 100644 index 0000000..ec9b772 --- /dev/null +++ b/src/http/mod.rs @@ -0,0 +1,31 @@ +use core::fmt; + +use alloc::string::String; + +#[cfg(feature = "macros")] +pub mod macros; +mod request; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub enum ContentType { + #[default] + TextPlain, + ApplicationJson, + OctetStream, + Other(String), +} + +impl fmt::Display for ContentType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ContentType::TextPlain => write!(f, "text/plain"), + ContentType::ApplicationJson => write!(f, "application/json"), + ContentType::OctetStream => write!(f, "application/octet-stream"), + ContentType::Other(s) => write!(f, "{s}"), + } + } +} + +// re-exports +pub type Method = request::Method; +pub type Request = request::Request; diff --git a/src/http/request.rs b/src/http/request.rs new file mode 100644 index 0000000..77c3f20 --- /dev/null +++ b/src/http/request.rs @@ -0,0 +1,52 @@ +use core::fmt; + +use alloc::{format, string::String, vec::Vec}; + +use super::ContentType; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum Method { + #[default] + Get, + Post, + Put, + Delete, +} + +impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Method::Get => write!(f, "GET"), + Method::Post => write!(f, "POST"), + Method::Put => write!(f, "PUT"), + Method::Delete => write!(f, "DELETE"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Request { + pub method: Method, + pub uri: String, + pub headers: Vec<(String, String)>, + pub body: Vec, + pub content_type: Option, +} + +impl fmt::Display for Request { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut headers_and_body = String::new(); + for (header, value) in &self.headers { + headers_and_body.push_str(format!("{header}: {value}\n").as_str()); + } + if let Some(content_type) = &self.content_type { + headers_and_body.push_str(format!("Content-Type: {content_type}\n").as_str()); + } + if !self.body.is_empty() { + headers_and_body.push_str(format!("Content-Length: {}\n", self.body.len()).as_str()); + headers_and_body + .push_str(format!("\n{}\n", String::from_utf8_lossy(&self.body)).as_str()); + } + write!(f, "{} {}\n{}", self.method, self.uri, headers_and_body) + } +} diff --git a/src/lib.rs b/src/lib.rs index 557fcc4..a60057c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,8 @@ extern crate alloc; pub mod constants; pub mod dns; +#[cfg(feature = "http")] +pub mod http; pub mod netc; pub mod socket; pub mod traits; diff --git a/src/socket/macros.rs b/src/socket/macros.rs index 8548b29..0d1794e 100644 --- a/src/socket/macros.rs +++ b/src/socket/macros.rs @@ -1,4 +1,4 @@ -#![allow(unused)] +#[allow(unused)] macro_rules! some_or_none { () => { None From 300d25b9396074c449d1d0d1b4c62227c6da31a7 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 28 Oct 2024 18:37:14 +0000 Subject: [PATCH 07/44] fix: macros calling some_or_none macro --- src/http/macros.rs | 14 ++------------ src/lib.rs | 2 ++ src/macros.rs | 13 +++++++++++++ src/socket/macros.rs | 20 +++++--------------- 4 files changed, 22 insertions(+), 27 deletions(-) create mode 100644 src/macros.rs diff --git a/src/http/macros.rs b/src/http/macros.rs index 8d91186..79cacfb 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -1,13 +1,3 @@ -#[allow(unused)] -macro_rules! some_or_none { - () => { - None - }; - ($entity:expr) => { - Some($entity) - }; -} - /// /// # Example /// Example GET request @@ -100,8 +90,8 @@ macro_rules! request { method: $method, uri: $uri.to_string(), headers: a_vec![$(($header.to_string(), $value.to_string()),)*], - content_type: some_or_none!($($content_type)?), - body: some_or_none!($($body)?).unwrap_or(Vec::new()), + content_type: $crate::some_or_none!($($content_type)?), + body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), } }}; } diff --git a/src/lib.rs b/src/lib.rs index a60057c..f36641d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,8 @@ pub mod constants; pub mod dns; #[cfg(feature = "http")] pub mod http; +#[cfg(feature = "macros")] +pub mod macros; pub mod netc; pub mod socket; pub mod traits; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..a9338e6 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,13 @@ +/// Utility macro used internally to allow optional parameters +/// in macros. +/// +/// This macro is not intended to be used directly. +#[macro_export] +macro_rules! some_or_none { + () => { + None + }; + ($entity:expr) => { + Some($entity) + }; +} diff --git a/src/socket/macros.rs b/src/socket/macros.rs index 0d1794e..c7c5f73 100644 --- a/src/socket/macros.rs +++ b/src/socket/macros.rs @@ -1,13 +1,3 @@ -#[allow(unused)] -macro_rules! some_or_none { - () => { - None - }; - ($entity:expr) => { - Some($entity) - }; -} - #[macro_export] /// Get the current timestamp macro_rules! timestamp { @@ -96,13 +86,13 @@ macro_rules! tls_socket { $(enable_rsa_signatures $enable_rsa_signatures:expr,)? $(reset_max_fragment_length $mfl:expr,)? ) => { - let seed = some_or_none!($($seed)?); + let seed = $crate::some_or_none!($($seed)?); let seed = seed.unwrap_or(timestamp!()); - let cert = some_or_none!($($cert)?); - let ca = some_or_none!($($ca)?); - let enable_rsa_signatures = some_or_none!($($enable_rsa_signatures)?); + let cert = $crate::some_or_none!($($cert)?); + let ca = $crate::some_or_none!($($ca)?); + let enable_rsa_signatures = $crate::some_or_none!($($enable_rsa_signatures)?); let enable_rsa_signatures = enable_rsa_signatures.unwrap_or(true); - let reset_max_fragment_length = some_or_none!($($mfl)?); + let reset_max_fragment_length = $crate::some_or_none!($($mfl)?); let reset_max_fragment_length = reset_max_fragment_length.unwrap_or(false); tls_socket! { From 80d4a4a85bbc1fa897ca69d599b4733d45d93e2b Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 28 Oct 2024 18:58:44 +0000 Subject: [PATCH 08/44] feat: re-export SocketAddrV4 --- src/socket/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/socket/mod.rs b/src/socket/mod.rs index 62e5f07..974eb35 100644 --- a/src/socket/mod.rs +++ b/src/socket/mod.rs @@ -6,7 +6,7 @@ #![allow(clippy::module_name_repetitions)] -use core::net::{Ipv4Addr, SocketAddrV4}; +use core::net::Ipv4Addr; use psp::sys::{in_addr, sockaddr}; use super::netc; @@ -81,3 +81,4 @@ impl ToSocketAddr for sockaddr { // re-exports pub type SocketAddr = core::net::SocketAddr; +pub type SocketAddrV4 = core::net::SocketAddrV4; From 147d5b78153bf00ee8866f72519faf7100cc2289 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 28 Oct 2024 18:58:54 +0000 Subject: [PATCH 09/44] fix: macro imports --- src/socket/macros.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/socket/macros.rs b/src/socket/macros.rs index c7c5f73..b6cdb7d 100644 --- a/src/socket/macros.rs +++ b/src/socket/macros.rs @@ -86,6 +86,8 @@ macro_rules! tls_socket { $(enable_rsa_signatures $enable_rsa_signatures:expr,)? $(reset_max_fragment_length $mfl:expr,)? ) => { + use $crate::timestamp; + let seed = $crate::some_or_none!($($seed)?); let seed = seed.unwrap_or(timestamp!()); let cert = $crate::some_or_none!($($cert)?); From 6bf169009cacfde748574db1c6b9690c1d75e64b Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 28 Oct 2024 19:19:22 +0000 Subject: [PATCH 10/44] fix: tls macros --- src/socket/macros.rs | 3 +++ src/socket/tls.rs | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/socket/macros.rs b/src/socket/macros.rs index b6cdb7d..7fe1c56 100644 --- a/src/socket/macros.rs +++ b/src/socket/macros.rs @@ -47,10 +47,12 @@ macro_rules! tls_socket { reset_max_fragment_length $mfl:expr, ) => { use core::net::Ipv4Addr; + use core::str::FromStr; use $crate::socket::state::Ready; use $crate::types::TlsSocketOptions; use $crate::socket::tcp::TcpSocket; use $crate::socket::tls::TlsSocket; + use $crate::traits::io::Open; use $crate::socket::{error::SocketError, SocketAddr, SocketAddrV4}; let mut $name: Result, SocketError> = Err(SocketError::Other); @@ -170,6 +172,7 @@ macro_rules! read { #[macro_export] macro_rules! write { ($buf:ident => $socket:ident) => {{ + use core::slice::SlicePattern; $socket.write_all($buf.as_slice()) }}; } diff --git a/src/socket/tls.rs b/src/socket/tls.rs index 2b24ca6..945ed01 100644 --- a/src/socket/tls.rs +++ b/src/socket/tls.rs @@ -1,5 +1,7 @@ #![allow(clippy::module_name_repetitions)] +use core::fmt::Debug; + use alloc::string::String; use embedded_io::{ErrorType, Read, Write}; use embedded_tls::{blocking::TlsConnection, Aes128GcmSha256, NoVerify, TlsConfig, TlsContext}; @@ -27,6 +29,9 @@ pub const MAX_FRAGMENT_LENGTH: u16 = 16_384; /// A TLS socket. /// This is a wrapper around a [`TcpSocket`] that provides a TLS connection. +/// +/// # Notes +/// For the Debug trait a dummy implementation is provided. pub struct TlsSocket<'a, S: SocketState = NotReady> { /// The TLS connection tls_connection: TlsConnection<'a, TcpSocket, Aes128GcmSha256>, @@ -36,6 +41,12 @@ pub struct TlsSocket<'a, S: SocketState = NotReady> { _marker: core::marker::PhantomData, } +impl Debug for TlsSocket<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("TlsSocket").finish() + } +} + impl<'a> TlsSocket<'_> { /// Create a new TLS socket. /// This will create a new TLS connection using the provided [`TcpSocket`]. From 547d3a22f75a45f2caee0a6ea826f73290a1d070 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 28 Oct 2024 19:58:27 +0000 Subject: [PATCH 11/44] feat: allow to set TCP socket flags --- src/socket/macros.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/socket/macros.rs b/src/socket/macros.rs index 7fe1c56..4adf1a4 100644 --- a/src/socket/macros.rs +++ b/src/socket/macros.rs @@ -1,3 +1,5 @@ +use alloc::string::ToString; + #[macro_export] /// Get the current timestamp macro_rules! timestamp { @@ -27,6 +29,9 @@ macro_rules! timestamp { /// - `enable_rsa_signatures`: (Optional, default `true`) Whether to enable RSA signatures /// - `reset_max_fragment_length`: (Optional, default `false`) Whether to reset the max fragment length /// +/// # Safety +/// - The macro will panic if the provided IP address is invalid +/// /// # Example /// ```no_run /// tls_socket! { @@ -40,6 +45,8 @@ macro_rules! tls_socket { ( name: $name:ident, host $host:expr => $remote:expr, + send_flags $send_flags:expr, + recv_flags $recv_flags:expr, seed $seed:expr, cert $cert:expr, ca $ca:expr, @@ -60,7 +67,14 @@ macro_rules! tls_socket { let ip = Ipv4Addr::from_str($remote).unwrap(); let addr = SocketAddr::V4(SocketAddrV4::new(ip, 443)); let s = TcpSocket::new(); - if let Ok(s) = s { + + if let Ok(mut s) = s { + if let Some(send_flags) = $send_flags { + s.set_send_flags(send_flags); + } + if let Some(recv_flags) = $recv_flags { + s.set_recv_flags(recv_flags); + } let s = s.connect(addr); if let Ok(s) = s { let mut read_buf = TlsSocket::new_buffer(); @@ -82,6 +96,8 @@ macro_rules! tls_socket { ( name: $name:ident, host $host:expr => $remote:expr, + $(send_flags $send_flags:expr,)? + $(recv_flags $recv_flags:expr,)? $(seed $seed:expr,)? $(cert $cert:expr,)? $(ca $ca:expr,)? @@ -98,10 +114,14 @@ macro_rules! tls_socket { let enable_rsa_signatures = enable_rsa_signatures.unwrap_or(true); let reset_max_fragment_length = $crate::some_or_none!($($mfl)?); let reset_max_fragment_length = reset_max_fragment_length.unwrap_or(false); + let send_flags = $crate::some_or_none!($($send_flags)?); + let recv_flags = $crate::some_or_none!($($recv_flags)?); tls_socket! { name: $name, host $host => $remote, + send_flags send_flags, + recv_flags recv_flags, seed seed, cert cert, ca ca, @@ -112,11 +132,17 @@ macro_rules! tls_socket { ( name: $name:ident, host $host:expr => $remote:expr, + $(send_flags $send_flags:expr,)? + $(recv_flags $recv_flags:expr,)? opts $opts:expr, ) => { + let send_flags = $crate::some_or_none!($($send_flags)?); + let recv_flags = $crate::some_or_none!($($recv_flags)?); tls_socket! { name: $name, host $host => $remote, + send_flags send_flags, + recv_flags recv_flags, seed $opts.seed(), cert $opts.cert(), ca $opts.ca(), From 4ddf52c045f2a4e4940df297533e7fc715691bdc Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Mon, 25 Nov 2024 16:40:48 +0000 Subject: [PATCH 12/44] feat: http request macros --- src/http/macros.rs | 123 ++++++++++++++++++++++--------------------- src/http/mod.rs | 20 +++++++ src/http/request.rs | 19 +++++-- src/socket/macros.rs | 2 - 4 files changed, 100 insertions(+), 64 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index 79cacfb..5504318 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -1,10 +1,14 @@ +use alloc::{string::ToString, vec::Vec}; + +use crate::http::{ContentType, Method}; + +/// Macro helping craft HTTP requests /// /// # Example /// Example GET request /// ```no_run /// request! { -/// host "www.example.com", -/// GET > "/", +/// "www.example.com" get "/", /// "User-Agent" => "Mozilla/5.0", /// } /// ``` @@ -12,86 +16,87 @@ /// Example POST request /// ```no_run /// request! { -/// host "www.example.com", -/// POST > "/users/create", -/// content_type "application/json", +/// "www.example.com" post "/users/create", +/// content_type ContentType::ApplicationJson, /// body body, #[macro_export] macro_rules! request { ( - host $host:expr, - GET $uri:expr, + $host:tt get $uri:tt, $($header:expr => $value:expr,)* ) => { - request! { - host $host, - $crate::http::Method::Get => $uri, - $($header => $value,)* + { + use alloc::string::ToString; + use alloc::vec::Vec; + use alloc::vec as a_vec; + $crate::http::Request { + method: $crate::http::Method::Get, + uri: $uri.to_string(), + headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], + content_type: None, + body: Vec::new(), + http_version: $crate::http::HttpVersion::V1_1, + } } }; ( - host $host:expr, - POST $uri:expr, - $(content_type $content_type:expr,)? - $($header:expr => $value:expr,)* + $host:tt post $uri:tt $($content_type:expr)?, + $($header:tt => $value:tt),* $(body $body:expr)? ) => { - request! { - host $host, - $crate::http::Method::Post => $uri, - $(content_type $content_type,)? - $($header => $value,)* - $(body $body)? + { + use alloc::string::ToString; + use alloc::vec::Vec; + use alloc::vec as a_vec; + $crate::http::Request { + method: $crate::http::Method::Post, + uri: $uri.to_string(), + headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], + content_type: $crate::some_or_none!($($content_type)?), + body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), + http_version: $crate::http::HttpVersion::V1_1, + } } }; ( - host $host:expr, - PUT $uri:expr, - $(content_type $content_type:expr,)? - $($header:expr => $value:expr,)* + $host:tt put $uri:tt $($content_type:expr)?, + $($header:tt => $value:tt),* $(body $body:expr)? ) => { - request! { - host $host, - $crate::http::Method::Put => $uri, - $(content_type $content_type,)? - $($header => $value,)* - $(body $body)? + { + use alloc::string::ToString; + use alloc::vec::Vec; + use alloc::vec as a_vec; + $crate::http::Request { + method: $crate::http::Method::Put, + uri: $uri.to_string(), + headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], + content_type: $crate::some_or_none!($($content_type)?), + body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), + http_version: $crate::http::HttpVersion::V1_1, + } } }; ( - host $host:expr, - DELETE $uri:expr, - $(content_type $content_type:expr,)? - $($header:expr => $value:expr,)* + $host:tt delete $uri:tt $($content_type:expr)?, + $($header:tt => $value:tt),* + $(body $body:expr)? ) => { - request! { - host $host, - $crate::http::Method::Delete => $uri, - $(content_type $content_type,)? - $($header => $value,)* - $(body $body)? + { + use alloc::string::ToString; + use alloc::vec::Vec; + use alloc::vec as a_vec; + $crate::http::Request { + method: $crate::http::Method::Delete, + uri: $uri.to_string(), + headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], + content_type: $crate::some_or_none!($($content_type)?), + body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), + http_version: $crate::http::HttpVersion::V1_1, + } } }; - - ( - host $host:expr, - $method:expr => $uri:expr, - $(content_type $content_type:expr,)? - $($header:expr => $value:expr,)* - $(body $body:expr)? - ) => {{ - use alloc::string::ToString; - use alloc::vec as a_vec; - $crate::http::Request { - method: $method, - uri: $uri.to_string(), - headers: a_vec![$(($header.to_string(), $value.to_string()),)*], - content_type: $crate::some_or_none!($($content_type)?), - body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), - } - }}; } diff --git a/src/http/mod.rs b/src/http/mod.rs index ec9b772..ee4524e 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -6,6 +6,26 @@ use alloc::string::String; pub mod macros; mod request; +/// Enum for different supported HTTP versions +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum HttpVersion { + V1, + #[default] + V1_1, + V2, +} + +impl fmt::Display for HttpVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + HttpVersion::V1 => write!(f, "HTTP/1"), + HttpVersion::V1_1 => write!(f, "HTTP/1.1"), + HttpVersion::V2 => write!(f, "HTTP/2"), + } + } +} + +/// Content Type of the HTTP packet's body. #[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum ContentType { #[default] diff --git a/src/http/request.rs b/src/http/request.rs index 77c3f20..c13077a 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -1,8 +1,14 @@ use core::fmt; -use alloc::{format, string::String, vec::Vec}; +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; +use rand_chacha::rand_core::impls; +use regex::bytes; -use super::ContentType; +use super::{ContentType, HttpVersion}; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum Method { @@ -27,6 +33,7 @@ impl fmt::Display for Method { #[derive(Debug, Clone, PartialEq, Eq)] pub struct Request { pub method: Method, + pub http_version: HttpVersion, pub uri: String, pub headers: Vec<(String, String)>, pub body: Vec, @@ -36,6 +43,7 @@ pub struct Request { impl fmt::Display for Request { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut headers_and_body = String::new(); + for (header, value) in &self.headers { headers_and_body.push_str(format!("{header}: {value}\n").as_str()); } @@ -47,6 +55,11 @@ impl fmt::Display for Request { headers_and_body .push_str(format!("\n{}\n", String::from_utf8_lossy(&self.body)).as_str()); } - write!(f, "{} {}\n{}", self.method, self.uri, headers_and_body) + + write!( + f, + "{} {} {}\n{}", + self.method, self.uri, self.http_version, headers_and_body + ) } } diff --git a/src/socket/macros.rs b/src/socket/macros.rs index 4adf1a4..1fb830c 100644 --- a/src/socket/macros.rs +++ b/src/socket/macros.rs @@ -1,5 +1,3 @@ -use alloc::string::ToString; - #[macro_export] /// Get the current timestamp macro_rules! timestamp { From 337987a5dba9d5557e18a96d43199e1fedb0ee25 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Tue, 26 Nov 2024 23:36:15 +0000 Subject: [PATCH 13/44] feat: add configurable http version to request macro --- src/http/macros.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index 5504318..548de1f 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -16,13 +16,13 @@ use crate::http::{ContentType, Method}; /// Example POST request /// ```no_run /// request! { -/// "www.example.com" post "/users/create", -/// content_type ContentType::ApplicationJson, +/// "www.example.com" post "/users/create" ContentType::ApplicationJson, /// body body, +/// ``` #[macro_export] macro_rules! request { ( - $host:tt get $uri:tt, + $host:tt get $uri:tt $(ver $http_version:expr)?, $($header:expr => $value:expr,)* ) => { { @@ -35,13 +35,13 @@ macro_rules! request { headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: None, body: Vec::new(), - http_version: $crate::http::HttpVersion::V1_1, + http_version: $crate::some_or_none!($(http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), } } }; ( - $host:tt post $uri:tt $($content_type:expr)?, + $host:tt post $uri:tt $($content_type:expr)? $(ver $http_version:expr)?, $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -55,13 +55,13 @@ macro_rules! request { headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), - http_version: $crate::http::HttpVersion::V1_1, + http_version: $crate::some_or_none!($(http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), } } }; ( - $host:tt put $uri:tt $($content_type:expr)?, + $host:tt put $uri:tt $($content_type:expr)? $(ver $http_version:expr)?, $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -75,13 +75,13 @@ macro_rules! request { headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), - http_version: $crate::http::HttpVersion::V1_1, + http_version: $crate::some_or_none!($(http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), } } }; ( - $host:tt delete $uri:tt $($content_type:expr)?, + $host:tt delete $uri:tt $($content_type:expr)? $(ver $http_version:expr)?, $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -95,7 +95,7 @@ macro_rules! request { headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), - http_version: $crate::http::HttpVersion::V1_1, + http_version: $crate::some_or_none!($(http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), } } }; From 816da8579659a9df0dbff9a7c73d2df080a70144 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Tue, 26 Nov 2024 23:40:11 +0000 Subject: [PATCH 14/44] fix: disallowed token after `expr` fragments --- src/http/macros.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index 548de1f..2f0f85f 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -22,7 +22,7 @@ use crate::http::{ContentType, Method}; #[macro_export] macro_rules! request { ( - $host:tt get $uri:tt $(ver $http_version:expr)?, + $host:tt get $uri:tt $(; $http_version:expr)?, $($header:expr => $value:expr,)* ) => { { @@ -41,7 +41,7 @@ macro_rules! request { }; ( - $host:tt post $uri:tt $($content_type:expr)? $(ver $http_version:expr)?, + $host:tt post $uri:tt $($content_type:expr)? $(; $http_version:expr)?, $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -61,7 +61,7 @@ macro_rules! request { }; ( - $host:tt put $uri:tt $($content_type:expr)? $(ver $http_version:expr)?, + $host:tt put $uri:tt $($content_type:expr)? $(; $http_version:expr)?, $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -81,7 +81,7 @@ macro_rules! request { }; ( - $host:tt delete $uri:tt $($content_type:expr)? $(ver $http_version:expr)?, + $host:tt delete $uri:tt $($content_type:expr)? $(; $http_version:expr)?, $($header:tt => $value:tt),* $(body $body:expr)? ) => { From 52ae666bb5c659123608c32a7920fdf436156f68 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Tue, 26 Nov 2024 23:41:35 +0000 Subject: [PATCH 15/44] style: fix clippy warnings --- src/http/macros.rs | 4 ---- src/http/mod.rs | 2 ++ src/http/request.rs | 8 +------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index 2f0f85f..263b009 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -1,7 +1,3 @@ -use alloc::{string::ToString, vec::Vec}; - -use crate::http::{ContentType, Method}; - /// Macro helping craft HTTP requests /// /// # Example diff --git a/src/http/mod.rs b/src/http/mod.rs index ee4524e..ccf7c1d 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1,3 +1,5 @@ +#![allow(clippy::module_name_repetitions)] + use core::fmt; use alloc::string::String; diff --git a/src/http/request.rs b/src/http/request.rs index c13077a..8556f48 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -1,12 +1,6 @@ use core::fmt; -use alloc::{ - format, - string::{String, ToString}, - vec::Vec, -}; -use rand_chacha::rand_core::impls; -use regex::bytes; +use alloc::{format, string::String, vec::Vec}; use super::{ContentType, HttpVersion}; From 3dd04a7a829a5869bd7b4a5c76894889dda00532 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Tue, 26 Nov 2024 23:56:53 +0000 Subject: [PATCH 16/44] fix: typo --- src/http/macros.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index 263b009..ca2bf0c 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -31,13 +31,13 @@ macro_rules! request { headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: None, body: Vec::new(), - http_version: $crate::some_or_none!($(http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), + http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), } } }; ( - $host:tt post $uri:tt $($content_type:expr)? $(; $http_version:expr)?, + $host:tt post $uri:tt $($content_type:expr)? $(=> $http_version:expr)?, $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -51,7 +51,7 @@ macro_rules! request { headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), - http_version: $crate::some_or_none!($(http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), + http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), } } }; @@ -71,7 +71,7 @@ macro_rules! request { headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), - http_version: $crate::some_or_none!($(http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), + http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), } } }; @@ -91,7 +91,7 @@ macro_rules! request { headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), - http_version: $crate::some_or_none!($(http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), + http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), } } }; From b92deb99c6e7f63ec8d37540deee0b71e7d93768 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Tue, 26 Nov 2024 23:57:46 +0000 Subject: [PATCH 17/44] fix: typo --- src/http/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index ca2bf0c..a7a085a 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -37,7 +37,7 @@ macro_rules! request { }; ( - $host:tt post $uri:tt $($content_type:expr)? $(=> $http_version:expr)?, + $host:tt post $uri:tt $($content_type:expr)? $(; $http_version:expr)?, $($header:tt => $value:tt),* $(body $body:expr)? ) => { From 276bf25375d9cb411192ffebe6f6df6de165e674 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Wed, 27 Nov 2024 00:10:13 +0000 Subject: [PATCH 18/44] refactor: rename uri to path --- src/http/macros.rs | 20 ++++++++++++-------- src/http/request.rs | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index a7a085a..b33b786 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -1,5 +1,7 @@ /// Macro helping craft HTTP requests /// +/// # Parameters +/// /// # Example /// Example GET request /// ```no_run @@ -15,10 +17,12 @@ /// "www.example.com" post "/users/create" ContentType::ApplicationJson, /// body body, /// ``` +/// +/// Exa #[macro_export] macro_rules! request { ( - $host:tt get $uri:tt $(; $http_version:expr)?, + $host:tt get $path:tt $(; $http_version:expr)?, $($header:expr => $value:expr,)* ) => { { @@ -27,7 +31,7 @@ macro_rules! request { use alloc::vec as a_vec; $crate::http::Request { method: $crate::http::Method::Get, - uri: $uri.to_string(), + path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: None, body: Vec::new(), @@ -37,7 +41,7 @@ macro_rules! request { }; ( - $host:tt post $uri:tt $($content_type:expr)? $(; $http_version:expr)?, + $host:tt post $path:tt $($content_type:expr)? $(; $http_version:expr)?, $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -47,7 +51,7 @@ macro_rules! request { use alloc::vec as a_vec; $crate::http::Request { method: $crate::http::Method::Post, - uri: $uri.to_string(), + path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), @@ -57,7 +61,7 @@ macro_rules! request { }; ( - $host:tt put $uri:tt $($content_type:expr)? $(; $http_version:expr)?, + $host:tt put $path:tt $($content_type:expr)? $(; $http_version:expr)?, $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -67,7 +71,7 @@ macro_rules! request { use alloc::vec as a_vec; $crate::http::Request { method: $crate::http::Method::Put, - uri: $uri.to_string(), + path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), @@ -77,7 +81,7 @@ macro_rules! request { }; ( - $host:tt delete $uri:tt $($content_type:expr)? $(; $http_version:expr)?, + $host:tt delete $path:tt $($content_type:expr)? $(; $http_version:expr)?, $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -87,7 +91,7 @@ macro_rules! request { use alloc::vec as a_vec; $crate::http::Request { method: $crate::http::Method::Delete, - uri: $uri.to_string(), + path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), diff --git a/src/http/request.rs b/src/http/request.rs index 8556f48..1987563 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -28,7 +28,7 @@ impl fmt::Display for Method { pub struct Request { pub method: Method, pub http_version: HttpVersion, - pub uri: String, + pub path: String, pub headers: Vec<(String, String)>, pub body: Vec, pub content_type: Option, @@ -53,7 +53,7 @@ impl fmt::Display for Request { write!( f, "{} {} {}\n{}", - self.method, self.uri, self.http_version, headers_and_body + self.method, self.path, self.http_version, headers_and_body ) } } From 749c2ef0f06d25225d4c99ea8b29895a1937d4b9 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Wed, 27 Nov 2024 00:19:07 +0000 Subject: [PATCH 19/44] feat: add request render function --- src/http/request.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/http/request.rs b/src/http/request.rs index 1987563..d1503b7 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -1,9 +1,16 @@ use core::fmt; -use alloc::{format, string::String, vec::Vec}; +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; use super::{ContentType, HttpVersion}; +/// HTTP request method +/// +/// Defaults to [`Method::Get`] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum Method { #[default] @@ -24,6 +31,14 @@ impl fmt::Display for Method { } } +/// HTTP request +/// +/// # Fields +/// - [`method`]: HTTP request method +/// - [`http_version`]: HTTP version +/// - [`path`]: HTTP path +/// - [`headers`]: HTTP headers +/// - [`body`]: HTTP body #[derive(Debug, Clone, PartialEq, Eq)] pub struct Request { pub method: Method, @@ -57,3 +72,13 @@ impl fmt::Display for Request { ) } } + +impl Request { + /// Render the request as a vector of bytes + /// + /// # Returns + /// A vector of bytes, representing the request + pub fn render(&self) -> Vec { + self.to_string().into_bytes() + } +} From 275add3c7594c787d084d7cdf998d07752aee45a Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Wed, 27 Nov 2024 00:22:35 +0000 Subject: [PATCH 20/44] docs: improve request macro examples --- src/http/macros.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index b33b786..59d2c76 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -1,9 +1,11 @@ /// Macro helping craft HTTP requests /// +/// By default, the HTTP version is set to 1.1. +/// /// # Parameters /// -/// # Example -/// Example GET request +/// # Examples +/// ## Example GET request /// ```no_run /// request! { /// "www.example.com" get "/", @@ -11,14 +13,19 @@ /// } /// ``` /// -/// Example POST request +/// ## Example POST request /// ```no_run /// request! { /// "www.example.com" post "/users/create" ContentType::ApplicationJson, /// body body, /// ``` /// -/// Exa +/// ## Example With HTTP 1.0 +/// ```no_run +/// request! { +/// "www.example.com" get "/"; HttpVersion::V1, +/// } +/// ``` #[macro_export] macro_rules! request { ( From dceb676a42fb2d1e4b5ac0952e1a556f2d392537 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Wed, 27 Nov 2024 00:39:05 +0000 Subject: [PATCH 21/44] docs: improve request macro examples --- src/http/macros.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index 59d2c76..00dff72 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -2,8 +2,6 @@ /// /// By default, the HTTP version is set to 1.1. /// -/// # Parameters -/// /// # Examples /// ## Example GET request /// ```no_run @@ -26,6 +24,16 @@ /// "www.example.com" get "/"; HttpVersion::V1, /// } /// ``` +/// +/// ## Example With Formatted Header +/// ```no_run +/// request! { +/// "www.example.com" get "/"; HttpVersion::V1, +/// /// enclose the header value in parentheses if it is not +/// /// a string, or more specifically a single token tree (tt). +/// "User-Agent" => (format!("Mozilla/5.0 ({})", "test")), +/// } +/// ``` #[macro_export] macro_rules! request { ( From bf4c7fb44424b98a2c7ebfd915a3a3a217ffc4f6 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Fri, 6 Dec 2024 23:53:36 +0000 Subject: [PATCH 22/44] feat: gate httparse behind crate flag --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 55e3dc3..9913bc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ rust-version = "1.81" [features] default = ["http"] macros = [] -http = [] +http = ["dep:httparse"] [dependencies] psp = { version = "0.3.10" } @@ -27,5 +27,5 @@ lazy_static = { version = "1.5.0", default-features = false, features = [ "spin_no_std", ] } bitflags = { version = "2.6.0", default-features = false } -httparse = { version = "1.9.5", default-features = false } +httparse = { version = "1.9.5", default-features = false, optional = true} # reqwless = {version = "0.13.0", features = ["alloc"]} From 8c97bbae3cbca51bdeb026709afaf7eb2734df6e Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Fri, 6 Dec 2024 23:54:49 +0000 Subject: [PATCH 23/44] feat: wip --- src/http/macros.rs | 23 ++++++++ src/http/mod.rs | 11 +++- src/http/request.rs | 39 +++++++++++++ src/http/response.rs | 133 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 src/http/response.rs diff --git a/src/http/macros.rs b/src/http/macros.rs index 00dff72..4797cd5 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -57,6 +57,7 @@ macro_rules! request { ( $host:tt post $path:tt $($content_type:expr)? $(; $http_version:expr)?, + $(authorization $auth:expr,)? $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -115,3 +116,25 @@ macro_rules! request { } }; } + +macro_rules! new_response { + () => {}; +} + +#[macro_export] +macro_rules! parse_response { + ( + $response:expr, + $(max_headers $max_headers:tt,)? + ) => {{ + use alloc::vec; + let me = $crate::some_or_none!($($max_headers)?).unwrap_or(16); + let mut headers = vec![httparse::EMPTY_HEADER; me]; + + let mut res = httparse::Response::new(&mut headers); + + let parsed = + httparse::ParserConfig::default().parse_response(&mut res, $response.as_bytes()); + parsed + }}; +} diff --git a/src/http/mod.rs b/src/http/mod.rs index ccf7c1d..0b7327a 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -7,14 +7,16 @@ use alloc::string::String; #[cfg(feature = "macros")] pub mod macros; mod request; +mod response; /// Enum for different supported HTTP versions #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum HttpVersion { + /// HTTP/1 V1, + /// HTTP/1.1 #[default] V1_1, - V2, } impl fmt::Display for HttpVersion { @@ -22,7 +24,6 @@ impl fmt::Display for HttpVersion { match self { HttpVersion::V1 => write!(f, "HTTP/1"), HttpVersion::V1_1 => write!(f, "HTTP/1.1"), - HttpVersion::V2 => write!(f, "HTTP/2"), } } } @@ -31,9 +32,13 @@ impl fmt::Display for HttpVersion { #[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum ContentType { #[default] + /// text/plain TextPlain, + /// application/json ApplicationJson, + /// application/octet-stream OctetStream, + /// Any other content type, as a string Other(String), } @@ -51,3 +56,5 @@ impl fmt::Display for ContentType { // re-exports pub type Method = request::Method; pub type Request = request::Request; + +pub type Response<'a, 'b> = httparse::Response<'a, 'b>; diff --git a/src/http/request.rs b/src/http/request.rs index d1503b7..0a3ca87 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -8,15 +8,51 @@ use alloc::{ use super::{ContentType, HttpVersion}; +/// HTTP basic authorization type +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BasicAuthorization { + /// Provide ID and password + IdPassword(String, String), + /// Provide the already encoded string "ID:Password" + Encoded(String), +} + +/// HTTP authorization type +/// +/// Defaults to [`Authorization::Basic`] +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub enum Authorization { + #[default] + /// No authorization + None, + /// Basic authorization + /// + /// # Fields + /// - first: ID + /// - second: Password + Basic(BasicAuthorization), + /// Bearer authorization + /// + /// # Fields + /// - first: Bearer token + Bearer(String), + /// Any other authorization, as a string + Other(String), +} + /// HTTP request method /// /// Defaults to [`Method::Get`] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum Method { + /// GET #[default] Get, + /// POST Post, + /// PUT Put, + /// DELETE Delete, } @@ -43,6 +79,7 @@ impl fmt::Display for Method { pub struct Request { pub method: Method, pub http_version: HttpVersion, + pub authorization: Authorization, pub path: String, pub headers: Vec<(String, String)>, pub body: Vec, @@ -53,6 +90,8 @@ impl fmt::Display for Request { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut headers_and_body = String::new(); + if !matches!(self.authorization, Authorization::None) {} + for (header, value) in &self.headers { headers_and_body.push_str(format!("{header}: {value}\n").as_str()); } diff --git a/src/http/response.rs b/src/http/response.rs new file mode 100644 index 0000000..89d82e6 --- /dev/null +++ b/src/http/response.rs @@ -0,0 +1,133 @@ +use alloc::{string::String, vec::Vec}; +use bitflags::bitflags; + +use crate::parse_response; + +use super::HttpVersion; + +pub struct Response { + pub http_version: HttpVersion, + pub status: Code, + pub headers: Vec<(String, String)>, + pub body: Vec, +} + +bitflags! { + pub struct XCodes: u16 { + const Ok = 200; + const Created = 201; + const Accepted = 202; + const NonAuthoritativeInformation = 203; + const NoContent = 204; + const ResetContent = 205; + const PartialContent = 206; + const MultipleChoices = 300; + const MovedPermanently = 301; + const Found = 302; + const SeeOther = 303; + const NotModified = 304; + const UseProxy = 305; + const TemporaryRedirect = 307; + const PermanentRedirect = 308; + const BadRequest = 400; + const Unauthorized = 401; + const PaymentRequired = 402; + const Forbidden = 403; + const NotFound = 404; + const MethodNotAllowed = 405; + const InternalServerError = 500; + } +} + +#[repr(u16)] +pub enum Code { + Ok = 200, + Created = 201, + Accepted = 202, + NonAuthoritativeInformation = 203, + NoContent = 204, + ResetContent = 205, + PartialContent = 206, + MultipleChoices = 300, + MovedPermanently = 301, + Found = 302, + SeeOther = 303, + NotModified = 304, + UseProxy = 305, + TemporaryRedirect = 307, + PermanentRedirect = 308, + BadRequest = 400, + Unauthorized = 401, + PaymentRequired = 402, + Forbidden = 403, + NotFound = 404, + MethodNotAllowed = 405, + InternalServerError = 500, + Other(u16), +} + +impl From for u16 { + fn from(value: Code) -> Self { + match value { + Code::Other(x) => x, + _ => u16::from(value), + } + } +} + +impl From for Code { + fn from(value: u16) -> Self { + match value { + 200 => Code::Ok, + 201 => Code::Created, + 202 => Code::Accepted, + 203 => Code::NonAuthoritativeInformation, + 204 => Code::NoContent, + 205 => Code::ResetContent, + 206 => Code::PartialContent, + 300 => Code::MultipleChoices, + 301 => Code::MovedPermanently, + 302 => Code::Found, + 303 => Code::SeeOther, + 304 => Code::NotModified, + 305 => Code::UseProxy, + 307 => Code::TemporaryRedirect, + 308 => Code::PermanentRedirect, + 400 => Code::BadRequest, + 401 => Code::Unauthorized, + 402 => Code::PaymentRequired, + 403 => Code::Forbidden, + 404 => Code::NotFound, + 405 => Code::MethodNotAllowed, + 500 => Code::InternalServerError, + x => Code::Other(x), + } + } +} + +fn parse_responsex(response: String) { + let resx = response.clone(); + let mut headers = [httparse::EMPTY_HEADER; 16]; + let mut res = httparse::Response::new(&mut headers); + + let r = httparse::ParserConfig::default() + .allow_multiple_spaces_in_response_status_delimiters(true) + .parse_response(&mut res, response.as_bytes()) + .unwrap(); + if r.is_partial() { + panic!("Partial response"); + } else { + res.code; + } + + let x = parse_response! { + resx, + max_headers 16, + }; + if let Ok(x) = x { + if x.is_complete() { + res.code; + if let Code::Ok = Code::from(res.code.unwrap()) {} + } + } +} From fa9ea3507bc28bb61077b3ea8cbef26467c473e1 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Fri, 6 Dec 2024 23:55:32 +0000 Subject: [PATCH 24/44] chore: update deps --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f813c1..8763741 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,9 +543,9 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "regex" -version = "1.10.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "regex-automata", "regex-syntax", @@ -553,18 +553,18 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "sec1" diff --git a/Cargo.toml b/Cargo.toml index 9913bc8..bbe7769 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ embedded-tls = { version = "0.17.0", default-features = false } embedded-io = { version = "0.6.1", default-features = false } rand = { version = "0.8.5", default-features = false } rand_chacha = { version = "0.3.1", default-features = false } -regex = { version = "1.10", default-features = false } +regex = { version = "1.11", default-features = false } lazy_static = { version = "1.5.0", default-features = false, features = [ "spin_no_std", ] } From dba3ad4e8f2eae4138068f659958cd1c1cdb1c9f Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sat, 7 Dec 2024 18:10:11 +0000 Subject: [PATCH 25/44] wip --- src/http/response.rs | 88 ++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/src/http/response.rs b/src/http/response.rs index 89d82e6..86cb7ce 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -1,10 +1,14 @@ -use alloc::{string::String, vec::Vec}; -use bitflags::bitflags; +use alloc::{ + borrow::ToOwned, + string::{String, ToString}, + vec::Vec, +}; use crate::parse_response; use super::HttpVersion; +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Response { pub http_version: HttpVersion, pub status: Code, @@ -12,33 +16,7 @@ pub struct Response { pub body: Vec, } -bitflags! { - pub struct XCodes: u16 { - const Ok = 200; - const Created = 201; - const Accepted = 202; - const NonAuthoritativeInformation = 203; - const NoContent = 204; - const ResetContent = 205; - const PartialContent = 206; - const MultipleChoices = 300; - const MovedPermanently = 301; - const Found = 302; - const SeeOther = 303; - const NotModified = 304; - const UseProxy = 305; - const TemporaryRedirect = 307; - const PermanentRedirect = 308; - const BadRequest = 400; - const Unauthorized = 401; - const PaymentRequired = 402; - const Forbidden = 403; - const NotFound = 404; - const MethodNotAllowed = 405; - const InternalServerError = 500; - } -} - +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum Code { Ok = 200, @@ -64,6 +42,16 @@ pub enum Code { MethodNotAllowed = 405, InternalServerError = 500, Other(u16), + Unparsable, +} + +impl From> for Code { + fn from(value: Option) -> Self { + match value { + Some(x) => x.into(), + None => Code::Unparsable, + } + } } impl From for u16 { @@ -106,7 +94,7 @@ impl From for Code { } fn parse_responsex(response: String) { - let resx = response.clone(); + // let resx = response.clone(); let mut headers = [httparse::EMPTY_HEADER; 16]; let mut res = httparse::Response::new(&mut headers); @@ -120,14 +108,36 @@ fn parse_responsex(response: String) { res.code; } - let x = parse_response! { - resx, - max_headers 16, - }; - if let Ok(x) = x { - if x.is_complete() { - res.code; - if let Code::Ok = Code::from(res.code.unwrap()) {} - } + if r.is_complete() { + let mut body = Vec::with_capacity(16000); + res.parse(&body); + // if let Code::Ok = res.code.unwrap().into() {} + + let resp = Response { + http_version: HttpVersion::V1_1, + status: res.code.into(), + headers: res + .headers + .iter() + .map(|x| { + let val: String = String::from_utf8_lossy(x.value).to_string(); + (x.name.into(), val) + }) + .collect::<_>(), + body: "todo!()".into(), + }; + + resp.body; } + + // let x = parse_response! { + // response, + // max_headers 16, + // }; + // if let Ok(x) = x { + // if x.is_complete() { + // res.code; + // if let Code::Ok = res.code.into() {} + // } + // } } From 47f00f32579bb204a8a8e9f8df99dcf3d31aa1c4 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sat, 7 Dec 2024 19:11:11 +0000 Subject: [PATCH 26/44] wip --- src/http/response.rs | 2 -- src/socket/error.rs | 8 ++++++-- src/socket/macros.rs | 11 ++++++++--- src/socket/udp.rs | 6 ++++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/http/response.rs b/src/http/response.rs index 86cb7ce..8de45cb 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -4,8 +4,6 @@ use alloc::{ vec::Vec, }; -use crate::parse_response; - use super::HttpVersion; #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/src/socket/error.rs b/src/socket/error.rs index d649907..9d62cc8 100644 --- a/src/socket/error.rs +++ b/src/socket/error.rs @@ -1,15 +1,19 @@ use core::fmt::Display; +use alloc::string::String; + /// An error that can occur with a socket -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum SocketError { /// Unsupported address family UnsupportedAddressFamily, /// Socket error with errno Errno(i32), /// Other error + Other(String), + /// Unknown error #[default] - Other, + Unknown, } impl Display for SocketError { diff --git a/src/socket/macros.rs b/src/socket/macros.rs index 1fb830c..b251e90 100644 --- a/src/socket/macros.rs +++ b/src/socket/macros.rs @@ -21,6 +21,8 @@ macro_rules! timestamp { /// # Parameters /// - `name`: The name of the variable where the socket will be stored /// - `host`: The hostname and address to connect to +/// - `send_flags`: (Optional) The send flags to be used (by the underlying TCP socket) +/// - `recv_flags`: (Optional) The receive flags to be used (by the underlying TCP socket) /// - `seed`: (Optional) The seed to use for the RNG, if not provided, the current timestamp is used /// - `cert`: (Optional) The certificate to use /// - `ca`: (Optional) The CA to use @@ -51,6 +53,7 @@ macro_rules! tls_socket { enable_rsa_signatures $enable_rsa_signatures:expr, reset_max_fragment_length $mfl:expr, ) => { + use alloc::format; use core::net::Ipv4Addr; use core::str::FromStr; use $crate::socket::state::Ready; @@ -60,7 +63,7 @@ macro_rules! tls_socket { use $crate::traits::io::Open; use $crate::socket::{error::SocketError, SocketAddr, SocketAddrV4}; - let mut $name: Result, SocketError> = Err(SocketError::Other); + let mut $name: Result, SocketError> = Err(SocketError::Unknown); let ip = Ipv4Addr::from_str($remote).unwrap(); let addr = SocketAddr::V4(SocketAddrV4::new(ip, 443)); @@ -85,10 +88,12 @@ macro_rules! tls_socket { options.set_reset_max_fragment_length($mfl); let $name = $name.open(&options); } else { - $name = Err(s.err().unwrap()); + $name = Err(SocketError::Other( + format!("Failed to connect to {}: {}", $host, s.err().unwrap()))); } } else { - $name = Err(s.err().unwrap()); + $name = Err(SocketError::Other( + format!("Failed to create socket: {}", s.err().unwrap()))); } }; ( diff --git a/src/socket/udp.rs b/src/socket/udp.rs index 155746b..94ac180 100644 --- a/src/socket/udp.rs +++ b/src/socket/udp.rs @@ -1,6 +1,6 @@ #![allow(clippy::module_name_repetitions)] -use alloc::vec::Vec; +use alloc::{borrow::ToOwned, vec::Vec}; use core::net::{IpAddr, Ipv4Addr, SocketAddr}; use embedded_io::{ErrorType, Read, Write}; use psp::sys::{self, sockaddr, socklen_t}; @@ -228,7 +228,9 @@ impl UdpSocket { mut self, buf: &mut [u8], ) -> Result<(usize, UdpSocket), SocketError> { - let mut sockaddr = self.remote.ok_or(SocketError::Other)?; + let mut sockaddr = self + .remote + .ok_or(SocketError::Other("Remote not set".to_owned()))?; let result = unsafe { sys::sceNetInetRecvfrom( *self.fd, From 18eead969e1d68ac7f1eba28b1ae8069324ddfcf Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sat, 7 Dec 2024 19:17:32 +0000 Subject: [PATCH 27/44] fix: missing authorization field in macro --- src/http/macros.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/http/macros.rs b/src/http/macros.rs index 4797cd5..658c12e 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -38,6 +38,7 @@ macro_rules! request { ( $host:tt get $path:tt $(; $http_version:expr)?, + $(authorization $auth:expr,)? $($header:expr => $value:expr,)* ) => { { @@ -48,6 +49,7 @@ macro_rules! request { method: $crate::http::Method::Get, path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], + authorization: $crate::some_or_none!($($auth)?), content_type: None, body: Vec::new(), http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), @@ -69,6 +71,7 @@ macro_rules! request { method: $crate::http::Method::Post, path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], + authorization: $crate::some_or_none!($($auth)?), content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), @@ -78,6 +81,7 @@ macro_rules! request { ( $host:tt put $path:tt $($content_type:expr)? $(; $http_version:expr)?, + $(authorization $auth:expr,)? $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -89,6 +93,7 @@ macro_rules! request { method: $crate::http::Method::Put, path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], + $(authorization $auth:expr,)? content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), @@ -98,6 +103,7 @@ macro_rules! request { ( $host:tt delete $path:tt $($content_type:expr)? $(; $http_version:expr)?, + $(authorization $auth:expr,)? $($header:tt => $value:tt),* $(body $body:expr)? ) => { @@ -109,6 +115,7 @@ macro_rules! request { method: $crate::http::Method::Delete, path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], + authorization: $crate::some_or_none!($($auth)?), content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), From 6ca2caad4d256b6d183db4f0e550856b91ae84d1 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sat, 7 Dec 2024 19:21:00 +0000 Subject: [PATCH 28/44] fix: authorization field in request macro --- src/http/macros.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index 658c12e..9038480 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -45,11 +45,12 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::Authorization::None); $crate::http::Request { method: $crate::http::Method::Get, path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], - authorization: $crate::some_or_none!($($auth)?), + authorization: auth, content_type: None, body: Vec::new(), http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), @@ -67,11 +68,12 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::Authorization::None); $crate::http::Request { method: $crate::http::Method::Post, path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], - authorization: $crate::some_or_none!($($auth)?), + authorization: auth, content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), @@ -89,11 +91,12 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::Authorization::None); $crate::http::Request { method: $crate::http::Method::Put, path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], - $(authorization $auth:expr,)? + authorization: auth, content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), @@ -111,11 +114,12 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::Authorization::None); $crate::http::Request { method: $crate::http::Method::Delete, path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], - authorization: $crate::some_or_none!($($auth)?), + authorization: auth, content_type: $crate::some_or_none!($($content_type)?), body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), From ae99263ad79ec6aec38b369ec54f455b4ada0497 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sat, 7 Dec 2024 19:23:49 +0000 Subject: [PATCH 29/44] fix: import --- src/http/macros.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index 9038480..95d7b52 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -45,7 +45,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; - let auth = some_or_none!($($auth)?).unwrap_or($crate::http::Authorization::None); + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::request::Authorization::None); $crate::http::Request { method: $crate::http::Method::Get, path: $path.to_string(), @@ -68,7 +68,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; - let auth = some_or_none!($($auth)?).unwrap_or($crate::http::Authorization::None); + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::request::Authorization::None); $crate::http::Request { method: $crate::http::Method::Post, path: $path.to_string(), @@ -91,7 +91,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; - let auth = some_or_none!($($auth)?).unwrap_or($crate::http::Authorization::None); + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::request::Authorization::None); $crate::http::Request { method: $crate::http::Method::Put, path: $path.to_string(), @@ -114,7 +114,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; - let auth = some_or_none!($($auth)?).unwrap_or($crate::http::Authorization::None); + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::request::Authorization::None); $crate::http::Request { method: $crate::http::Method::Delete, path: $path.to_string(), From a5b933e422b27c98b167680cb5a55636ea1d04c9 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sat, 7 Dec 2024 19:35:50 +0000 Subject: [PATCH 30/44] refactor: http sub-modules --- src/http/mod.rs | 28 +-------------------- src/http/request.rs | 37 +++------------------------- src/http/types.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 60 deletions(-) create mode 100644 src/http/types.rs diff --git a/src/http/mod.rs b/src/http/mod.rs index 0b7327a..fe20b55 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -2,12 +2,11 @@ use core::fmt; -use alloc::string::String; - #[cfg(feature = "macros")] pub mod macros; mod request; mod response; +pub mod types; /// Enum for different supported HTTP versions #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] @@ -28,31 +27,6 @@ impl fmt::Display for HttpVersion { } } -/// Content Type of the HTTP packet's body. -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub enum ContentType { - #[default] - /// text/plain - TextPlain, - /// application/json - ApplicationJson, - /// application/octet-stream - OctetStream, - /// Any other content type, as a string - Other(String), -} - -impl fmt::Display for ContentType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ContentType::TextPlain => write!(f, "text/plain"), - ContentType::ApplicationJson => write!(f, "application/json"), - ContentType::OctetStream => write!(f, "application/octet-stream"), - ContentType::Other(s) => write!(f, "{s}"), - } - } -} - // re-exports pub type Method = request::Method; pub type Request = request::Request; diff --git a/src/http/request.rs b/src/http/request.rs index 0a3ca87..ea1638d 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -6,39 +6,10 @@ use alloc::{ vec::Vec, }; -use super::{ContentType, HttpVersion}; - -/// HTTP basic authorization type -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum BasicAuthorization { - /// Provide ID and password - IdPassword(String, String), - /// Provide the already encoded string "ID:Password" - Encoded(String), -} - -/// HTTP authorization type -/// -/// Defaults to [`Authorization::Basic`] -#[derive(Debug, Default, Clone, PartialEq, Eq)] -pub enum Authorization { - #[default] - /// No authorization - None, - /// Basic authorization - /// - /// # Fields - /// - first: ID - /// - second: Password - Basic(BasicAuthorization), - /// Bearer authorization - /// - /// # Fields - /// - first: Bearer token - Bearer(String), - /// Any other authorization, as a string - Other(String), -} +use super::{ + types::{Authorization, ContentType}, + HttpVersion, +}; /// HTTP request method /// diff --git a/src/http/types.rs b/src/http/types.rs new file mode 100644 index 0000000..2657e1a --- /dev/null +++ b/src/http/types.rs @@ -0,0 +1,59 @@ +use alloc::string::String; +use core::fmt; + +/// HTTP basic authorization type +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BasicAuthorization { + /// Provide ID and password + IdPassword(String, String), + /// Provide the already encoded string "ID:Password" + Encoded(String), +} + +/// HTTP authorization type +/// +/// Defaults to [`Authorization::Basic`] +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub enum Authorization { + #[default] + /// No authorization + None, + /// Basic authorization + /// + /// # Fields + /// - first: ID + /// - second: Password + Basic(BasicAuthorization), + /// Bearer authorization + /// + /// # Fields + /// - first: Bearer token + Bearer(String), + /// Any other authorization, as a string + Other(String), +} + +/// Content Type of the HTTP packet's body. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub enum ContentType { + #[default] + /// text/plain + TextPlain, + /// application/json + ApplicationJson, + /// application/octet-stream + OctetStream, + /// Any other content type, as a string + Other(String), +} + +impl fmt::Display for ContentType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ContentType::TextPlain => write!(f, "text/plain"), + ContentType::ApplicationJson => write!(f, "application/json"), + ContentType::OctetStream => write!(f, "application/octet-stream"), + ContentType::Other(s) => write!(f, "{s}"), + } + } +} From 2ee680ff9ef62a7b4015fe6c758d52d3aa0f53b5 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sat, 7 Dec 2024 19:39:52 +0000 Subject: [PATCH 31/44] fix: macros --- Cargo.toml | 3 ++- src/http/macros.rs | 12 ++++------ src/http/response.rs | 55 +------------------------------------------- 3 files changed, 7 insertions(+), 63 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bbe7769..608e451 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,8 @@ rust-version = "1.81" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["http"] +# TODO: revert to empty default +default = ["http", "macros"] macros = [] http = ["dep:httparse"] diff --git a/src/http/macros.rs b/src/http/macros.rs index 95d7b52..af4e35f 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -45,7 +45,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; - let auth = some_or_none!($($auth)?).unwrap_or($crate::http::request::Authorization::None); + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); $crate::http::Request { method: $crate::http::Method::Get, path: $path.to_string(), @@ -68,7 +68,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; - let auth = some_or_none!($($auth)?).unwrap_or($crate::http::request::Authorization::None); + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); $crate::http::Request { method: $crate::http::Method::Post, path: $path.to_string(), @@ -91,7 +91,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; - let auth = some_or_none!($($auth)?).unwrap_or($crate::http::request::Authorization::None); + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); $crate::http::Request { method: $crate::http::Method::Put, path: $path.to_string(), @@ -114,7 +114,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; - let auth = some_or_none!($($auth)?).unwrap_or($crate::http::request::Authorization::None); + let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); $crate::http::Request { method: $crate::http::Method::Delete, path: $path.to_string(), @@ -128,10 +128,6 @@ macro_rules! request { }; } -macro_rules! new_response { - () => {}; -} - #[macro_export] macro_rules! parse_response { ( diff --git a/src/http/response.rs b/src/http/response.rs index 8de45cb..e1ff9ab 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -1,8 +1,4 @@ -use alloc::{ - borrow::ToOwned, - string::{String, ToString}, - vec::Vec, -}; +use alloc::{string::String, vec::Vec}; use super::HttpVersion; @@ -90,52 +86,3 @@ impl From for Code { } } } - -fn parse_responsex(response: String) { - // let resx = response.clone(); - let mut headers = [httparse::EMPTY_HEADER; 16]; - let mut res = httparse::Response::new(&mut headers); - - let r = httparse::ParserConfig::default() - .allow_multiple_spaces_in_response_status_delimiters(true) - .parse_response(&mut res, response.as_bytes()) - .unwrap(); - if r.is_partial() { - panic!("Partial response"); - } else { - res.code; - } - - if r.is_complete() { - let mut body = Vec::with_capacity(16000); - res.parse(&body); - // if let Code::Ok = res.code.unwrap().into() {} - - let resp = Response { - http_version: HttpVersion::V1_1, - status: res.code.into(), - headers: res - .headers - .iter() - .map(|x| { - let val: String = String::from_utf8_lossy(x.value).to_string(); - (x.name.into(), val) - }) - .collect::<_>(), - body: "todo!()".into(), - }; - - resp.body; - } - - // let x = parse_response! { - // response, - // max_headers 16, - // }; - // if let Ok(x) = x { - // if x.is_complete() { - // res.code; - // if let Code::Ok = res.code.into() {} - // } - // } -} From 05436b9c6e9327ecd19b0aeae5f3b3d9be0d9c51 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sat, 7 Dec 2024 19:42:05 +0000 Subject: [PATCH 32/44] fix: missing import in macro --- src/http/macros.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/http/macros.rs b/src/http/macros.rs index af4e35f..7d2a526 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -45,6 +45,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; + use psp_net::some_or_none; let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); $crate::http::Request { method: $crate::http::Method::Get, @@ -68,6 +69,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; + use psp_net::some_or_none; let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); $crate::http::Request { method: $crate::http::Method::Post, @@ -91,6 +93,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; + use psp_net::some_or_none; let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); $crate::http::Request { method: $crate::http::Method::Put, @@ -114,6 +117,7 @@ macro_rules! request { use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; + use psp_net::some_or_none; let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); $crate::http::Request { method: $crate::http::Method::Delete, From 76926bf20aeea83c0020250a38cfa2e2082b1e01 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sun, 8 Dec 2024 15:34:13 +0000 Subject: [PATCH 33/44] feat: psp dep optional to allow tests --- Cargo.lock | 23 +++++++++++++++-------- Cargo.toml | 9 +++++---- src/lib.rs | 6 +++++- src/traits/mod.rs | 1 + src/types/mod.rs | 3 +++ 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8763741..b164213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.6.0" @@ -401,18 +407,18 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro2", "quote", @@ -437,9 +443,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "polyval" @@ -479,9 +485,9 @@ dependencies = [ [[package]] name = "psp" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10e4e12cb65d4c15d08be4326fb6887e7f4f28359e4e517553d75690fe8f16e" +checksum = "ad5aaa74ac3077dfbbb2d13f19cc4f9d32534f4c620dedaa2fb2f706191d3b46" dependencies = [ "bitflags", "libm", @@ -495,6 +501,7 @@ dependencies = [ name = "psp-net" version = "0.5.4" dependencies = [ + "base64", "bitflags", "dns-protocol", "embedded-io", diff --git a/Cargo.toml b/Cargo.toml index 608e451..e04cf74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,13 @@ rust-version = "1.81" [features] # TODO: revert to empty default -default = ["http", "macros"] +default = ["http", "macros", "psp"] macros = [] http = ["dep:httparse"] +psp = ["dep:psp"] [dependencies] -psp = { version = "0.3.10" } +psp = { version = "0.3.11", optional = true} dns-protocol = { version = "0.1.1", default-features = false } embedded-tls = { version = "0.17.0", default-features = false } embedded-io = { version = "0.6.1", default-features = false } @@ -28,5 +29,5 @@ lazy_static = { version = "1.5.0", default-features = false, features = [ "spin_no_std", ] } bitflags = { version = "2.6.0", default-features = false } -httparse = { version = "1.9.5", default-features = false, optional = true} -# reqwless = {version = "0.13.0", features = ["alloc"]} +httparse = { version = "1.9.5", default-features = false, optional = true } +base64 = {version = "0.22", default-features = false, features = ["alloc"] } diff --git a/src/lib.rs b/src/lib.rs index f36641d..d766ba0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +#![cfg_attr(not(test), no_std)] #![feature(trait_alias)] #![doc = include_str!("../README.md")] #![allow(clippy::cast_sign_loss)] @@ -9,13 +9,17 @@ extern crate alloc; pub mod constants; +#[cfg(feature = "psp")] pub mod dns; #[cfg(feature = "http")] pub mod http; #[cfg(feature = "macros")] pub mod macros; +#[cfg(feature = "psp")] pub mod netc; +#[cfg(feature = "psp")] pub mod socket; pub mod traits; pub mod types; +#[cfg(feature = "psp")] pub mod utils; diff --git a/src/traits/mod.rs b/src/traits/mod.rs index fa98265..6188b75 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -3,6 +3,7 @@ use alloc::vec::Vec; use core::fmt::Debug; +#[cfg(feature = "psp")] pub mod dns; pub mod io; diff --git a/src/types/mod.rs b/src/types/mod.rs index 3c7f2ef..df9e1ef 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,4 +1,5 @@ mod socket_flags; +#[cfg(feature = "psp")] mod socket_options; // re-exports @@ -6,5 +7,7 @@ pub type Certificate<'a> = embedded_tls::Certificate<'a>; pub use socket_flags::SocketRecvFlags; pub use socket_flags::SocketSendFlags; +#[cfg(feature = "psp")] pub use socket_options::SocketOptions; +#[cfg(feature = "psp")] pub use socket_options::TlsSocketOptions; From 028fc93c2b8f6969d4339871180f3473651c49db Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sun, 8 Dec 2024 15:34:28 +0000 Subject: [PATCH 34/44] ci: steup tests --- .github/workflows/test.yaml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..99c1713 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,31 @@ +name: Tests + +on: + push: + branches: + - master + pull_request: + branches: + - master + +env: + CARGO_TERM_COLOR: always + TOOLCHAIN_VERSION: nightly-2024-06-19 + +jobs: + test: + name: Run tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.TOOLCHAIN_VERSION }} + override: true + + - uses: actions-rs/cargo@v1 + with: + command: test + args: --no-default-features --features=http,macros From 0e501e868f02ee3b94f3b82931d713035495bb23 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sun, 8 Dec 2024 15:34:38 +0000 Subject: [PATCH 35/44] tests: setup --- src/http/macros.rs | 11 ++++++--- src/http/types.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index 7d2a526..cef1d24 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -5,6 +5,7 @@ /// # Examples /// ## Example GET request /// ```no_run +/// use psp_net::request; /// request! { /// "www.example.com" get "/", /// "User-Agent" => "Mozilla/5.0", @@ -13,6 +14,7 @@ /// /// ## Example POST request /// ```no_run +/// use psp_net::request; /// request! { /// "www.example.com" post "/users/create" ContentType::ApplicationJson, /// body body, @@ -20,19 +22,22 @@ /// /// ## Example With HTTP 1.0 /// ```no_run +/// use psp_net::request; +/// use psp_net::http::HttpVersion; /// request! { /// "www.example.com" get "/"; HttpVersion::V1, -/// } +/// }; /// ``` /// /// ## Example With Formatted Header /// ```no_run +/// use psp_net::request; /// request! { /// "www.example.com" get "/"; HttpVersion::V1, /// /// enclose the header value in parentheses if it is not /// /// a string, or more specifically a single token tree (tt). /// "User-Agent" => (format!("Mozilla/5.0 ({})", "test")), -/// } +/// }; /// ``` #[macro_export] macro_rules! request { @@ -42,7 +47,7 @@ macro_rules! request { $($header:expr => $value:expr,)* ) => { { - use alloc::string::ToString; + use alloc::{String, string::ToString}; use alloc::vec::Vec; use alloc::vec as a_vec; use psp_net::some_or_none; diff --git a/src/http/types.rs b/src/http/types.rs index 2657e1a..441ca53 100644 --- a/src/http/types.rs +++ b/src/http/types.rs @@ -1,15 +1,29 @@ -use alloc::string::String; +use alloc::{format, string::String}; +use base64::Engine; use core::fmt; /// HTTP basic authorization type #[derive(Debug, Clone, PartialEq, Eq)] pub enum BasicAuthorization { - /// Provide ID and password + /// Provide ID and password. Calling `to_string` will return the encoded string, + /// or any other method relying on the `Display` trait IdPassword(String, String), /// Provide the already encoded string "ID:Password" Encoded(String), } +impl fmt::Display for BasicAuthorization { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BasicAuthorization::IdPassword(id, password) => { + let engine = base64::engine::general_purpose::STANDARD; + write!(f, "{}", engine.encode(format!("{id}:{password}"))) + } + BasicAuthorization::Encoded(encoded) => write!(f, "{encoded}"), + } + } +} + /// HTTP authorization type /// /// Defaults to [`Authorization::Basic`] @@ -33,6 +47,17 @@ pub enum Authorization { Other(String), } +impl fmt::Display for Authorization { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Authorization::None => write!(f, ""), + Authorization::Basic(basic_authorization) => write!(f, "Basic {basic_authorization}"), + Authorization::Bearer(token) => write!(f, "Bearer {token}"), + Authorization::Other(s) => write!(f, "{s}"), + } + } +} + /// Content Type of the HTTP packet's body. #[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum ContentType { @@ -57,3 +82,35 @@ impl fmt::Display for ContentType { } } } + +#[cfg(test)] +mod tests { + use super::*; + use alloc::string::{String, ToString}; + + macro_rules! table_tests { + ($func: expr, $($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + $func($value); + } + )* + } + } + + fn test_authorization(value: (Authorization, String)) { + let (authorization, expected) = value; + let actual = authorization.to_string(); + assert_eq!(actual, expected); + } + macro_rules! authorization_tests { + ($($name:ident: $value:expr,)*) => { + table_tests!{test_authorization, $($name: $value,)*} + } + } + + authorization_tests! { + none: (Authorization::None, String::new()), + } +} From 58a417390d6c1df4d269690059759d1a890e2e5d Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sun, 8 Dec 2024 15:43:26 +0000 Subject: [PATCH 36/44] test: add authorization tests --- src/http/types.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/http/types.rs b/src/http/types.rs index 441ca53..8e52dd9 100644 --- a/src/http/types.rs +++ b/src/http/types.rs @@ -1,4 +1,4 @@ -use alloc::{format, string::String}; +use alloc::{borrow::ToOwned, format, string::String}; use base64::Engine; use core::fmt; @@ -12,6 +12,38 @@ pub enum BasicAuthorization { Encoded(String), } +impl BasicAuthorization { + fn new(id: &str, password: &str) -> Self { + BasicAuthorization::IdPassword(id.to_owned(), password.to_owned()) + } + fn new_encoded(encoded: &str) -> Self { + BasicAuthorization::Encoded(encoded.to_owned()) + } +} + +impl From<(&str, &str)> for BasicAuthorization { + fn from((id, password): (&str, &str)) -> Self { + BasicAuthorization::new(id, password) + } +} +impl From<&str> for BasicAuthorization { + fn from(encoded: &str) -> Self { + BasicAuthorization::new_encoded(encoded) + } +} + +impl From for BasicAuthorization { + fn from(encoded: String) -> Self { + BasicAuthorization::new_encoded(&encoded) + } +} + +impl From<(String, String)> for BasicAuthorization { + fn from((id, password): (String, String)) -> Self { + BasicAuthorization::new(&id, &password) + } +} + impl fmt::Display for BasicAuthorization { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -112,5 +144,8 @@ mod tests { authorization_tests! { none: (Authorization::None, String::new()), + basic: (Authorization::Basic(BasicAuthorization::new("user", "password")), "Basic dXNlcjpwYXNzd29yZA==".to_string()), + bearer: (Authorization::Bearer("token".to_string()), "Bearer token".to_string()), + other: (Authorization::Other("other".to_string()), "other".to_string()), } } From b00120bb83322e95c4c88437191d0aa312ebf2de Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sun, 8 Dec 2024 16:32:20 +0000 Subject: [PATCH 37/44] wip --- src/http/macros.rs | 124 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 110 insertions(+), 14 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index cef1d24..e4ccb41 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -5,35 +5,45 @@ /// # Examples /// ## Example GET request /// ```no_run -/// use psp_net::request; -/// request! { +/// # extern crate alloc; +/// # use psp_net::request; +/// # use alloc::string::String; +/// let req = request! { /// "www.example.com" get "/", -/// "User-Agent" => "Mozilla/5.0", -/// } +/// "User-Agent" => "Mozilla/5.0" +/// }; /// ``` /// /// ## Example POST request /// ```no_run -/// use psp_net::request; -/// request! { +/// # extern crate alloc; +/// # use psp_net::request; +/// # use alloc::string::String; +/// # let body = "test".to_string(); +/// let req = request! { /// "www.example.com" post "/users/create" ContentType::ApplicationJson, -/// body body, +/// body body +/// }; /// ``` /// /// ## Example With HTTP 1.0 /// ```no_run -/// use psp_net::request; -/// use psp_net::http::HttpVersion; -/// request! { +/// # extern crate alloc; +/// # use psp_net::request; +/// # use alloc::string::String; +/// # use psp_net::http::HttpVersion; +/// let req = request! { /// "www.example.com" get "/"; HttpVersion::V1, /// }; /// ``` /// /// ## Example With Formatted Header /// ```no_run -/// use psp_net::request; -/// request! { -/// "www.example.com" get "/"; HttpVersion::V1, +/// # extern crate alloc; +/// # use psp_net::request; +/// # use alloc::string::String; +/// let req = request! { +/// "www.example.com" get "/", /// /// enclose the header value in parentheses if it is not /// /// a string, or more specifically a single token tree (tt). /// "User-Agent" => (format!("Mozilla/5.0 ({})", "test")), @@ -41,13 +51,34 @@ /// ``` #[macro_export] macro_rules! request { + ( + $host:tt get $path:tt $(; $http_version:expr)?, + $($header:expr => $value:expr,)* + ) => { + { + use alloc::string::{String, ToString}; + use alloc::vec::Vec; + use alloc::vec as a_vec; + use psp_net::some_or_none; + let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); + $crate::_request! { + http_version http_ver, + host $host, + path $path, + method $crate::http::Method::Get, + auth $crate::http::types::Authorization::None, + $($header => $value),* + } + } + }; + ( $host:tt get $path:tt $(; $http_version:expr)?, $(authorization $auth:expr,)? $($header:expr => $value:expr,)* ) => { { - use alloc::{String, string::ToString}; + use alloc::string::{String, ToString}; use alloc::vec::Vec; use alloc::vec as a_vec; use psp_net::some_or_none; @@ -154,3 +185,68 @@ macro_rules! parse_response { parsed }}; } + +#[macro_export] +#[doc(hidden)] +macro_rules! _request { + // no body, no content type + ( + http_version $http_version:expr, + host $host:tt, + path $path:tt, + method $method:tt, + auth $auth:expr, + $(header $header:tt => $value:tt),* + ) => { + $crate::http::Request { + method: $crate::http::Method::Get, + path: $path.to_string(), + headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], + authorization: auth, + content_type: None, + body: Vec::new(), + http_version: $http_version, + } + }; + // body, no content type + ( + http_version $http_version:expr, + host $host:tt, + path $path:tt, + method $method:tt, + auth $auth:expr, + $(header $header:tt => $value:tt),* + body $body:expr + ) => { + $crate::http::Request { + method: $method, + path: $path.to_string(), + headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], + authorization: $auth, + content_type: None, + body: $body, + http_version: $http_version, + } + }; + // body and content type + ( + http_version $http_version:expr, + host $host:tt, + path $path:tt, + method $method:tt, + auth $auth:expr, + content_type $content_type:expr, + body $body:expr, + $(header $header:tt => $value:tt),* + ) => { + $crate::http::Request { + method: $method, + path: $path.to_string(), + headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], + authorization: $auth, + content_type: $content_type, + body: $body, + http_version: $http_version, + } + }; +} From de567cd47e97f7f9d6db4760bc428950ef3d4ef4 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Sun, 8 Dec 2024 17:09:25 +0000 Subject: [PATCH 38/44] fix: _request macro --- src/http/macros.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index e4ccb41..8049b0c 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -10,7 +10,7 @@ /// # use alloc::string::String; /// let req = request! { /// "www.example.com" get "/", -/// "User-Agent" => "Mozilla/5.0" +/// "User-Agent" => "Mozilla/5.0", /// }; /// ``` /// @@ -67,7 +67,7 @@ macro_rules! request { path $path, method $crate::http::Method::Get, auth $crate::http::types::Authorization::None, - $($header => $value),* + $($header => $value,)* } } }; @@ -194,15 +194,15 @@ macro_rules! _request { http_version $http_version:expr, host $host:tt, path $path:tt, - method $method:tt, + method $method:expr, auth $auth:expr, - $(header $header:tt => $value:tt),* + $($header:expr => $value:expr,)* ) => { $crate::http::Request { method: $crate::http::Method::Get, path: $path.to_string(), headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], - authorization: auth, + authorization: $auth, content_type: None, body: Vec::new(), http_version: $http_version, @@ -213,10 +213,10 @@ macro_rules! _request { http_version $http_version:expr, host $host:tt, path $path:tt, - method $method:tt, + method $method:expr, auth $auth:expr, - $(header $header:tt => $value:tt),* - body $body:expr + body $body:expr, + $($header:expr => $value:expr,)* ) => { $crate::http::Request { method: $method, @@ -233,11 +233,11 @@ macro_rules! _request { http_version $http_version:expr, host $host:tt, path $path:tt, - method $method:tt, + method $method:expr, auth $auth:expr, content_type $content_type:expr, body $body:expr, - $(header $header:tt => $value:tt),* + $($header:expr => $value:expr,)* ) => { $crate::http::Request { method: $method, From d9d70a700a6504f9c14253b1ea4cb3fe663f853c Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Tue, 10 Dec 2024 20:52:19 +0000 Subject: [PATCH 39/44] fix: request rendering --- src/http/request.rs | 6 ++++-- src/http/types.rs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/http/request.rs b/src/http/request.rs index ea1638d..4a79ffe 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -61,7 +61,9 @@ impl fmt::Display for Request { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut headers_and_body = String::new(); - if !matches!(self.authorization, Authorization::None) {} + if !matches!(self.authorization, Authorization::None) { + headers_and_body.push_str(format!("Authorization: {}\n", self.authorization).as_str()); + } for (header, value) in &self.headers { headers_and_body.push_str(format!("{header}: {value}\n").as_str()); @@ -77,7 +79,7 @@ impl fmt::Display for Request { write!( f, - "{} {} {}\n{}", + "{} {} {}\n{}\n", self.method, self.path, self.http_version, headers_and_body ) } diff --git a/src/http/types.rs b/src/http/types.rs index 8e52dd9..89e8e2f 100644 --- a/src/http/types.rs +++ b/src/http/types.rs @@ -13,10 +13,10 @@ pub enum BasicAuthorization { } impl BasicAuthorization { - fn new(id: &str, password: &str) -> Self { + pub fn new(id: &str, password: &str) -> Self { BasicAuthorization::IdPassword(id.to_owned(), password.to_owned()) } - fn new_encoded(encoded: &str) -> Self { + pub fn new_encoded(encoded: &str) -> Self { BasicAuthorization::Encoded(encoded.to_owned()) } } From 19e65cab3a7b2dbf07af9e288cb4192d332860b1 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Tue, 10 Dec 2024 20:52:35 +0000 Subject: [PATCH 40/44] test: write test for _request macro --- src/http/macros.rs | 276 +++++++++++++++++++++++++++++++++------------ 1 file changed, 203 insertions(+), 73 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index 8049b0c..36e4774 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -4,30 +4,31 @@ /// /// # Examples /// ## Example GET request -/// ```no_run +/// ``` /// # extern crate alloc; /// # use psp_net::request; /// # use alloc::string::String; /// let req = request! { /// "www.example.com" get "/", -/// "User-Agent" => "Mozilla/5.0", +/// "User-Agent" => "Mozilla/5.0"; /// }; /// ``` /// /// ## Example POST request -/// ```no_run +/// ``` /// # extern crate alloc; /// # use psp_net::request; /// # use alloc::string::String; /// # let body = "test".to_string(); /// let req = request! { /// "www.example.com" post "/users/create" ContentType::ApplicationJson, -/// body body +/// body body, +/// "User-Agent" => (format!("Mozilla/5.0 ({})", "test")); /// }; /// ``` /// /// ## Example With HTTP 1.0 -/// ```no_run +/// ``` /// # extern crate alloc; /// # use psp_net::request; /// # use alloc::string::String; @@ -38,7 +39,7 @@ /// ``` /// /// ## Example With Formatted Header -/// ```no_run +/// ``` /// # extern crate alloc; /// # use psp_net::request; /// # use alloc::string::String; @@ -46,20 +47,21 @@ /// "www.example.com" get "/", /// /// enclose the header value in parentheses if it is not /// /// a string, or more specifically a single token tree (tt). -/// "User-Agent" => (format!("Mozilla/5.0 ({})", "test")), +/// "User-Agent" => (format!("Mozilla/5.0 ({})", "test")); /// }; /// ``` #[macro_export] macro_rules! request { + // get, no authorization ( $host:tt get $path:tt $(; $http_version:expr)?, - $($header:expr => $value:expr,)* + $($header:expr => $value:expr;)* ) => { { - use alloc::string::{String, ToString}; + use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; - use psp_net::some_or_none; + let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); $crate::_request! { http_version http_ver, @@ -67,38 +69,87 @@ macro_rules! request { path $path, method $crate::http::Method::Get, auth $crate::http::types::Authorization::None, - $($header => $value,)* + body Vec::new(), + $($header => $value;)* } } }; + // get, with authorization ( $host:tt get $path:tt $(; $http_version:expr)?, - $(authorization $auth:expr,)? - $($header:expr => $value:expr,)* + authorization $auth:expr, + $($header:expr => $value:expr;)* ) => { { - use alloc::string::{String, ToString}; + use alloc::string::ToString; use alloc::vec::Vec; use alloc::vec as a_vec; - use psp_net::some_or_none; - let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); - $crate::http::Request { - method: $crate::http::Method::Get, - path: $path.to_string(), - headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], - authorization: auth, - content_type: None, - body: Vec::new(), - http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), + + let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); + $crate::_request! { + http_version http_ver, + host $host, + path $path, + method $crate::http::Method::Get, + auth $auth, + body Vec::new(), + $($header => $value;)* + } + } + }; + + // post, no authorization + ( + $host:tt post $path:tt $($content_type:expr)? $(; $http_version:expr)?, + body $body:expr, + $($header:expr => $value:expr;)* + ) => { + { + let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); + $crate::_request! { + http_version http_ver, + host $host, + path $path, + method $crate::http::Method::Post, + auth $crate::http::types::Authorization::None, + content_type $crate::some_or_none!($($content_type)?), + body $body, + $($header => $value;)* } } }; + // post, with authorization ( $host:tt post $path:tt $($content_type:expr)? $(; $http_version:expr)?, - $(authorization $auth:expr,)? - $($header:tt => $value:tt),* + authorization $auth:expr, + body $body:expr, + $($header:expr => $value:expr;)* + ) => { + { + use alloc::string::ToString; + use alloc::vec::Vec; + use alloc::vec as a_vec; + use psp_net::some_or_none; + let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); + $crate::_request! { + http_version http_ver, + host $host, + path $path, + method $crate::http::Method::Post, + auth: $auth, + content_type $crate::some_or_none!($($content_type)?), + body $body, + $($header => $value;)* + }, + } + }; + + // put, no authorization + ( + $host:tt put $path:tt $($content_type:expr)? $(; $http_version:expr)?, + $($header:expr => $value:expr;)* $(body $body:expr)? ) => { { @@ -107,23 +158,27 @@ macro_rules! request { use alloc::vec as a_vec; use psp_net::some_or_none; let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); + let body = $crate::some_or_none!($($body)?).unwrap_or(Vec::new()); + let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); $crate::http::Request { - method: $crate::http::Method::Post, - path: $path.to_string(), - headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], - authorization: auth, + http_version: http_ver, + host: $host, + path: $path, + method: $crate::http::Method::Put, + authorization: $crate::http::types::Authorization::None, content_type: $crate::some_or_none!($($content_type)?), - body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), - http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), + body: $body, + $($header => $value;)* } } }; + // put, with authorization ( $host:tt put $path:tt $($content_type:expr)? $(; $http_version:expr)?, - $(authorization $auth:expr,)? - $($header:tt => $value:tt),* - $(body $body:expr)? + authorization $auth:expr, + $($header:expr => $value:expr;)* + body $body:expr, ) => { { use alloc::string::ToString; @@ -131,22 +186,25 @@ macro_rules! request { use alloc::vec as a_vec; use psp_net::some_or_none; let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); + let body = $crate::some_or_none!($($body)?).unwrap_or(Vec::new()); + let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); $crate::http::Request { + http_version: http_ver, + host: $host, + path: $path, method: $crate::http::Method::Put, - path: $path.to_string(), - headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], - authorization: auth, + authorization: $auth, content_type: $crate::some_or_none!($($content_type)?), - body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), - http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), + body: $body, + $($header => $value;)* } } }; + // delete, no authorization ( $host:tt delete $path:tt $($content_type:expr)? $(; $http_version:expr)?, - $(authorization $auth:expr,)? - $($header:tt => $value:tt),* + $($header:expr => $value:expr;)* $(body $body:expr)? ) => { { @@ -155,17 +213,21 @@ macro_rules! request { use alloc::vec as a_vec; use psp_net::some_or_none; let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); + let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); $crate::http::Request { + http_version: http_ver, + host: $host, + path: $path, method: $crate::http::Method::Delete, - path: $path.to_string(), - headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], - authorization: auth, + authorization: $auth, content_type: $crate::some_or_none!($($content_type)?), - body: $crate::some_or_none!($($body)?).unwrap_or(Vec::new()), - http_version: $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1), + body: body, + $($header => $value;)* } } }; + + // delete, with authorization } #[macro_export] @@ -188,27 +250,10 @@ macro_rules! parse_response { #[macro_export] #[doc(hidden)] +/// Internal macro to create a [`crate::http::Request`]. +/// It is not intended to be used directly, but serves as a support to [`request!`] macro. macro_rules! _request { - // no body, no content type - ( - http_version $http_version:expr, - host $host:tt, - path $path:tt, - method $method:expr, - auth $auth:expr, - $($header:expr => $value:expr,)* - ) => { - $crate::http::Request { - method: $crate::http::Method::Get, - path: $path.to_string(), - headers: a_vec![("Host".to_string(), $host.to_string()), $(($header.to_string(), $value.to_string()),)*], - authorization: $auth, - content_type: None, - body: Vec::new(), - http_version: $http_version, - } - }; - // body, no content type + // no content type ( http_version $http_version:expr, host $host:tt, @@ -216,8 +261,10 @@ macro_rules! _request { method $method:expr, auth $auth:expr, body $body:expr, - $($header:expr => $value:expr,)* - ) => { + $($header:expr => $value:expr;)* + ) => {{ + use alloc::string::ToString; + use alloc::vec as a_vec; $crate::http::Request { method: $method, path: $path.to_string(), @@ -227,8 +274,8 @@ macro_rules! _request { body: $body, http_version: $http_version, } - }; - // body and content type + }}; + // content type ( http_version $http_version:expr, host $host:tt, @@ -237,8 +284,10 @@ macro_rules! _request { auth $auth:expr, content_type $content_type:expr, body $body:expr, - $($header:expr => $value:expr,)* - ) => { + $($header:expr => $value:expr;)* + ) => {{ + use alloc::string::ToString; + use alloc::vec as a_vec; $crate::http::Request { method: $method, path: $path.to_string(), @@ -248,5 +297,86 @@ macro_rules! _request { body: $body, http_version: $http_version, } + }}; +} + +#[cfg(test)] +mod test { + use crate::http::{ + types::{Authorization, BasicAuthorization, ContentType}, + HttpVersion, Method, }; + + #[test] + fn test_get_request_no_authorization() { + let req = request! { + "www.example.com" get "/", + "User-Agent" => "Mozilla/5.0"; + }; + assert_eq!( + req.to_string(), + "GET / HTTP/1.1\nHost: www.example.com\nUser-Agent: Mozilla/5.0\n\n" + ); + } + + #[test] + fn test_get_request_with_authorization() { + let auth = Authorization::Basic(BasicAuthorization::new_encoded("dXNlcjpwYXNzd29yZA==")); + let req = request! { + "www.example.com" get "/", + authorization auth, + "User-Agent" => "Mozilla/5.0"; + }; + assert_eq!( + req.to_string(), + "GET / HTTP/1.1\nAuthorization: Basic dXNlcjpwYXNzd29yZA==\nHost: www.example.com\nUser-Agent: Mozilla/5.0\n\n" + ); + } + + fn test_post_request_no_authorization() { + let ct = ContentType::ApplicationJson; + let hv = HttpVersion::V1_1; + let body = Vec::new(); + let req = request! { + "www.example.com" post "/test" ct; hv, + body body, + "User-Agent" => "Mozilla/5.0"; + }; + assert_eq!( + req.to_string(), + "POST /users/create HTTP/1.1\nHost: www.example.com\nUser-Agent: Mozilla/5.0\n\n" + ); + } + + /// Test the macro [`_request!`] that is used internally as a support to [`request!`] + /// macro to create a [`Request`](crate::http::Request). + fn test_internal_request() { + // no content-type + let req = _request! { + http_version HttpVersion::V1_1, + host "www.example.com", + path "/", + method Method::Get, + auth Authorization::None, + body Vec::new(), + }; + + assert_eq!(req.to_string(), "GET / HTTP/1.1\nHost: www.example.com\n\n"); + + // content-type + let req = _request! { + http_version HttpVersion::V1_1, + host "www.example.com", + path "/", + method Method::Get, + auth Authorization::None, + content_type Some(ContentType::ApplicationJson), + body Vec::new(), + }; + + assert_eq!( + req.to_string(), + "GET / HTTP/1.1\nHost: www.example.com\nContent-Type: application/json\n\n" + ); + } } From 060beee7056e31dd6144011854730f473211c12f Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Tue, 10 Dec 2024 20:59:32 +0000 Subject: [PATCH 41/44] fix: tests --- src/http/macros.rs | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/http/macros.rs b/src/http/macros.rs index 36e4774..6e49336 100644 --- a/src/http/macros.rs +++ b/src/http/macros.rs @@ -17,9 +17,9 @@ /// ## Example POST request /// ``` /// # extern crate alloc; -/// # use psp_net::request; +/// # use psp_net::{request, http::types::ContentType}; /// # use alloc::string::String; -/// # let body = "test".to_string(); +/// # let body = "test".as_bytes().to_vec(); /// let req = request! { /// "www.example.com" post "/users/create" ContentType::ApplicationJson, /// body body, @@ -58,10 +58,6 @@ macro_rules! request { $($header:expr => $value:expr;)* ) => { { - use alloc::string::ToString; - use alloc::vec::Vec; - use alloc::vec as a_vec; - let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); $crate::_request! { http_version http_ver, @@ -82,10 +78,6 @@ macro_rules! request { $($header:expr => $value:expr;)* ) => { { - use alloc::string::ToString; - use alloc::vec::Vec; - use alloc::vec as a_vec; - let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); $crate::_request! { http_version http_ver, @@ -128,10 +120,6 @@ macro_rules! request { $($header:expr => $value:expr;)* ) => { { - use alloc::string::ToString; - use alloc::vec::Vec; - use alloc::vec as a_vec; - use psp_net::some_or_none; let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); $crate::_request! { http_version http_ver, @@ -153,10 +141,6 @@ macro_rules! request { $(body $body:expr)? ) => { { - use alloc::string::ToString; - use alloc::vec::Vec; - use alloc::vec as a_vec; - use psp_net::some_or_none; let auth = some_or_none!($($auth)?).unwrap_or($crate::http::types::Authorization::None); let body = $crate::some_or_none!($($body)?).unwrap_or(Vec::new()); let http_ver = $crate::some_or_none!($($http_version)?).unwrap_or($crate::http::HttpVersion::V1_1); @@ -333,23 +317,23 @@ mod test { ); } + #[test] fn test_post_request_no_authorization() { - let ct = ContentType::ApplicationJson; - let hv = HttpVersion::V1_1; let body = Vec::new(); let req = request! { - "www.example.com" post "/test" ct; hv, + "www.example.com" post "/test", body body, "User-Agent" => "Mozilla/5.0"; }; assert_eq!( req.to_string(), - "POST /users/create HTTP/1.1\nHost: www.example.com\nUser-Agent: Mozilla/5.0\n\n" + "POST /test HTTP/1.1\nHost: www.example.com\nUser-Agent: Mozilla/5.0\n\n" ); } /// Test the macro [`_request!`] that is used internally as a support to [`request!`] /// macro to create a [`Request`](crate::http::Request). + #[test] fn test_internal_request() { // no content-type let req = _request! { From 6c99aa2cdc5f496df4f122749d299eacd283ced4 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Tue, 10 Dec 2024 21:01:44 +0000 Subject: [PATCH 42/44] docs: improve documentation --- src/http/types.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/http/types.rs b/src/http/types.rs index 89e8e2f..4ecbb28 100644 --- a/src/http/types.rs +++ b/src/http/types.rs @@ -5,7 +5,7 @@ use core::fmt; /// HTTP basic authorization type #[derive(Debug, Clone, PartialEq, Eq)] pub enum BasicAuthorization { - /// Provide ID and password. Calling `to_string` will return the encoded string, + /// Provide ID and password. Calling [`to_string`](Self::to_string) will return the encoded string, /// or any other method relying on the `Display` trait IdPassword(String, String), /// Provide the already encoded string "ID:Password" @@ -13,9 +13,20 @@ pub enum BasicAuthorization { } impl BasicAuthorization { + /// Create a new basic authorization using ID and password. + /// + /// In particular, it returns a [`BasicAuthorization::IdPassword`] variant + /// of the [`BasicAuthorization`] enum. + #[must_use] pub fn new(id: &str, password: &str) -> Self { BasicAuthorization::IdPassword(id.to_owned(), password.to_owned()) } + + /// Create a new basic authorization using the already encoded string + /// + /// In particular, it returns a [`BasicAuthorization::Encoded`] variant + /// of the [`BasicAuthorization`] enum. + #[must_use] pub fn new_encoded(encoded: &str) -> Self { BasicAuthorization::Encoded(encoded.to_owned()) } From 61d774c3ed1ec805b1b630f1be770f36924552f0 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Tue, 10 Dec 2024 21:03:41 +0000 Subject: [PATCH 43/44] build: bump version and revert default features --- Cargo.lock | 2 +- Cargo.toml | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b164213..35fc5ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -499,7 +499,7 @@ dependencies = [ [[package]] name = "psp-net" -version = "0.5.4" +version = "0.6.1" dependencies = [ "base64", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index e04cf74..c384e33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "psp-net" -version = "0.5.4" +version = "0.6.1" edition = "2021" license-file = "LICENSE" keywords = ["psp", "net", "networking", "embedded", "gamedev"] @@ -11,8 +11,7 @@ rust-version = "1.81" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -# TODO: revert to empty default -default = ["http", "macros", "psp"] +default = ["psp"] macros = [] http = ["dep:httparse"] psp = ["dep:psp"] From bf350db29b0b0e2409e491dec5b959e1307053c3 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti Date: Tue, 10 Dec 2024 21:06:32 +0000 Subject: [PATCH 44/44] ci: bumps toolchain version --- .github/workflows/check-and-lint.yaml | 2 +- .github/workflows/publish.yaml | 2 +- .github/workflows/push-to-master.yaml | 2 +- .github/workflows/test.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-and-lint.yaml b/.github/workflows/check-and-lint.yaml index a4cb6e4..c2b6723 100644 --- a/.github/workflows/check-and-lint.yaml +++ b/.github/workflows/check-and-lint.yaml @@ -10,7 +10,7 @@ on: env: CARGO_TERM_COLOR: always - TOOLCHAIN_VERSION: nightly-2024-06-19 + TOOLCHAIN_VERSION: nightly-2024-12-09 jobs: fmt: diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index a44cf65..cb737b0 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -11,7 +11,7 @@ jobs: env: CARGO_TERM_COLOR: always - TOOLCHAIN_VERSION: nightly-2024-06-19 + TOOLCHAIN_VERSION: nightly-2024-12-09 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/push-to-master.yaml b/.github/workflows/push-to-master.yaml index a1eed52..c706fc0 100644 --- a/.github/workflows/push-to-master.yaml +++ b/.github/workflows/push-to-master.yaml @@ -11,7 +11,7 @@ jobs: env: CARGO_TERM_COLOR: always - TOOLCHAIN_VERSION: nightly-2024-06-19 + TOOLCHAIN_VERSION: nightly-2024-12-09 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 99c1713..98f5fc9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,7 +10,7 @@ on: env: CARGO_TERM_COLOR: always - TOOLCHAIN_VERSION: nightly-2024-06-19 + TOOLCHAIN_VERSION: nightly-2024-12-09 jobs: test: