General Upgrade, NEW: WebServers, Better Docs
Some checks failed
CI / checks (push) Failing after 5m18s
Some checks failed
CI / checks (push) Failing after 5m18s
This commit is contained in:
parent
5f11d476b6
commit
6e5c985719
122 changed files with 10309 additions and 5206 deletions
|
|
@ -1,61 +1,124 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use mtp_common::CommunicationError;
|
||||
use rustls::{
|
||||
ClientConfig as RustlsClientConfig, DigitallySignedStruct, RootCertStore, SignatureScheme,
|
||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||
pki_types::{ServerName, UnixTime, pem::PemObject},
|
||||
};
|
||||
use wtransport::{ClientConfig, Endpoint};
|
||||
use rustls::{ClientConfig as RustlsClientConfig, RootCertStore, pki_types::pem::PemObject};
|
||||
use wtransport::{ClientConfig as WTransportClientConfig, Endpoint};
|
||||
|
||||
use crate::{ConnectionHandle, Policy, Receiver, Sender};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NoopCertVerifier;
|
||||
#[cfg(feature = "insecure-tls")]
|
||||
mod noop_verifier {
|
||||
use rustls::{
|
||||
DigitallySignedStruct, SignatureScheme,
|
||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||
pki_types::{CertificateDer, ServerName, UnixTime},
|
||||
};
|
||||
|
||||
impl ServerCertVerifier for NoopCertVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
_end_entity: &rustls::pki_types::CertificateDer<'_>,
|
||||
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
|
||||
_server_name: &ServerName<'_>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: UnixTime,
|
||||
) -> Result<ServerCertVerified, rustls::Error> {
|
||||
Ok(ServerCertVerified::assertion())
|
||||
#[derive(Debug)]
|
||||
pub(super) struct NoopCertVerifier;
|
||||
|
||||
impl ServerCertVerifier for NoopCertVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
_end_entity: &CertificateDer<'_>,
|
||||
_intermediates: &[CertificateDer<'_>],
|
||||
_server_name: &ServerName<'_>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: UnixTime,
|
||||
) -> Result<ServerCertVerified, rustls::Error> {
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer<'_>,
|
||||
_dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer<'_>,
|
||||
_dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
vec![
|
||||
SignatureScheme::RSA_PKCS1_SHA1,
|
||||
SignatureScheme::RSA_PKCS1_SHA256,
|
||||
SignatureScheme::RSA_PKCS1_SHA384,
|
||||
SignatureScheme::RSA_PKCS1_SHA512,
|
||||
SignatureScheme::ECDSA_NISTP256_SHA256,
|
||||
SignatureScheme::ECDSA_NISTP384_SHA384,
|
||||
SignatureScheme::RSA_PSS_SHA256,
|
||||
SignatureScheme::RSA_PSS_SHA384,
|
||||
SignatureScheme::RSA_PSS_SHA512,
|
||||
SignatureScheme::ED25519,
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TLS and transport settings for a native client connection.
|
||||
///
|
||||
/// Certificate verification uses system roots by default. Disabling verification
|
||||
/// requires an explicit call to [`Self::with_insecure_certificate_verification`]
|
||||
/// and the `insecure-tls` compile-time feature.
|
||||
pub struct ClientConfig {
|
||||
server_cert: Option<Vec<u8>>,
|
||||
pinned_hash: Option<[u8; 32]>,
|
||||
insecure_certificate_verification: bool,
|
||||
policy: Policy,
|
||||
}
|
||||
|
||||
impl ClientConfig {
|
||||
pub fn new(policy: Policy) -> Self {
|
||||
Self {
|
||||
server_cert: None,
|
||||
pinned_hash: None,
|
||||
insecure_certificate_verification: false,
|
||||
policy,
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &rustls::pki_types::CertificateDer<'_>,
|
||||
_dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
pub fn with_server_certificate(mut self, cert_pem: Vec<u8>) -> Self {
|
||||
self.server_cert = Some(cert_pem);
|
||||
self.insecure_certificate_verification = false;
|
||||
self.pinned_hash = None;
|
||||
self
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &rustls::pki_types::CertificateDer<'_>,
|
||||
_dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
/// Pin the connection to a specific SPKI SHA-256 hash.
|
||||
///
|
||||
/// The client will only accept server certificates whose DER-encoded Subject
|
||||
/// Public Key Info matches the given 32-byte hash. This is the recommended
|
||||
/// approach for trusting self-signed certificates without disabling
|
||||
/// verification entirely.
|
||||
pub fn with_pinned_certificate_hash(mut self, hash: [u8; 32]) -> Self {
|
||||
self.pinned_hash = Some(hash);
|
||||
self.server_cert = None;
|
||||
self.insecure_certificate_verification = false;
|
||||
self
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
vec![
|
||||
SignatureScheme::RSA_PKCS1_SHA1,
|
||||
SignatureScheme::RSA_PKCS1_SHA256,
|
||||
SignatureScheme::RSA_PKCS1_SHA384,
|
||||
SignatureScheme::RSA_PKCS1_SHA512,
|
||||
SignatureScheme::ECDSA_NISTP256_SHA256,
|
||||
SignatureScheme::ECDSA_NISTP384_SHA384,
|
||||
SignatureScheme::RSA_PSS_SHA256,
|
||||
SignatureScheme::RSA_PSS_SHA384,
|
||||
SignatureScheme::RSA_PSS_SHA512,
|
||||
SignatureScheme::ED25519,
|
||||
]
|
||||
/// Disable server certificate verification.
|
||||
///
|
||||
/// Requires the `insecure-tls` feature at compile time and the environment
|
||||
/// variable `MTP_INSECURE_TLS=1` at runtime. Returns an error if either
|
||||
/// condition is not met.
|
||||
///
|
||||
/// Intended only for local development with a self-signed host.
|
||||
#[cfg(feature = "insecure-tls")]
|
||||
pub fn with_insecure_certificate_verification(mut self) -> Self {
|
||||
self.server_cert = None;
|
||||
self.pinned_hash = None;
|
||||
self.insecure_certificate_verification = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,13 +126,47 @@ pub async fn connect(
|
|||
url: &str,
|
||||
server_cert: Option<Vec<u8>>,
|
||||
policy: Policy,
|
||||
) -> Result<(Sender, Receiver), CommunicationError> {
|
||||
let config = match server_cert {
|
||||
Some(cert_pem) => ClientConfig::new(policy).with_server_certificate(cert_pem),
|
||||
None => ClientConfig::new(policy),
|
||||
};
|
||||
connect_with_config(url, config).await
|
||||
}
|
||||
|
||||
/// Connect using explicit TLS and transport configuration.
|
||||
pub async fn connect_with_config(
|
||||
url: &str,
|
||||
config: ClientConfig,
|
||||
) -> Result<(Sender, Receiver), CommunicationError> {
|
||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||
|
||||
let client_config = if let Some(cert_pem) = server_cert {
|
||||
configure_client_with_cert(cert_pem, &policy)?
|
||||
let client_config = if config.insecure_certificate_verification {
|
||||
#[cfg(feature = "insecure-tls")]
|
||||
{
|
||||
let env_val = std::env::var("MTP_INSECURE_TLS")
|
||||
.map(|v| v == "1")
|
||||
.unwrap_or(false);
|
||||
if !env_val {
|
||||
return Err(CommunicationError::Other(
|
||||
"insecure TLS requires MTP_INSECURE_TLS=1 in the environment".into(),
|
||||
));
|
||||
}
|
||||
client_config_insecure(&config.policy)?
|
||||
}
|
||||
#[cfg(not(feature = "insecure-tls"))]
|
||||
{
|
||||
unreachable!(
|
||||
"insecure_certificate_verification is only set when \
|
||||
the insecure-tls feature is enabled"
|
||||
)
|
||||
}
|
||||
} else if let Some(hash) = config.pinned_hash {
|
||||
crate::pinning::configure_client_pinned_hash(hash, &config.policy)?
|
||||
} else if let Some(cert_pem) = config.server_cert {
|
||||
configure_client_with_cert(cert_pem, &config.policy)?
|
||||
} else {
|
||||
configure_client_system_roots(&policy)?
|
||||
configure_client_system_roots(&config.policy)?
|
||||
};
|
||||
|
||||
let endpoint = Endpoint::client(client_config)
|
||||
|
|
@ -81,7 +178,7 @@ pub async fn connect(
|
|||
.map_err(|e| CommunicationError::ConnectingError(e.to_string()))?;
|
||||
|
||||
let handle = Arc::new(ConnectionHandle::new());
|
||||
let policy = Arc::new(policy);
|
||||
let policy = Arc::new(config.policy);
|
||||
|
||||
let sender = Sender::new(connection.clone(), handle.clone(), policy.clone());
|
||||
let receiver = Receiver::new(connection, handle, policy);
|
||||
|
|
@ -92,7 +189,7 @@ pub async fn connect(
|
|||
fn configure_client_with_cert(
|
||||
server_cert: Vec<u8>,
|
||||
policy: &Policy,
|
||||
) -> Result<ClientConfig, CommunicationError> {
|
||||
) -> Result<WTransportClientConfig, CommunicationError> {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
|
||||
let certs = rustls::pki_types::CertificateDer::pem_slice_iter(&server_cert)
|
||||
|
|
@ -108,7 +205,10 @@ fn configure_client_with_cert(
|
|||
client_config_from_roots(root_store, policy)
|
||||
}
|
||||
|
||||
fn client_config_insecure(policy: &Policy) -> Result<ClientConfig, CommunicationError> {
|
||||
#[cfg(feature = "insecure-tls")]
|
||||
fn client_config_insecure(policy: &Policy) -> Result<WTransportClientConfig, CommunicationError> {
|
||||
use noop_verifier::NoopCertVerifier;
|
||||
|
||||
let mut tls_config = RustlsClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(NoopCertVerifier))
|
||||
|
|
@ -116,7 +216,7 @@ fn client_config_insecure(policy: &Policy) -> Result<ClientConfig, Communication
|
|||
|
||||
tls_config.alpn_protocols = vec![b"h3".to_vec()];
|
||||
|
||||
Ok(ClientConfig::builder()
|
||||
Ok(WTransportClientConfig::builder()
|
||||
.with_bind_default()
|
||||
.with_custom_tls(tls_config)
|
||||
.keep_alive_interval(policy.keep_alive_interval)
|
||||
|
|
@ -125,16 +225,11 @@ fn client_config_insecure(policy: &Policy) -> Result<ClientConfig, Communication
|
|||
.build())
|
||||
}
|
||||
|
||||
fn configure_client_system_roots(policy: &Policy) -> Result<ClientConfig, CommunicationError> {
|
||||
// Check if insecure mode is enabled via env variable MTP_INSECURE
|
||||
let insecure = std::env::var("MTP_INSECURE").is_ok();
|
||||
if insecure {
|
||||
// Insecure mode: skip certificate verification entirely
|
||||
return client_config_insecure(policy);
|
||||
}
|
||||
fn configure_client_system_roots(
|
||||
policy: &Policy,
|
||||
) -> Result<WTransportClientConfig, CommunicationError> {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
|
||||
// Load native certs
|
||||
let certs = rustls_native_certs::load_native_certs().certs;
|
||||
|
||||
for cert in certs {
|
||||
|
|
@ -147,14 +242,14 @@ fn configure_client_system_roots(policy: &Policy) -> Result<ClientConfig, Commun
|
|||
fn client_config_from_roots(
|
||||
root_store: RootCertStore,
|
||||
policy: &Policy,
|
||||
) -> Result<ClientConfig, CommunicationError> {
|
||||
) -> Result<WTransportClientConfig, CommunicationError> {
|
||||
let mut tls_config = RustlsClientConfig::builder()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth();
|
||||
|
||||
tls_config.alpn_protocols = vec![b"h3".to_vec()];
|
||||
|
||||
Ok(ClientConfig::builder()
|
||||
Ok(WTransportClientConfig::builder()
|
||||
.with_bind_default()
|
||||
.with_custom_tls(tls_config)
|
||||
.keep_alive_interval(policy.keep_alive_interval)
|
||||
|
|
|
|||
|
|
@ -4,16 +4,17 @@ use crate::pipe::PipeReader;
|
|||
use mtp_codec::CommunicationValue;
|
||||
use mtp_common::CommunicationError;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use tokio::sync::{Mutex, Notify, RwLock, Semaphore, mpsc};
|
||||
use tokio::time::{Duration, sleep, timeout};
|
||||
use tracing::{debug, info, instrument, trace};
|
||||
use tracing::{debug, info, instrument, trace, warn};
|
||||
use wtransport::Connection;
|
||||
|
||||
#[cfg(feature = "pipes")]
|
||||
#[derive(Debug)]
|
||||
pub enum TransportEvent {
|
||||
pub enum TransportEvent<R = wtransport::RecvStream> {
|
||||
Message(CommunicationValue),
|
||||
Pipe(PipeReader),
|
||||
Pipe(PipeReader<R>),
|
||||
}
|
||||
|
||||
const APPLICATION_CLOSE_REASON: &str = "mtp-close";
|
||||
|
|
@ -28,6 +29,8 @@ pub enum SendMode {
|
|||
pub struct Policy {
|
||||
pub send_mode: SendMode,
|
||||
pub max_message_size: u64,
|
||||
/// Receive limit used until the application-level handshake completes.
|
||||
pub handshake_max_message_size: u64,
|
||||
pub close_frame_len: u32,
|
||||
pub application_close_code: u32,
|
||||
pub open_stream_timeout: Duration,
|
||||
|
|
@ -37,8 +40,6 @@ pub struct Policy {
|
|||
pub keep_alive_interval: Option<Duration>,
|
||||
pub max_idle_timeout: Option<Duration>,
|
||||
pub force_close_delay: Duration,
|
||||
pub max_transient_recv_errors: usize,
|
||||
pub transient_recv_backoff: Duration,
|
||||
pub persistent_stream_max_retries: usize,
|
||||
pub persistent_stream_retry_backoff: Duration,
|
||||
pub receiver_queue_capacity: usize,
|
||||
|
|
@ -50,7 +51,8 @@ impl Default for Policy {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
send_mode: SendMode::PersistentStream,
|
||||
max_message_size: 1_000_000_000,
|
||||
max_message_size: 16 * 1024 * 1024,
|
||||
handshake_max_message_size: 64 * 1024,
|
||||
close_frame_len: u32::MAX,
|
||||
application_close_code: 0,
|
||||
open_stream_timeout: Duration::from_millis(2_000),
|
||||
|
|
@ -60,8 +62,6 @@ impl Default for Policy {
|
|||
keep_alive_interval: Some(Duration::from_secs(3)),
|
||||
max_idle_timeout: Some(Duration::from_secs(30)),
|
||||
force_close_delay: Duration::from_millis(300),
|
||||
max_transient_recv_errors: 20,
|
||||
transient_recv_backoff: Duration::from_millis(100),
|
||||
persistent_stream_max_retries: 4,
|
||||
persistent_stream_retry_backoff: Duration::from_millis(20),
|
||||
receiver_queue_capacity: 1000,
|
||||
|
|
@ -82,6 +82,11 @@ impl Policy {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_handshake_max_message_size(mut self, max_message_size: u64) -> Self {
|
||||
self.handshake_max_message_size = max_message_size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_timeouts(
|
||||
mut self,
|
||||
open_stream_timeout: Duration,
|
||||
|
|
@ -179,15 +184,15 @@ impl Sender {
|
|||
match timeout(policy.write_timeout, write_result).await {
|
||||
Ok(Ok(())) => Ok(()),
|
||||
Ok(Err(wtransport::error::StreamWriteError::Stopped(code))) => {
|
||||
log::warn!("[Sender] write failed: peer sent STOP_SENDING (error code {code})");
|
||||
warn!("[Sender] write failed: peer sent STOP_SENDING (error code {code})");
|
||||
Err(CommunicationError::StreamClosed)
|
||||
}
|
||||
Ok(Err(other)) => {
|
||||
log::warn!("[Sender] write failed: {other}");
|
||||
warn!("[Sender] write failed: {other}");
|
||||
Err(CommunicationError::StreamError)
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!("[Sender] write timed out (len={})", bytes.len());
|
||||
warn!("[Sender] write timed out (len={})", bytes.len());
|
||||
Err(CommunicationError::StreamError)
|
||||
}
|
||||
}
|
||||
|
|
@ -260,6 +265,12 @@ impl Sender {
|
|||
}
|
||||
|
||||
let err = res.err().unwrap_or(CommunicationError::StreamError);
|
||||
if !matches!(
|
||||
err,
|
||||
CommunicationError::StreamError | CommunicationError::StreamClosed
|
||||
) {
|
||||
return Err(err);
|
||||
}
|
||||
*stream_opt = None;
|
||||
tries += 1;
|
||||
if tries > policy.persistent_stream_max_retries {
|
||||
|
|
@ -283,15 +294,15 @@ impl Sender {
|
|||
match timeout(policy.write_timeout, stream.finish()).await {
|
||||
Ok(Ok(())) => Ok(()),
|
||||
Ok(Err(wtransport::error::StreamWriteError::Stopped(code))) => {
|
||||
log::warn!("[Sender] finish failed: peer sent STOP_SENDING (error code {code})");
|
||||
warn!("[Sender] finish failed: peer sent STOP_SENDING (error code {code})");
|
||||
Err(CommunicationError::StreamClosed)
|
||||
}
|
||||
Ok(Err(other)) => {
|
||||
log::warn!("[Sender] finish failed: {other}");
|
||||
warn!("[Sender] finish failed: {other}");
|
||||
Err(CommunicationError::StreamError)
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!("[Sender] finish timed out");
|
||||
warn!("[Sender] finish timed out");
|
||||
Err(CommunicationError::StreamError)
|
||||
}
|
||||
}
|
||||
|
|
@ -308,30 +319,30 @@ impl Sender {
|
|||
match timeout(policy.write_timeout, stream.write_all(&len_bytes)).await {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(wtransport::error::StreamWriteError::Stopped(code))) => {
|
||||
log::warn!(
|
||||
warn!(
|
||||
"[Sender] close frame write failed: peer sent STOP_SENDING (error code {code})"
|
||||
);
|
||||
}
|
||||
Ok(Err(other)) => {
|
||||
log::warn!("[Sender] close frame write failed: {other}");
|
||||
warn!("[Sender] close frame write failed: {other}");
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!("[Sender] close frame write timed out");
|
||||
warn!("[Sender] close frame write timed out");
|
||||
}
|
||||
}
|
||||
|
||||
match timeout(policy.write_timeout, stream.finish()).await {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(wtransport::error::StreamWriteError::Stopped(code))) => {
|
||||
log::warn!(
|
||||
warn!(
|
||||
"[Sender] close frame finish failed: peer sent STOP_SENDING (error code {code})"
|
||||
);
|
||||
}
|
||||
Ok(Err(other)) => {
|
||||
log::warn!("[Sender] close frame finish failed: {other}");
|
||||
warn!("[Sender] close frame finish failed: {other}");
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!("[Sender] close frame finish timed out");
|
||||
warn!("[Sender] close frame finish timed out");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -402,17 +413,15 @@ impl Sender {
|
|||
match timeout(self.policy.write_timeout, stream.finish()).await {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(wtransport::error::StreamWriteError::Stopped(code))) => {
|
||||
log::warn!(
|
||||
"[Sender] finish_stream: peer sent STOP_SENDING (error code {code})"
|
||||
);
|
||||
warn!("[Sender] finish_stream: peer sent STOP_SENDING (error code {code})");
|
||||
return Err(CommunicationError::StreamClosed);
|
||||
}
|
||||
Ok(Err(other)) => {
|
||||
log::warn!("[Sender] finish_stream failed: {other}");
|
||||
warn!("[Sender] finish_stream failed: {other}");
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!("[Sender] finish_stream timed out");
|
||||
warn!("[Sender] finish_stream timed out");
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
}
|
||||
|
|
@ -478,13 +487,13 @@ impl Sender {
|
|||
if let Some(mut stream) = stream_guard.lock().await.take() {
|
||||
match timeout(policy.write_timeout, stream.finish()).await {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(wtransport::error::StreamWriteError::Stopped(code))) => log::warn!(
|
||||
Ok(Err(wtransport::error::StreamWriteError::Stopped(code))) => warn!(
|
||||
"[Sender] persistent stream finish failed: peer sent STOP_SENDING (error code {code})"
|
||||
),
|
||||
Ok(Err(e)) => {
|
||||
log::warn!("[Sender] persistent stream finish failed: {e}")
|
||||
warn!("[Sender] persistent stream finish failed: {e}")
|
||||
}
|
||||
Err(_) => log::warn!("[Sender] persistent stream finish timed out"),
|
||||
Err(_) => warn!("[Sender] persistent stream finish timed out"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -526,11 +535,11 @@ impl Sender {
|
|||
|
||||
match timeout(policy.write_timeout, close_write).await {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(wtransport::error::StreamWriteError::Stopped(code))) => log::warn!(
|
||||
Ok(Err(wtransport::error::StreamWriteError::Stopped(code))) => warn!(
|
||||
"[Sender] close_and_wait failed: peer sent STOP_SENDING (error code {code})"
|
||||
),
|
||||
Ok(Err(e)) => log::warn!("[Sender] close_and_wait failed: {e}"),
|
||||
Err(_) => log::warn!("[Sender] close_and_wait timed out"),
|
||||
Ok(Err(e)) => warn!("[Sender] close_and_wait failed: {e}"),
|
||||
Err(_) => warn!("[Sender] close_and_wait timed out"),
|
||||
}
|
||||
} else {
|
||||
let _ = Self::send_close_frame(&connection, &policy).await;
|
||||
|
|
@ -585,6 +594,7 @@ struct ReceiverInner {
|
|||
handle: Arc<ConnectionHandle>,
|
||||
ping_control: Arc<RwLock<PingControl>>,
|
||||
queue_notify: Arc<Notify>,
|
||||
max_message_size: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
impl Clone for Receiver {
|
||||
|
|
@ -611,6 +621,26 @@ struct PingControl {
|
|||
|
||||
impl Receiver {
|
||||
pub fn new(connection: Connection, handle: Arc<ConnectionHandle>, policy: Arc<Policy>) -> Self {
|
||||
Self::new_with_max_message_size(connection, handle, policy.clone(), policy.max_message_size)
|
||||
}
|
||||
|
||||
pub(crate) fn new_for_handshake(
|
||||
connection: Connection,
|
||||
handle: Arc<ConnectionHandle>,
|
||||
policy: Arc<Policy>,
|
||||
) -> Self {
|
||||
let initial_max = policy
|
||||
.handshake_max_message_size
|
||||
.min(policy.max_message_size);
|
||||
Self::new_with_max_message_size(connection, handle, policy, initial_max)
|
||||
}
|
||||
|
||||
fn new_with_max_message_size(
|
||||
connection: Connection,
|
||||
handle: Arc<ConnectionHandle>,
|
||||
policy: Arc<Policy>,
|
||||
initial_max_message_size: u64,
|
||||
) -> Self {
|
||||
#[cfg(feature = "pipes")]
|
||||
let (msg_tx, msg_rx) = mpsc::channel::<Result<CommunicationValue, CommunicationError>>(
|
||||
policy.receiver_queue_capacity,
|
||||
|
|
@ -629,6 +659,8 @@ impl Receiver {
|
|||
let accept_ping_control = ping_control.clone();
|
||||
let queue_notify = Arc::new(Notify::new());
|
||||
let accept_queue_notify = queue_notify.clone();
|
||||
let max_message_size = Arc::new(AtomicU64::new(initial_max_message_size));
|
||||
let accept_max_message_size = max_message_size.clone();
|
||||
let stream_limit = Arc::new(Semaphore::new(policy.max_concurrent_stream_tasks.max(1)));
|
||||
let accept_stream_limit = stream_limit.clone();
|
||||
debug!(
|
||||
|
|
@ -695,6 +727,7 @@ impl Receiver {
|
|||
let stream_handle = conn_handle.clone();
|
||||
let stream_policy = accept_policy.clone();
|
||||
let stream_ping_control = accept_ping_control.clone();
|
||||
let stream_max_message_size = accept_max_message_size.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _permit = permit;
|
||||
|
|
@ -713,7 +746,8 @@ impl Receiver {
|
|||
break;
|
||||
}
|
||||
|
||||
match Self::read_one_frame(&mut s, &stream_policy).await {
|
||||
let frame_limit = stream_max_message_size.load(Ordering::Relaxed);
|
||||
match Self::read_one_frame(&mut s, &stream_policy, frame_limit).await {
|
||||
Ok(ReceivedFrame::Message(msg)) => {
|
||||
frame_count += 1;
|
||||
|
||||
|
|
@ -721,8 +755,8 @@ impl Receiver {
|
|||
{
|
||||
let pipe_request_type =
|
||||
mtp_codec::CommunicationType::PipeRequest
|
||||
.to_id(&mtp_codec::TypeMap::latest());
|
||||
if msg.get_type() == pipe_request_type
|
||||
.try_to_id(&mtp_codec::TypeMap::latest());
|
||||
if Some(msg.get_type()) == pipe_request_type
|
||||
&& frame_count == 1
|
||||
{
|
||||
let pipe_id = msg.get_id();
|
||||
|
|
@ -751,17 +785,17 @@ impl Receiver {
|
|||
}
|
||||
|
||||
let ping_type = mtp_codec::CommunicationType::Ping
|
||||
.to_id(&mtp_codec::TypeMap::latest());
|
||||
.try_to_id(&mtp_codec::TypeMap::latest());
|
||||
let pong_type = mtp_codec::CommunicationType::Pong
|
||||
.to_id(&mtp_codec::TypeMap::latest());
|
||||
.try_to_id(&mtp_codec::TypeMap::latest());
|
||||
let control = {
|
||||
let control = stream_ping_control.read().await;
|
||||
if msg.get_type() == ping_type {
|
||||
if Some(msg.get_type()) == ping_type {
|
||||
control
|
||||
.pong_sender
|
||||
.clone()
|
||||
.map(|sender| (Some(sender), None))
|
||||
} else if msg.get_type() == pong_type {
|
||||
} else if Some(msg.get_type()) == pong_type {
|
||||
control
|
||||
.pong_observer
|
||||
.clone()
|
||||
|
|
@ -781,7 +815,7 @@ impl Receiver {
|
|||
);
|
||||
}
|
||||
if let Err(e) = sender.send(&pong).await {
|
||||
log::warn!("[Receiver] failed to send Pong: {e}");
|
||||
warn!("[Receiver] failed to send Pong: {e}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
@ -882,16 +916,24 @@ impl Receiver {
|
|||
handle,
|
||||
ping_control,
|
||||
queue_notify,
|
||||
max_message_size,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Change the receive cap for subsequently parsed frames.
|
||||
pub fn set_max_message_size(&self, max_message_size: u64) {
|
||||
self.inner
|
||||
.max_message_size
|
||||
.store(max_message_size, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/* Respond to reserved Ping frames without exposing them to application I/O. */
|
||||
pub fn respond_to_pings(&self, sender: Sender) {
|
||||
if let Ok(mut control) = self.inner.ping_control.try_write() {
|
||||
control.pong_sender = Some(sender);
|
||||
} else {
|
||||
log::warn!("[Receiver] could not register Ping responder: control lock busy");
|
||||
warn!("[Receiver] could not register Ping responder: control lock busy");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -900,7 +942,7 @@ impl Receiver {
|
|||
if let Ok(mut control) = self.inner.ping_control.try_write() {
|
||||
control.pong_observer = Some(observer);
|
||||
} else {
|
||||
log::warn!("[Receiver] could not register Pong observer: control lock busy");
|
||||
warn!("[Receiver] could not register Pong observer: control lock busy");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -908,6 +950,7 @@ impl Receiver {
|
|||
async fn read_one_frame(
|
||||
stream: &mut wtransport::RecvStream,
|
||||
policy: &Policy,
|
||||
max_message_size: u64,
|
||||
) -> Result<ReceivedFrame, CommunicationError> {
|
||||
use wtransport::error::{StreamReadError, StreamReadExactError};
|
||||
|
||||
|
|
@ -918,23 +961,23 @@ impl Receiver {
|
|||
return Ok(ReceivedFrame::Idle);
|
||||
}
|
||||
Ok(Err(StreamReadExactError::FinishedEarly(n))) => {
|
||||
log::warn!(
|
||||
warn!(
|
||||
"[Receiver] length-prefix read ended early ({n}/4 bytes): stream closed by peer"
|
||||
);
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
Ok(Err(StreamReadExactError::Read(StreamReadError::Reset(code)))) => {
|
||||
log::warn!(
|
||||
warn!(
|
||||
"[Receiver] length-prefix read failed: peer sent RESET_STREAM (error code {code})"
|
||||
);
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
Ok(Err(other)) => {
|
||||
log::warn!("[Receiver] length-prefix read failed: {other}");
|
||||
warn!("[Receiver] length-prefix read failed: {other}");
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!("[Receiver] length-prefix read timed out");
|
||||
warn!("[Receiver] length-prefix read timed out");
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
}
|
||||
|
|
@ -945,32 +988,50 @@ impl Receiver {
|
|||
}
|
||||
|
||||
let len_usize = len as usize;
|
||||
if len as u64 > policy.max_message_size {
|
||||
if len as u64 > max_message_size {
|
||||
return Err(CommunicationError::MessageTooLarge);
|
||||
}
|
||||
|
||||
let mut buf = vec![0u8; len_usize];
|
||||
match timeout(policy.read_timeout, stream.read_exact(&mut buf)).await {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(StreamReadExactError::FinishedEarly(n))) => {
|
||||
log::warn!(
|
||||
"[Receiver] body read ended early ({n}/{len_usize} bytes): stream closed by peer"
|
||||
);
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
Ok(Err(StreamReadExactError::Read(StreamReadError::Reset(code)))) => {
|
||||
log::warn!(
|
||||
"[Receiver] body read failed: peer sent RESET_STREAM (error code {code})"
|
||||
);
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
Ok(Err(other)) => {
|
||||
log::warn!("[Receiver] body read failed: {other}");
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!("[Receiver] body read timed out (len={len_usize})");
|
||||
return Err(CommunicationError::StreamError);
|
||||
// Grow in bounded chunks instead of trusting the peer's length prefix
|
||||
// enough to allocate the complete frame up front.
|
||||
let mut buf = Vec::new();
|
||||
buf.try_reserve(len_usize.min(16 * 1024))
|
||||
.map_err(|_| CommunicationError::MessageTooLarge)?;
|
||||
while buf.len() < len_usize {
|
||||
let chunk_len = (len_usize - buf.len()).min(16 * 1024);
|
||||
let mut chunk = [0u8; 16 * 1024];
|
||||
match timeout(
|
||||
policy.read_timeout,
|
||||
stream.read_exact(&mut chunk[..chunk_len]),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(())) => {
|
||||
buf.try_reserve(chunk_len)
|
||||
.map_err(|_| CommunicationError::MessageTooLarge)?;
|
||||
buf.extend_from_slice(&chunk[..chunk_len]);
|
||||
}
|
||||
Ok(Err(StreamReadExactError::FinishedEarly(n))) => {
|
||||
warn!(
|
||||
"[Receiver] body read ended early ({}/{len_usize} bytes): stream closed by peer",
|
||||
buf.len() + n
|
||||
);
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
Ok(Err(StreamReadExactError::Read(StreamReadError::Reset(code)))) => {
|
||||
warn!(
|
||||
"[Receiver] body read failed: peer sent RESET_STREAM (error code {code})"
|
||||
);
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
Ok(Err(other)) => {
|
||||
warn!("[Receiver] body read failed: {other}");
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("[Receiver] body read timed out (len={len_usize})");
|
||||
return Err(CommunicationError::StreamError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1155,7 +1216,8 @@ mod tests {
|
|||
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.max_message_size, 16 * 1024 * 1024);
|
||||
assert_eq!(p.handshake_max_message_size, 64 * 1024);
|
||||
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));
|
||||
|
|
@ -1165,8 +1227,6 @@ mod tests {
|
|||
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.persistent_stream_max_retries, 4);
|
||||
assert_eq!(p.persistent_stream_retry_backoff, Duration::from_millis(20));
|
||||
assert_eq!(p.receiver_queue_capacity, 1000);
|
||||
|
|
|
|||
21
transport/src/framing.rs
Normal file
21
transport/src/framing.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use crate::{Policy, TransportSendStream};
|
||||
use mtp_codec::CommunicationValue;
|
||||
use mtp_common::CommunicationError;
|
||||
|
||||
/// Writes the canonical length-prefixed MTP frame used by every transport.
|
||||
pub(crate) async fn write_frame<S: TransportSendStream>(
|
||||
stream: &mut S,
|
||||
value: &CommunicationValue,
|
||||
policy: &Policy,
|
||||
) -> Result<(), CommunicationError> {
|
||||
let bytes = value.to_bytes().map_err(|_| CommunicationError::Encode)?;
|
||||
if bytes.len() as u64 > policy.max_message_size
|
||||
|| bytes.len() as u64 >= policy.close_frame_len as u64
|
||||
{
|
||||
return Err(CommunicationError::MessageTooLarge);
|
||||
}
|
||||
stream
|
||||
.write_all(&(bytes.len() as u32).to_be_bytes())
|
||||
.await?;
|
||||
stream.write_all(&bytes).await
|
||||
}
|
||||
403
transport/src/generic_connection.rs
Normal file
403
transport/src/generic_connection.rs
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
//! Transport-neutral MTP framing.
|
||||
//!
|
||||
//! These types are used by non-wtransport backends. The established
|
||||
//! [`crate::Sender`] and [`crate::Receiver`] remain source compatible native
|
||||
//! wrappers while the framing implementation below is shared by adapters.
|
||||
|
||||
use crate::{
|
||||
Policy, TransportConnection, TransportRecvStream, TransportSendStream, framing::write_frame,
|
||||
};
|
||||
use mtp_codec::CommunicationValue;
|
||||
use mtp_common::CommunicationError;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{Mutex, RwLock, Semaphore, mpsc};
|
||||
use tokio::time::timeout;
|
||||
|
||||
#[cfg(feature = "pipes")]
|
||||
use crate::pipe::{PipeReader, PipeWriter};
|
||||
|
||||
pub struct GenericSender<C: TransportConnection> {
|
||||
connection: C,
|
||||
policy: Arc<Policy>,
|
||||
persistent: Arc<Mutex<Option<C::SendStream>>>,
|
||||
send_lock: Arc<Mutex<()>>,
|
||||
}
|
||||
|
||||
impl<C: TransportConnection> Clone for GenericSender<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connection: self.connection.clone(),
|
||||
policy: self.policy.clone(),
|
||||
persistent: self.persistent.clone(),
|
||||
send_lock: self.send_lock.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: TransportConnection> GenericSender<C> {
|
||||
pub fn new(connection: C, policy: Arc<Policy>) -> Self {
|
||||
Self {
|
||||
connection,
|
||||
policy,
|
||||
persistent: Arc::new(Mutex::new(None)),
|
||||
send_lock: Arc::new(Mutex::new(())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn open(&self) -> Result<C::SendStream, CommunicationError> {
|
||||
timeout(self.policy.open_stream_timeout, self.connection.open_uni())
|
||||
.await
|
||||
.map_err(|_| CommunicationError::StreamError)?
|
||||
}
|
||||
|
||||
pub async fn send(&self, value: &CommunicationValue) -> Result<(), CommunicationError> {
|
||||
let _lock = self.send_lock.lock().await;
|
||||
if self.connection.close_reason().is_some() {
|
||||
return Err(CommunicationError::StreamClosed);
|
||||
}
|
||||
match self.policy.send_mode {
|
||||
crate::SendMode::SingleStreamPerMessage => {
|
||||
let mut stream = self.open().await?;
|
||||
timeout(
|
||||
self.policy.write_timeout,
|
||||
write_frame(&mut stream, value, &self.policy),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| CommunicationError::StreamError)??;
|
||||
timeout(self.policy.write_timeout, stream.finish())
|
||||
.await
|
||||
.map_err(|_| CommunicationError::StreamError)?
|
||||
}
|
||||
crate::SendMode::PersistentStream => {
|
||||
let mut stream = self.persistent.lock().await;
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if stream.is_none() {
|
||||
*stream = Some(self.open().await?);
|
||||
}
|
||||
let result = timeout(
|
||||
self.policy.write_timeout,
|
||||
write_frame(stream.as_mut().unwrap(), value, &self.policy),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| CommunicationError::StreamError)
|
||||
.and_then(|r| r);
|
||||
if result.is_ok() {
|
||||
return result;
|
||||
}
|
||||
*stream = None;
|
||||
attempts += 1;
|
||||
if attempts > self.policy.persistent_stream_max_retries {
|
||||
return result;
|
||||
}
|
||||
tokio::time::sleep(
|
||||
self.policy.persistent_stream_retry_backoff * attempts as u32,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pipes")]
|
||||
pub async fn open_pipe(
|
||||
&self,
|
||||
pipe_id: u32,
|
||||
description: &str,
|
||||
) -> Result<PipeWriter<C::SendStream>, CommunicationError> {
|
||||
if self.connection.close_reason().is_some() {
|
||||
return Err(CommunicationError::StreamClosed);
|
||||
}
|
||||
|
||||
let mut stream = self.open().await?;
|
||||
|
||||
let request = CommunicationValue::new(mtp_codec::CommunicationType::PipeRequest)
|
||||
.with_id(pipe_id)
|
||||
.add_typed_default(
|
||||
mtp_codec::DataType::Description,
|
||||
mtp_codec::DataValue::Str(description.to_string()),
|
||||
);
|
||||
|
||||
timeout(
|
||||
self.policy.write_timeout,
|
||||
write_frame(&mut stream, &request, &self.policy),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| CommunicationError::StreamError)??;
|
||||
|
||||
Ok(PipeWriter { stream })
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
self.connection
|
||||
.close(self.policy.application_close_code, b"mtp-close");
|
||||
}
|
||||
pub fn is_closed(&self) -> bool {
|
||||
self.connection.close_reason().is_some()
|
||||
}
|
||||
pub fn is_open(&self) -> bool {
|
||||
!self.is_closed()
|
||||
}
|
||||
pub fn close_reason(&self) -> Option<CommunicationError> {
|
||||
self.connection.close_reason()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GenericReceiver<C: TransportConnection> {
|
||||
incoming: Arc<Mutex<mpsc::Receiver<Result<CommunicationValue, CommunicationError>>>>,
|
||||
#[cfg(feature = "pipes")]
|
||||
pipes: Arc<Mutex<mpsc::Receiver<PipeReader<C::RecvStream>>>>,
|
||||
connection: C,
|
||||
ping_sender: Arc<RwLock<Option<GenericSender<C>>>>,
|
||||
}
|
||||
|
||||
impl<C: TransportConnection> Clone for GenericReceiver<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
incoming: self.incoming.clone(),
|
||||
#[cfg(feature = "pipes")]
|
||||
pipes: self.pipes.clone(),
|
||||
connection: self.connection.clone(),
|
||||
ping_sender: self.ping_sender.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: TransportConnection> GenericReceiver<C> {
|
||||
pub fn new(connection: C, policy: Arc<Policy>) -> Self {
|
||||
let (tx, rx) = mpsc::channel(policy.receiver_queue_capacity);
|
||||
#[cfg(feature = "pipes")]
|
||||
let (pipe_tx, pipe_rx) = mpsc::channel(policy.receiver_queue_capacity);
|
||||
let ping_sender: Arc<RwLock<Option<GenericSender<C>>>> = Arc::new(RwLock::new(None));
|
||||
let task_ping_sender = ping_sender.clone();
|
||||
let task_connection = connection.clone();
|
||||
let task_policy = policy.clone();
|
||||
tokio::spawn(async move {
|
||||
let limit = Arc::new(Semaphore::new(
|
||||
task_policy.max_concurrent_stream_tasks.max(1),
|
||||
));
|
||||
loop {
|
||||
let stream = match timeout(
|
||||
task_policy.accept_stream_timeout,
|
||||
task_connection.accept_uni(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(stream)) => stream,
|
||||
Ok(Err(error)) => {
|
||||
let _ = tx.send(Err(error)).await;
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
if task_connection.close_reason().is_some() {
|
||||
let _ = tx.send(Err(CommunicationError::StreamClosed)).await;
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
let tx = tx.clone();
|
||||
#[cfg(feature = "pipes")]
|
||||
let pipe_tx = pipe_tx.clone();
|
||||
let policy = task_policy.clone();
|
||||
let permit = limit.clone();
|
||||
let ping_sender = task_ping_sender.clone();
|
||||
tokio::spawn(async move {
|
||||
let Ok(_permit) = permit.acquire_owned().await else {
|
||||
return;
|
||||
};
|
||||
let mut stream = stream;
|
||||
let mut frames = 0usize;
|
||||
loop {
|
||||
if policy
|
||||
.max_frames_per_stream
|
||||
.is_some_and(|max| frames >= max)
|
||||
{
|
||||
let _ = tx.send(Err(CommunicationError::StreamError)).await;
|
||||
break;
|
||||
}
|
||||
let mut len = [0; 4];
|
||||
match timeout(policy.read_timeout, stream.read_exact(&mut len)).await {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(_)) | Err(_) => break,
|
||||
}
|
||||
let len = u32::from_be_bytes(len);
|
||||
if len == policy.close_frame_len {
|
||||
let _ = tx.send(Err(CommunicationError::StreamClosed)).await;
|
||||
break;
|
||||
}
|
||||
if len as u64 > policy.max_message_size {
|
||||
let _ = tx.send(Err(CommunicationError::MessageTooLarge)).await;
|
||||
break;
|
||||
}
|
||||
let target_len = len as usize;
|
||||
let mut body = Vec::new();
|
||||
if body.try_reserve(target_len.min(16 * 1024)).is_err() {
|
||||
let _ = tx.send(Err(CommunicationError::MessageTooLarge)).await;
|
||||
break;
|
||||
}
|
||||
while body.len() < target_len {
|
||||
let chunk_len = (target_len - body.len()).min(16 * 1024);
|
||||
let mut chunk = [0u8; 16 * 1024];
|
||||
if !matches!(
|
||||
timeout(
|
||||
policy.read_timeout,
|
||||
stream.read_exact(&mut chunk[..chunk_len]),
|
||||
)
|
||||
.await,
|
||||
Ok(Ok(()))
|
||||
) || body.try_reserve(chunk_len).is_err()
|
||||
{
|
||||
let _ = tx.send(Err(CommunicationError::StreamError)).await;
|
||||
break;
|
||||
}
|
||||
body.extend_from_slice(&chunk[..chunk_len]);
|
||||
}
|
||||
if body.len() != target_len {
|
||||
break;
|
||||
}
|
||||
frames += 1;
|
||||
let message = match CommunicationValue::from_bytes(&body) {
|
||||
Ok(message) => message,
|
||||
Err(_) => {
|
||||
let _ = tx
|
||||
.send(Err(CommunicationError::ParseCommunicationValue))
|
||||
.await;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "pipes")]
|
||||
{
|
||||
let pipe_request_type = mtp_codec::CommunicationType::PipeRequest
|
||||
.try_to_id(&mtp_codec::TypeMap::latest());
|
||||
if Some(message.get_type()) == pipe_request_type && frames == 1 {
|
||||
let pipe_id = message.get_id();
|
||||
let description = message
|
||||
.get_str(mtp_codec::DataType::Description)
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let pipe_reader = PipeReader {
|
||||
stream,
|
||||
description,
|
||||
pipe_id,
|
||||
};
|
||||
|
||||
if pipe_tx.send(pipe_reader).await.is_err() {
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if message.is_type(mtp_codec::CommunicationType::Ping) {
|
||||
if let Some(sender) = ping_sender.read().await.clone() {
|
||||
let mut pong =
|
||||
CommunicationValue::new(mtp_codec::CommunicationType::Pong)
|
||||
.with_id(message.get_id());
|
||||
if let Some(timestamp) =
|
||||
message.get_data_opt(mtp_codec::DataType::Timestamp)
|
||||
{
|
||||
pong = pong.add_typed_default(
|
||||
mtp_codec::DataType::Timestamp,
|
||||
timestamp.clone(),
|
||||
);
|
||||
}
|
||||
let _ = sender.send(&pong).await;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if tx.send(Ok(message)).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
Self {
|
||||
incoming: Arc::new(Mutex::new(rx)),
|
||||
#[cfg(feature = "pipes")]
|
||||
pipes: Arc::new(Mutex::new(pipe_rx)),
|
||||
connection,
|
||||
ping_sender,
|
||||
}
|
||||
}
|
||||
pub async fn respond_to_pings(&self, sender: GenericSender<C>) {
|
||||
*self.ping_sender.write().await = Some(sender);
|
||||
}
|
||||
pub async fn receive(&self) -> Result<CommunicationValue, CommunicationError> {
|
||||
self.incoming
|
||||
.lock()
|
||||
.await
|
||||
.recv()
|
||||
.await
|
||||
.unwrap_or(Err(CommunicationError::StreamClosed))
|
||||
}
|
||||
|
||||
#[cfg(feature = "pipes")]
|
||||
pub async fn receive_event(
|
||||
&self,
|
||||
) -> Result<crate::TransportEvent<C::RecvStream>, CommunicationError> {
|
||||
let mut incoming = self.incoming.lock().await;
|
||||
let mut pipes = self.pipes.lock().await;
|
||||
tokio::select! {
|
||||
msg = incoming.recv() => {
|
||||
match msg {
|
||||
Some(Ok(val)) => Ok(crate::TransportEvent::Message(val)),
|
||||
Some(Err(e)) => Err(e),
|
||||
None => Err(self
|
||||
.connection
|
||||
.close_reason()
|
||||
.unwrap_or(CommunicationError::StreamClosed)),
|
||||
}
|
||||
}
|
||||
pipe = pipes.recv() => {
|
||||
match pipe {
|
||||
Some(reader) => Ok(crate::TransportEvent::Pipe(reader)),
|
||||
None => Err(self
|
||||
.connection
|
||||
.close_reason()
|
||||
.unwrap_or(CommunicationError::StreamClosed)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pipes")]
|
||||
pub async fn receive_pipe(&self) -> Result<PipeReader<C::RecvStream>, CommunicationError> {
|
||||
self.pipes
|
||||
.lock()
|
||||
.await
|
||||
.recv()
|
||||
.await
|
||||
.ok_or(CommunicationError::StreamClosed)
|
||||
}
|
||||
|
||||
#[cfg(feature = "pipes")]
|
||||
pub fn try_receive_pipe(
|
||||
&self,
|
||||
) -> Result<Option<PipeReader<C::RecvStream>>, CommunicationError> {
|
||||
match self.pipes.try_lock() {
|
||||
Ok(mut rx) => match rx.try_recv() {
|
||||
Ok(reader) => Ok(Some(reader)),
|
||||
Err(mpsc::error::TryRecvError::Empty) => Ok(None),
|
||||
Err(mpsc::error::TryRecvError::Disconnected) => {
|
||||
Err(CommunicationError::StreamClosed)
|
||||
}
|
||||
},
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_closed(&self) -> bool {
|
||||
self.connection.close_reason().is_some()
|
||||
}
|
||||
pub fn is_open(&self) -> bool {
|
||||
!self.is_closed()
|
||||
}
|
||||
pub fn close_reason(&self) -> Option<CommunicationError> {
|
||||
self.connection.close_reason()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,24 +3,53 @@ use mtp_common::CommunicationError;
|
|||
use rustls::pki_types::{PrivateKeyDer, pem::PemObject};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use tracing::debug;
|
||||
use wtransport::{Connection as WTConnection, Endpoint, ServerConfig};
|
||||
|
||||
fn generate_self_signed_cert() -> (Vec<u8>, Vec<u8>) {
|
||||
let key_pair = match rcgen::KeyPair::generate() {
|
||||
Ok(key_pair) => key_pair,
|
||||
Err(e) => panic!("failed to generate self-signed key pair: {e}"),
|
||||
};
|
||||
let params = match rcgen::CertificateParams::new(vec!["localhost".into(), "127.0.0.1".into()]) {
|
||||
Ok(params) => params,
|
||||
Err(e) => panic!("failed to build self-signed certificate params: {e}"),
|
||||
};
|
||||
let cert = match params.self_signed(&key_pair) {
|
||||
Ok(cert) => cert,
|
||||
Err(e) => panic!("failed to self-sign certificate: {e}"),
|
||||
};
|
||||
fn generate_self_signed_cert() -> Result<(Vec<u8>, Vec<u8>), CommunicationError> {
|
||||
let key_pair = rcgen::KeyPair::generate().map_err(|e| {
|
||||
CommunicationError::Other(format!("failed to generate self-signed key pair: {e}"))
|
||||
})?;
|
||||
let params = rcgen::CertificateParams::new(vec!["localhost".into(), "127.0.0.1".into()])
|
||||
.map_err(|e| {
|
||||
CommunicationError::Other(format!(
|
||||
"failed to build self-signed certificate params: {e}"
|
||||
))
|
||||
})?;
|
||||
let cert = params
|
||||
.self_signed(&key_pair)
|
||||
.map_err(|e| CommunicationError::Other(format!("failed to self-sign certificate: {e}")))?;
|
||||
let cert_pem = cert.pem();
|
||||
let key_pem = key_pair.serialize_pem();
|
||||
(cert_pem.into_bytes(), key_pem.into_bytes())
|
||||
Ok((cert_pem.into_bytes(), key_pem.into_bytes()))
|
||||
}
|
||||
|
||||
enum HostCredentials {
|
||||
Pem { cert_pem: Vec<u8>, key_pem: Vec<u8> },
|
||||
SelfSigned,
|
||||
}
|
||||
|
||||
/// TLS and transport settings for a native host.
|
||||
pub struct HostConfig {
|
||||
credentials: HostCredentials,
|
||||
policy: Policy,
|
||||
}
|
||||
|
||||
impl HostConfig {
|
||||
pub fn new(cert_pem: Vec<u8>, key_pem: Vec<u8>, policy: Policy) -> Self {
|
||||
Self {
|
||||
credentials: HostCredentials::Pem { cert_pem, key_pem },
|
||||
policy,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a self-signed certificate for local development.
|
||||
pub fn self_signed(policy: Policy) -> Self {
|
||||
Self {
|
||||
credentials: HostCredentials::SelfSigned,
|
||||
policy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Host {
|
||||
|
|
@ -61,19 +90,24 @@ pub async fn host(
|
|||
cert_pem: Vec<u8>,
|
||||
key_pem: Vec<u8>,
|
||||
policy: Policy,
|
||||
) -> Result<Host, CommunicationError> {
|
||||
host_with_config(ip, port, HostConfig::new(cert_pem, key_pem, policy)).await
|
||||
}
|
||||
|
||||
/// Start a host using explicit TLS and transport configuration.
|
||||
pub async fn host_with_config(
|
||||
ip: IpAddr,
|
||||
port: u16,
|
||||
config: HostConfig,
|
||||
) -> Result<Host, CommunicationError> {
|
||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||
|
||||
// When MTP_INSECURE is set, generate a self-signed cert so the host can
|
||||
// run without externally-provided TLS credentials.
|
||||
let is_insecure = std::env::var("MTP_INSECURE").is_ok();
|
||||
let (use_cert_pem, use_key_pem) = if is_insecure {
|
||||
generate_self_signed_cert()
|
||||
} else {
|
||||
(cert_pem, key_pem)
|
||||
let (cert_pem, key_pem) = match config.credentials {
|
||||
HostCredentials::Pem { cert_pem, key_pem } => (cert_pem, key_pem),
|
||||
HostCredentials::SelfSigned => generate_self_signed_cert()?,
|
||||
};
|
||||
|
||||
let server_config = configure_server(ip, port, use_cert_pem, use_key_pem, &policy).await?;
|
||||
let server_config = configure_server(ip, port, cert_pem, key_pem, &config.policy).await?;
|
||||
let endpoint = Endpoint::server(server_config)
|
||||
.map_err(|e| CommunicationError::Other(format!("Endpoint creation failed: {}", e)))?;
|
||||
|
||||
|
|
@ -83,7 +117,7 @@ pub async fn host(
|
|||
|
||||
let (incoming_tx, incoming_rx) = tokio::sync::mpsc::channel(16);
|
||||
|
||||
let policy = Arc::new(policy);
|
||||
let policy = Arc::new(config.policy);
|
||||
|
||||
let task = tokio::spawn(async move {
|
||||
loop {
|
||||
|
|
@ -92,7 +126,7 @@ pub async fn host(
|
|||
let request = match incoming_session.await {
|
||||
Ok(req) => req,
|
||||
Err(e) => {
|
||||
log::debug!("incoming WebTransport session failed: {e}");
|
||||
debug!("incoming WebTransport session failed: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
|
@ -103,7 +137,7 @@ pub async fn host(
|
|||
{
|
||||
Ok(conn) => conn,
|
||||
Err(e) => {
|
||||
log::debug!("WebTransport request accept failed: {e}");
|
||||
debug!("WebTransport request accept failed: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
|
@ -128,7 +162,7 @@ async fn handle_connection(
|
|||
let handle = Arc::new(ConnectionHandle::new());
|
||||
|
||||
let sender = Sender::new(connection.clone(), handle.clone(), policy.clone());
|
||||
let receiver = Receiver::new(connection, handle, policy);
|
||||
let receiver = Receiver::new_for_handshake(connection, handle, policy);
|
||||
let _ = tx.send((sender, receiver)).await;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
pub mod client;
|
||||
pub mod connection;
|
||||
pub mod connection_handle;
|
||||
mod framing;
|
||||
pub mod generic_connection;
|
||||
pub mod pinning;
|
||||
pub mod transport_traits;
|
||||
|
||||
#[cfg(feature = "pipes")]
|
||||
pub mod pipe;
|
||||
|
||||
pub use connection::{Policy, Receiver, SendMode, Sender};
|
||||
pub use generic_connection::{GenericReceiver, GenericSender};
|
||||
|
||||
#[cfg(feature = "pipes")]
|
||||
pub use connection::TransportEvent;
|
||||
#[cfg(feature = "pipes")]
|
||||
pub use pipe::{PipeReader, PipeWriter};
|
||||
|
||||
pub use client::connect;
|
||||
pub use client::{ClientConfig, connect, connect_with_config};
|
||||
pub use connection_handle::ConnectionHandle;
|
||||
pub use transport_traits::{TransportConnection, TransportRecvStream, TransportSendStream};
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
pub mod host;
|
||||
#[cfg(feature = "host")]
|
||||
pub use host::{Host, host};
|
||||
pub use host::{Host, HostConfig, host, host_with_config};
|
||||
|
|
|
|||
175
transport/src/pinning.rs
Normal file
175
transport/src/pinning.rs
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
use rustls::{
|
||||
ClientConfig as RustlsClientConfig, DigitallySignedStruct, SignatureScheme,
|
||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||
pki_types::{CertificateDer, ServerName, UnixTime},
|
||||
};
|
||||
use sha2::{Digest, Sha256};
|
||||
use wtransport::ClientConfig as WTransportClientConfig;
|
||||
|
||||
use crate::Policy;
|
||||
|
||||
use mtp_common::CommunicationError;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A certificate verifier that pins a connection to a specific SPKI
|
||||
/// (Subject Public Key Info) SHA-256 hash. The client will only accept
|
||||
/// server certificates whose DER-encoded SPKI matches the provided hash.
|
||||
#[derive(Debug)]
|
||||
pub struct PinnedCertVerifier {
|
||||
expected_hash: [u8; 32],
|
||||
}
|
||||
|
||||
impl PinnedCertVerifier {
|
||||
pub fn new(expected_hash: [u8; 32]) -> Self {
|
||||
Self { expected_hash }
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerCertVerifier for PinnedCertVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &CertificateDer<'_>,
|
||||
_intermediates: &[CertificateDer<'_>],
|
||||
_server_name: &ServerName<'_>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: UnixTime,
|
||||
) -> Result<ServerCertVerified, rustls::Error> {
|
||||
let der = end_entity.as_ref();
|
||||
|
||||
let spki = extract_spki(der).map_err(|_| {
|
||||
rustls::Error::General("failed to extract SPKI from certificate".into())
|
||||
})?;
|
||||
|
||||
let computed = Sha256::digest(&spki);
|
||||
|
||||
if computed.as_slice() != self.expected_hash.as_slice() {
|
||||
return Err(rustls::Error::General(format!(
|
||||
"certificate SPKI hash mismatch: expected {:02x?}, got {:02x?}",
|
||||
self.expected_hash, computed
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer<'_>,
|
||||
_dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &CertificateDer<'_>,
|
||||
_dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
vec![
|
||||
SignatureScheme::RSA_PKCS1_SHA256,
|
||||
SignatureScheme::ECDSA_NISTP256_SHA256,
|
||||
SignatureScheme::RSA_PSS_SHA256,
|
||||
SignatureScheme::ED25519,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the Subject Public Key Info (SPKI) field from a DER-encoded X.509
|
||||
/// certificate. Returns the raw bytes of the SPKI sequence.
|
||||
fn extract_spki(der: &[u8]) -> Result<Vec<u8>, ()> {
|
||||
let (_, outer) = parse_der_sequence(der).map_err(|_| ())?;
|
||||
|
||||
let (_, tbs) = parse_der_sequence(outer).map_err(|_| ())?;
|
||||
|
||||
// Skip version (context [0]), serial number, signature algorithm, issuer,
|
||||
// validity, subject to reach subjectPublicKeyInfo (index 6).
|
||||
let mut offset = 0;
|
||||
let mut element_index = 0;
|
||||
|
||||
while offset < tbs.len() && element_index < 6 {
|
||||
let (len, _) = parse_der_element(&tbs[offset..]).map_err(|_| ())?;
|
||||
offset += len;
|
||||
element_index += 1;
|
||||
}
|
||||
|
||||
if element_index != 6 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let (spki_len, spki) = parse_der_element(&tbs[offset..]).map_err(|_| ())?;
|
||||
if spki_len == 0 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(spki.to_vec())
|
||||
}
|
||||
|
||||
fn parse_der_element(data: &[u8]) -> Result<(usize, &[u8]), ()> {
|
||||
if data.len() < 2 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let mut offset = 1;
|
||||
|
||||
let len_byte = data[offset];
|
||||
offset += 1;
|
||||
|
||||
let content_len = if len_byte & 0x80 == 0 {
|
||||
len_byte as usize
|
||||
} else {
|
||||
let num_bytes = (len_byte & 0x7F) as usize;
|
||||
if offset + num_bytes > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
let mut len = 0usize;
|
||||
for i in 0..num_bytes {
|
||||
len = (len << 8) | data[offset + i] as usize;
|
||||
}
|
||||
offset += num_bytes;
|
||||
len
|
||||
};
|
||||
|
||||
if offset + content_len > data.len() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let total_len = offset + content_len;
|
||||
Ok((total_len, &data[offset..offset + content_len]))
|
||||
}
|
||||
|
||||
fn parse_der_sequence(data: &[u8]) -> Result<(usize, &[u8]), ()> {
|
||||
if data.is_empty() || data[0] != 0x30 {
|
||||
return Err(());
|
||||
}
|
||||
parse_der_element(data)
|
||||
}
|
||||
|
||||
/// Build a [`WTransportClientConfig`] that verifies the server certificate
|
||||
/// against a pinned SPKI SHA-256 hash.
|
||||
pub fn configure_client_pinned_hash(
|
||||
expected_hash: [u8; 32],
|
||||
policy: &Policy,
|
||||
) -> Result<WTransportClientConfig, CommunicationError> {
|
||||
let verifier = PinnedCertVerifier::new(expected_hash);
|
||||
|
||||
let mut tls_config = RustlsClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(verifier))
|
||||
.with_no_client_auth();
|
||||
|
||||
tls_config.alpn_protocols = vec![b"h3".to_vec()];
|
||||
|
||||
Ok(WTransportClientConfig::builder()
|
||||
.with_bind_default()
|
||||
.with_custom_tls(tls_config)
|
||||
.keep_alive_interval(policy.keep_alive_interval)
|
||||
.max_idle_timeout(policy.max_idle_timeout)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?
|
||||
.build())
|
||||
}
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tracing::warn;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PipeWriter {
|
||||
pub(crate) stream: wtransport::SendStream,
|
||||
pub struct PipeWriter<S = wtransport::SendStream> {
|
||||
pub(crate) stream: S,
|
||||
}
|
||||
|
||||
impl PipeWriter {
|
||||
pub async fn finish(mut self) -> Result<(), mtp_common::CommunicationError> {
|
||||
self.stream.finish().await.map_err(|e| {
|
||||
log::warn!("[PipeWriter] finish failed: {e}");
|
||||
warn!("[PipeWriter] finish failed: {e}");
|
||||
mtp_common::CommunicationError::StreamWriteError(e)
|
||||
})
|
||||
}
|
||||
|
|
@ -20,7 +21,18 @@ impl PipeWriter {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for PipeWriter {
|
||||
impl<S: tokio::io::AsyncWrite + Send + Unpin> PipeWriter<S> {
|
||||
pub async fn finish_async(mut self) -> Result<(), mtp_common::CommunicationError> {
|
||||
tokio::io::AsyncWriteExt::shutdown(&mut self)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("[PipeWriter] finish_async failed: {e}");
|
||||
mtp_common::CommunicationError::StreamError
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: tokio::io::AsyncWrite + Send + Unpin> AsyncWrite for PipeWriter<S> {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
|
|
@ -39,13 +51,13 @@ impl AsyncWrite for PipeWriter {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PipeReader {
|
||||
pub(crate) stream: wtransport::RecvStream,
|
||||
pub struct PipeReader<R = wtransport::RecvStream> {
|
||||
pub(crate) stream: R,
|
||||
pub(crate) description: String,
|
||||
pub(crate) pipe_id: u32,
|
||||
}
|
||||
|
||||
impl PipeReader {
|
||||
impl<R> PipeReader<R> {
|
||||
pub fn description(&self) -> &str {
|
||||
&self.description
|
||||
}
|
||||
|
|
@ -55,7 +67,7 @@ impl PipeReader {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for PipeReader {
|
||||
impl<R: tokio::io::AsyncRead + Send + Unpin> AsyncRead for PipeReader<R> {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
|
|
|
|||
108
transport/src/transport_traits.rs
Normal file
108
transport/src/transport_traits.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
//! Transport-neutral primitives used by alternative MTP hosts.
|
||||
//!
|
||||
//! The existing public `Sender` and `Receiver` remain backed by
|
||||
//! `wtransport`. These traits are deliberately introduced separately so new
|
||||
//! QUIC/WebTransport backends can be added without changing that API in one
|
||||
//! breaking step.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mtp_common::CommunicationError;
|
||||
|
||||
/// A writable unidirectional stream suitable for MTP frames.
|
||||
///
|
||||
/// Implementors must also implement [`tokio::io::AsyncWrite`] so that the
|
||||
/// stream can back a raw pipe via [`crate::PipeWriter`] when the `pipes`
|
||||
/// feature is enabled.
|
||||
#[async_trait]
|
||||
pub trait TransportSendStream: tokio::io::AsyncWrite + Send + Sync {
|
||||
async fn write_all(&mut self, buf: &[u8]) -> Result<(), CommunicationError>;
|
||||
async fn finish(&mut self) -> Result<(), CommunicationError>;
|
||||
}
|
||||
|
||||
/// A readable unidirectional stream suitable for MTP frames.
|
||||
///
|
||||
/// Implementors must also implement [`tokio::io::AsyncRead`] so that the
|
||||
/// stream can back a raw pipe via [`crate::PipeReader`] when the `pipes`
|
||||
/// feature is enabled.
|
||||
#[async_trait]
|
||||
pub trait TransportRecvStream: tokio::io::AsyncRead + Send + Sync {
|
||||
async fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), CommunicationError>;
|
||||
async fn read_chunk(&mut self, max: usize) -> Result<Option<Vec<u8>>, CommunicationError>;
|
||||
}
|
||||
|
||||
/// A QUIC/WebTransport connection that provides MTP's unidirectional streams.
|
||||
#[async_trait]
|
||||
pub trait TransportConnection: Clone + Send + Sync + 'static {
|
||||
type SendStream: TransportSendStream + 'static;
|
||||
type RecvStream: TransportRecvStream + 'static;
|
||||
|
||||
async fn open_uni(&self) -> Result<Self::SendStream, CommunicationError>;
|
||||
async fn accept_uni(&self) -> Result<Self::RecvStream, CommunicationError>;
|
||||
fn close_reason(&self) -> Option<CommunicationError>;
|
||||
fn close(&self, code: u32, reason: &[u8]);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TransportSendStream for wtransport::SendStream {
|
||||
async fn write_all(&mut self, buf: &[u8]) -> Result<(), CommunicationError> {
|
||||
wtransport::SendStream::write_all(self, buf)
|
||||
.await
|
||||
.map_err(|_| CommunicationError::StreamError)
|
||||
}
|
||||
|
||||
async fn finish(&mut self) -> Result<(), CommunicationError> {
|
||||
wtransport::SendStream::finish(self)
|
||||
.await
|
||||
.map_err(|_| CommunicationError::StreamError)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TransportRecvStream for wtransport::RecvStream {
|
||||
async fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), CommunicationError> {
|
||||
wtransport::RecvStream::read_exact(self, buf)
|
||||
.await
|
||||
.map_err(|_| CommunicationError::StreamError)
|
||||
}
|
||||
|
||||
async fn read_chunk(&mut self, max: usize) -> Result<Option<Vec<u8>>, CommunicationError> {
|
||||
let mut buf = vec![0; max];
|
||||
match wtransport::RecvStream::read(self, &mut buf).await {
|
||||
Ok(Some(size)) => {
|
||||
buf.truncate(size);
|
||||
Ok(Some(buf))
|
||||
}
|
||||
Ok(None) => Ok(None),
|
||||
Err(_) => Err(CommunicationError::StreamError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TransportConnection for wtransport::Connection {
|
||||
type SendStream = wtransport::SendStream;
|
||||
type RecvStream = wtransport::RecvStream;
|
||||
|
||||
async fn open_uni(&self) -> Result<Self::SendStream, CommunicationError> {
|
||||
let opening = wtransport::Connection::open_uni(self)
|
||||
.await
|
||||
.map_err(CommunicationError::ConnectionError)?;
|
||||
opening.await.map_err(|_| CommunicationError::StreamError)
|
||||
}
|
||||
|
||||
async fn accept_uni(&self) -> Result<Self::RecvStream, CommunicationError> {
|
||||
wtransport::Connection::accept_uni(self)
|
||||
.await
|
||||
.map_err(|_| CommunicationError::StreamError)
|
||||
}
|
||||
|
||||
fn close_reason(&self) -> Option<CommunicationError> {
|
||||
self.quic_connection()
|
||||
.close_reason()
|
||||
.map(|_| CommunicationError::StreamClosed)
|
||||
}
|
||||
|
||||
fn close(&self, code: u32, reason: &[u8]) {
|
||||
self.quic_connection().close(code.into(), reason);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue