use bytes::Buf;
use http3::{
error::{Code, internal_error::InternalConnectionError},
proto::varint::VarInt,
quic::StreamId,
};
#[derive(Debug, Clone)]
pub struct Datagram<B> {
stream_id: StreamId,
payload: B,
}
impl<B> Datagram<B>
where
B: Buf,
{
#[allow(unknown_lints, clippy::manual_is_multiple_of)]
pub fn new(stream_id: StreamId, payload: B) -> Self {
assert!(
stream_id.into_inner() % 4 == 0,
"StreamId is not divisible by 4"
);
Self { stream_id, payload }
}
pub fn decode(mut buf: B) -> Result<Self, InternalConnectionError> {
let q_stream_id = VarInt::decode(&mut buf).map_err(|_| {
InternalConnectionError::new(Code::H3_DATAGRAM_ERROR, "invalid stream id".to_string())
})?;
let stream_id = StreamId::try_from(u64::from(q_stream_id) * 4).map_err(|_| {
InternalConnectionError::new(Code::H3_DATAGRAM_ERROR, "invalid stream id".to_string())
})?;
let payload = buf;
Ok(Self { stream_id, payload })
}
#[inline]
pub fn stream_id(&self) -> StreamId {
self.stream_id
}
#[inline]
pub fn payload(&self) -> &B {
&self.payload
}
pub fn encode(self) -> EncodedDatagram<B> {
let mut buffer = [0; VarInt::MAX_SIZE];
let varint = VarInt::from(self.stream_id) / 4;
varint.encode(&mut buffer.as_mut_slice());
EncodedDatagram {
stream_id: buffer,
len: varint.size(),
pos: 0,
payload: self.payload,
}
}
pub fn into_payload(self) -> B {
self.payload
}
}
#[cfg(test)]
mod tests {
use bytes::{Buf, Bytes};
use http3::quic::StreamId;
use super::Datagram;
#[test]
fn encode_preserves_quarter_stream_id() {
let stream_id = StreamId::try_from(4).unwrap();
let mut encoded = Datagram::new(stream_id, Bytes::from_static(b"payload")).encode();
let decoded = Datagram::decode(encoded.copy_to_bytes(encoded.remaining())).unwrap();
assert_eq!(decoded.stream_id(), stream_id);
assert_eq!(decoded.into_payload(), Bytes::from_static(b"payload"));
}
}
#[derive(Debug)]
pub struct EncodedDatagram<B: Buf> {
stream_id: [u8; VarInt::MAX_SIZE],
len: usize,
pos: usize,
payload: B,
}
impl<B> Buf for EncodedDatagram<B>
where
B: Buf,
{
fn remaining(&self) -> usize {
self.len - self.pos + self.payload.remaining()
}
fn chunk(&self) -> &[u8] {
if self.len - self.pos > 0 {
&self.stream_id[self.pos..self.len]
} else {
self.payload.chunk()
}
}
fn advance(&mut self, mut cnt: usize) {
let remaining_header = self.len - self.pos;
if remaining_header > 0 {
let advanced = usize::min(cnt, remaining_header);
self.pos += advanced;
cnt -= advanced;
}
self.payload.advance(cnt);
}
}