use std::{
convert::TryInto,
error,
fmt,
result,
str::FromStr,
sync::atomic::{AtomicUsize, Ordering},
time::SystemTime,
};
use hex::{self, FromHexError};
use rand::{thread_rng, Rng};
use lazy_static::lazy_static;
const TIMESTAMP_SIZE: usize = 4;
const PROCESS_ID_SIZE: usize = 5;
const COUNTER_SIZE: usize = 3;
const TIMESTAMP_OFFSET: usize = 0;
const PROCESS_ID_OFFSET: usize = TIMESTAMP_OFFSET + TIMESTAMP_SIZE;
const COUNTER_OFFSET: usize = PROCESS_ID_OFFSET + PROCESS_ID_SIZE;
const MAX_U24: usize = 0xFF_FFFF;
lazy_static! {
static ref OID_COUNTER: AtomicUsize = AtomicUsize::new(thread_rng().gen_range(0..=MAX_U24));
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Error {
#[non_exhaustive]
InvalidHexStringCharacter { c: char, index: usize, hex: String },
#[non_exhaustive]
InvalidHexStringLength { length: usize, hex: String },
}
pub type Result<T> = result::Result<T, Error>;
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::InvalidHexStringCharacter { c, index, hex } => {
write!(
fmt,
"invalid character '{}' was found at index {} in the provided hex string: \
\"{}\"",
c, index, hex
)
}
Error::InvalidHexStringLength { length, hex } => {
write!(
fmt,
"provided hex string representation must be exactly 12 bytes, instead got: \
\"{}\", length {}",
hex, length
)
}
}
}
}
impl error::Error for Error {}
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct ObjectId {
id: [u8; 12],
}
impl Default for ObjectId {
fn default() -> Self {
Self::new()
}
}
impl FromStr for ObjectId {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Self::parse_str(s)
}
}
impl From<[u8; 12]> for ObjectId {
fn from(bytes: [u8; 12]) -> Self {
Self { id: bytes }
}
}
impl ObjectId {
pub fn new() -> ObjectId {
let timestamp = ObjectId::gen_timestamp();
let process_id = ObjectId::gen_process_id();
let counter = ObjectId::gen_count();
let mut buf: [u8; 12] = [0; 12];
buf[TIMESTAMP_OFFSET..(TIMESTAMP_SIZE + TIMESTAMP_OFFSET)]
.clone_from_slice(×tamp[..TIMESTAMP_SIZE]);
buf[PROCESS_ID_OFFSET..(PROCESS_ID_SIZE + PROCESS_ID_OFFSET)]
.clone_from_slice(&process_id[..PROCESS_ID_SIZE]);
buf[COUNTER_OFFSET..(COUNTER_SIZE + COUNTER_OFFSET)]
.clone_from_slice(&counter[..COUNTER_SIZE]);
ObjectId::from_bytes(buf)
}
pub const fn from_bytes(bytes: [u8; 12]) -> ObjectId {
ObjectId { id: bytes }
}
pub fn parse_str(s: impl AsRef<str>) -> Result<ObjectId> {
let s = s.as_ref();
let bytes: Vec<u8> = hex::decode(s.as_bytes()).map_err(|e| match e {
FromHexError::InvalidHexCharacter { c, index } => Error::InvalidHexStringCharacter {
c,
index,
hex: s.to_string(),
},
FromHexError::InvalidStringLength | FromHexError::OddLength => {
Error::InvalidHexStringLength {
length: s.len(),
hex: s.to_string(),
}
}
})?;
if bytes.len() != 12 {
Err(Error::InvalidHexStringLength {
length: s.len(),
hex: s.to_string(),
})
} else {
let mut byte_array: [u8; 12] = [0; 12];
byte_array[..].copy_from_slice(&bytes[..]);
Ok(ObjectId::from_bytes(byte_array))
}
}
pub fn timestamp(&self) -> crate::DateTime {
let mut buf = [0; 4];
buf.copy_from_slice(&self.id[0..4]);
let seconds_since_epoch = u32::from_be_bytes(buf);
crate::DateTime::from_millis(seconds_since_epoch as i64 * 1000)
}
pub const fn bytes(&self) -> [u8; 12] {
self.id
}
pub fn to_hex(self) -> String {
hex::encode(self.id)
}
fn gen_timestamp() -> [u8; 4] {
let timestamp: u32 = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("system clock is before 1970")
.as_secs()
.try_into()
.unwrap(); timestamp.to_be_bytes()
}
fn gen_process_id() -> [u8; 5] {
lazy_static! {
static ref BUF: [u8; 5] = thread_rng().gen();
}
*BUF
}
fn gen_count() -> [u8; 3] {
let u_counter = OID_COUNTER.fetch_add(1, Ordering::SeqCst);
let u = u_counter % (MAX_U24 + 1);
let u_int = u as u64;
let buf = u_int.to_be_bytes();
let buf_u24: [u8; 3] = [buf[5], buf[6], buf[7]];
buf_u24
}
}
impl fmt::Display for ObjectId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.to_hex())
}
}
impl fmt::Debug for ObjectId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ObjectId").field(&self.to_hex()).finish()
}
}
#[cfg(test)]
use crate::tests::LOCK;
#[test]
fn count_generated_is_big_endian() {
let _guard = LOCK.run_exclusively();
let start = 1_122_866;
OID_COUNTER.store(start, Ordering::SeqCst);
let count_bytes = ObjectId::gen_count();
let mut buf: [u8; 4] = [0; 4];
buf[1..=COUNTER_SIZE].clone_from_slice(&count_bytes[..COUNTER_SIZE]);
let count = u32::from_be_bytes(buf);
assert_eq!(start as u32, count);
let oid = ObjectId::new();
assert_eq!(0x11u8, oid.bytes()[COUNTER_OFFSET]);
assert_eq!(0x22u8, oid.bytes()[COUNTER_OFFSET + 1]);
assert_eq!(0x33u8, oid.bytes()[COUNTER_OFFSET + 2]);
}
#[test]
fn test_counter_overflow_u24_max() {
let _guard = LOCK.run_exclusively();
let start = MAX_U24;
OID_COUNTER.store(start, Ordering::SeqCst);
let oid = ObjectId::new();
assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET]);
assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET + 1]);
assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET + 2]);
let oid_new = ObjectId::new();
assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET]);
assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET + 1]);
assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET + 2]);
}
#[test]
fn test_counter_overflow_usize_max() {
let _guard = LOCK.run_exclusively();
let start = usize::max_value();
OID_COUNTER.store(start, Ordering::SeqCst);
let oid = ObjectId::new();
assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET]);
assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET + 1]);
assert_eq!(0xFFu8, oid.bytes()[COUNTER_OFFSET + 2]);
let oid_new = ObjectId::new();
assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET]);
assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET + 1]);
assert_eq!(0x00u8, oid_new.bytes()[COUNTER_OFFSET + 2]);
}
#[cfg(test)]
mod test {
use time::macros::datetime;
#[test]
fn test_display() {
let id = super::ObjectId::parse_str("53e37d08776f724e42000000").unwrap();
assert_eq!(format!("{}", id), "53e37d08776f724e42000000")
}
#[test]
fn test_debug() {
let id = super::ObjectId::parse_str("53e37d08776f724e42000000").unwrap();
assert_eq!(
format!("{:?}", id),
"ObjectId(\"53e37d08776f724e42000000\")"
);
assert_eq!(
format!("{:#?}", id),
"ObjectId(\n \"53e37d08776f724e42000000\",\n)"
);
}
#[test]
fn test_timestamp() {
let id = super::ObjectId::parse_str("000000000000000000000000").unwrap();
assert_eq!(datetime!(1970-01-01 0:00 UTC), id.timestamp().to_time_0_3());
let id = super::ObjectId::parse_str("7FFFFFFF0000000000000000").unwrap();
assert_eq!(
datetime!(2038-01-19 3:14:07 UTC),
id.timestamp().to_time_0_3()
);
let id = super::ObjectId::parse_str("800000000000000000000000").unwrap();
assert_eq!(
datetime!(2038-01-19 3:14:08 UTC),
id.timestamp().to_time_0_3()
);
let id = super::ObjectId::parse_str("FFFFFFFF0000000000000000").unwrap();
assert_eq!(
datetime!(2106-02-07 6:28:15 UTC),
id.timestamp().to_time_0_3()
);
}
}