snowflake_gen/layout.rs
1use crate::error::SnowflakeError;
2
3/// Describes how the 63 usable bits of an `i64` snowflake ID are partitioned.
4///
5/// All four fields must sum to exactly **63**. The sign bit is reserved so
6/// that all generated IDs are positive and can be stored in signed integer
7/// columns without surprises.
8///
9/// # Field ordering (MSB → LSB)
10///
11/// ```text
12/// [sign(1)] [timestamp(T)] [machine_id(M)] [node_id(N)] [sequence(S)]
13/// ```
14///
15/// # Example
16///
17/// ```rust
18/// use snowflake_gen::BitLayout;
19///
20/// // Classic Twitter layout
21/// let layout = BitLayout::default();
22/// assert_eq!(layout.max_sequence(), 4095);
23/// assert_eq!(layout.max_machine_id(), 31);
24/// assert_eq!(layout.max_node_id(), 31);
25/// ```
26#[must_use]
27#[derive(Copy, Clone, Debug, PartialEq, Eq)]
28pub struct BitLayout {
29 /// Number of bits allocated to the millisecond timestamp.
30 pub(crate) timestamp_bits: u8,
31 /// Number of bits allocated to the machine identifier.
32 pub(crate) machine_id_bits: u8,
33 /// Number of bits allocated to the node (worker) identifier.
34 pub(crate) node_id_bits: u8,
35 /// Number of bits allocated to the per-millisecond sequence counter.
36 pub(crate) sequence_bits: u8,
37
38 // Precomputed shift offsets (derived from the widths above).
39 pub(crate) node_id_shift: u8,
40 pub(crate) machine_id_shift: u8,
41 pub(crate) timestamp_shift: u8,
42}
43
44impl BitLayout {
45 /// Creates a validated `BitLayout`.
46 ///
47 /// Returns [`SnowflakeError::InvalidBitLayout`] if the four bit widths do
48 /// not sum to exactly 63.
49 ///
50 /// # Example
51 ///
52 /// ```rust
53 /// use snowflake_gen::BitLayout;
54 ///
55 /// let layout = BitLayout::new(41, 5, 5, 12).unwrap(); // classic Twitter
56 /// let bad = BitLayout::new(40, 5, 5, 12); // sums to 62 → error
57 /// assert!(bad.is_err());
58 /// ```
59 pub fn new(
60 timestamp_bits: u8,
61 machine_id_bits: u8,
62 node_id_bits: u8,
63 sequence_bits: u8,
64 ) -> Result<Self, SnowflakeError> {
65 let total = timestamp_bits
66 .checked_add(machine_id_bits)
67 .and_then(|s| s.checked_add(node_id_bits))
68 .and_then(|s| s.checked_add(sequence_bits))
69 .unwrap_or(u8::MAX);
70
71 if total != 63 {
72 return Err(SnowflakeError::InvalidBitLayout {
73 t: timestamp_bits,
74 m: machine_id_bits,
75 n: node_id_bits,
76 s: sequence_bits,
77 total,
78 });
79 }
80
81 let node_id_shift = sequence_bits;
82 let machine_id_shift = node_id_shift + node_id_bits;
83 let timestamp_shift = machine_id_shift + machine_id_bits;
84
85 Ok(Self {
86 timestamp_bits,
87 machine_id_bits,
88 node_id_bits,
89 sequence_bits,
90 node_id_shift,
91 machine_id_shift,
92 timestamp_shift,
93 })
94 }
95
96 /// Returns the number of bits allocated to the timestamp.
97 #[inline]
98 pub const fn timestamp_bits(&self) -> u8 {
99 self.timestamp_bits
100 }
101
102 /// Returns the number of bits allocated to the machine identifier.
103 #[inline]
104 pub const fn machine_id_bits(&self) -> u8 {
105 self.machine_id_bits
106 }
107
108 /// Returns the number of bits allocated to the node identifier.
109 #[inline]
110 pub const fn node_id_bits(&self) -> u8 {
111 self.node_id_bits
112 }
113
114 /// Returns the number of bits allocated to the sequence counter.
115 #[inline]
116 pub const fn sequence_bits(&self) -> u8 {
117 self.sequence_bits
118 }
119
120 /// Maximum allowed sequence value (IDs per millisecond − 1).
121 ///
122 /// ```rust
123 /// use snowflake_gen::BitLayout;
124 /// assert_eq!(BitLayout::default().max_sequence(), 4095);
125 /// ```
126 #[inline]
127 pub const fn max_sequence(&self) -> u32 {
128 (1u32 << self.sequence_bits) - 1
129 }
130
131 /// Maximum allowed `machine_id`.
132 ///
133 /// ```rust
134 /// use snowflake_gen::BitLayout;
135 /// assert_eq!(BitLayout::default().max_machine_id(), 31);
136 /// ```
137 #[inline]
138 pub const fn max_machine_id(&self) -> i64 {
139 (1i64 << self.machine_id_bits) - 1
140 }
141
142 /// Maximum allowed `node_id`.
143 ///
144 /// ```rust
145 /// use snowflake_gen::BitLayout;
146 /// assert_eq!(BitLayout::default().max_node_id(), 31);
147 /// ```
148 #[inline]
149 pub const fn max_node_id(&self) -> i64 {
150 (1i64 << self.node_id_bits) - 1
151 }
152
153 /// Maximum milliseconds the timestamp field can represent.
154 ///
155 /// With 41 timestamp bits this is ~69 years from epoch.
156 #[inline]
157 pub const fn max_timestamp_millis(&self) -> i64 {
158 (1i64 << self.timestamp_bits) - 1
159 }
160
161 /// Maximum IDs that can be generated per second across all nodes.
162 #[inline]
163 pub fn max_ids_per_second(&self) -> u64 {
164 (self.max_sequence() as u64 + 1)
165 * (self.max_machine_id() as u64 + 1)
166 * (self.max_node_id() as u64 + 1)
167 * 1_000
168 }
169}
170
171impl Default for BitLayout {
172 fn default() -> Self {
173 Self::new(41, 5, 5, 12).expect("default layout is always valid")
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn default_layout_is_valid() {
183 let l = BitLayout::default();
184 assert_eq!(l.max_sequence(), 4095);
185 assert_eq!(l.max_machine_id(), 31);
186 assert_eq!(l.max_node_id(), 31);
187 }
188
189 #[test]
190 fn custom_layout_capacities() {
191 let l = BitLayout::new(38, 8, 7, 10).unwrap();
192 assert_eq!(l.max_sequence(), 1023);
193 assert_eq!(l.max_machine_id(), 255);
194 assert_eq!(l.max_node_id(), 127);
195 }
196
197 #[test]
198 fn invalid_bit_layout_rejected() {
199 assert!(BitLayout::new(40, 5, 5, 12).is_err());
200 }
201
202 #[test]
203 fn max_timestamp_millis() {
204 let l = BitLayout::default();
205 // 2^41 - 1 = 2_199_023_255_551 (~69 years in ms)
206 assert_eq!(l.max_timestamp_millis(), 2_199_023_255_551);
207 }
208
209 #[test]
210 fn max_ids_per_second() {
211 let l = BitLayout::default();
212 // (4095 + 1) * (31 + 1) * (31 + 1) * 1000
213 assert_eq!(l.max_ids_per_second(), 4_096 * 32 * 32 * 1_000);
214 }
215
216 #[test]
217 fn zero_width_fields() {
218 // All bits to timestamp and sequence, zero machine/node
219 let l = BitLayout::new(51, 0, 0, 12).unwrap();
220 assert_eq!(l.max_machine_id(), 0);
221 assert_eq!(l.max_node_id(), 0);
222 assert_eq!(l.max_sequence(), 4095);
223 }
224
225 #[test]
226 fn max_sequence_bits_after_widening() {
227 // 31 sequence bits — the widened u32 return type must handle this
228 let l = BitLayout::new(16, 8, 8, 31).unwrap();
229 assert_eq!(l.max_sequence(), (1u32 << 31) - 1);
230 }
231}