moqtap_client/draft12/session/setup.rs
1use moqtap_codec::draft12::message::{ClientSetup, ServerSetup};
2use moqtap_codec::kvp::KeyValuePair;
3use moqtap_codec::varint::VarInt;
4
5/// The PATH setup parameter. It is the one setup parameter whose sender is
6/// restricted, and the restriction runs one way: PATH is the client's.
7const PATH: u64 = 0x01;
8
9/// Errors from setup message validation or version negotiation.
10#[derive(Debug, thiserror::Error, PartialEq, Eq)]
11pub enum SetupError {
12 /// Client and server share no supported protocol version.
13 #[error("no common version between client and server")]
14 NoCommonVersion,
15 /// A required setup parameter is missing.
16 #[error("missing required parameter: {0}")]
17 MissingParameter(
18 /// Name of the missing parameter.
19 &'static str,
20 ),
21 /// A setup parameter was sent by the endpoint that may not send it.
22 #[error("setup parameter {0:#x} may not be sent by this endpoint")]
23 WrongParameterRole(
24 /// Key of the offending parameter.
25 u64,
26 ),
27 /// A PATH parameter was offered on a session that is not native QUIC.
28 #[error("PATH may not be used when WebTransport is used")]
29 PathOverWebTransport,
30 /// The supported versions list is empty.
31 #[error("no versions offered")]
32 EmptyVersionList,
33}
34
35/// Validate a CLIENT_SETUP message.
36///
37/// Section 8.3.2.2 puts no role restriction on MAX_REQUEST_ID: it
38/// "communicates an initial value for the Maximum Request ID to the receiving
39/// endpoint", which is something either endpoint may do, and a client that
40/// grants the server a request budget during setup is doing exactly that. PATH
41/// is the restricted parameter, and it belongs to the client, so a
42/// CLIENT_SETUP is where it is legal.
43pub fn validate_client_setup(msg: &ClientSetup) -> Result<(), SetupError> {
44 if msg.supported_versions.is_empty() {
45 return Err(SetupError::EmptyVersionList);
46 }
47 Ok(())
48}
49
50/// Validate a SERVER_SETUP message.
51///
52/// Section 8.3.2.1 on PATH: "It MUST NOT be used by the server, or when
53/// WebTransport is used", and a PATH received from the server closes the
54/// session with Invalid Path. Nothing else in a SERVER_SETUP is restricted by
55/// sender.
56///
57/// # Errors
58///
59/// [`SetupError::WrongParameterRole`] if the server sent a PATH.
60pub fn validate_server_setup(msg: &ServerSetup) -> Result<(), SetupError> {
61 if has_path(&msg.parameters) {
62 return Err(SetupError::WrongParameterRole(PATH));
63 }
64 Ok(())
65}
66
67/// Refuse a PATH parameter on a session that is not native QUIC.
68///
69/// The same sentence in Section 8.3.2.1 forbids PATH "when WebTransport is
70/// used" and closes the session with Invalid Path on one received there. Which
71/// transport carries the session is known to the connection and not to the
72/// endpoint, so this is a separate call rather than part of
73/// `validate_client_setup`.
74///
75/// # Errors
76///
77/// [`SetupError::PathOverWebTransport`] if a PATH is offered over
78/// WebTransport.
79pub fn validate_client_path_transport(
80 parameters: &[KeyValuePair],
81 over_webtransport: bool,
82) -> Result<(), SetupError> {
83 if over_webtransport && has_path(parameters) {
84 return Err(SetupError::PathOverWebTransport);
85 }
86 Ok(())
87}
88
89fn has_path(parameters: &[KeyValuePair]) -> bool {
90 let path = VarInt::from_u64(PATH).unwrap();
91 parameters.iter().any(|p| p.key == path)
92}
93
94/// Negotiate a version from the client's offered list and the server's
95/// selected version.
96///
97/// # Errors
98///
99/// [`SetupError::NoCommonVersion`] if the server selected a version the
100/// client did not offer.
101pub fn negotiate_version(
102 client_versions: &[VarInt],
103 server_version: VarInt,
104) -> Result<VarInt, SetupError> {
105 if client_versions.contains(&server_version) {
106 Ok(server_version)
107 } else {
108 Err(SetupError::NoCommonVersion)
109 }
110}