Skip to main content

moqtap_client/draft17/session/
request_id.rs

1use moqtap_codec::varint::VarInt;
2
3/// Role of the endpoint (determines request ID parity).
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Role {
6    /// Client uses even request IDs: 0, 2, 4, ...
7    Client,
8    /// Server uses odd request IDs: 1, 3, 5, ...
9    Server,
10}
11
12/// Errors from request ID allocation or validation.
13#[derive(Debug, thiserror::Error, PartialEq, Eq)]
14pub enum RequestIdError {
15    /// The request ID exceeds the current MAX_REQUEST_ID.
16    #[error("request ID {0} exceeds max {1}")]
17    ExceedsMax(u64, u64),
18    /// The request ID has the wrong parity for the given role.
19    #[error("request ID {0} has wrong parity for {1:?}")]
20    WrongParity(u64, Role),
21    /// MAX_REQUEST_ID must only increase; it decreased.
22    #[error("max request ID can only increase: was {0}, got {1}")]
23    Decreased(u64, u64),
24    /// No request IDs are available (max is 0 or exhausted).
25    #[error("no request IDs available (blocked)")]
26    Blocked,
27}
28
29/// Allocates and validates request IDs per the MoQT spec.
30///
31/// - Client: even IDs (0, 2, 4, ...)
32/// - Server: odd IDs (1, 3, 5, ...)
33/// - Default MAX_REQUEST_ID: 0 (no requests until increased)
34/// - MAX_REQUEST_ID can only increase
35pub struct RequestIdAllocator {
36    role: Role,
37    next_id: u64,
38    max_id: u64,
39}
40
41impl RequestIdAllocator {
42    /// Create a new allocator for the given role, starting at ID 0 or 1.
43    pub fn new(role: Role) -> Self {
44        let next_id = match role {
45            Role::Client => 0,
46            Role::Server => 1,
47        };
48        // Draft-17 removed MAX_REQUEST_ID; allocator is never blocked.
49        Self { role, next_id, max_id: u64::MAX }
50    }
51
52    /// Allocate the next request ID.
53    pub fn allocate(&mut self) -> Result<VarInt, RequestIdError> {
54        if self.max_id == 0 || self.next_id > self.max_id {
55            return Err(RequestIdError::Blocked);
56        }
57        let id = VarInt::from_u64(self.next_id).unwrap();
58        self.next_id += 2;
59        Ok(id)
60    }
61
62    /// Update the maximum allowed request ID (can only increase).
63    pub fn update_max(&mut self, new_max: u64) -> Result<(), RequestIdError> {
64        if new_max <= self.max_id {
65            return Err(RequestIdError::Decreased(self.max_id, new_max));
66        }
67        self.max_id = new_max;
68        Ok(())
69    }
70
71    /// Validate a request ID received from the peer.
72    pub fn validate_peer_id(&self, id: u64) -> Result<(), RequestIdError> {
73        // Peer has opposite parity
74        let expected_even = match self.role {
75            Role::Client => false, // peer is Server, expects odd
76            Role::Server => true,  // peer is Client, expects even
77        };
78        let is_even = id % 2 == 0;
79        if is_even != expected_even {
80            let peer_role = match self.role {
81                Role::Client => Role::Server,
82                Role::Server => Role::Client,
83            };
84            return Err(RequestIdError::WrongParity(id, peer_role));
85        }
86        if id > self.max_id {
87            return Err(RequestIdError::ExceedsMax(id, self.max_id));
88        }
89        Ok(())
90    }
91
92    /// Check if we are blocked (max_id is 0 or next_id > max_id).
93    pub fn is_blocked(&self) -> bool {
94        self.max_id == 0 || self.next_id > self.max_id
95    }
96
97    /// Get the current maximum request ID.
98    pub fn max_id(&self) -> u64 {
99        self.max_id
100    }
101}