use thiserror::Error as ThisError;
#[derive(ThisError, Debug)]
pub enum Error {
#[error("There is not enough ID now")]
TimeOverflow,
#[error("SystemTime before UNIX EPOCH!")]
SystemTimeException,
#[error("Too many threads")]
WorkerIDOverflow,
#[error("SystemTime before EPOCH!")]
EpochException,
}
use std::cell::RefCell;
use std::sync::atomic::{AtomicUsize, Ordering};
const UUID_TICKS_BETWEEN_EPOCHS: u64 = 0x01B2_1DD2_1381_4000;
const TIMESTAMP42SHIFT: u8 = 13;
static COUNTER: AtomicUsize = AtomicUsize::new(0);
thread_local! {
static WORKER_ID: [u8;2] = {
let id = COUNTER.fetch_add(1, Ordering::SeqCst);
if id > u16::max_value() as usize {
panic!("too many threads")
};
(id as u16).to_be_bytes()
};
static SEQ: RefCell<u16> = RefCell::new(0);
static TIMESTAMP: RefCell<u64> = RefCell::new(now().unwrap());
}
fn now() -> Result<u64, Error> {
use std::time::{SystemTime, UNIX_EPOCH};
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| Error::SystemTimeException)?;
Ok((time.as_nanos() / 100) as u64 + UUID_TICKS_BETWEEN_EPOCHS)
}
fn worker_id() -> [u8; 2] {
WORKER_ID.with(|f| *f)
}
fn time_inc(min_interval: u16) -> Result<u64, Error> {
TIMESTAMP.with(|t| {
let mut time = t.borrow_mut();
if cfg!(test) && (*time) >= now()? {
return Err(Error::TimeOverflow);
}
*time += u64::from(min_interval);
Ok(*time)
})
}
fn next(min_interval: u16) -> Result<(u64, u16), Error> {
SEQ.with(|s| {
let mut seq = s.borrow_mut();
if *seq < ((1 << 14) - 1) {
*seq += 1;
Ok((timestamp(), *seq))
} else {
let t = time_inc(min_interval)?;
*seq = 0;
Ok((t, 0))
}
})
}
fn timestamp() -> u64 {
TIMESTAMP.with(|t| *t.borrow())
}
#[cfg(test)]
fn seq() -> u16 {
SEQ.with(|s| *s.borrow())
}
pub fn next_short_128(machine_id: [u8; 4]) -> Result<[u8; 16], Error> {
let (t, s) = next(1)?;
let w = worker_id();
let time_low = ((t & 0xFFFF_FFFF) as u32).to_be_bytes();
let time_mid = (((t >> 32) & 0xFFFF) as u16).to_be_bytes();
let time_high_and_version = ((((t >> 48) & 0x0FFF) as u16) | (1 << 12)).to_be_bytes();
Ok([
time_low[0],
time_low[1],
time_low[2],
time_low[3],
time_mid[0],
time_mid[1],
time_high_and_version[0],
time_high_and_version[1],
(((s & 0x3F00) >> 8) as u8) | 0x80,
(s & 0xFF) as u8,
w[0],
w[1],
machine_id[0],
machine_id[1],
machine_id[2],
machine_id[3],
])
}
pub fn next_short_96(machine_id: [u8; 3], epoch: u64) -> Result<[u8; 12], Error> {
let (mut t, s) = next(1 << TIMESTAMP42SHIFT)?;
t = (t
.checked_sub(UUID_TICKS_BETWEEN_EPOCHS)
.ok_or_else(|| Error::EpochException)?
.checked_sub(epoch)
.ok_or_else(|| Error::EpochException)?)
>> TIMESTAMP42SHIFT;
let t_hi = (t >> 2).to_be_bytes();
let [t_low_and_s_hi, s_low] = (((t as u16) << 14) | s).to_be_bytes();
let w = worker_id();
Ok([
t_hi[3],
t_hi[4],
t_hi[5],
t_hi[6],
t_hi[7],
t_low_and_s_hi,
s_low,
w[0],
w[1],
machine_id[0],
machine_id[1],
machine_id[2],
])
}
pub fn short_96_to_128(short_96: [u8; 12], epoch: u64, machine_id_hi: u8) -> [u8; 16] {
let c = short_96;
let t = ((u64::from_le_bytes([c[5], c[4], c[3], c[2], c[1], c[0], 0, 0]) >> 6)
<< TIMESTAMP42SHIFT)
+ epoch
+ UUID_TICKS_BETWEEN_EPOCHS;
let s = u16::from_le_bytes([c[6], c[5]]) & 0x3fff;
let time_low = ((t & 0xFFFF_FFFF) as u32).to_be_bytes();
let time_mid = (((t >> 32) & 0xFFFF) as u16).to_be_bytes();
let time_high_and_version = ((((t >> 48) & 0x0FFF) as u16) | (1 << 12)).to_be_bytes();
[
time_low[0],
time_low[1],
time_low[2],
time_low[3],
time_mid[0],
time_mid[1],
time_high_and_version[0],
time_high_and_version[1],
(((s & 0x3F00) >> 8) as u8) | 0x80,
(s & 0xFF) as u8,
c[7],
c[8],
machine_id_hi,
c[9],
c[10],
c[11],
]
}
pub fn next_short_64(epoch: u64) -> Result<[u8; 8], Error> {
let w = worker_id();
if w[0] != 0 {
return Err(Error::WorkerIDOverflow);
}
let (mut t, s) = next(1 << TIMESTAMP42SHIFT)?;
t = (t
.checked_sub(UUID_TICKS_BETWEEN_EPOCHS)
.ok_or_else(|| Error::EpochException)?
.checked_sub(epoch)
.ok_or_else(|| Error::EpochException)?)
>> TIMESTAMP42SHIFT;
let t_hi = (t >> 2).to_be_bytes();
let [t_low_and_s_hi, s_low] = (((t as u16) << 14) | s).to_be_bytes();
Ok([
t_hi[3],
t_hi[4],
t_hi[5],
t_hi[6],
t_hi[7],
t_low_and_s_hi,
s_low,
w[1],
])
}
pub fn short_64_to_96(short_64: [u8; 8], machine_id: [u8; 3]) -> [u8; 12] {
let s = short_64;
[
s[0],
s[1],
s[2],
s[3],
s[4],
s[5],
s[6],
0,
s[7],
machine_id[0],
machine_id[1],
machine_id[2],
]
}
pub fn short_64_to_128(short_64: [u8; 8], epoch: u64, machine_id: [u8; 4]) -> [u8; 16] {
let short96 = short_64_to_96(short_64, [machine_id[1], machine_id[2], machine_id[3]]);
short_96_to_128(short96, epoch, machine_id[0])
}
#[test]
fn test_128() {
use uuid::Uuid;
use uuid::Variant;
let id = next_short_128([1, 1, 1, 1]).unwrap();
let hex: String = id.into_iter().map(|val| format!("{:0>2x}", val)).collect();
let my_uuid = Uuid::parse_str(&hex).unwrap();
let (ticks, counter) = my_uuid.to_timestamp().unwrap().to_rfc4122();
assert_eq!(ticks, timestamp());
assert_eq!(counter, seq());
while seq() != 0 {
next_short_128([1, 1, 1, 1]).unwrap();
}
let ticks = timestamp();
for count in 0..1 << 14 {
assert_eq!(count, seq());
next_short_128([1, 1, 1, 1]).unwrap();
}
assert_eq!(ticks + 1, timestamp());
assert_eq!(my_uuid.get_version_num(), 1usize);
assert_eq!(my_uuid.get_variant().unwrap(), Variant::RFC4122);
}
#[test]
fn test_96() {
use uuid::Uuid;
use uuid::Variant;
let id96 = next_short_96([1, 1, 1], 0).unwrap();
let id128 = short_96_to_128(id96, 0, 0);
let hex: String = id128
.into_iter()
.map(|val| format!("{:0>2x}", val))
.collect();
let my_uuid = Uuid::parse_str(&hex).unwrap();
let (ticks, counter) = my_uuid.to_timestamp().unwrap().to_rfc4122();
assert_eq!(ticks, timestamp() >> TIMESTAMP42SHIFT << TIMESTAMP42SHIFT);
assert_eq!(counter, seq());
while seq() != 0 {
next_short_96([1, 1, 1], 0).unwrap();
}
let ticks = timestamp();
for count in 0..1 << 14 {
assert_eq!(count, seq());
next_short_96([1, 1, 1], 0).unwrap();
}
assert_eq!(ticks + (1 << TIMESTAMP42SHIFT), timestamp());
assert_eq!(my_uuid.get_version_num(), 1usize);
assert_eq!(my_uuid.get_variant().unwrap(), Variant::RFC4122);
}
#[test]
fn test_64() {
use uuid::Uuid;
use uuid::Variant;
let id64 = next_short_64(0).unwrap();
let id128 = short_64_to_128(id64, 0, [1u8, 1, 1, 1]);
let hex: String = id128
.into_iter()
.map(|val| format!("{:0>2x}", val))
.collect();
let my_uuid = Uuid::parse_str(&hex).unwrap();
let (ticks, counter) = my_uuid.to_timestamp().unwrap().to_rfc4122();
assert_eq!(ticks, timestamp() >> TIMESTAMP42SHIFT << TIMESTAMP42SHIFT);
assert_eq!(counter, seq());
while seq() != 0 {
next_short_64(0).unwrap();
}
let ticks = timestamp();
for count in 0..1 << 14 {
assert_eq!(count, seq());
next_short_64(0).unwrap();
}
assert_eq!(ticks + (1 << TIMESTAMP42SHIFT), timestamp());
assert_eq!(my_uuid.get_version_num(), 1usize);
assert_eq!(my_uuid.get_variant().unwrap(), Variant::RFC4122);
}
use std::sync::atomic::AtomicU16;
static SEQ_ATOM: AtomicU16 = AtomicU16::new(0);
fn next_atom() -> Result<(u64, u16), Error> {
let ts = now()?;
let seq = SEQ_ATOM.fetch_add(1, Ordering::SeqCst);
Ok((ts, seq))
}
pub fn uuidv1(machine_id: [u8; 6]) -> Result<[u8; 16], Error> {
let (t, s) = next_atom()?;
let time_low = ((t & 0xFFFF_FFFF) as u32).to_be_bytes();
let time_mid = (((t >> 32) & 0xFFFF) as u16).to_be_bytes();
let time_high_and_version = ((((t >> 48) & 0x0FFF) as u16) | (1 << 12)).to_be_bytes();
let clock_seq_and_reserved = (s & 0x3FFF | 0x8000).to_be_bytes();
Ok([
time_low[0],
time_low[1],
time_low[2],
time_low[3],
time_mid[0],
time_mid[1],
time_high_and_version[0],
time_high_and_version[1],
clock_seq_and_reserved[0],
clock_seq_and_reserved[1],
machine_id[0],
machine_id[1],
machine_id[2],
machine_id[3],
machine_id[4],
machine_id[5],
])
}
pub fn next_short_128_sync(machine_id: [u8; 6]) -> Result<[u8; 16], Error> {
uuidv1(machine_id)
}
#[cfg(test)]
fn seq_sync() -> u16 {
SEQ_ATOM.load(Ordering::SeqCst)
}
#[test]
fn test_uuidv1() {
use uuid::Uuid;
use uuid::Variant;
let id128 = next_short_128_sync([0, 0, 0, 0, 0, 0]).unwrap();
let hex: String = id128
.into_iter()
.map(|val| format!("{:0>2x}", val))
.collect();
let my_uuid = Uuid::parse_str(&hex).unwrap();
let (_ticks, counter) = my_uuid.to_timestamp().unwrap().to_rfc4122();
assert_eq!(counter, seq_sync() - 1);
assert_eq!(my_uuid.get_version_num(), 1usize);
assert_eq!(my_uuid.get_variant().unwrap(), Variant::RFC4122);
}