The MoQT transport that joule-code-collab's network rung needs lived in the private joulesperbit repo (as inv-moq + jwp), so a public consumer couldn't depend on it. Port both into the public open-standards joule-code workspace, self-contained (concrete dep versions, not workspace-inherited) so a bare git clone builds them: - crates/jwp: Joule Wire Protocol — energy-aware binary framing (CBOR, zstd/lz4, TCP + optional QUIC). Self-contained, external deps only. - crates/joule-moq: MoQT (Media-over-QUIC) wire protocol + JWP signed-receipt extension for CommunicationOS — Publisher/Subscriber/Relay traits, MockFabric (in-process) + a production QUIC path (quic feature). Renamed from inv-moq to drop the invisible-* namespace and match the joule-code branding; depends only on jwp. Both build (default + quic) and pass their tests (joule-moq 29, jwp 152) in the new home. The transport-moqt example now path-deps the public joule-moq instead of reaching into joulesperbit, and its end-to-end BFT-ops-over-MoQT-with-signed- receipts tests pass. This unblocks public consumers (agent-zed) from git-depping the transport for cross-machine fleet coordination. Co-Authored-By: Claude Opus 4.8 <[email protected]>
121 lines
3.7 KiB
Rust
121 lines
3.7 KiB
Rust
//! Wire-level codec primitives for MoQT.
|
|
//!
|
|
//! MoQT uses QUIC-style variable-length integers (RFC 9000 §16): the two most
|
|
//! significant bits of the first byte encode the integer length (1, 2, 4, or
|
|
//! 8 bytes), the remaining bits hold the value. Strings and byte vectors are
|
|
//! length-prefixed by varint.
|
|
|
|
use crate::error::{MoqError, MoqResult};
|
|
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
|
|
|
/// Maximum value representable as a QUIC varint.
|
|
pub const VARINT_MAX: u64 = (1 << 62) - 1;
|
|
|
|
/// Encode an unsigned integer as a QUIC varint into `dst`.
|
|
pub fn encode_varint<B: BufMut>(value: u64, dst: &mut B) -> MoqResult<()> {
|
|
if value > VARINT_MAX {
|
|
return Err(MoqError::VarintOverflow);
|
|
}
|
|
if value < (1 << 6) {
|
|
dst.put_u8(value as u8);
|
|
} else if value < (1 << 14) {
|
|
dst.put_u16((value as u16) | (0b01 << 14));
|
|
} else if value < (1 << 30) {
|
|
dst.put_u32((value as u32) | (0b10 << 30));
|
|
} else {
|
|
dst.put_u64(value | (0b11u64 << 62));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Decode a QUIC varint from `src`. Advances the cursor.
|
|
pub fn decode_varint<B: Buf>(src: &mut B) -> MoqResult<u64> {
|
|
if !src.has_remaining() {
|
|
return Err(MoqError::WireDecode("varint: empty buffer".into()));
|
|
}
|
|
let first = src.chunk()[0];
|
|
let len_bits = first >> 6;
|
|
let len = 1usize << len_bits;
|
|
if src.remaining() < len {
|
|
return Err(MoqError::WireDecode(format!(
|
|
"varint: need {} bytes, have {}",
|
|
len,
|
|
src.remaining()
|
|
)));
|
|
}
|
|
let value = match len {
|
|
1 => (src.get_u8() & 0x3f) as u64,
|
|
2 => (src.get_u16() & 0x3fff) as u64,
|
|
4 => (src.get_u32() & 0x3fff_ffff) as u64,
|
|
8 => src.get_u64() & 0x3fff_ffff_ffff_ffff,
|
|
_ => unreachable!("len_bits is 2 bits"),
|
|
};
|
|
Ok(value)
|
|
}
|
|
|
|
/// Encode a byte slice prefixed by its varint length.
|
|
pub fn encode_bytes<B: BufMut>(slice: &[u8], dst: &mut B) -> MoqResult<()> {
|
|
encode_varint(slice.len() as u64, dst)?;
|
|
dst.put_slice(slice);
|
|
Ok(())
|
|
}
|
|
|
|
/// Decode a varint-length-prefixed byte vector.
|
|
pub fn decode_bytes<B: Buf>(src: &mut B) -> MoqResult<Bytes> {
|
|
let len = decode_varint(src)? as usize;
|
|
if src.remaining() < len {
|
|
return Err(MoqError::WireDecode(format!(
|
|
"bytes: need {} bytes, have {}",
|
|
len,
|
|
src.remaining()
|
|
)));
|
|
}
|
|
let mut buf = BytesMut::with_capacity(len);
|
|
buf.resize(len, 0);
|
|
src.copy_to_slice(&mut buf);
|
|
Ok(buf.freeze())
|
|
}
|
|
|
|
/// A trait for types that can be serialized to / deserialized from the
|
|
/// MoQT wire format.
|
|
pub trait WireCodec: Sized {
|
|
fn encode(&self, dst: &mut BytesMut) -> MoqResult<()>;
|
|
fn decode(src: &mut Bytes) -> MoqResult<Self>;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn roundtrip(value: u64) {
|
|
let mut buf = BytesMut::new();
|
|
encode_varint(value, &mut buf).unwrap();
|
|
let mut bytes = buf.freeze();
|
|
let decoded = decode_varint(&mut bytes).unwrap();
|
|
assert_eq!(decoded, value);
|
|
assert_eq!(bytes.remaining(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn varint_boundaries() {
|
|
for v in [0u64, 1, 63, 64, 16_383, 16_384, 1_073_741_823, 1_073_741_824, VARINT_MAX] {
|
|
roundtrip(v);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn varint_overflow() {
|
|
let mut buf = BytesMut::new();
|
|
let result = encode_varint(VARINT_MAX + 1, &mut buf);
|
|
assert!(matches!(result, Err(MoqError::VarintOverflow)));
|
|
}
|
|
|
|
#[test]
|
|
fn bytes_roundtrip() {
|
|
let mut buf = BytesMut::new();
|
|
encode_bytes(b"hello", &mut buf).unwrap();
|
|
let mut bytes = buf.freeze();
|
|
let decoded = decode_bytes(&mut bytes).unwrap();
|
|
assert_eq!(&decoded[..], b"hello");
|
|
}
|
|
}
|