Skip to content

Commit a8344bd

Browse files
authored
fix/feat: Improve io and fix serde features (#50)
* fix/feat: Improve io and fix serde features * fix: test with no default features Signed-off-by: Jeremy HERGAULT <jeremy.hergault@worldline.com>
1 parent 1b29fdb commit a8344bd

9 files changed

Lines changed: 171 additions & 11 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ thiserror = "2"
2828
aquamarine = "0.6"
2929
bytes = "1"
3030
chrono = "0.4"
31+
serde = { version = "1", features = ["derive"] }
3132
url = { version = "2", features = ["serde"] }
3233
tokio = { version = "1", features = ["fs", "macros", "net", "parking_lot", "rt", "rt-multi-thread", "signal", "sync", "time"] }
3334
config = { version = "0.15", default-features = false, features = ["toml", "json", "yaml", "json5", "convert-case", "async"] }

cargo-prosa/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ thiserror.workspace = true
1818
aquamarine.workspace = true
1919
clap = "4"
2020
clap_complete = "4"
21-
serde = "1"
21+
serde.workspace = true
2222
toml.workspace = true
2323
toml_edit = { version = "0.22", features = ["serde"] }
2424
serde_json = "1"

prosa/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ bytes.workspace = true
4343
tracing = "0.1"
4444
tracing-subscriber = {version = "0.3", features = ["std", "env-filter"]}
4545
thiserror.workspace = true
46+
base64 = "0.22"
4647
url = { version = "2", features = ["serde"] }
4748
rlimit = "0.10"
4849

@@ -53,7 +54,7 @@ tokio.workspace = true
5354
tokio-openssl = { version = "0.6", optional = true }
5455
async-http-proxy = { version = "1", optional = true, features = ["runtime-tokio","basic-auth"] }
5556

56-
serde = { version = "1", features = ["derive"] }
57+
serde.workspace = true
5758
config.workspace = true
5859
glob = { version = "0.3" }
5960
toml.workspace = true

prosa/src/io.rs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Module that define IO that could be use by a ProSA processor
22
use std::{
33
fmt,
4+
hash::{Hash, Hasher},
45
net::{SocketAddrV4, SocketAddrV6},
56
path::Path,
67
};
@@ -50,7 +51,7 @@ pub fn url_is_ssl(url: &Url) -> bool {
5051
}
5152

5253
/// Internal Socket adress enum to define IPv4, IPv6 and unix socket.
53-
#[derive(Debug)]
54+
#[derive(Debug, Clone)]
5455
pub enum SocketAddr {
5556
#[cfg(target_family = "unix")]
5657
/// UNIX socket address
@@ -73,6 +74,50 @@ impl SocketAddr {
7374
}
7475
}
7576

77+
/// Returns `true` if the adress is a UNIX address, and `false` otherwise.
78+
pub fn is_unix(&self) -> bool {
79+
#[cfg(target_family = "unix")]
80+
{
81+
matches!(self, SocketAddr::Unix(_))
82+
}
83+
#[cfg(not(target_family = "unix"))]
84+
{
85+
false
86+
}
87+
}
88+
89+
/// Returns `true` if the adress is an IPV4 address, and `false` otherwise.
90+
pub fn is_ipv4(&self) -> bool {
91+
matches!(self, SocketAddr::V4(_))
92+
}
93+
94+
/// Returns `true` if the adress is an IPV6 address, and `false` otherwise.
95+
pub fn is_ipv6(&self) -> bool {
96+
matches!(self, SocketAddr::V6(_))
97+
}
98+
99+
/// Returns the IP address associated with this socket address.
100+
pub fn ip(&self) -> std::net::IpAddr {
101+
match self {
102+
#[cfg(target_family = "unix")]
103+
SocketAddr::Unix(_) => std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED),
104+
SocketAddr::V4(ipv4) => std::net::IpAddr::V4(*ipv4.ip()),
105+
SocketAddr::V6(ipv6) => std::net::IpAddr::V6(*ipv6.ip()),
106+
}
107+
}
108+
109+
/// Changes the IP address associated with this socket address.
110+
pub const fn set_ip(&mut self, new_ip: std::net::IpAddr) {
111+
match new_ip {
112+
std::net::IpAddr::V4(ipv4_addr) => {
113+
*self = SocketAddr::V4(SocketAddrV4::new(ipv4_addr, self.port()))
114+
}
115+
std::net::IpAddr::V6(ipv6_addr) => {
116+
*self = SocketAddr::V6(SocketAddrV6::new(ipv6_addr, self.port(), 0, 0))
117+
}
118+
}
119+
}
120+
76121
/// Returns the port number associated with this socket address.
77122
pub const fn port(&self) -> u16 {
78123
match self {
@@ -106,6 +151,17 @@ impl PartialEq for SocketAddr {
106151
}
107152
}
108153

154+
impl Hash for SocketAddr {
155+
fn hash<H: Hasher>(&self, state: &mut H) {
156+
match self {
157+
#[cfg(target_family = "unix")]
158+
SocketAddr::Unix(unix) => unix.as_pathname().hash(state),
159+
SocketAddr::V4(ipv4) => ipv4.hash(state),
160+
SocketAddr::V6(ipv6) => ipv6.hash(state),
161+
}
162+
}
163+
}
164+
109165
impl fmt::Display for SocketAddr {
110166
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111167
match self {
@@ -123,6 +179,27 @@ impl fmt::Display for SocketAddr {
123179
}
124180
}
125181

182+
impl<I: Into<std::net::IpAddr>> From<(I, u16)> for SocketAddr {
183+
fn from(pieces: (I, u16)) -> Self {
184+
match pieces.0.into() {
185+
std::net::IpAddr::V4(ipv4) => SocketAddr::V4(SocketAddrV4::new(ipv4, pieces.1)),
186+
std::net::IpAddr::V6(ipv6) => SocketAddr::V6(SocketAddrV6::new(ipv6, pieces.1, 0, 0)),
187+
}
188+
}
189+
}
190+
191+
impl From<std::net::SocketAddrV4> for SocketAddr {
192+
fn from(ipv4: std::net::SocketAddrV4) -> Self {
193+
SocketAddr::V4(ipv4)
194+
}
195+
}
196+
197+
impl From<std::net::SocketAddrV6> for SocketAddr {
198+
fn from(ipv6: std::net::SocketAddrV6) -> Self {
199+
SocketAddr::V6(ipv6)
200+
}
201+
}
202+
126203
impl From<std::net::SocketAddr> for SocketAddr {
127204
fn from(addr: std::net::SocketAddr) -> Self {
128205
match addr {

prosa/src/io/listener.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ impl StreamListener {
176176
/// use prosa_utils::config::ssl::{SslConfig, SslConfigContext};
177177
/// use prosa::io::listener::StreamListener;
178178
///
179+
/// # #[cfg(feature="openssl")]
179180
/// async fn accepting() -> Result<(), io::Error> {
180181
/// let ssl_acceptor = SslConfig::default().init_tls_server_context(None).unwrap().build();
181182
/// let stream_listener: StreamListener = StreamListener::bind("0.0.0.0:10000").await?.ssl_acceptor(ssl_acceptor, None);
@@ -232,6 +233,7 @@ impl StreamListener {
232233
/// use prosa_utils::config::ssl::{SslConfig, SslConfigContext};
233234
/// use prosa::io::listener::StreamListener;
234235
///
236+
/// # #[cfg(feature="openssl")]
235237
/// async fn accepting() -> Result<(), io::Error> {
236238
/// let ssl_acceptor = SslConfig::default().init_tls_server_context(None).unwrap().build();
237239
/// let stream_listener: StreamListener = StreamListener::bind("0.0.0.0:10000").await?.ssl_acceptor(ssl_acceptor, None);

prosa/src/io/stream.rs

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use prosa_utils::config::ssl::SslConfig;
1212
#[cfg(feature = "openssl")]
1313
use prosa_utils::config::ssl::SslConfigContext;
1414

15+
use base64::{Engine as _, engine::general_purpose::STANDARD};
1516
use serde::{Deserialize, Serialize};
1617
use tokio::{
1718
io::{AsyncRead, AsyncWrite, ReadBuf},
@@ -41,7 +42,7 @@ pub enum Stream {
4142
}
4243

4344
impl Stream {
44-
/// Returns the local address that this stream is bound to.
45+
/// Returns the socket address of the remote peer of this TCP connection.
4546
///
4647
/// ```
4748
/// use tokio::io;
@@ -51,10 +52,41 @@ impl Stream {
5152
/// use std::net::{Ipv4Addr, SocketAddrV4};
5253
///
5354
/// async fn accepting() -> Result<(), io::Error> {
54-
/// let stream: Stream = Stream::connect_tcp("127.0.0.1:80").await?;
55+
/// let stream: Stream = Stream::connect_tcp("127.0.0.1:8080").await?;
56+
///
57+
/// assert_eq!(stream.peer_addr()?,
58+
/// SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080)));
59+
///
60+
/// Ok(())
61+
/// }
62+
/// ```
63+
pub fn peer_addr(&self) -> Result<SocketAddr, io::Error> {
64+
match self {
65+
#[cfg(target_family = "unix")]
66+
Stream::Unix(s) => s.peer_addr().map(|addr| addr.into()),
67+
Stream::Tcp(s) => s.peer_addr().map(|addr| addr.into()),
68+
#[cfg(feature = "openssl")]
69+
Stream::OpenSsl(s) => s.get_ref().peer_addr().map(|addr| addr.into()),
70+
#[cfg(feature = "http-proxy")]
71+
Stream::TcpHttpProxy(s) => s.peer_addr().map(|addr| addr.into()),
72+
#[cfg(all(feature = "openssl", feature = "http-proxy"))]
73+
Stream::OpenSslHttpProxy(s) => s.get_ref().peer_addr().map(|addr| addr.into()),
74+
}
75+
}
76+
77+
/// Returns the local address that this stream is bound to.
78+
///
79+
/// ```
80+
/// use tokio::io;
81+
/// use url::Url;
82+
/// use prosa::io::stream::Stream;
83+
/// use prosa::io::SocketAddr;
84+
/// use std::net::{IpAddr, Ipv4Addr, SocketAddrV4};
85+
///
86+
/// async fn accepting() -> Result<(), io::Error> {
87+
/// let stream: Stream = Stream::connect_tcp("127.0.0.1:8080").await?;
5588
///
56-
/// assert_eq!(stream.local_addr()?,
57-
/// SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80)));
89+
/// assert_eq!(stream.local_addr()?.ip(), IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
5890
///
5991
/// Ok(())
6092
/// }
@@ -752,6 +784,36 @@ impl TargetSetting {
752784
self.ssl.is_some() || url_is_ssl(&self.url)
753785
}
754786

787+
/// Method to get authentication value out of URL username/password
788+
///
789+
/// - If user password is provided, it return *Basic* authentication with base64 encoded username:password
790+
/// - If only password is provided, it return *Bearer* authentication with the password as token
791+
///
792+
/// ```
793+
/// use url::Url;
794+
/// use prosa::io::stream::TargetSetting;
795+
///
796+
/// let basic_auth_target = TargetSetting::from(Url::parse("http://user:pass@localhost:8080").unwrap());
797+
/// assert_eq!(Some(String::from("Basic dXNlcjpwYXNz")), basic_auth_target.get_authentication());
798+
///
799+
/// let bearer_auth_target = TargetSetting::from(Url::parse("http://:token@localhost:8080").unwrap());
800+
/// assert_eq!(Some(String::from("Bearer token")), bearer_auth_target.get_authentication());
801+
/// ```
802+
pub fn get_authentication(&self) -> Option<String> {
803+
if let Some(password) = self.url.password() {
804+
if self.url.username().is_empty() {
805+
Some(format!("Bearer {password}"))
806+
} else {
807+
Some(format!(
808+
"Basic {}",
809+
STANDARD.encode(format!("{}:{}", self.url.username(), password))
810+
))
811+
}
812+
} else {
813+
None
814+
}
815+
}
816+
755817
/// Method to init the ssl context out of the ssl target configuration.
756818
/// Must be call when the configuration is retrieved
757819
pub fn init_ssl_context(&mut self) {
@@ -876,6 +938,14 @@ impl fmt::Display for TargetSetting {
876938
}
877939
}
878940

941+
// Mask username and password to avoid data leak in logs
942+
if !url.username().is_empty() {
943+
let _ = url.set_username("***");
944+
}
945+
if url.password().is_some() {
946+
let _ = url.set_password(Some("***"));
947+
}
948+
879949
if let Some(proxy_url) = &self.proxy {
880950
write!(f, "{url} -proxy {proxy_url}")
881951
} else {

prosa_macros/src/io.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ fn generate_struct(mut item_struct: syn::ItemStruct) -> syn::parse::Result<syn::
5252
// Add the Address field that is the remote address
5353
fields.named.push(
5454
syn::Field::parse_named
55-
.parse2(quote! { addr: std::option::Option<std::net::SocketAddr> })
55+
.parse2(quote! { addr: std::option::Option<prosa::io::SocketAddr> })
5656
.unwrap(),
5757
);
5858
// Add the buffer object to read from the net object
@@ -115,11 +115,11 @@ fn generate_struct_impl(
115115
}
116116
}
117117
}
118-
impl #item_generics std::convert::From<(IO, std::net::SocketAddr)> for #item_ident #item_generics
118+
impl #item_generics std::convert::From<(IO, prosa::io::SocketAddr)> for #item_ident #item_generics
119119
where
120120
IO: 'static + tokio::io::AsyncReadExt + tokio::io::AsyncWriteExt + std::os::fd::AsRawFd + std::marker::Unpin + std::marker::Send
121121
{
122-
fn from(socket: (IO, std::net::SocketAddr)) -> Self {
122+
fn from(socket: (IO, prosa::io::SocketAddr)) -> Self {
123123
let (stream, addr) = socket;
124124
let socket_id = stream.as_raw_fd() as u32;
125125
#item_ident {

prosa_utils/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ hex = "0.4"
3535

3636
# Config
3737
glob = { version = "0.3", optional = true }
38-
serde = { version = "1", optional = true, features = ["derive"] }
38+
serde = { workspace = true, optional = true }
3939
serde_yaml = { version = "0.9", optional = true }
4040

4141
# Config OpenSSL

prosa_utils/src/config/ssl.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub trait SslStore<C, S> {
1919
/// use prosa_utils::config::ssl::{Store, SslStore};
2020
///
2121
/// let store = Store::File { path: "./target".into() };
22+
/// # #[cfg(feature="config-openssl")]
2223
/// let ssl_store = store.get_store().unwrap();
2324
/// ```
2425
fn get_store(&self) -> Result<S, ConfigError>;
@@ -29,9 +30,11 @@ pub trait SslStore<C, S> {
2930
/// use prosa_utils::config::ssl::{Store, SslStore};
3031
///
3132
/// let store = Store::File { path: "./target".into() };
33+
/// # #[cfg(feature="config-openssl")]
3234
/// let certs_map = store.get_certs().unwrap();
3335
///
3436
/// // No cert in target
37+
/// # #[cfg(feature="config-openssl")]
3538
/// assert!(certs_map.is_empty());
3639
/// ```
3740
fn get_certs(&self) -> Result<HashMap<String, C>, ConfigError>;
@@ -91,6 +94,7 @@ pub trait SslConfigContext<C, S> {
9194
///
9295
/// let mut client_config = SslConfig::default();
9396
/// client_config.set_store(Store::File { path: "./target".into() });
97+
/// # #[cfg(feature="config-openssl")]
9498
/// if let Ok(mut ssl_context_builder) = client_config.init_tls_client_context() {
9599
/// let ssl_context = ssl_context_builder.build();
96100
/// }
@@ -103,6 +107,7 @@ pub trait SslConfigContext<C, S> {
103107
/// use prosa_utils::config::ssl::{SslConfig, SslConfigContext as _};
104108
///
105109
/// let server_config = SslConfig::new_pkcs12("server.pkcs12".into());
110+
/// # #[cfg(feature="config-openssl")]
106111
/// if let Ok(mut ssl_context_builder) = server_config.init_tls_server_context(None) {
107112
/// let ssl_context = ssl_context_builder.build();
108113
/// }
@@ -118,9 +123,11 @@ pub trait SslConfigContext<C, S> {
118123
/// use std::pin::Pin;
119124
/// use tokio::net::TcpStream;
120125
/// use tokio_openssl::SslStream;
126+
/// # #[cfg(feature="config-openssl")]
121127
/// use openssl::ssl::{ErrorCode, Ssl, SslMethod, SslVerifyMode};
122128
/// use prosa_utils::config::ssl::{SslConfig, SslConfigContext};
123129
///
130+
/// # #[cfg(feature="config-openssl")]
124131
/// async fn client() -> Result<(), io::Error> {
125132
/// let mut stream = TcpStream::connect("localhost:4443").await?;
126133
///
@@ -147,9 +154,11 @@ pub trait SslConfigContext<C, S> {
147154
/// use std::pin::Pin;
148155
/// use tokio::net::TcpListener;
149156
/// use tokio_openssl::SslStream;
157+
/// # #[cfg(feature="config-openssl")]
150158
/// use openssl::ssl::{ErrorCode, Ssl, SslMethod, SslVerifyMode};
151159
/// use prosa_utils::config::ssl::{SslConfig, SslConfigContext};
152160
///
161+
/// # #[cfg(feature="config-openssl")]
153162
/// async fn server() -> Result<(), io::Error> {
154163
/// let listener = TcpListener::bind("0.0.0.0:4443").await?;
155164
///

0 commit comments

Comments
 (0)