Skip to main content

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}