Skip to main content

qubit_id/snowflake/
sonyflake_generator.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Sonyflake-style 63-bit ID generator.
11
12use std::sync::{
13    Arc,
14    Mutex,
15};
16use std::thread;
17use std::time::{
18    Duration,
19    SystemTime,
20    UNIX_EPOCH,
21};
22
23use super::time_slice::TimeSlice;
24use crate::{
25    IdError,
26    IdGenerator,
27};
28
29const DEFAULT_BITS_SEQUENCE: u8 = 8;
30const DEFAULT_BITS_MACHINE: u8 = 16;
31const DEFAULT_TIME_UNIT_NANOS: u128 = 10_000_000;
32const MIN_TIME_UNIT_NANOS: u128 = 1_000_000;
33const DEFAULT_START_MILLIS: u64 = 1_735_689_600_000;
34
35/// Sonyflake-style generator using configurable time, sequence, and machine bits.
36///
37/// By default, the layout is compatible with Sonyflake's commonly documented
38/// allocation: 39 bits of time in 10 ms units, 8 sequence bits, and 16 machine
39/// bits. The sign bit is not used.
40pub struct SonyflakeGenerator {
41    bits_time: u8,
42    bits_sequence: u8,
43    bits_machine: u8,
44    time_unit: Duration,
45    start_time: SystemTime,
46    machine_id: u64,
47    clock: Arc<dyn Fn() -> SystemTime + Send + Sync>,
48    state: Mutex<TimeSlice>,
49}
50
51impl SonyflakeGenerator {
52    /// Creates a Sonyflake-style generator with default layout and epoch.
53    ///
54    /// # Parameters
55    /// - `machine_id`: Machine identifier in `0..=65535`.
56    ///
57    /// # Returns
58    /// A configured generator.
59    ///
60    /// # Errors
61    /// Returns [`IdError::MachineIdOutOfRange`] when `machine_id` does not fit
62    /// in the default 16-bit machine field.
63    pub fn new(machine_id: u64) -> Result<Self, IdError> {
64        Self::with_epoch(machine_id, UNIX_EPOCH + Duration::from_millis(DEFAULT_START_MILLIS))
65    }
66
67    /// Creates a Sonyflake-style generator with default layout and explicit epoch.
68    ///
69    /// # Parameters
70    /// - `machine_id`: Machine identifier in `0..=65535`.
71    /// - `start_time`: Start time used as elapsed-time origin.
72    ///
73    /// # Returns
74    /// A configured generator using the system clock.
75    ///
76    /// # Errors
77    /// Returns the same errors as [`SonyflakeGenerator::with_options`].
78    pub fn with_epoch(machine_id: u64, start_time: SystemTime) -> Result<Self, IdError> {
79        Self::with_options(
80            machine_id,
81            DEFAULT_BITS_SEQUENCE,
82            DEFAULT_BITS_MACHINE,
83            Duration::from_nanos(DEFAULT_TIME_UNIT_NANOS as u64),
84            start_time,
85        )
86    }
87
88    /// Creates a Sonyflake-style generator with explicit layout.
89    ///
90    /// Passing `0` for either bit length selects the Sonyflake default for that
91    /// field.
92    ///
93    /// # Parameters
94    /// - `machine_id`: Machine identifier.
95    /// - `bits_sequence`: Sequence bit length, or `0` for default.
96    /// - `bits_machine_id`: Machine bit length, or `0` for default.
97    /// - `time_unit`: Time unit; must be at least one millisecond.
98    /// - `start_time`: Start time used as elapsed-time origin.
99    ///
100    /// # Returns
101    /// A configured generator using the system clock.
102    ///
103    /// # Errors
104    /// Returns [`IdError::InvalidBitLength`] for invalid bit allocation,
105    /// [`IdError::InvalidTimeUnit`] for sub-millisecond time units,
106    /// [`IdError::StartTimeAhead`] when `start_time` is in the future, or
107    /// [`IdError::MachineIdOutOfRange`] when `machine_id` does not fit.
108    pub fn with_options(
109        machine_id: u64,
110        bits_sequence: u8,
111        bits_machine_id: u8,
112        time_unit: Duration,
113        start_time: SystemTime,
114    ) -> Result<Self, IdError> {
115        Self::with_clock(
116            machine_id,
117            bits_sequence,
118            bits_machine_id,
119            time_unit,
120            start_time,
121            SystemTime::now,
122        )
123    }
124
125    /// Creates a Sonyflake-style generator with an explicit clock.
126    ///
127    /// # Parameters
128    /// - `machine_id`: Machine identifier.
129    /// - `bits_sequence`: Sequence bit length, or `0` for default.
130    /// - `bits_machine_id`: Machine bit length, or `0` for default.
131    /// - `time_unit`: Time unit; must be at least one millisecond.
132    /// - `start_time`: Start time used as elapsed-time origin.
133    /// - `clock`: Function returning the current time.
134    ///
135    /// # Returns
136    /// A configured generator.
137    ///
138    /// # Errors
139    /// Returns the same validation errors as [`SonyflakeGenerator::with_options`].
140    pub fn with_clock<F>(
141        machine_id: u64,
142        bits_sequence: u8,
143        bits_machine_id: u8,
144        time_unit: Duration,
145        start_time: SystemTime,
146        clock: F,
147    ) -> Result<Self, IdError>
148    where
149        F: Fn() -> SystemTime + Send + Sync + 'static,
150    {
151        let bits_sequence = Self::normalize_bits("sequence", bits_sequence, DEFAULT_BITS_SEQUENCE)?;
152        let bits_machine = Self::normalize_bits("machine", bits_machine_id, DEFAULT_BITS_MACHINE)?;
153        let bits_time = 63_u8
154            .checked_sub(bits_sequence)
155            .and_then(|value| value.checked_sub(bits_machine))
156            .ok_or(IdError::InvalidBitLength {
157                name: "time",
158                bits: 0,
159                reason: "63 - sequence bits - machine bits must be at least 32",
160            })?;
161        if bits_time < 32 {
162            return Err(IdError::InvalidBitLength {
163                name: "time",
164                bits: bits_time,
165                reason: "time bit length must be at least 32",
166            });
167        }
168
169        let nanos = time_unit.as_nanos();
170        if nanos < MIN_TIME_UNIT_NANOS {
171            return Err(IdError::InvalidTimeUnit {
172                nanos,
173                min_nanos: MIN_TIME_UNIT_NANOS,
174            });
175        }
176
177        if start_time > clock() {
178            return Err(IdError::StartTimeAhead);
179        }
180
181        let max_machine_id = (1_u64 << bits_machine) - 1;
182        if machine_id > max_machine_id {
183            return Err(IdError::MachineIdOutOfRange {
184                machine_id,
185                max: max_machine_id,
186            });
187        }
188
189        Ok(Self {
190            bits_time,
191            bits_sequence,
192            bits_machine,
193            time_unit,
194            start_time,
195            machine_id,
196            clock: Arc::new(clock),
197            state: Mutex::new(TimeSlice::with_sequence(0, (1_u64 << bits_sequence) - 1)),
198        })
199    }
200
201    /// Normalizes and validates a Sonyflake bit length.
202    ///
203    /// # Parameters
204    /// - `name`: Name of the setting for diagnostics.
205    /// - `bits`: Provided bit length.
206    /// - `default_bits`: Default bit length used when `bits` is zero.
207    ///
208    /// # Returns
209    /// Normalized bit length.
210    ///
211    /// # Errors
212    /// Returns [`IdError::InvalidBitLength`] when the normalized value is 31 or
213    /// greater.
214    fn normalize_bits(name: &'static str, bits: u8, default_bits: u8) -> Result<u8, IdError> {
215        let normalized = if bits == 0 { default_bits } else { bits };
216        if normalized >= 31 {
217            return Err(IdError::InvalidBitLength {
218                name,
219                bits: normalized,
220                reason: "bit length must be less than 31",
221            });
222        }
223        Ok(normalized)
224    }
225
226    /// Returns the number of time bits.
227    ///
228    /// # Returns
229    /// Time bit length.
230    pub const fn bits_time(&self) -> u8 {
231        self.bits_time
232    }
233
234    /// Returns the number of sequence bits.
235    ///
236    /// # Returns
237    /// Sequence bit length.
238    pub const fn bits_sequence(&self) -> u8 {
239        self.bits_sequence
240    }
241
242    /// Returns the number of machine bits.
243    ///
244    /// # Returns
245    /// Machine bit length.
246    pub const fn bits_machine(&self) -> u8 {
247        self.bits_machine
248    }
249
250    /// Returns the maximum representable elapsed time unit.
251    ///
252    /// # Returns
253    /// Maximum elapsed time value.
254    pub const fn max_elapsed_time(&self) -> u64 {
255        (1_u64 << self.bits_time) - 1
256    }
257
258    /// Returns the maximum representable sequence.
259    ///
260    /// # Returns
261    /// Maximum sequence number.
262    pub const fn max_sequence(&self) -> u64 {
263        (1_u64 << self.bits_sequence) - 1
264    }
265
266    /// Returns the maximum representable machine identifier.
267    ///
268    /// # Returns
269    /// Maximum machine identifier.
270    pub const fn max_machine_id(&self) -> u64 {
271        (1_u64 << self.bits_machine) - 1
272    }
273
274    /// Composes a Sonyflake-style ID from explicit parts.
275    ///
276    /// # Parameters
277    /// - `elapsed_time`: Time units elapsed since the start time.
278    /// - `sequence`: Sequence value inside the time unit.
279    /// - `machine_id`: Machine identifier.
280    ///
281    /// # Returns
282    /// Encoded ID.
283    ///
284    /// # Errors
285    /// Returns range errors when any part does not fit the configured layout.
286    pub fn compose(&self, elapsed_time: u64, sequence: u64, machine_id: u64) -> Result<u64, IdError> {
287        if elapsed_time > self.max_elapsed_time() {
288            return Err(IdError::TimestampOverflow {
289                timestamp: elapsed_time,
290                max: self.max_elapsed_time(),
291            });
292        }
293        if sequence > self.max_sequence() {
294            return Err(IdError::SequenceOverflow {
295                sequence,
296                max: self.max_sequence(),
297            });
298        }
299        if machine_id > self.max_machine_id() {
300            return Err(IdError::MachineIdOutOfRange {
301                machine_id,
302                max: self.max_machine_id(),
303            });
304        }
305        Ok((elapsed_time << (self.bits_sequence + self.bits_machine)) | (sequence << self.bits_machine) | machine_id)
306    }
307
308    /// Extracts elapsed time from a Sonyflake-style ID.
309    ///
310    /// # Parameters
311    /// - `id`: ID generated by this layout.
312    ///
313    /// # Returns
314    /// Elapsed time units since the start time.
315    pub fn extract_elapsed_time(&self, id: u64) -> u64 {
316        id >> (self.bits_sequence + self.bits_machine)
317    }
318
319    /// Extracts sequence from a Sonyflake-style ID.
320    ///
321    /// # Parameters
322    /// - `id`: ID generated by this layout.
323    ///
324    /// # Returns
325    /// Sequence number.
326    pub fn extract_sequence(&self, id: u64) -> u64 {
327        let mask = ((1_u64 << self.bits_sequence) - 1) << self.bits_machine;
328        (id & mask) >> self.bits_machine
329    }
330
331    /// Extracts machine ID from a Sonyflake-style ID.
332    ///
333    /// # Parameters
334    /// - `id`: ID generated by this layout.
335    ///
336    /// # Returns
337    /// Machine identifier.
338    pub fn extract_machine_id(&self, id: u64) -> u64 {
339        id & ((1_u64 << self.bits_machine) - 1)
340    }
341
342    /// Converts a time value into elapsed Sonyflake units.
343    ///
344    /// # Parameters
345    /// - `time`: Time to convert.
346    ///
347    /// # Returns
348    /// Elapsed time units since the start time.
349    ///
350    /// # Errors
351    /// Returns [`IdError::TimeBeforeEpoch`] when `time` is before `start_time`.
352    fn elapsed_time_for(&self, time: SystemTime) -> Result<u64, IdError> {
353        let elapsed = time
354            .duration_since(self.start_time)
355            .map_err(|_| IdError::TimeBeforeEpoch)?;
356        let elapsed_units = elapsed.as_nanos() / self.time_unit.as_nanos();
357        if elapsed_units > u128::from(self.max_elapsed_time()) {
358            return Err(IdError::TimestampOverflow {
359                timestamp: u64::try_from(elapsed_units).unwrap_or(u64::MAX),
360                max: self.max_elapsed_time(),
361            });
362        }
363        Ok(elapsed_units as u64)
364    }
365
366    /// Reads the current elapsed time from the configured clock.
367    ///
368    /// # Returns
369    /// Current elapsed time units.
370    ///
371    /// # Errors
372    /// Returns [`IdError::TimeBeforeEpoch`] when the clock is before start time.
373    fn current_elapsed_time(&self) -> Result<u64, IdError> {
374        self.elapsed_time_for((self.clock)())
375    }
376}
377
378impl IdGenerator<u64> for SonyflakeGenerator {
379    type Error = IdError;
380
381    /// Generates the next Sonyflake-style ID.
382    fn next_id(&self) -> Result<u64, Self::Error> {
383        let mut state = self.state.lock().expect("generator state mutex should not be poisoned");
384        let current = self.current_elapsed_time()?;
385
386        if state.timestamp < current {
387            state.timestamp = current;
388            state.sequence = 0;
389        } else {
390            state.sequence = (state.sequence + 1) & self.max_sequence();
391            if state.sequence == 0 {
392                state.timestamp += 1;
393                let overtime = state.timestamp.saturating_sub(current);
394                drop(state);
395                thread::sleep(Duration::from_nanos(
396                    (u128::from(overtime) * self.time_unit.as_nanos()) as u64,
397                ));
398                state = self.state.lock().expect("generator state mutex should not be poisoned");
399            }
400        }
401
402        self.compose(state.timestamp, state.sequence, self.machine_id)
403    }
404}