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}