This commit is contained in:
Alex Emmet 2026-06-23 23:18:03 +02:00
commit ade0c3cde4
24 changed files with 1701 additions and 321 deletions

View file

@ -56,7 +56,6 @@ impl Default for Policy {
enum ReceivedFrame {
Message(CommunicationValue),
ClosedByPeer,
#[allow(dead_code)]
Idle,
}
@ -517,3 +516,48 @@ impl Receiver {
self.handle.close_reason()
}
}
/* ================================ TESTS ================================ */
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_send_mode_derive() {
assert_eq!(SendMode::PersistentStream, SendMode::PersistentStream);
assert_ne!(SendMode::PersistentStream, SendMode::SingleStreamPerMessage);
}
#[test]
fn test_policy_default_values() {
let p = Policy::default();
assert_eq!(p.send_mode, SendMode::PersistentStream);
assert_eq!(p.max_message_size, 1_000_000_000);
assert_eq!(p.close_frame_len, u32::MAX);
assert_eq!(p.application_close_code, 0);
assert_eq!(p.open_stream_timeout, Duration::from_millis(2_000));
assert_eq!(p.write_timeout, Duration::from_millis(2_000));
assert_eq!(p.accept_stream_timeout, Duration::from_millis(10_000));
assert_eq!(p.read_timeout, Duration::from_millis(30_000));
assert_eq!(p.keep_alive_interval, Some(Duration::from_secs(3)));
assert_eq!(p.max_idle_timeout, Some(Duration::from_secs(30)));
assert_eq!(p.force_close_delay, Duration::from_millis(300));
assert_eq!(p.max_transient_recv_errors, 20);
assert_eq!(p.transient_recv_backoff, Duration::from_millis(100));
assert_eq!(p.receiver_queue_capacity, 1000);
}
#[test]
fn test_policy_clone() {
let p = Policy::default();
let cloned = p.clone();
assert_eq!(p.send_mode, cloned.send_mode);
}
#[test]
fn test_policy_debug() {
let p = Policy::default();
let debug_str = format!("{:?}", p);
assert!(debug_str.contains("Policy"));
}
}

View file

@ -63,3 +63,70 @@ impl Default for ConnectionHandle {
Self::new()
}
}
/* ================================ TESTS ================================ */
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_is_open() {
let h = ConnectionHandle::new();
assert!(h.is_open());
assert!(!h.is_closed());
}
#[test]
fn test_close_transitions_state() {
let h = ConnectionHandle::new();
h.close(Some(CommunicationError::StreamClosed));
assert!(!h.is_open());
assert!(h.is_closed());
}
#[test]
fn test_close_reason_some() {
let h = ConnectionHandle::new();
h.close(Some(CommunicationError::UseAfterClosed));
assert!(h.close_reason().is_some());
}
#[test]
fn test_close_reason_none() {
let h = ConnectionHandle::new();
h.close(None);
assert!(h.close_reason().is_none());
}
#[test]
fn test_default_is_new() {
let h = ConnectionHandle::default();
assert!(h.is_open());
}
#[test]
fn test_close_with_error() {
let h = ConnectionHandle::new();
h.close_with_error(CommunicationError::MessageTooLarge);
assert!(h.is_closed());
assert!(h.close_reason().is_some());
}
#[test]
fn test_multiple_close_first_wins() {
let h = ConnectionHandle::new();
h.close(Some(CommunicationError::StreamClosed));
h.close(Some(CommunicationError::UseAfterClosed));
// First close reason is preserved
assert!(h.close_reason().is_some());
}
#[test]
fn test_close_sends_reason() {
let h = ConnectionHandle::new();
let mut rx = h.subscribe_close();
h.close(Some(CommunicationError::ClosedLocally));
// After close, the watch channel is updated
assert!(rx.borrow_and_update().is_some());
}
}

View file

@ -1,4 +1,5 @@
use crate::{ConnectionHandle, Policy, Receiver, Sender};
use log;
use mtp_common::CommunicationError;
use rustls::pki_types::{PrivateKeyDer, pem::PemObject};
use std::net::{IpAddr, SocketAddr};
@ -13,7 +14,13 @@ pub struct Host {
impl Host {
pub async fn next(&mut self) -> Option<(Sender, Receiver)> {
self.incoming.recv().await
log::warn!("[transport Host::next] waiting on recv...");
let result = self.incoming.recv().await;
match &result {
Some(_) => log::warn!("[transport Host::next] received connection"),
None => log::warn!("[transport Host::next] incoming channel closed - sender dropped"),
}
result
}
pub fn local_addr(&self) -> std::net::SocketAddr {
@ -22,6 +29,7 @@ impl Host {
}
pub async fn host(
ip: IpAddr,
port: u16,
cert_pem: Vec<u8>,
key_pem: Vec<u8>,
@ -29,7 +37,7 @@ pub async fn host(
) -> Result<Host, CommunicationError> {
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let server_config = configure_server(port, cert_pem, key_pem, &policy).await?;
let server_config = configure_server(ip, port, cert_pem, key_pem, &policy).await?;
let endpoint = Endpoint::server(server_config)
.map_err(|e| CommunicationError::Other(format!("Endpoint creation failed: {}", e)))?;
@ -42,25 +50,37 @@ pub async fn host(
let policy = Arc::new(policy);
let task = tokio::spawn(async move {
log::warn!("[transport bg task] started");
loop {
log::warn!("[transport bg task] waiting for connection...");
let incoming_session = endpoint.accept().await;
log::warn!("[transport bg task] got incoming session");
let request = match incoming_session.await {
Ok(req) => req,
Err(_) => {
Ok(req) => {
log::warn!("[transport bg task] got request");
req
}
Err(e) => {
log::warn!("[transport bg task] incoming session error: {e}");
continue;
}
};
let connection = match request.accept().await {
Ok(conn) => conn,
Err(_) => {
Ok(conn) => {
log::warn!("[transport bg task] connection accepted");
conn
}
Err(e) => {
log::warn!("[transport bg task] accept error: {e}");
continue;
}
};
let incoming_tx = incoming_tx.clone();
let policy = policy.clone();
eprintln!("[transport bg task] spawning handle_connection");
tokio::spawn(handle_connection(connection, incoming_tx, policy));
}
});
@ -85,6 +105,7 @@ async fn handle_connection(
}
async fn configure_server(
bind_ip: IpAddr,
port: u16,
cert_pem: Vec<u8>,
key_pem: Vec<u8>,
@ -104,12 +125,6 @@ async fn configure_server(
tls_config.alpn_protocols = vec![b"h3".to_vec()];
let bind_ip = std::env::var("mtp_BIND")
.ok()
.and_then(|s| if s.is_empty() { None } else { Some(s) })
.unwrap_or_else(|| "::".to_string())
.parse::<IpAddr>()
.map_err(|e| CommunicationError::ParseError(e.to_string()))?;
let bind_addr = SocketAddr::new(bind_ip, port);
let server_config = ServerConfig::builder()