Packages

macula

4.2.8
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());
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)
}
/// 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)));
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()
}
}