Packages

macula

4.8.0
7.1.0 7.0.0 6.0.0 5.2.2 5.2.1 5.2.0 5.1.0 5.0.0 4.8.0 4.7.1 4.7.0 4.6.0 4.5.0 4.4.10 4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.1 4.3.0 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.1 4.1.0 4.0.0 3.16.0 3.15.3 3.15.2 3.15.1 3.14.0 3.13.0 3.12.1 3.12.0 3.11.1 3.11.0 3.10.3 3.10.2 3.10.1 3.9.0 3.8.0 3.7.0 3.5.0 3.4.0 3.3.0 3.2.0 3.1.0 3.0.0 2.1.1 2.1.0 2.0.0 1.5.2 1.5.1 1.4.30 1.4.29 1.4.28 1.4.27 1.4.26 1.4.25 1.4.24 1.4.23 1.4.22 1.4.21 1.4.20 1.4.19 1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.1 1.3.0 1.2.0 1.1.0 1.0.10 1.0.9 1.0.8 1.0.7 1.0.6 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 0.48.6 0.48.5 0.48.4 0.48.3 0.48.2 0.48.1 0.48.0 0.47.1 0.47.0 0.46.3 0.46.1 0.46.0 0.45.3 0.45.2 0.45.1 0.45.0 0.44.2 0.44.1 0.44.0 0.43.3 0.43.2 0.43.1 0.43.0 0.42.9 0.42.8 0.42.7 0.42.6 0.42.5 0.42.4 0.42.3 0.42.2 0.42.1 0.42.0 0.41.1 0.41.0 0.40.1 0.40.0 0.39.9 0.39.8 0.39.7 0.39.6 0.39.5 0.39.4 0.39.3 0.39.2 0.39.1 0.39.0 0.38.8 0.38.7 0.38.6 0.38.5 0.38.4 0.38.3 0.38.2 0.38.1 0.38.0 0.37.7 0.37.6 0.37.5 0.37.4 0.37.3 0.37.2 0.37.1 0.37.0 0.36.6 0.36.5 0.36.4 0.36.3 0.36.2 0.36.1 0.36.0 0.35.4 0.35.3 0.35.2 0.35.1 0.35.0 0.34.1 0.34.0 0.33.1 0.33.0 0.32.5 0.32.4 0.32.3 0.32.2 0.32.1 0.32.0 0.31.9 0.31.8 0.31.7 0.31.6 0.31.5 0.31.4 0.31.3 0.31.2 0.31.1 0.31.0 0.30.10 0.30.9 0.30.8 0.30.7 0.30.6 0.30.5 0.30.4 0.30.3 0.30.2 0.30.1 0.30.0 0.29.0 0.28.3 0.28.2 0.28.1 0.28.0 0.27.1 0.27.0 0.26.1 0.26.0 0.25.6 0.25.5 0.25.4 0.25.3 0.25.2 0.25.1 0.25.0 0.24.6 0.24.5 0.24.4 0.24.3 0.24.2 0.24.1 0.24.0 0.23.3 0.23.2 0.23.1 0.23.0 0.22.12 0.22.11 0.22.10 0.22.9 0.22.8 0.22.7 0.22.6 0.22.5 0.22.4 0.22.3 0.22.2 0.22.1 0.22.0 0.21.7 0.21.6 0.21.5 0.21.4 0.21.2 0.21.1 0.21.0 0.20.25 0.20.24 0.20.23 0.20.22 0.20.21 0.20.20 0.20.19 0.20.18 0.20.17 0.20.16 0.20.15 0.20.14 0.20.13 0.20.12 0.20.11 0.20.10 0.20.9 0.20.8 0.20.7 0.20.6 0.20.5 0.20.3 0.20.2 0.20.1 0.20.0 0.19.2 0.19.1 0.19.0 0.18.1 0.18.0 0.17.4 0.17.3 0.17.2 0.17.1 0.17.0 0.16.6 0.16.5 0.16.4 0.16.3 0.16.2 0.16.1 0.16.0 0.15.1 0.15.0 0.14.3 0.14.2 0.14.1 0.14.0 0.12.6 0.12.5 0.12.3 0.11.3 0.10.2 0.10.1 0.10.0 0.9.2 0.9.1 0.9.0 0.8.25 0.8.24 0.8.23 0.8.22 0.8.21 0.8.20 0.8.19 0.8.18 0.8.17 0.8.16 0.8.15 0.8.14 0.8.13 0.8.12 0.8.11 0.8.10 0.8.9 0.8.8 0.8.7 0.8.6 0.8.5 0.8.4 0.8.3 0.8.2 0.8.1 0.8.0 0.7.30 0.7.29 0.7.28 0.7.27 0.7.26 0.7.25 0.7.24 0.7.23 0.7.22 0.7.21 0.7.20 0.7.19 0.7.18 0.7.17 0.7.16 0.7.15 0.7.14 0.7.13 0.7.12 0.7.11 0.7.10 0.7.9 0.7.8 0.7.7 0.7.6 0.7.5 0.7.4 0.7.3 0.7.2 0.7.1 0.7.0 0.6.7 0.6.6 0.6.5 0.6.4 0.6.3 0.6.2 0.6.1 0.6.0 0.5.0 0.4.4 0.4.3 0.4.2 0.4.1 0.4.0 0.3.4 0.3.3 0.3.2 0.3.1

Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise

Current section

Files

Jump to
macula native macula_quic src config.rs
Raw

native/macula_quic/src/config.rs

use quinn::{ClientConfig, ServerConfig, TransportConfig};
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use std::fs;
use std::net::{IpAddr, SocketAddr, UdpSocket};
use std::sync::Arc;
use std::time::Duration;
use crate::cert;
/// Build a Quinn ServerConfig from Erlang options.
///
/// Required: certfile, keyfile
/// Optional: alpn (default ["macula"]), idle_timeout_ms, keep_alive_interval_ms,
/// peer_bidi_stream_count, peer_unidi_stream_count
pub fn build_server_config(
certfile: &str,
keyfile: &str,
alpn: &[String],
idle_timeout_ms: u64,
keep_alive_ms: u64,
bidi_streams: u32,
uni_streams: u32,
) -> Result<ServerConfig, String> {
let certs = load_certs(certfile)?;
let key = load_key(keyfile)?;
let mut server_crypto = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)
.map_err(|e| format!("TLS config error: {}", e))?;
server_crypto.alpn_protocols = alpn.iter().map(|s| s.as_bytes().to_vec()).collect();
let mut transport = TransportConfig::default();
transport.max_idle_timeout(Some(
quinn::IdleTimeout::try_from(Duration::from_millis(idle_timeout_ms))
.map_err(|e| format!("idle_timeout: {}", e))?,
));
transport.keep_alive_interval(Some(Duration::from_millis(keep_alive_ms)));
transport.max_concurrent_bidi_streams(bidi_streams.into());
transport.max_concurrent_uni_streams(uni_streams.into());
apply_flow_control_defaults(&mut transport);
let mut config =
ServerConfig::with_crypto(Arc::new(
quinn::crypto::rustls::QuicServerConfig::try_from(server_crypto)
.map_err(|e| format!("QUIC server config: {}", e))?,
));
config.transport_config(Arc::new(transport));
Ok(config)
}
/// Bump Quinn's per-stream and per-connection flow-control windows
/// well above the conservative defaults (1.25MB stream, 1.25MB *
/// streams connection). Macula peering uses ONE long-lived bidi
/// stream per connection over which we multiplex pubsub EVENTs,
/// CALL/REPLY, DHT records, blob streams. With many small frames
/// in flight and the receiver doing per-frame verify (~200µs
/// Ed25519), the default 1.25MB window exhausts long before the
/// receiver acks consumed bytes — surfaces as receiver-bound
/// throughput in pubsub flood torture.
///
/// 16 MB stream window absorbs ~100k 150-byte EVENT frames before
/// backpressure; 64 MB connection window scales with our typical
/// 1-2 streams per peering. send_window matches.
fn apply_flow_control_defaults(transport: &mut TransportConfig) {
transport.stream_receive_window(16u32.checked_mul(1024 * 1024).unwrap().into());
transport.receive_window(64u32.checked_mul(1024 * 1024).unwrap().into());
transport.send_window(64u64.checked_mul(1024 * 1024).unwrap());
}
/// Build a Quinn ClientConfig.
///
/// Three trust modes:
/// - `pinned_pubkey = Some(pk)` — pubkey-anchored (sovereign overlay
/// path). Validates the leaf cert's Ed25519 SubjectPublicKeyInfo
/// against `pk`. No CA chain. See PLAN_SOVEREIGN_OVERLAY_PHASE1
/// §4.4.
/// - `pinned_pubkey = None`, `verify = true` — webpki + system CAs
/// (existing public-IP path with Let's Encrypt-anchored certs).
/// - `pinned_pubkey = None`, `verify = false` — skip all verification
/// (development/test only).
pub fn build_client_config(
alpn: &[String],
verify: bool,
pinned_pubkey: Option<Vec<u8>>,
idle_timeout_ms: u64,
keep_alive_ms: u64,
) -> Result<ClientConfig, String> {
let client_crypto = if let Some(pk) = pinned_pubkey {
rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(cert::PubkeyPinVerifier::new(pk)))
.with_no_client_auth()
} else if verify {
let mut roots = rustls::RootCertStore::empty();
roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
rustls::ClientConfig::builder()
.with_root_certificates(roots)
.with_no_client_auth()
} else {
rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(SkipServerVerification::new()))
.with_no_client_auth()
};
let mut crypto = client_crypto;
crypto.alpn_protocols = alpn.iter().map(|s| s.as_bytes().to_vec()).collect();
let mut transport = TransportConfig::default();
transport.max_idle_timeout(Some(
quinn::IdleTimeout::try_from(Duration::from_millis(idle_timeout_ms))
.map_err(|e| format!("idle_timeout: {}", e))?,
));
transport.keep_alive_interval(Some(Duration::from_millis(keep_alive_ms)));
apply_flow_control_defaults(&mut transport);
let mut config = ClientConfig::new(Arc::new(
quinn::crypto::rustls::QuicClientConfig::try_from(crypto)
.map_err(|e| format!("QUIC client config: {}", e))?,
));
config.transport_config(Arc::new(transport));
Ok(config)
}
/// Create a UDP socket bound to a specific address.
/// Uses socket2 for SO_REUSEPORT and fine-grained control.
pub fn create_bound_socket(addr: IpAddr, port: u16) -> Result<UdpSocket, String> {
let socket_addr = SocketAddr::new(addr, port);
let domain = match addr {
IpAddr::V4(_) => socket2::Domain::IPV4,
IpAddr::V6(_) => socket2::Domain::IPV6,
};
let socket = socket2::Socket::new(domain, socket2::Type::DGRAM, Some(socket2::Protocol::UDP))
.map_err(|e| format!("socket create: {}", e))?;
socket
.set_reuse_port(true)
.map_err(|e| format!("SO_REUSEPORT: {}", e))?;
socket
.set_nonblocking(true)
.map_err(|e| format!("nonblocking: {}", e))?;
if addr.is_ipv6() {
// [::] (unspecified) = dual-stack (accepts IPv4 + IPv6)
// Specific IPv6 (e.g. fd00::1) = v6-only (per-identity binding)
let v6_only = !addr.is_unspecified();
socket
.set_only_v6(v6_only)
.map_err(|e| format!("IPV6_V6ONLY: {}", e))?;
}
socket
.bind(&socket_addr.into())
.map_err(|e| format!("bind {}:{}: {}", addr, port, e))?;
Ok(socket.into())
}
// ── TLS helpers ────────────────────────────────────────────────
fn load_certs(path: &str) -> Result<Vec<CertificateDer<'static>>, String> {
let data = fs::read(path).map_err(|e| format!("read cert {}: {}", path, e))?;
rustls_pemfile::certs(&mut &data[..])
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("parse cert {}: {}", path, e))
}
fn load_key(path: &str) -> Result<PrivateKeyDer<'static>, String> {
let data = fs::read(path).map_err(|e| format!("read key {}: {}", path, e))?;
rustls_pemfile::private_key(&mut &data[..])
.map_err(|e| format!("parse key {}: {}", path, e))?
.ok_or_else(|| format!("no private key found in {}", path))
}
// ── Development mode: skip TLS verification ────────────────────
#[derive(Debug)]
struct SkipServerVerification(Arc<rustls::crypto::CryptoProvider>);
impl SkipServerVerification {
fn new() -> Self {
Self(Arc::new(rustls::crypto::ring::default_provider()))
}
}
impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self.0.signature_verification_algorithms.supported_schemes()
}
}