feat: WarzonePhone lossy VoIP protocol — Phase 1 complete
Rust workspace with 7 crates implementing a custom VoIP protocol designed for extremely lossy connections (5-70% loss, 100-500kbps, 300-800ms RTT). 89 tests passing across all crates. Crates: - wzp-proto: Wire format, traits, adaptive quality controller, jitter buffer, session FSM - wzp-codec: Opus encoder/decoder (audiopus), Codec2 stubs, adaptive switching, resampling - wzp-fec: RaptorQ fountain codes, interleaving, block management (proven 30-70% loss recovery) - wzp-crypto: X25519+ChaCha20-Poly1305, Warzone identity compatible, anti-replay, rekeying - wzp-transport: QUIC via quinn with DATAGRAM frames, path monitoring, signaling streams - wzp-relay: Integration stub (Phase 2) - wzp-client: Integration stub (Phase 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
58
crates/wzp-transport/src/reliable.rs
Normal file
58
crates/wzp-transport/src/reliable.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
//! Reliable stream transport for signaling messages.
|
||||
//!
|
||||
//! Uses length-prefixed framing (4-byte big-endian length + serde_json) over QUIC streams.
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use quinn::Connection;
|
||||
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}"))
|
||||
})?;
|
||||
|
||||
let json = serde_json::to_vec(msg)
|
||||
.map_err(|e| TransportError::Internal(format!("signal serialize error: {e}")))?;
|
||||
|
||||
let mut frame = BytesMut::with_capacity(4 + json.len());
|
||||
frame.put_u32(json.len() as u32);
|
||||
frame.put_slice(&json);
|
||||
|
||||
send.write_all(&frame)
|
||||
.await
|
||||
.map_err(|e| TransportError::Internal(format!("stream write error: {e}")))?;
|
||||
|
||||
send.finish()
|
||||
.map_err(|e| TransportError::Internal(format!("stream finish error: {e}")))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Receive a signaling message from a QUIC receive stream.
|
||||
///
|
||||
/// Reads a 4-byte big-endian length prefix, then the JSON payload.
|
||||
pub async fn recv_signal(recv: &mut quinn::RecvStream) -> Result<SignalMessage, TransportError> {
|
||||
// Read 4-byte length prefix
|
||||
let mut len_buf = [0u8; 4];
|
||||
recv.read_exact(&mut len_buf)
|
||||
.await
|
||||
.map_err(|e| TransportError::Internal(format!("stream read length error: {e}")))?;
|
||||
|
||||
let len = u32::from_be_bytes(len_buf) as usize;
|
||||
if len > 1_048_576 {
|
||||
return Err(TransportError::Internal(format!(
|
||||
"signal message too large: {len} bytes"
|
||||
)));
|
||||
}
|
||||
|
||||
let mut payload = vec![0u8; len];
|
||||
recv.read_exact(&mut payload)
|
||||
.await
|
||||
.map_err(|e| TransportError::Internal(format!("stream read payload error: {e}")))?;
|
||||
|
||||
serde_json::from_slice(&payload)
|
||||
.map_err(|e| TransportError::Internal(format!("signal deserialize error: {e}")))
|
||||
}
|
||||
Reference in New Issue
Block a user