1use crate::coding::*;
5
6use super::{Message, Parameters, Version};
7
8const PARAM_PROBE: u64 = 0x1;
10const PARAM_PATH: u64 = 0x2;
12const PARAM_ROLE: u64 = 0x3;
14const PARAM_COST: u64 = 0x4;
16const PARAM_HOP: u64 = 0x5;
18
19pub const DEFAULT_COST: u64 = 1;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
33pub enum ProbeLevel {
34 #[default]
36 None,
37 Report,
41 Increase,
43}
44
45impl ProbeLevel {
46 pub fn detect<S: crate::transport::poll::Session>(session: &S) -> Self {
58 use web_transport_trait::Stats as _;
59 let stats = session.stats();
60 match stats.estimated_send_rate().is_some() || stats.rtt().is_some() {
61 true => Self::Report,
62 false => Self::None,
63 }
64 }
65
66 fn from_code(code: u64) -> Self {
68 match code {
69 0 => Self::None,
70 1 => Self::Report,
71 _ => Self::Increase,
72 }
73 }
74
75 fn to_code(self) -> u64 {
77 match self {
78 Self::None => 0,
79 Self::Report => 1,
80 Self::Increase => 2,
81 }
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97#[non_exhaustive]
98pub enum Role {
99 Publisher,
101 Subscriber,
103}
104
105impl Role {
106 fn from_code(code: u64) -> Option<Self> {
111 match code {
112 1 => Some(Role::Publisher),
113 2 => Some(Role::Subscriber),
114 _ => None,
115 }
116 }
117
118 fn to_code(self) -> u64 {
120 match self {
121 Role::Publisher => 1,
122 Role::Subscriber => 2,
123 }
124 }
125
126 pub(crate) fn from_origins(publishes: bool, consumes: bool) -> Option<Self> {
131 match (publishes, consumes) {
132 (true, false) => Some(Role::Publisher),
133 (false, true) => Some(Role::Subscriber),
134 _ => None,
135 }
136 }
137
138 pub fn as_str(self) -> &'static str {
140 match self {
141 Role::Publisher => "publisher",
142 Role::Subscriber => "subscriber",
143 }
144 }
145}
146
147impl std::fmt::Display for Role {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 f.write_str(self.as_str())
150 }
151}
152
153#[derive(Clone, Default, PartialEq, Eq)]
159pub struct Setup {
160 pub probe: ProbeLevel,
162 pub path: Option<String>,
169 pub role: Option<Role>,
173 pub cost: Option<u64>,
179 pub hop: Option<crate::Hop>,
185}
186
187impl std::fmt::Debug for Setup {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 f.debug_struct("Setup")
190 .field("probe", &self.probe)
191 .field(
193 "path",
194 &self.path.as_deref().map(|path| path.split('?').next().unwrap()),
195 )
196 .field("role", &self.role)
197 .field("cost", &self.cost)
198 .field("hop", &self.hop)
199 .finish()
200 }
201}
202
203impl Message for Setup {
204 const CONTROL: Option<crate::stats::Control> = Some(crate::stats::Control::Session);
205 const MAX_SIZE: usize = crate::setup::MAX_SETUP_SIZE;
206
207 fn decode_msg<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
208 if !version.has_setup_stream() {
209 return Err(DecodeError::Version);
210 }
211
212 let params = Parameters::decode(r, version)?;
213 let probe = params
214 .get_varint(PARAM_PROBE, version)?
215 .map(ProbeLevel::from_code)
216 .unwrap_or_default();
217 let path = match params.get_bytes(PARAM_PATH) {
218 Some(bytes) => Some(
219 std::str::from_utf8(bytes)
220 .map_err(|_| DecodeError::InvalidValue)?
221 .to_string(),
222 ),
223 None => None,
224 };
225 let role = params.get_varint(PARAM_ROLE, version)?.and_then(Role::from_code);
226 let cost = params.get_varint(PARAM_COST, version)?;
227 let hop = params
230 .get_varint(PARAM_HOP, version)?
231 .and_then(|id| crate::Hop::new(id).ok());
232
233 Ok(Self {
234 probe,
235 path,
236 role,
237 cost,
238 hop,
239 })
240 }
241
242 fn encode_msg<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
243 if !version.has_setup_stream() {
244 return Err(EncodeError::Version);
245 }
246
247 let mut params = Parameters::default();
248 if self.probe != ProbeLevel::None {
250 params.set_varint(PARAM_PROBE, self.probe.to_code(), version)?;
251 }
252 if let Some(path) = &self.path {
253 params.set_bytes(PARAM_PATH, path.as_bytes().to_vec());
254 }
255 if let Some(role) = self.role {
258 params.set_varint(PARAM_ROLE, role.to_code(), version)?;
259 }
260 if let Some(cost) = self.cost {
261 params.set_varint(PARAM_COST, cost, version)?;
262 }
263 if let Some(hop) = self.hop {
264 params.set_varint(PARAM_HOP, hop.id(), version)?;
265 }
266
267 params.encode(w, version)
268 }
269}
270
271#[derive(Clone, Default)]
277pub(crate) struct PeerSetup(kio::Shared<PeerSetupState>);
278
279#[derive(Default)]
280struct PeerSetupState {
281 seen: bool,
282 setup: Option<Setup>,
283}
284
285impl PeerSetup {
286 pub fn set(&self, setup: Setup) {
288 let mut state = self.0.lock();
289 state.seen = true;
290 state.setup = Some(setup);
291 }
292
293 pub fn claim(&self) -> Result<(), crate::Error> {
295 let mut state = self.0.lock();
296 if state.seen {
297 return Err(crate::Error::ProtocolViolation);
298 }
299 state.seen = true;
300 Ok(())
301 }
302
303 pub fn poll_probe_level(&self, waiter: &kio::Waiter) -> std::task::Poll<ProbeLevel> {
305 self.poll_get(waiter, |setup| setup.probe)
306 }
307
308 pub fn poll_cost(&self, waiter: &kio::Waiter) -> std::task::Poll<Option<u64>> {
311 self.poll_get(waiter, |setup| setup.cost)
312 }
313
314 pub fn poll_hop(&self, waiter: &kio::Waiter) -> std::task::Poll<Option<crate::Hop>> {
317 self.poll_get(waiter, |setup| setup.hop)
318 }
319
320 fn poll_get<T>(&self, waiter: &kio::Waiter, f: impl FnOnce(&Setup) -> T) -> std::task::Poll<T> {
326 let slot = std::task::ready!(self.0.poll(waiter, |setup| {
327 if setup.setup.is_some() {
328 std::task::Poll::Ready(())
329 } else {
330 std::task::Poll::Pending
331 }
332 }));
333 std::task::Poll::Ready(f(slot.setup.as_ref().expect("waited for Some")))
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 fn round_trip(msg: &Setup) -> Setup {
342 let mut buf = bytes::BytesMut::new();
343 msg.encode(&mut buf, Version::Lite05).unwrap();
344 let mut slice = &buf[..];
345 let got = Setup::decode(&mut slice, Version::Lite05).unwrap();
346 assert!(bytes::Buf::remaining(&slice) == 0, "trailing bytes after decode");
347 got
348 }
349
350 #[test]
351 fn empty_round_trip() {
352 let msg = Setup::default();
353 assert_eq!(round_trip(&msg), msg);
354 }
355
356 #[test]
360 fn detect_reports_nothing_without_stats() {
361 use crate::lite::test_transport::{SinkSession, SinkStats};
362 let session = SinkSession::new(Default::default()).with_stats(SinkStats::default());
363 assert_eq!(ProbeLevel::detect(&session), ProbeLevel::None);
364 }
365
366 #[test]
369 fn detect_reports_with_either_metric() {
370 use crate::lite::test_transport::{SinkSession, SinkStats};
371
372 let rtt_only = SinkStats::default().with_rtt(std::time::Duration::from_millis(40));
373 let session = SinkSession::new(Default::default()).with_stats(rtt_only);
374 assert_eq!(ProbeLevel::detect(&session), ProbeLevel::Report);
375
376 let rate_only = SinkStats::default().with_send_rate(1_000_000);
377 let session = SinkSession::new(Default::default()).with_stats(rate_only);
378 assert_eq!(ProbeLevel::detect(&session), ProbeLevel::Report);
379 }
380
381 #[test]
382 fn probe_levels_round_trip() {
383 for probe in [ProbeLevel::None, ProbeLevel::Report, ProbeLevel::Increase] {
384 let msg = Setup {
385 probe,
386 ..Default::default()
387 };
388 assert_eq!(round_trip(&msg), msg);
389 }
390 }
391
392 #[test]
393 fn cost_round_trip() {
394 for cost in [None, Some(0), Some(1), Some(7)] {
397 let msg = Setup {
398 cost,
399 ..Default::default()
400 };
401 assert_eq!(round_trip(&msg), msg);
402 }
403 }
404
405 #[test]
408 fn parameter_values_use_the_version_codec() {
409 let msg = Setup {
410 cost: Some(100),
411 ..Default::default()
412 };
413 for (version, wire) in [
414 (Version::Lite06, &[0x05, 0x01, 0x04, 0x02, 0x40, 0x64][..]),
415 (Version::Lite07, &[0x04, 0x01, 0x04, 0x01, 0x64][..]),
416 ] {
417 let mut buf = Vec::new();
418 msg.encode(&mut buf, version).unwrap();
419 assert_eq!(buf, wire, "{version}");
420 assert_eq!(Setup::decode(&mut &buf[..], version).unwrap(), msg, "{version}");
421 }
422 }
423
424 #[test]
426 fn encode_enforces_the_setup_limit() {
427 let at_limit = crate::setup::MAX_SETUP_SIZE - 6;
429 let msg = Setup {
430 path: Some("a".repeat(at_limit)),
431 ..Default::default()
432 };
433 assert_eq!(round_trip(&msg), msg);
434
435 let msg = Setup {
436 path: Some("a".repeat(at_limit + 1)),
437 ..Default::default()
438 };
439 let mut buf = bytes::BytesMut::new();
440 assert!(matches!(
441 msg.encode(&mut buf, Version::Lite05),
442 Err(EncodeError::TooLarge)
443 ));
444 assert!(buf.is_empty());
445 }
446
447 #[test]
448 fn path_round_trip() {
449 let msg = Setup {
450 probe: ProbeLevel::Report,
451 path: Some("/room/123".to_string()),
452 ..Default::default()
453 };
454 assert_eq!(round_trip(&msg), msg);
455 }
456
457 #[test]
458 fn hop_round_trip() {
459 let msg = Setup {
460 hop: Some(crate::Hop::new(42).unwrap()),
461 ..Default::default()
462 };
463 assert_eq!(round_trip(&msg), msg);
464 }
465
466 #[test]
469 fn hop_zero_decodes_as_none() {
470 use crate::coding::Encode;
471
472 let version = Version::Lite05;
473 let mut params = Parameters::default();
474 params.set_varint(super::PARAM_HOP, 0, version).unwrap();
475 let mut body = bytes::BytesMut::new();
476 params.encode(&mut body, version).unwrap();
477 let mut buf = bytes::BytesMut::new();
479 (body.len() as u64).encode(&mut buf, version).unwrap();
480 buf.extend_from_slice(&body);
481 let mut slice = &buf[..];
482 let got = Setup::decode(&mut slice, version).unwrap();
483 assert_eq!(got.hop, None);
484 }
485
486 #[test]
487 fn empty_path_round_trips() {
488 let msg = Setup {
491 path: Some(String::new()),
492 ..Default::default()
493 };
494 assert_eq!(round_trip(&msg), msg);
495 }
496
497 #[test]
498 fn roles_round_trip() {
499 for role in [Some(Role::Publisher), Some(Role::Subscriber), None] {
500 let msg = Setup {
501 path: Some("/room/123".to_string()),
502 role,
503 ..Default::default()
504 };
505 assert_eq!(round_trip(&msg), msg);
506 }
507 }
508
509 #[test]
510 fn unknown_probe_level_saturates_to_increase() {
511 let mut params = Parameters::default();
514 params.set_varint(PARAM_PROBE, 99, Version::Lite05).unwrap();
515 let mut body = Vec::new();
516 params.encode(&mut body, Version::Lite05).unwrap();
517
518 let mut buf = bytes::BytesMut::new();
519 body.len().encode(&mut buf, Version::Lite05).unwrap();
520 buf.extend_from_slice(&body);
521
522 let mut slice = &buf[..];
523 let got = Setup::decode(&mut slice, Version::Lite05).unwrap();
524 assert_eq!(got.probe, ProbeLevel::Increase);
525 }
526
527 #[test]
528 fn role_wire_codes() {
529 for (role, code) in [(Role::Publisher, 1u64), (Role::Subscriber, 2)] {
532 assert_eq!(role.to_code(), code);
533 assert_eq!(Role::from_code(code), Some(role));
534 }
535 }
536
537 #[test]
538 fn unknown_role_decodes_as_bidirectional() {
539 for code in [0u64, 9, 250] {
543 let mut params = Parameters::default();
544 params.set_varint(PARAM_ROLE, code, Version::Lite05).unwrap();
545 let mut body = Vec::new();
546 params.encode(&mut body, Version::Lite05).unwrap();
547
548 let mut buf = bytes::BytesMut::new();
549 body.len().encode(&mut buf, Version::Lite05).unwrap();
550 buf.extend_from_slice(&body);
551
552 let mut slice = &buf[..];
553 let got = Setup::decode(&mut slice, Version::Lite05).unwrap();
554 assert_eq!(got.role, None, "role code {code} should decode as bidirectional");
555 }
556 }
557
558 #[test]
559 fn rejects_before_lite05() {
560 let msg = Setup::default();
561 let mut buf = bytes::BytesMut::new();
562 assert!(matches!(
563 msg.encode(&mut buf, Version::Lite04),
564 Err(EncodeError::Version)
565 ));
566 }
567
568 #[test]
569 fn ignores_unknown_parameters() {
570 let mut params = Parameters::default();
572 params.set_bytes(PARAM_PATH, b"/foo".to_vec());
573 params.set_bytes(0xbeef, b"whatever".to_vec());
574
575 let mut body = Vec::new();
576 params.encode(&mut body, Version::Lite05).unwrap();
577
578 let mut buf = bytes::BytesMut::new();
580 body.len().encode(&mut buf, Version::Lite05).unwrap();
581 buf.extend_from_slice(&body);
582
583 let mut slice = &buf[..];
584 let got = Setup::decode(&mut slice, Version::Lite05).unwrap();
585 assert_eq!(got.path.as_deref(), Some("/foo"));
586 }
587}