Skip to main content

moqtap_client/draft18/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 and draft-18 keeps it gone;
49        // allocator is never blocked.
50        Self { role, next_id, max_id: u64::MAX }
51    }
52
53    /// Allocate the next request ID.
54    pub fn allocate(&mut self) -> Result<VarInt, RequestIdError> {
55        if self.max_id == 0 || self.next_id > self.max_id {
56            return Err(RequestIdError::Blocked);
57        }
58        let id = VarInt::from_u64(self.next_id).unwrap();
59        self.next_id += 2;
60        Ok(id)
61    }
62
63    /// Update the maximum allowed request ID (can only increase).
64    pub fn update_max(&mut self, new_max: u64) -> Result<(), RequestIdError> {
65        if new_max <= self.max_id {
66            return Err(RequestIdError::Decreased(self.max_id, new_max));
67        }
68        self.max_id = new_max;
69        Ok(())
70    }
71
72    /// Validate a request ID received from the peer.
73    pub fn validate_peer_id(&self, id: u64) -> Result<(), RequestIdError> {
74        // Peer has opposite parity
75        let expected_even = match self.role {
76            Role::Client => false, // peer is Server, expects odd
77            Role::Server => true,  // peer is Client, expects even
78        };
79        let is_even = id % 2 == 0;
80        if is_even != expected_even {
81            let peer_role = match self.role {
82                Role::Client => Role::Server,
83                Role::Server => Role::Client,
84            };
85            return Err(RequestIdError::WrongParity(id, peer_role));
86        }
87        if id > self.max_id {
88            return Err(RequestIdError::ExceedsMax(id, self.max_id));
89        }
90        Ok(())
91    }
92
93    /// Check if we are blocked (max_id is 0 or next_id > max_id).
94    pub fn is_blocked(&self) -> bool {
95        self.max_id == 0 || self.next_id > self.max_id
96    }
97
98    /// Get the current maximum request ID.
99    pub fn max_id(&self) -> u64 {
100        self.max_id
101    }
102}