Skip to main content

sigid_core/
generator.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! ID generator
4//!
5//! # Security Warning
6//!
7//! The default `Xorshift64` PRNG used in this module is **NOT**
8//! cryptographically secure. It is suitable for generating unique IDs
9//! where predictability is not a security concern (e.g., database IDs,
10//! log entries, session IDs with additional security layers).
11//!
12//! For security-sensitive applications (e.g., API keys, tokens,
13//! password reset links), enable the `secure` feature which uses
14//! `getrandom` for cryptographically secure seed generation.
15
16use crate::{Error, SigId26, COUNTER_MAX, EPOCH, TIMESTAMP_MAX, WORKER_MAX};
17
18/// ID generator as a pure state machine
19#[derive(Debug, Clone)]
20pub struct Generator {
21    seed: u64,
22    last_ms: u64,
23    counter: u16,
24    worker_id: u16,
25}
26
27impl Generator {
28    /// Create new generator with seed
29    pub const fn new(seed: u64) -> Self {
30        // Защита от нулевого сида для Xorshift
31        let seed = if seed == 0 { 0x9e3779b97f4a7c15 } else { seed };
32        Self {
33            seed,
34            last_ms: 0,
35            counter: 0,
36            worker_id: 0,
37        }
38    }
39
40    /// Create new generator with seed and worker_id
41    pub const fn with_worker_id(seed: u64, worker_id: u16) -> Self {
42        let seed = if seed == 0 { 0x9e3779b97f4a7c15 } else { seed };
43        Self {
44            seed,
45            last_ms: 0,
46            counter: 0,
47            worker_id: worker_id & WORKER_MAX,
48        }
49    }
50
51    /// Set worker ID (16 bits)
52    pub const fn set_worker_id(mut self, worker_id: u16) -> Self {
53        self.worker_id = worker_id & WORKER_MAX;
54        self
55    }
56
57    /// Get current worker ID
58    pub const fn get_worker_id(&self) -> u16 {
59        self.worker_id
60    }
61
62    /// Reset counter
63    pub fn reset(&mut self) {
64        self.counter = 0;
65        self.last_ms = 0;
66    }
67
68    /// Generate ID from timestamp (milliseconds since UNIX_EPOCH)
69    pub fn generate(&mut self, ms: u64) -> Result<SigId26, Error> {
70        let timestamp = ms.saturating_sub(EPOCH);
71
72        // Проверяем переполнение timestamp
73        if timestamp > TIMESTAMP_MAX {
74            return Err(Error::TimestampOverflow);
75        }
76
77        // Update counter
78        if ms == self.last_ms {
79            // Проверяем достижение лимита ДО инкремента
80            if self.counter >= COUNTER_MAX {
81                return self.wait_for_next_ms(ms);
82            }
83            self.counter += 1;
84        } else {
85            self.last_ms = ms;
86            self.counter = 0;
87        }
88
89        let random = self.next_random();
90
91        // Используем единую схему с worker_id (48+16+14+50)
92        Ok(SigId26::raw_new_with_worker(
93            timestamp,
94            self.worker_id,
95            self.counter,
96            random,
97        ))
98    }
99
100    /// Wait for next millisecond or return error
101    fn wait_for_next_ms(&mut self, _ms: u64) -> Result<SigId26, Error> {
102        // В no_std окружении нет способа получить время
103        #[cfg(not(feature = "std"))]
104        {
105            return Err(Error::CounterOverflow);
106        }
107
108        // Если включена std, пытаемся получить реальное время
109        #[cfg(feature = "std")]
110        {
111            use std::time::{SystemTime, UNIX_EPOCH};
112
113            let now_ms = SystemTime::now()
114                .duration_since(UNIX_EPOCH)
115                .map_err(|_| Error::TimestampOverflow)?
116                .as_millis() as u64;
117
118            // Если живое время меньше или равно last_ms,
119            // значит мы не можем перейти на следующую миллисекунду.
120            // Возвращаем ошибку!
121            if now_ms <= self.last_ms {
122                return Err(Error::CounterOverflow);
123            }
124
125            // Время перешло на следующую миллисекунду - обновляем состояние
126            self.last_ms = now_ms;
127            self.counter = 0;
128            let timestamp = now_ms.saturating_sub(EPOCH);
129            let random = self.next_random();
130            Ok(SigId26::raw_new_with_worker(
131                timestamp,
132                self.worker_id,
133                self.counter,
134                random,
135            ))
136        }
137    }
138
139    /// Xorshift64 random generator
140    fn next_random(&mut self) -> u64 {
141        let mut x = self.seed;
142        x ^= x << 13;
143        x ^= x >> 7;
144        x ^= x << 17;
145        self.seed = x;
146        x
147    }
148}
149
150impl Default for Generator {
151    fn default() -> Self {
152        Self::new(0x9e3779b97f4a7c15)
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_generator_worker_id() {
162        let mut gen = Generator::with_worker_id(0x123456789abcdef, 0x1234);
163        assert_eq!(gen.get_worker_id(), 0x1234);
164
165        let id = gen.generate(1234567890).unwrap();
166        assert_eq!(id.worker_id(), 0x1234);
167    }
168
169    #[test]
170    fn test_generator_counter_increment() {
171        let mut gen = Generator::new(0x123456789abcdef);
172        let ms = 1234567890;
173
174        let id1 = gen.generate(ms).unwrap();
175        let id2 = gen.generate(ms).unwrap();
176
177        assert_eq!(id2.counter(), id1.counter() + 1);
178    }
179
180    #[test]
181    fn test_generator_timestamp() {
182        let mut gen = Generator::new(0x123456789abcdef);
183        let ms = 1700000000000;
184        let id = gen.generate(ms).unwrap();
185        assert_eq!(id.timestamp_ms(), ms);
186    }
187
188    #[test]
189    fn test_counter_overflow() {
190        let mut gen = Generator::new(0x123456789abcdef);
191
192        // Берем время из ДАЛЕКОГО БУДУЩЕГО (3000 год),
193        // чтобы системное время компьютера (2026 год) гарантированно было меньше!
194        // 3000-01-01 00:00:00 UTC в миллисекундах
195        let ms = 32503680000000u64;
196
197        // COUNTER_MAX = 16383. Делаем ровно COUNTER_MAX + 1 успешных генераций
198        for i in 0..=COUNTER_MAX {
199            let result = gen.generate(ms);
200            assert!(result.is_ok(), "Unexpected error at iteration {}", i);
201        }
202
203        // Следующий вызов в ТУ ЖЕ миллисекунду ОБЯЗАН выдать CounterOverflow,
204        // так как SystemTime::now() вернет 2026 год, что меньше 3000-го года!
205        let result = gen.generate(ms);
206
207        assert!(
208            result.is_err(),
209            "Expected CounterOverflow error after {} generated IDs",
210            COUNTER_MAX + 1
211        );
212        match result {
213            Err(Error::CounterOverflow) => {}
214            _ => panic!("Expected CounterOverflow error, got {:?}", result),
215        }
216    }
217
218    #[cfg(feature = "std")]
219    #[test]
220    fn test_generator_now() {
221        let mut gen = Generator::new(0x123456789abcdef);
222        let id = gen.generate(1700000000000).unwrap();
223        assert!(id.is_valid());
224    }
225}