Skip to main content

moqtap_client/draft13/session/
setup.rs

1use moqtap_codec::draft13::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}