1use crate::{
2 ALPN_14, ALPN_15, ALPN_16, ALPN_17, ALPN_18, ALPN_19, ALPN_LITE, ALPN_LITE_03, ALPN_LITE_04, ALPN_LITE_05_WIP,
3 Error, NEGOTIATED, OriginConsumer, OriginProducer, Session, StatsHandle, Version, Versions,
4 coding::{self, Decode, Encode, Stream},
5 ietf, lite, setup,
6};
7
8#[derive(Default, Clone)]
10pub struct Client {
11 publish: Option<OriginConsumer>,
12 consume: Option<OriginProducer>,
13 stats: StatsHandle,
14 versions: Versions,
15 path: Option<String>,
16}
17
18impl Client {
19 pub fn new() -> Self {
20 Default::default()
21 }
22
23 pub fn with_publish(mut self, publish: impl Into<Option<OriginConsumer>>) -> Self {
24 self.publish = publish.into();
25 self
26 }
27
28 pub fn with_consume(mut self, consume: impl Into<Option<OriginProducer>>) -> Self {
29 self.consume = consume.into();
30 self
31 }
32
33 pub fn with_stats(mut self, stats: StatsHandle) -> Self {
37 self.stats = stats;
38 self
39 }
40
41 pub fn with_origin(self, origin: OriginProducer) -> Self {
45 let consumer = origin.consume();
46 self.with_publish(consumer).with_consume(origin)
47 }
48
49 pub fn with_versions(mut self, versions: Versions) -> Self {
50 self.versions = versions;
51 self
52 }
53
54 pub fn with_path(mut self, path: impl Into<String>) -> Self {
62 let path = path.into();
63 self.path = Some(if path.is_empty() {
64 "/".to_string()
65 } else if path.starts_with('/') {
66 path
67 } else {
68 format!("/{path}")
69 });
70 self
71 }
72
73 pub async fn connect<S: web_transport_trait::Session>(&self, session: S) -> Result<Session, Error> {
75 if self.publish.is_none() && self.consume.is_none() {
76 tracing::warn!("not publishing or consuming anything");
77 }
78
79 let (encoding, supported) = match session.protocol() {
82 Some(ALPN_19) => {
83 let v = self
84 .versions
85 .select(Version::Ietf(ietf::Version::Draft19))
86 .ok_or(Error::Version)?;
87
88 ietf::start(
90 session.clone(),
91 None,
92 None,
93 true,
94 self.publish.clone(),
95 self.consume.clone(),
96 self.stats.clone(),
97 ietf::Version::Draft19,
98 )?;
99
100 tracing::debug!(version = ?v, "connected");
101 return Ok(Session::new(session, v, None));
102 }
103 Some(ALPN_18) => {
104 let v = self
105 .versions
106 .select(Version::Ietf(ietf::Version::Draft18))
107 .ok_or(Error::Version)?;
108
109 ietf::start(
111 session.clone(),
112 None,
113 None,
114 true,
115 self.publish.clone(),
116 self.consume.clone(),
117 self.stats.clone(),
118 ietf::Version::Draft18,
119 )?;
120
121 tracing::debug!(version = ?v, "connected");
122 return Ok(Session::new(session, v, None));
123 }
124 Some(ALPN_17) => {
125 let v = self
126 .versions
127 .select(Version::Ietf(ietf::Version::Draft17))
128 .ok_or(Error::Version)?;
129
130 ietf::start(
132 session.clone(),
133 None,
134 None,
135 true,
136 self.publish.clone(),
137 self.consume.clone(),
138 self.stats.clone(),
139 ietf::Version::Draft17,
140 )?;
141
142 tracing::debug!(version = ?v, "connected");
143 return Ok(Session::new(session, v, None));
144 }
145 Some(ALPN_16) => {
146 let v = self
147 .versions
148 .select(Version::Ietf(ietf::Version::Draft16))
149 .ok_or(Error::Version)?;
150 (v, v.into())
151 }
152 Some(ALPN_15) => {
153 let v = self
154 .versions
155 .select(Version::Ietf(ietf::Version::Draft15))
156 .ok_or(Error::Version)?;
157 (v, v.into())
158 }
159 Some(ALPN_14) => {
160 let v = self
161 .versions
162 .select(Version::Ietf(ietf::Version::Draft14))
163 .ok_or(Error::Version)?;
164 (v, v.into())
165 }
166 Some(ALPN_LITE_05_WIP) => {
167 self.versions
168 .select(Version::Lite(lite::Version::Lite05Wip))
169 .ok_or(Error::Version)?;
170
171 let setup = lite::Setup {
172 path: self.path.clone(),
173 };
174 let recv_bw = lite::start(
175 session.clone(),
176 None,
177 self.publish.clone(),
178 self.consume.clone(),
179 self.stats.clone(),
180 lite::Version::Lite05Wip,
181 setup,
182 )?;
183
184 return Ok(Session::new(session, lite::Version::Lite05Wip.into(), recv_bw));
185 }
186 Some(ALPN_LITE_04) => {
187 self.versions
188 .select(Version::Lite(lite::Version::Lite04))
189 .ok_or(Error::Version)?;
190
191 let recv_bw = lite::start(
192 session.clone(),
193 None,
194 self.publish.clone(),
195 self.consume.clone(),
196 self.stats.clone(),
197 lite::Version::Lite04,
198 lite::Setup::default(),
199 )?;
200
201 return Ok(Session::new(session, lite::Version::Lite04.into(), recv_bw));
202 }
203 Some(ALPN_LITE_03) => {
204 self.versions
205 .select(Version::Lite(lite::Version::Lite03))
206 .ok_or(Error::Version)?;
207
208 let recv_bw = lite::start(
210 session.clone(),
211 None,
212 self.publish.clone(),
213 self.consume.clone(),
214 self.stats.clone(),
215 lite::Version::Lite03,
216 lite::Setup::default(),
217 )?;
218
219 return Ok(Session::new(session, lite::Version::Lite03.into(), recv_bw));
220 }
221 Some(ALPN_LITE) | None => {
222 let supported = self.versions.filter(&NEGOTIATED.into()).ok_or(Error::Version)?;
223 (Version::Ietf(ietf::Version::Draft14), supported)
224 }
225 Some(p) => return Err(Error::UnknownAlpn(p.to_string())),
226 };
227
228 let mut stream = Stream::open(&session, encoding).await?;
229
230 let ietf_encoding = ietf::Version::try_from(encoding).map_err(|_| Error::Version)?;
232
233 let mut parameters = ietf::Parameters::default();
234 parameters.set_varint(ietf::ParameterVarInt::MaxRequestId, u32::MAX as u64);
235 parameters.set_bytes(ietf::ParameterBytes::Implementation, b"moq-lite-rs".to_vec());
236 let parameters = parameters.encode_bytes(ietf_encoding)?;
237
238 let client = setup::Client {
239 versions: supported.clone().into(),
240 parameters,
241 };
242
243 stream.writer.encode(&client).await?;
244
245 let mut server: setup::Server = stream.reader.decode().await?;
246
247 let version = supported
248 .iter()
249 .find(|v| coding::Version::from(**v) == server.version)
250 .copied()
251 .ok_or(Error::Version)?;
252
253 let recv_bw = match version {
254 Version::Lite(v) => {
255 let stream = stream.with_version(v);
256 lite::start(
258 session.clone(),
259 Some(stream),
260 self.publish.clone(),
261 self.consume.clone(),
262 self.stats.clone(),
263 v,
264 lite::Setup::default(),
265 )?
266 }
267 Version::Ietf(v) => {
268 let parameters = ietf::Parameters::decode(&mut server.parameters, v)?;
270 let request_id_max = parameters
271 .get_varint(ietf::ParameterVarInt::MaxRequestId)
272 .map(ietf::RequestId);
273
274 let stream = stream.with_version(v);
275 ietf::start(
276 session.clone(),
277 Some(stream),
278 request_id_max,
279 true,
280 self.publish.clone(),
281 self.consume.clone(),
282 self.stats.clone(),
283 v,
284 )?;
285 None
286 }
287 };
288
289 Ok(Session::new(session, version, recv_bw))
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use std::{
297 collections::VecDeque,
298 sync::{Arc, Mutex},
299 };
300
301 use crate::coding::{Decode, Encode};
302 use bytes::{BufMut, Bytes};
303
304 #[derive(Debug, Clone, Default)]
305 struct FakeError;
306
307 impl std::fmt::Display for FakeError {
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 write!(f, "fake transport error")
310 }
311 }
312
313 impl std::error::Error for FakeError {}
314
315 impl web_transport_trait::Error for FakeError {
316 fn session_error(&self) -> Option<(u32, String)> {
317 Some((0, "closed".to_string()))
318 }
319 }
320
321 #[derive(Clone, Default)]
322 struct FakeSession {
323 state: Arc<FakeSessionState>,
324 }
325
326 #[derive(Default)]
327 struct FakeSessionState {
328 protocol: Option<&'static str>,
329 control_stream: Mutex<Option<(FakeSendStream, FakeRecvStream)>>,
330 close_events: Mutex<Vec<(u32, String)>>,
331 close_notify: tokio::sync::Notify,
332 control_writes: Arc<Mutex<Vec<u8>>>,
333 }
334
335 impl FakeSession {
336 fn new(protocol: Option<&'static str>, server_control_bytes: Vec<u8>) -> Self {
337 let writes = Arc::new(Mutex::new(Vec::new()));
338 let send = FakeSendStream { writes: writes.clone() };
339 let recv = FakeRecvStream {
340 data: VecDeque::from(server_control_bytes),
341 };
342 let state = FakeSessionState {
343 protocol,
344 control_stream: Mutex::new(Some((send, recv))),
345 close_events: Mutex::new(Vec::new()),
346 close_notify: tokio::sync::Notify::new(),
347 control_writes: writes,
348 };
349 Self { state: Arc::new(state) }
350 }
351
352 fn control_writes(&self) -> Vec<u8> {
353 self.state.control_writes.lock().unwrap().clone()
354 }
355
356 async fn wait_for_first_close(&self) -> (u32, String) {
357 loop {
358 let notified = self.state.close_notify.notified();
359 if let Some(close) = self.state.close_events.lock().unwrap().first().cloned() {
360 return close;
361 }
362 notified.await;
363 }
364 }
365 }
366
367 impl web_transport_trait::Session for FakeSession {
368 type SendStream = FakeSendStream;
369 type RecvStream = FakeRecvStream;
370 type Error = FakeError;
371
372 async fn accept_uni(&self) -> Result<Self::RecvStream, Self::Error> {
373 std::future::pending().await
374 }
375
376 async fn accept_bi(&self) -> Result<(Self::SendStream, Self::RecvStream), Self::Error> {
377 std::future::pending().await
378 }
379
380 async fn open_bi(&self) -> Result<(Self::SendStream, Self::RecvStream), Self::Error> {
381 self.state.control_stream.lock().unwrap().take().ok_or(FakeError)
382 }
383
384 async fn open_uni(&self) -> Result<Self::SendStream, Self::Error> {
385 std::future::pending().await
386 }
387
388 fn send_datagram(&self, _payload: Bytes) -> Result<(), Self::Error> {
389 Ok(())
390 }
391
392 async fn recv_datagram(&self) -> Result<Bytes, Self::Error> {
393 std::future::pending().await
394 }
395
396 fn max_datagram_size(&self) -> usize {
397 1200
398 }
399
400 fn protocol(&self) -> Option<&str> {
401 self.state.protocol
402 }
403
404 fn close(&self, code: u32, reason: &str) {
405 self.state.close_events.lock().unwrap().push((code, reason.to_string()));
406 self.state.close_notify.notify_waiters();
407 }
408
409 async fn closed(&self) -> Self::Error {
410 self.state.close_notify.notified().await;
411 FakeError
412 }
413 }
414
415 #[derive(Clone, Default)]
416 struct FakeSendStream {
417 writes: Arc<Mutex<Vec<u8>>>,
418 }
419
420 impl web_transport_trait::SendStream for FakeSendStream {
421 type Error = FakeError;
422
423 async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
424 self.writes.lock().unwrap().put_slice(buf);
425 Ok(buf.len())
426 }
427
428 fn set_priority(&mut self, _order: u8) {}
429
430 fn finish(&mut self) -> Result<(), Self::Error> {
431 Ok(())
432 }
433
434 fn reset(&mut self, _code: u32) {}
435
436 async fn closed(&mut self) -> Result<(), Self::Error> {
437 Ok(())
438 }
439 }
440
441 struct FakeRecvStream {
442 data: VecDeque<u8>,
443 }
444
445 impl web_transport_trait::RecvStream for FakeRecvStream {
446 type Error = FakeError;
447
448 async fn read(&mut self, dst: &mut [u8]) -> Result<Option<usize>, Self::Error> {
449 if self.data.is_empty() {
450 return Ok(None);
451 }
452
453 let size = dst.len().min(self.data.len());
454 for slot in dst.iter_mut().take(size) {
455 *slot = self.data.pop_front().unwrap();
456 }
457 Ok(Some(size))
458 }
459
460 fn stop(&mut self, _code: u32) {}
461
462 async fn closed(&mut self) -> Result<(), Self::Error> {
463 Ok(())
464 }
465 }
466
467 fn mock_server_setup(negotiated: Version) -> Vec<u8> {
468 let mut encoded = Vec::new();
469 let server = setup::Server {
470 version: negotiated.into(),
471 parameters: Bytes::new(),
472 };
473 server
474 .encode(&mut encoded, Version::Ietf(ietf::Version::Draft14))
475 .unwrap();
476
477 let info = lite::SessionInfo { bitrate: Some(1) };
479 let lite_v = lite::Version::try_from(negotiated).unwrap();
480 info.encode(&mut encoded, lite_v).unwrap();
481
482 encoded
483 }
484
485 async fn run_alpn_lite_fallback_case(protocol: Option<&'static str>) {
486 let fake = FakeSession::new(protocol, mock_server_setup(Version::Lite(lite::Version::Lite01)));
487 let client = Client::new().with_versions(
488 [
489 Version::Lite(lite::Version::Lite03),
490 Version::Lite(lite::Version::Lite02),
491 Version::Lite(lite::Version::Lite01),
492 Version::Ietf(ietf::Version::Draft14),
493 ]
494 .into(),
495 );
496
497 let _session = client.connect(fake.clone()).await.unwrap();
498
499 let mut setup_bytes = Bytes::from(fake.control_writes());
501 let setup = setup::Client::decode(&mut setup_bytes, Version::Ietf(ietf::Version::Draft14)).unwrap();
502 let advertised: Vec<Version> = setup.versions.iter().map(|v| Version::try_from(*v).unwrap()).collect();
503 assert_eq!(
504 advertised,
505 vec![
506 Version::Lite(lite::Version::Lite02),
507 Version::Lite(lite::Version::Lite01),
508 Version::Ietf(ietf::Version::Draft14),
509 ]
510 );
511
512 let (code, _) = fake.wait_for_first_close().await;
515 assert_eq!(code, Error::Cancel.to_code());
516 }
517
518 #[tokio::test(start_paused = true)]
519 async fn alpn_lite_falls_back_to_draft14_and_switches_version_post_setup() {
520 run_alpn_lite_fallback_case(Some(ALPN_LITE)).await;
521 }
522
523 #[tokio::test(start_paused = true)]
524 async fn no_alpn_falls_back_to_draft14_and_switches_version_post_setup() {
525 run_alpn_lite_fallback_case(None).await;
526 }
527}