1//! A mechanism for allocating XIDs.
23use crate::errors::ConnectError;
4use crate::protocol::xc_misc::GetXIDRangeReply;
56#[cfg(feature = "std")]
7use std::error::Error;
89use core::fmt;
1011/// An allocator for X11 IDs.
12///
13/// This struct handles the client-side generation of X11 IDs. The ID allocation is based on a
14/// range of IDs that the server assigned us. This range is described by a base and a mask. From
15/// the X11 protocol reference manual:
16///
17/// > The resource-id-mask contains a single contiguous set of bits (at least 18). The client
18/// > allocates resource IDs [..] by choosing a value with only some subset of these bits set and
19/// > ORing it with resource-id-base.
20#[derive(Debug, Clone, Copy)]
21pub struct IdAllocator {
22 next_id: u32,
23 max_id: u32,
24 increment: u32,
25}
2627impl IdAllocator {
28/// Create a new instance of an ID allocator.
29 ///
30 /// The arguments should be the `resource_id_base` and `resource_id_mask` values that the X11
31 /// server sent in a `Setup` response.
32pub fn new(id_base: u32, id_mask: u32) -> Result<Self, ConnectError> {
33if id_mask == 0 {
34return Err(ConnectError::ZeroIdMask);
35 }
36// Find the right-most set bit in id_mask, e.g. for 0b110, this results in 0b010.
37let increment = id_mask & (1 + !id_mask);
38Ok(Self {
39 next_id: id_base,
40 max_id: id_base | id_mask,
41 increment,
42 })
43 }
4445/// Update the available range of IDs based on a GetXIDRangeReply
46pub fn update_xid_range(&mut self, xidrange: &GetXIDRangeReply) -> Result<(), IdsExhausted> {
47let (start, count) = (xidrange.start_id, xidrange.count);
48// Apparently (0, 1) is how the server signals "I am out of IDs".
49 // The second case avoids an underflow below and should never happen.
50if (start, count) == (0, 1) || count == 0 {
51return Err(IdsExhausted);
52 }
53self.next_id = start;
54self.max_id = start + (count - 1) * self.increment;
55Ok(())
56 }
5758/// Generate the next ID.
59pub fn generate_id(&mut self) -> Option<u32> {
60if self.next_id > self.max_id {
61None
62} else {
63let id = self.next_id;
64self.next_id += self.increment;
65Some(id)
66 }
67 }
68}
6970/// The XID range has been exhausted.
71#[derive(Debug, Copy, Clone)]
72pub struct IdsExhausted;
7374impl fmt::Display for IdsExhausted {
75fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76write!(f, "XID range has been exhausted")
77 }
78}
7980#[cfg(feature = "std")]
81impl Error for IdsExhausted {}
8283#[cfg(test)]
84mod test {
85use super::{GetXIDRangeReply, IdAllocator, IdsExhausted};
8687#[test]
88fn exhaustive() {
89let mut allocator = IdAllocator::new(0x2800, 0x1ff).unwrap();
90for expected in 0x2800..=0x29ff {
91assert_eq!(Some(expected), allocator.generate_id());
92 }
93assert_eq!(None, allocator.generate_id());
94 }
9596#[test]
97fn increment() {
98let mut allocator = IdAllocator::new(0, 0b1100).unwrap();
99assert_eq!(Some(0b0000), allocator.generate_id());
100assert_eq!(Some(0b0100), allocator.generate_id());
101assert_eq!(Some(0b1000), allocator.generate_id());
102assert_eq!(Some(0b1100), allocator.generate_id());
103assert_eq!(None, allocator.generate_id());
104 }
105106#[test]
107fn new_range() {
108let mut allocator = IdAllocator::new(0x420, 2).unwrap();
109assert_eq!(Some(0x420), allocator.generate_id());
110assert_eq!(Some(0x422), allocator.generate_id());
111// At this point the range is exhausted and a GetXIDRange request needs to be sent
112assert_eq!(None, allocator.generate_id());
113 allocator
114 .update_xid_range(&generate_get_xid_range_reply(0x13370, 3))
115 .unwrap();
116assert_eq!(Some(0x13370), allocator.generate_id());
117assert_eq!(Some(0x13372), allocator.generate_id());
118assert_eq!(Some(0x13374), allocator.generate_id());
119// At this point the range is exhausted and a GetXIDRange request needs to be sent
120assert_eq!(None, allocator.generate_id());
121 allocator
122 .update_xid_range(&generate_get_xid_range_reply(0x13370, 3))
123 .unwrap();
124assert_eq!(Some(0x13370), allocator.generate_id());
125 }
126127#[test]
128fn invalid_new_arg() {
129let err = IdAllocator::new(1234, 0).unwrap_err();
130if let super::ConnectError::ZeroIdMask = err {
131 } else {
132panic!("Wrong error: {:?}", err);
133 }
134 }
135136#[test]
137fn invalid_update_arg() {
138fn check_ids_exhausted(arg: &Result<(), IdsExhausted>) {
139if let Err(IdsExhausted) = arg {
140 } else {
141panic!("Expected IdsExhausted, got {:?}", arg);
142 }
143 }
144145let mut allocator = IdAllocator::new(0x420, 2).unwrap();
146 check_ids_exhausted(&allocator.update_xid_range(&generate_get_xid_range_reply(0, 1)));
147 check_ids_exhausted(&allocator.update_xid_range(&generate_get_xid_range_reply(1, 0)));
148 }
149150fn generate_get_xid_range_reply(start_id: u32, count: u32) -> GetXIDRangeReply {
151 GetXIDRangeReply {
152 sequence: 0,
153 length: 0,
154 start_id,
155 count,
156 }
157 }
158}