T1.5: Migrate emit/parse sites to v2 wire format
This commit is contained in:
@@ -22,8 +22,8 @@ pub fn server_config() -> (quinn::ServerConfig, Vec<u8>) {
|
||||
/// Create a server configuration with a deterministic self-signed certificate
|
||||
/// derived from a 32-byte seed. Same seed = same cert = same TLS fingerprint.
|
||||
pub fn server_config_from_seed(seed: &[u8; 32]) -> (quinn::ServerConfig, Vec<u8>) {
|
||||
use ed25519_dalek::pkcs8::EncodePrivateKey;
|
||||
use ed25519_dalek::SigningKey;
|
||||
use ed25519_dalek::pkcs8::EncodePrivateKey;
|
||||
use hkdf::Hkdf;
|
||||
use sha2::Sha256;
|
||||
|
||||
@@ -35,22 +35,23 @@ pub fn server_config_from_seed(seed: &[u8; 32]) -> (quinn::ServerConfig, Vec<u8>
|
||||
|
||||
// Create Ed25519 signing key and export as PKCS8 DER
|
||||
let signing_key = SigningKey::from_bytes(&ed_bytes);
|
||||
let pkcs8_doc = signing_key.to_pkcs8_der()
|
||||
let pkcs8_doc = signing_key
|
||||
.to_pkcs8_der()
|
||||
.expect("failed to encode Ed25519 key as PKCS8");
|
||||
let key_der_for_rcgen = rustls::pki_types::PrivateKeyDer::try_from(pkcs8_doc.as_bytes().to_vec())
|
||||
.expect("failed to wrap PKCS8 DER");
|
||||
let key_der_for_rcgen =
|
||||
rustls::pki_types::PrivateKeyDer::try_from(pkcs8_doc.as_bytes().to_vec())
|
||||
.expect("failed to wrap PKCS8 DER");
|
||||
|
||||
// Create rcgen KeyPair from DER
|
||||
let key_pair = rcgen::KeyPair::from_der_and_sign_algo(
|
||||
&key_der_for_rcgen,
|
||||
&rcgen::PKCS_ED25519,
|
||||
)
|
||||
.expect("failed to create KeyPair from seed-derived Ed25519 key");
|
||||
let key_pair = rcgen::KeyPair::from_der_and_sign_algo(&key_der_for_rcgen, &rcgen::PKCS_ED25519)
|
||||
.expect("failed to create KeyPair from seed-derived Ed25519 key");
|
||||
|
||||
// Build self-signed cert with this deterministic keypair
|
||||
let params = rcgen::CertificateParams::new(vec!["localhost".to_string()])
|
||||
.expect("failed to create CertificateParams");
|
||||
let cert = params.self_signed(&key_pair).expect("failed to self-sign cert");
|
||||
let cert = params
|
||||
.self_signed(&key_pair)
|
||||
.expect("failed to self-sign cert");
|
||||
let cert_der = rustls::pki_types::CertificateDer::from(cert.der().to_vec());
|
||||
let key_der = rustls::pki_types::PrivateKeyDer::try_from(key_pair.serialize_der())
|
||||
.expect("failed to serialize key DER");
|
||||
@@ -62,7 +63,7 @@ pub fn server_config_from_seed(seed: &[u8; 32]) -> (quinn::ServerConfig, Vec<u8>
|
||||
///
|
||||
/// Format: `xx:xx:xx:xx:...` (32 bytes = 64 hex chars with colons).
|
||||
pub fn tls_fingerprint(cert_der: &[u8]) -> String {
|
||||
use sha2::{Sha256, Digest};
|
||||
use sha2::{Digest, Sha256};
|
||||
let hash = Sha256::digest(cert_der);
|
||||
hash.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
@@ -148,7 +149,7 @@ fn transport_config() -> quinn::TransportConfig {
|
||||
let mut mtu_config = quinn::MtuDiscoveryConfig::default();
|
||||
mtu_config
|
||||
.upper_bound(1452)
|
||||
.interval(Duration::from_secs(300)) // re-probe every 5 min
|
||||
.interval(Duration::from_secs(300)) // re-probe every 5 min
|
||||
.black_hole_cooldown(Duration::from_secs(30)); // retry faster on lossy links
|
||||
config.mtu_discovery_config(Some(mtu_config));
|
||||
config.initial_mtu(1200); // safe starting point
|
||||
|
||||
@@ -28,13 +28,13 @@ pub async fn connect(
|
||||
server_name: &str,
|
||||
config: quinn::ClientConfig,
|
||||
) -> Result<quinn::Connection, TransportError> {
|
||||
let connecting = endpoint.connect_with(config, addr, server_name).map_err(|e| {
|
||||
TransportError::Internal(format!("connect error: {e}"))
|
||||
})?;
|
||||
let connecting = endpoint
|
||||
.connect_with(config, addr, server_name)
|
||||
.map_err(|e| TransportError::Internal(format!("connect error: {e}")))?;
|
||||
|
||||
let connection = connecting.await.map_err(|e| {
|
||||
TransportError::Internal(format!("connection failed: {e}"))
|
||||
})?;
|
||||
let connection = connecting
|
||||
.await
|
||||
.map_err(|e| TransportError::Internal(format!("connection failed: {e}")))?;
|
||||
|
||||
Ok(connection)
|
||||
}
|
||||
@@ -111,9 +111,9 @@ pub async fn accept(endpoint: &quinn::Endpoint) -> Result<quinn::Connection, Tra
|
||||
.await
|
||||
.ok_or(TransportError::ConnectionLost)?;
|
||||
|
||||
let connection = incoming.await.map_err(|e| {
|
||||
TransportError::Internal(format!("accept failed: {e}"))
|
||||
})?;
|
||||
let connection = incoming
|
||||
.await
|
||||
.map_err(|e| TransportError::Internal(format!("accept failed: {e}")))?;
|
||||
|
||||
Ok(connection)
|
||||
}
|
||||
|
||||
@@ -26,22 +26,20 @@ pub fn max_datagram_payload(connection: &quinn::Connection) -> Option<usize> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bytes::Bytes;
|
||||
use wzp_proto::{CodecId, MediaHeader};
|
||||
use wzp_proto::{CodecId, MediaHeader, MediaType};
|
||||
|
||||
fn test_packet() -> MediaPacket {
|
||||
MediaPacket {
|
||||
header: MediaHeader {
|
||||
version: 0,
|
||||
is_repair: false,
|
||||
version: 2,
|
||||
flags: 0,
|
||||
media_type: MediaType::Audio,
|
||||
codec_id: CodecId::Opus16k,
|
||||
has_quality_report: false,
|
||||
fec_ratio_encoded: 16,
|
||||
stream_id: 0,
|
||||
fec_ratio: 16,
|
||||
seq: 42,
|
||||
timestamp: 1000,
|
||||
fec_block: 1,
|
||||
fec_symbol: 0,
|
||||
reserved: 0,
|
||||
csrc_count: 0,
|
||||
},
|
||||
payload: Bytes::from_static(b"fake opus frame data"),
|
||||
quality_report: None,
|
||||
@@ -61,7 +59,7 @@ mod tests {
|
||||
#[test]
|
||||
fn serialize_deserialize_with_quality_report() {
|
||||
let mut packet = test_packet();
|
||||
packet.header.has_quality_report = true;
|
||||
packet.header.flags |= MediaHeader::FLAG_QUALITY;
|
||||
packet.quality_report = Some(wzp_proto::QualityReport {
|
||||
loss_pct: 50,
|
||||
rtt_4ms: 75,
|
||||
|
||||
@@ -30,7 +30,7 @@ pub struct PathMonitor {
|
||||
first_recv_time_ms: Option<u64>,
|
||||
last_recv_time_ms: Option<u64>,
|
||||
/// Sequence tracking for loss detection.
|
||||
highest_sent_seq: Option<u16>,
|
||||
highest_sent_seq: Option<u32>,
|
||||
total_sent: u64,
|
||||
total_received: u64,
|
||||
/// Last observed RTT for jitter calculation.
|
||||
@@ -64,7 +64,7 @@ impl PathMonitor {
|
||||
}
|
||||
|
||||
/// Record that we sent a packet with the given sequence number and timestamp.
|
||||
pub fn observe_sent(&mut self, seq: u16, timestamp_ms: u64) {
|
||||
pub fn observe_sent(&mut self, seq: u32, timestamp_ms: u64) {
|
||||
self.total_sent += 1;
|
||||
self.highest_sent_seq = Some(seq);
|
||||
|
||||
@@ -78,7 +78,7 @@ impl PathMonitor {
|
||||
}
|
||||
|
||||
/// Record that we received a packet with the given sequence number and timestamp.
|
||||
pub fn observe_received(&mut self, seq: u16, timestamp_ms: u64) {
|
||||
pub fn observe_received(&mut self, seq: u32, timestamp_ms: u64) {
|
||||
self.total_received += 1;
|
||||
|
||||
if self.first_recv_time_ms.is_none() {
|
||||
@@ -180,7 +180,12 @@ impl PathMonitor {
|
||||
return 0.0;
|
||||
}
|
||||
let mean = self.rtt_window.iter().sum::<f64>() / n as f64;
|
||||
let var = self.rtt_window.iter().map(|r| (r - mean).powi(2)).sum::<f64>() / n as f64;
|
||||
let var = self
|
||||
.rtt_window
|
||||
.iter()
|
||||
.map(|r| (r - mean).powi(2))
|
||||
.sum::<f64>()
|
||||
/ n as f64;
|
||||
var.sqrt()
|
||||
}
|
||||
|
||||
@@ -274,7 +279,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// Receive only 7 of them (30% loss)
|
||||
for i in [0u16, 1, 2, 3, 5, 7, 9] {
|
||||
for i in [0u32, 1, 2, 3, 5, 7, 9] {
|
||||
monitor.observe_received(i, i as u64 * 20 + 50);
|
||||
}
|
||||
|
||||
|
||||
@@ -127,9 +127,9 @@ impl QuinnTransport {
|
||||
}
|
||||
}
|
||||
|
||||
self.connection.send_datagram(data).map_err(|e| {
|
||||
TransportError::Internal(format!("send trunk datagram error: {e}"))
|
||||
})?;
|
||||
self.connection
|
||||
.send_datagram(data)
|
||||
.map_err(|e| TransportError::Internal(format!("send trunk datagram error: {e}")))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -146,7 +146,7 @@ impl QuinnTransport {
|
||||
Err(e) => {
|
||||
return Err(TransportError::Internal(format!(
|
||||
"recv trunk datagram error: {e}"
|
||||
)))
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -177,9 +177,9 @@ impl MediaTransport for QuinnTransport {
|
||||
monitor.observe_sent(packet.header.seq, packet.header.timestamp as u64);
|
||||
}
|
||||
|
||||
self.connection.send_datagram(data).map_err(|e| {
|
||||
TransportError::Internal(format!("send datagram error: {e}"))
|
||||
})?;
|
||||
self.connection
|
||||
.send_datagram(data)
|
||||
.map_err(|e| TransportError::Internal(format!("send datagram error: {e}")))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -192,7 +192,7 @@ impl MediaTransport for QuinnTransport {
|
||||
Err(e) => {
|
||||
return Err(TransportError::Internal(format!(
|
||||
"recv datagram error: {e}"
|
||||
)))
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -201,15 +201,15 @@ impl MediaTransport for QuinnTransport {
|
||||
// Record receive observation
|
||||
{
|
||||
let mut monitor = self.path_monitor.lock().unwrap();
|
||||
monitor.observe_received(
|
||||
packet.header.seq,
|
||||
packet.header.timestamp as u64,
|
||||
);
|
||||
monitor.observe_received(packet.header.seq, packet.header.timestamp as u64);
|
||||
}
|
||||
Ok(Some(packet))
|
||||
}
|
||||
None => {
|
||||
tracing::warn!(len = data.len(), "skipping malformed media datagram, continuing");
|
||||
tracing::warn!(
|
||||
len = data.len(),
|
||||
"skipping malformed media datagram, continuing"
|
||||
);
|
||||
// Don't return Ok(None) — that signals connection closed.
|
||||
// Recurse to read the next datagram instead.
|
||||
Box::pin(self.recv_media()).await
|
||||
@@ -241,10 +241,8 @@ impl MediaTransport for QuinnTransport {
|
||||
}
|
||||
|
||||
async fn close(&self) -> Result<(), TransportError> {
|
||||
self.connection.close(
|
||||
quinn::VarInt::from_u32(0),
|
||||
b"normal close",
|
||||
);
|
||||
self.connection
|
||||
.close(quinn::VarInt::from_u32(0), b"normal close");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@ use wzp_proto::{SignalMessage, TransportError};
|
||||
/// Send a signaling message over a new bidirectional QUIC stream.
|
||||
///
|
||||
/// Opens a new bidi stream, writes a length-prefixed JSON frame, then finishes the send side.
|
||||
pub async fn send_signal(connection: &Connection, msg: &SignalMessage) -> Result<(), TransportError> {
|
||||
let (mut send, _recv) = connection.open_bi().await.map_err(|e| {
|
||||
TransportError::Internal(format!("failed to open bidi stream: {e}"))
|
||||
})?;
|
||||
pub async fn send_signal(
|
||||
connection: &Connection,
|
||||
msg: &SignalMessage,
|
||||
) -> Result<(), TransportError> {
|
||||
let (mut send, _recv) = connection
|
||||
.open_bi()
|
||||
.await
|
||||
.map_err(|e| TransportError::Internal(format!("failed to open bidi stream: {e}")))?;
|
||||
|
||||
let json = serde_json::to_vec(msg)
|
||||
.map_err(|e| TransportError::Internal(format!("signal serialize error: {e}")))?;
|
||||
|
||||
Reference in New Issue
Block a user