Skip to main content

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