use std::sync::Arc;
use bytes::{Buf, Bytes, BytesMut};
use crate::draft16::endpoint::{Endpoint, EndpointError};
use crate::draft16::event::{ClientEvent, Direction, StreamKind};
use crate::draft16::observer::ConnectionObserver;
use crate::draft16::session::request_id::Role;
use crate::transport::quic::QuicTransport;
use crate::transport::{RecvStream, SendStream, Transport, TransportError};
use moqtap_codec::dispatch::{
AnyControlMessage, AnyDatagramHeader, AnyFetchHeader, AnySubgroupHeader,
};
use moqtap_codec::draft16::data_stream::{FetchHeader, SubgroupObject, SubgroupObjectReader};
use moqtap_codec::draft16::message::ControlMessage;
use moqtap_codec::error::CodecError;
use moqtap_codec::kvp::KeyValuePair;
use moqtap_codec::types::*;
use moqtap_codec::varint::VarInt;
use moqtap_codec::version::DraftVersion;
pub const MOQT_ALPN: &[u8] = b"moq-00";
#[derive(Debug, thiserror::Error)]
pub enum ConnectionError {
#[error("endpoint error: {0}")]
Endpoint(#[from] EndpointError),
#[error("codec error: {0}")]
Codec(#[from] CodecError),
#[error("transport error: {0}")]
Transport(#[from] TransportError),
#[error("varint error: {0}")]
VarInt(#[from] moqtap_codec::varint::VarIntError),
#[error("control stream not open")]
NoControlStream,
#[error("unexpected end of stream")]
UnexpectedEnd,
#[error("stream finished")]
StreamFinished,
#[error("invalid server address: {0}")]
InvalidAddress(String),
#[error("TLS config error: {0}")]
TlsConfig(String),
#[error("data stream state error: {0}")]
DataStreamState(&'static str),
}
#[derive(Debug, Clone)]
pub enum TransportType {
Quic,
WebTransport {
url: String,
},
}
pub struct ClientConfig {
pub draft: DraftVersion,
pub transport: TransportType,
pub skip_cert_verification: bool,
pub ca_certs: Vec<Vec<u8>>,
pub setup_parameters: Vec<KeyValuePair>,
}
impl ClientConfig {
pub fn alpn(&self) -> Vec<Vec<u8>> {
match &self.transport {
TransportType::Quic => vec![self.draft.quic_alpn().to_vec()],
TransportType::WebTransport { .. } => vec![b"h3".to_vec()],
}
}
}
pub struct FramedSendStream {
inner: SendStream,
draft: DraftVersion,
subgroup_io: Option<SubgroupObjectReader>,
}
impl FramedSendStream {
pub fn new(inner: SendStream, draft: DraftVersion) -> Self {
Self { inner, draft, subgroup_io: None }
}
pub fn stream_id(&self) -> u64 {
self.inner.stream_id()
}
pub async fn write_control(
&mut self,
msg: &AnyControlMessage,
) -> Result<Vec<u8>, ConnectionError> {
let mut buf = Vec::new();
msg.encode(&mut buf)?;
self.inner.write_all(&buf).await?;
Ok(buf)
}
pub async fn write_subgroup_header(
&mut self,
header: &AnySubgroupHeader,
) -> Result<(), ConnectionError> {
let mut buf = Vec::new();
header.encode(&mut buf);
self.inner.write_all(&buf).await?;
if let AnySubgroupHeader::Draft16(ref d16) = header {
self.subgroup_io = Some(SubgroupObjectReader::new(d16));
}
Ok(())
}
pub async fn write_fetch_header(
&mut self,
header: &AnyFetchHeader,
) -> Result<(), ConnectionError> {
let mut buf = Vec::new();
header.encode(&mut buf);
self.inner.write_all(&buf).await?;
Ok(())
}
pub async fn write_subgroup_object(
&mut self,
object: &SubgroupObject,
) -> Result<(), ConnectionError> {
let writer = self
.subgroup_io
.as_mut()
.ok_or(ConnectionError::DataStreamState("subgroup header not written yet"))?;
let mut buf = Vec::new();
writer.write_object(object, &mut buf)?;
self.inner.write_all(&buf).await?;
Ok(())
}
pub async fn finish(&mut self) -> Result<(), ConnectionError> {
self.inner.finish()?;
Ok(())
}
pub fn draft(&self) -> DraftVersion {
self.draft
}
}
pub struct FramedRecvStream {
inner: RecvStream,
buf: BytesMut,
draft: DraftVersion,
subgroup_io: Option<SubgroupObjectReader>,
}
impl FramedRecvStream {
pub fn new(inner: RecvStream, draft: DraftVersion) -> Self {
Self { inner, buf: BytesMut::with_capacity(4096), draft, subgroup_io: None }
}
pub fn stream_id(&self) -> u64 {
self.inner.stream_id()
}
async fn fill(&mut self) -> Result<bool, ConnectionError> {
let mut tmp = [0u8; 4096];
match self.inner.read(&mut tmp).await {
Ok(Some(n)) => {
self.buf.extend_from_slice(&tmp[..n]);
Ok(true)
}
Ok(None) => Ok(false),
Err(e) => Err(ConnectionError::Transport(e)),
}
}
async fn ensure(&mut self, n: usize) -> Result<(), ConnectionError> {
while self.buf.len() < n {
if !self.fill().await? {
return Err(ConnectionError::UnexpectedEnd);
}
}
Ok(())
}
pub async fn read_control(
&mut self,
capture_raw: bool,
) -> Result<(AnyControlMessage, Option<Vec<u8>>), ConnectionError> {
self.ensure(1).await?;
let type_len = varint_len(self.buf[0]);
self.ensure(type_len).await?;
let mut cursor = &self.buf[..type_len];
let _type_id = VarInt::decode(&mut cursor)?;
let (payload_len, len_field_size) = if self.draft.uses_fixed_length_framing() {
self.ensure(type_len + 2).await?;
let hi = self.buf[type_len] as usize;
let lo = self.buf[type_len + 1] as usize;
((hi << 8) | lo, 2)
} else {
self.ensure(type_len + 1).await?;
let payload_len_start = type_len;
let payload_len_varint_len = varint_len(self.buf[payload_len_start]);
self.ensure(type_len + payload_len_varint_len).await?;
let mut cursor = &self.buf[payload_len_start..type_len + payload_len_varint_len];
let payload_len = VarInt::decode(&mut cursor)?.into_inner() as usize;
(payload_len, payload_len_varint_len)
};
let total = type_len + len_field_size + payload_len;
self.ensure(total).await?;
let raw = capture_raw.then(|| self.buf[..total].to_vec());
let mut frame = &self.buf[..total];
let msg = AnyControlMessage::decode(self.draft, &mut frame)?;
self.buf.advance(total);
Ok((msg, raw))
}
pub async fn read_subgroup_header(&mut self) -> Result<AnySubgroupHeader, ConnectionError> {
self.ensure(1).await?;
loop {
let mut cursor = &self.buf[..];
match AnySubgroupHeader::decode(self.draft, &mut cursor) {
Ok(header) => {
let consumed = self.buf.len() - cursor.remaining();
self.buf.advance(consumed);
if let AnySubgroupHeader::Draft16(ref d16) = header {
self.subgroup_io = Some(SubgroupObjectReader::new(d16));
}
return Ok(header);
}
Err(CodecError::UnexpectedEnd) => {
if !self.fill().await? {
return Err(ConnectionError::UnexpectedEnd);
}
}
Err(e) => return Err(ConnectionError::Codec(e)),
}
}
}
pub async fn read_fetch_header(&mut self) -> Result<AnyFetchHeader, ConnectionError> {
self.ensure(1).await?;
loop {
let mut cursor = &self.buf[..];
match AnyFetchHeader::decode(self.draft, &mut cursor) {
Ok(header) => {
let consumed = self.buf.len() - cursor.remaining();
self.buf.advance(consumed);
return Ok(header);
}
Err(CodecError::UnexpectedEnd) => {
if !self.fill().await? {
return Err(ConnectionError::UnexpectedEnd);
}
}
Err(e) => return Err(ConnectionError::Codec(e)),
}
}
}
pub async fn read_subgroup_object(&mut self) -> Result<SubgroupObject, ConnectionError> {
if self.subgroup_io.is_none() {
return Err(ConnectionError::DataStreamState("subgroup header not read yet"));
}
loop {
let reader = self.subgroup_io.as_mut().unwrap();
let mut probe = reader.clone();
let mut cursor = &self.buf[..];
match probe.read_object(&mut cursor) {
Ok(obj) => {
let consumed = self.buf.len() - cursor.remaining();
self.buf.advance(consumed);
*reader = probe;
return Ok(obj);
}
Err(CodecError::UnexpectedEnd) => {
if !self.fill().await? {
return Err(ConnectionError::UnexpectedEnd);
}
}
Err(e) => return Err(ConnectionError::Codec(e)),
}
}
}
pub async fn read_fetch_stream_header(&mut self) -> Result<FetchHeader, ConnectionError> {
loop {
let mut cursor = &self.buf[..];
match FetchHeader::decode(&mut cursor) {
Ok(hdr) => {
let consumed = self.buf.len() - cursor.remaining();
self.buf.advance(consumed);
return Ok(hdr);
}
Err(CodecError::UnexpectedEnd) => {
if !self.fill().await? {
return Err(ConnectionError::UnexpectedEnd);
}
}
Err(e) => return Err(ConnectionError::Codec(e)),
}
}
}
pub fn draft(&self) -> DraftVersion {
self.draft
}
}
pub struct Connection {
transport: Transport,
endpoint: Endpoint,
draft: DraftVersion,
control_send: Option<FramedSendStream>,
control_recv: Option<FramedRecvStream>,
observer: Option<Box<dyn ConnectionObserver>>,
pending_events: Vec<ClientEvent>,
}
impl Connection {
pub async fn connect(addr: &str, config: ClientConfig) -> Result<Self, ConnectionError> {
let draft = config.draft;
let transport = match &config.transport {
TransportType::Quic => Self::connect_quic(addr, &config).await?,
TransportType::WebTransport { url } => {
let url = url.clone();
Self::connect_webtransport(&url, &config).await?
}
};
let (send, recv) = transport.open_bi().await?;
let mut control_send = FramedSendStream::new(send, draft);
let mut control_recv = FramedRecvStream::new(recv, draft);
let mut endpoint = Endpoint::new(Role::Client);
endpoint.connect()?;
let setup_msg = endpoint.send_client_setup(config.setup_parameters.clone())?;
let any_setup = AnyControlMessage::Draft16(setup_msg);
let raw_setup = control_send.write_control(&any_setup).await?;
let (server_setup, raw_server_setup) = control_recv.read_control(true).await?;
match &server_setup {
AnyControlMessage::Draft16(ControlMessage::ServerSetup(ref ss)) => {
endpoint.receive_server_setup(ss)?;
}
_ => {
return Err(ConnectionError::Endpoint(EndpointError::NotActive));
}
}
let mut pending_events = Vec::with_capacity(3);
pending_events.push(ClientEvent::ControlMessage {
direction: Direction::Send,
message: any_setup,
raw: Some(raw_setup),
});
pending_events.push(ClientEvent::ControlMessage {
direction: Direction::Receive,
message: server_setup,
raw: raw_server_setup,
});
pending_events.push(ClientEvent::SetupComplete { negotiated_version: 0xff000000 + 15 });
Ok(Self {
transport,
endpoint,
draft,
control_send: Some(control_send),
control_recv: Some(control_recv),
observer: None,
pending_events,
})
}
async fn connect_quic(addr: &str, config: &ClientConfig) -> Result<Transport, ConnectionError> {
let server_addr = addr.parse().map_err(|e: std::net::AddrParseError| {
ConnectionError::InvalidAddress(e.to_string())
})?;
let mut tls_config = if config.skip_cert_verification {
rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(SkipVerification))
.with_no_client_auth()
} else {
let mut roots = rustls::RootCertStore::empty();
roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
for der in &config.ca_certs {
roots
.add(rustls::pki_types::CertificateDer::from(der.clone()))
.map_err(|e| ConnectionError::TlsConfig(format!("bad CA cert: {e}")))?;
}
rustls::ClientConfig::builder().with_root_certificates(roots).with_no_client_auth()
};
tls_config.alpn_protocols = config.alpn();
let quic_config: quinn::crypto::rustls::QuicClientConfig =
tls_config.try_into().map_err(|e| ConnectionError::TlsConfig(format!("{e}")))?;
let client_config = quinn::ClientConfig::new(Arc::new(quic_config));
let mut quinn_endpoint = quinn::Endpoint::client("0.0.0.0:0".parse().unwrap())
.map_err(|e| ConnectionError::InvalidAddress(e.to_string()))?;
quinn_endpoint.set_default_client_config(client_config);
let server_name = addr.split(':').next().unwrap_or("localhost").to_string();
let quic = quinn_endpoint
.connect(server_addr, &server_name)
.map_err(TransportError::from)?
.await
.map_err(TransportError::from)?;
Ok(Transport::Quic(QuicTransport::new(quic)))
}
#[cfg(feature = "webtransport")]
async fn connect_webtransport(
url: &str,
config: &ClientConfig,
) -> Result<Transport, ConnectionError> {
use crate::transport::webtransport::WebTransportTransport;
let wt_config = if config.skip_cert_verification {
wtransport::ClientConfig::builder()
.with_bind_default()
.with_no_cert_validation()
.build()
} else {
wtransport::ClientConfig::builder().with_bind_default().with_native_certs().build()
};
let endpoint = wtransport::Endpoint::client(wt_config)
.map_err(|e| ConnectionError::Transport(TransportError::Connect(e.to_string())))?;
let connection = endpoint
.connect(url)
.await
.map_err(|e| ConnectionError::Transport(TransportError::Connect(e.to_string())))?;
Ok(Transport::WebTransport(WebTransportTransport::new(connection)))
}
#[cfg(not(feature = "webtransport"))]
async fn connect_webtransport(
_url: &str,
_config: &ClientConfig,
) -> Result<Transport, ConnectionError> {
Err(ConnectionError::Transport(TransportError::Connect(
"webtransport feature not enabled".into(),
)))
}
pub fn set_observer(&mut self, observer: Box<dyn ConnectionObserver>) {
self.observer = Some(observer);
for event in self.pending_events.drain(..) {
if let Some(ref obs) = self.observer {
obs.on_event_owned(event);
}
}
}
pub fn clear_observer(&mut self) {
self.observer = None;
}
fn emit(&self, event: ClientEvent) {
if let Some(ref obs) = self.observer {
obs.on_event_owned(event);
}
}
pub async fn send_control(&mut self, msg: &ControlMessage) -> Result<(), ConnectionError> {
let any = AnyControlMessage::Draft16(msg.clone());
let send = self.control_send.as_mut().ok_or(ConnectionError::NoControlStream)?;
let raw = send.write_control(&any).await?;
self.emit(ClientEvent::ControlMessage {
direction: Direction::Send,
message: any,
raw: Some(raw),
});
Ok(())
}
pub async fn recv_control(&mut self) -> Result<ControlMessage, ConnectionError> {
let recv = self.control_recv.as_mut().ok_or(ConnectionError::NoControlStream)?;
let capture_raw = self.observer.is_some();
let (any, raw) = recv.read_control(capture_raw).await?;
if capture_raw {
self.emit(ClientEvent::ControlMessage {
direction: Direction::Receive,
message: any.clone(),
raw,
});
}
match any {
AnyControlMessage::Draft16(msg) => Ok(msg),
_ => Err(ConnectionError::Codec(CodecError::UnknownMessageType(0))),
}
}
pub async fn recv_and_dispatch(&mut self) -> Result<ControlMessage, ConnectionError> {
let msg = self.recv_control().await?;
self.endpoint.receive_message(msg.clone())?;
if let ControlMessage::GoAway(ref ga) = msg {
self.emit(ClientEvent::Draining { new_session_uri: ga.new_session_uri.clone() });
}
Ok(msg)
}
pub async fn subscribe(
&mut self,
track_namespace: TrackNamespace,
track_name: Vec<u8>,
parameters: Vec<KeyValuePair>,
) -> Result<VarInt, ConnectionError> {
let (req_id, msg) = self.endpoint.subscribe(track_namespace, track_name, parameters)?;
self.send_control(&msg).await?;
Ok(req_id)
}
pub async fn unsubscribe(&mut self, request_id: VarInt) -> Result<(), ConnectionError> {
let msg = self.endpoint.unsubscribe(request_id)?;
self.send_control(&msg).await
}
pub async fn fetch(
&mut self,
track_namespace: TrackNamespace,
track_name: Vec<u8>,
start_group: VarInt,
start_object: VarInt,
end_group: VarInt,
end_object: VarInt,
) -> Result<VarInt, ConnectionError> {
let (req_id, msg) = self.endpoint.fetch(
track_namespace,
track_name,
start_group,
start_object,
end_group,
end_object,
)?;
self.send_control(&msg).await?;
Ok(req_id)
}
pub async fn joining_fetch(
&mut self,
joining_request_id: VarInt,
joining_start: VarInt,
) -> Result<VarInt, ConnectionError> {
let (req_id, msg) = self.endpoint.joining_fetch(joining_request_id, joining_start)?;
self.send_control(&msg).await?;
Ok(req_id)
}
pub async fn fetch_cancel(&mut self, request_id: VarInt) -> Result<(), ConnectionError> {
let msg = self.endpoint.fetch_cancel(request_id)?;
self.send_control(&msg).await
}
pub async fn subscribe_namespace(
&mut self,
namespace_prefix: TrackNamespace,
subscribe_options: VarInt,
parameters: Vec<KeyValuePair>,
) -> Result<VarInt, ConnectionError> {
let (req_id, msg) =
self.endpoint.subscribe_namespace(namespace_prefix, subscribe_options, parameters)?;
self.send_control(&msg).await?;
Ok(req_id)
}
pub async fn publish_namespace(
&mut self,
track_namespace: TrackNamespace,
parameters: Vec<KeyValuePair>,
) -> Result<VarInt, ConnectionError> {
let (req_id, msg) = self.endpoint.publish_namespace(track_namespace, parameters)?;
self.send_control(&msg).await?;
Ok(req_id)
}
pub async fn track_status(
&mut self,
track_namespace: TrackNamespace,
track_name: Vec<u8>,
parameters: Vec<KeyValuePair>,
) -> Result<VarInt, ConnectionError> {
let (req_id, msg) = self.endpoint.track_status(track_namespace, track_name, parameters)?;
self.send_control(&msg).await?;
Ok(req_id)
}
pub async fn publish(
&mut self,
track_namespace: TrackNamespace,
track_name: Vec<u8>,
track_alias: VarInt,
track_extensions: Vec<KeyValuePair>,
parameters: Vec<KeyValuePair>,
) -> Result<VarInt, ConnectionError> {
let (req_id, msg) = self.endpoint.publish(
track_namespace,
track_name,
track_alias,
track_extensions,
parameters,
)?;
self.send_control(&msg).await?;
Ok(req_id)
}
pub async fn publish_done(
&mut self,
request_id: VarInt,
status_code: VarInt,
stream_count: VarInt,
reason_phrase: Vec<u8>,
) -> Result<(), ConnectionError> {
let msg = self.endpoint.send_publish_done(
request_id,
status_code,
stream_count,
reason_phrase,
)?;
self.send_control(&msg).await
}
pub async fn open_subgroup_stream(
&self,
header: &AnySubgroupHeader,
) -> Result<FramedSendStream, ConnectionError> {
let send = self.transport.open_uni().await?;
let mut framed = FramedSendStream::new(send, self.draft);
let sid = framed.stream_id();
framed.write_subgroup_header(header).await?;
self.emit(ClientEvent::StreamOpened {
direction: Direction::Send,
stream_kind: StreamKind::Subgroup,
stream_id: sid,
});
self.emit(ClientEvent::DataStreamHeader {
stream_id: sid,
direction: Direction::Send,
header: header.clone(),
});
Ok(framed)
}
pub async fn accept_subgroup_stream(
&self,
) -> Result<(AnySubgroupHeader, FramedRecvStream), ConnectionError> {
let recv = self.transport.accept_uni().await?;
let mut framed = FramedRecvStream::new(recv, self.draft);
let sid = framed.stream_id();
let header = framed.read_subgroup_header().await?;
self.emit(ClientEvent::StreamOpened {
direction: Direction::Receive,
stream_kind: StreamKind::Subgroup,
stream_id: sid,
});
self.emit(ClientEvent::DataStreamHeader {
stream_id: sid,
direction: Direction::Receive,
header: header.clone(),
});
Ok((header, framed))
}
pub fn send_datagram(
&self,
header: &AnyDatagramHeader,
payload: &[u8],
) -> Result<(), ConnectionError> {
let mut buf = Vec::new();
header.encode(&mut buf);
buf.extend_from_slice(payload);
self.emit(ClientEvent::DatagramReceived {
direction: Direction::Send,
header: header.clone(),
payload_len: payload.len(),
});
self.transport.send_datagram(bytes::Bytes::from(buf))?;
Ok(())
}
pub async fn recv_datagram(&self) -> Result<(AnyDatagramHeader, Bytes), ConnectionError> {
let data = self.transport.recv_datagram().await?;
let mut cursor = &data[..];
let header = AnyDatagramHeader::decode(self.draft, &mut cursor)?;
let consumed = data.len() - cursor.len();
let payload = data.slice(consumed..);
self.emit(ClientEvent::DatagramReceived {
direction: Direction::Receive,
header: header.clone(),
payload_len: payload.len(),
});
Ok((header, payload))
}
pub fn endpoint(&self) -> &Endpoint {
&self.endpoint
}
pub fn endpoint_mut(&mut self) -> &mut Endpoint {
&mut self.endpoint
}
pub fn draft(&self) -> DraftVersion {
self.draft
}
pub fn close(&self, code: u32, reason: &[u8]) {
self.emit(ClientEvent::Closed { code, reason: reason.to_vec() });
self.transport.close(code, reason);
}
}
fn varint_len(first_byte: u8) -> usize {
1 << (first_byte >> 6)
}
#[derive(Debug)]
struct SkipVerification;
impl rustls::client::danger::ServerCertVerifier for SkipVerification {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::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: &rustls::pki_types::CertificateDer<'_>,
_dcs: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dcs: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
vec![
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
rustls::SignatureScheme::ED25519,
rustls::SignatureScheme::RSA_PSS_SHA256,
rustls::SignatureScheme::RSA_PSS_SHA384,
rustls::SignatureScheme::RSA_PSS_SHA512,
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn varint_len_single_byte() {
assert_eq!(varint_len(0x00), 1);
assert_eq!(varint_len(0x3F), 1);
}
#[test]
fn varint_len_two_bytes() {
assert_eq!(varint_len(0x40), 2);
assert_eq!(varint_len(0x7F), 2);
}
#[test]
fn varint_len_four_bytes() {
assert_eq!(varint_len(0x80), 4);
assert_eq!(varint_len(0xBF), 4);
}
#[test]
fn varint_len_eight_bytes() {
assert_eq!(varint_len(0xC0), 8);
assert_eq!(varint_len(0xFF), 8);
}
#[test]
fn client_config_alpn_quic_draft16() {
let config = ClientConfig {
draft: DraftVersion::Draft16,
transport: TransportType::Quic,
skip_cert_verification: false,
ca_certs: Vec::new(),
setup_parameters: Vec::new(),
};
assert_eq!(config.alpn(), vec![b"moqt-16".to_vec()]);
}
#[test]
fn client_config_alpn_webtransport() {
let config = ClientConfig {
draft: DraftVersion::Draft16,
transport: TransportType::WebTransport { url: "https://example.com".to_string() },
skip_cert_verification: false,
ca_certs: Vec::new(),
setup_parameters: Vec::new(),
};
assert_eq!(config.alpn(), vec![b"h3".to_vec()]);
}
#[test]
fn moqt_alpn_value() {
assert_eq!(MOQT_ALPN, b"moq-00");
}
#[test]
fn transport_type_debug() {
let quic = TransportType::Quic;
assert!(format!("{quic:?}").contains("Quic"));
let wt = TransportType::WebTransport { url: "https://example.com".to_string() };
assert!(format!("{wt:?}").contains("WebTransport"));
}
}