#![no_std]
#![warn(missing_docs)]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
use core::{fmt, time};
#[cfg(feature = "serde")]
mod serde;
type StrBuf = str_buf::StrBuf<[u8; 36]>;
const SEP: u8 = b'-';
#[inline(always)]
const fn byte_to_hex(byt: u8, idx: usize) -> u8 {
const BASE: usize = 4;
const BASE_DIGIT: usize = (1 << BASE) - 1;
const HEX_DIGITS: [u8; 16] = [b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f'];
HEX_DIGITS[((byt as usize) >> (BASE * idx)) & BASE_DIGIT]
}
#[inline]
fn hex_to_byte(hex: &[u8], cursor: usize, error_offset: usize) -> Result<u8, ParseError> {
let left = match hex[cursor] {
chr @ b'0'..=b'9' => chr - b'0',
chr @ b'a'..=b'f' => chr - b'a' + 10,
chr @ b'A'..=b'F' => chr - b'A' + 10,
chr => return Err(ParseError::InvalidByte(chr, cursor + error_offset)),
};
let right = match hex[cursor + 1] {
chr @ b'0'..=b'9' => chr - b'0',
chr @ b'a'..=b'f' => chr - b'a' + 10,
chr @ b'A'..=b'F' => chr - b'A' + 10,
chr => return Err(ParseError::InvalidByte(chr, cursor + 1 + error_offset)),
};
Ok(left * 16 + right)
}
pub const NAMESPACE_DNS: Uuid = Uuid::from_bytes([
0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
]);
pub const NAMESPACE_URL: Uuid = Uuid::from_bytes([
0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
]);
pub const NAMESPACE_OID: Uuid = Uuid::from_bytes([
0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
]);
pub const NAMESPACE_X500: Uuid = Uuid::from_bytes([
0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
]);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Version {
Nil = 0,
Mac,
Dce,
Md5,
Random,
Sha1,
}
#[derive(Clone, Debug, Copy)]
pub struct Timestamp {
ticks: u64,
counter: u16
}
const V1_NS_TICKS: u64 = 0x01B2_1DD2_1381_4000;
impl Timestamp {
#[inline(always)]
pub const fn from_parts(ticks: u64, counter: u16) -> Self {
Self {
ticks,
counter,
}
}
pub const fn from_unix(time: time::Duration) -> Self {
let ticks = V1_NS_TICKS + time.as_secs() * 10_000_000 + (time.subsec_nanos() as u64) / 100;
Self::from_parts(ticks, 0)
}
pub const fn set_counter(mut self, counter: u16) -> Self {
self.counter = counter;
self
}
pub const fn into_parts(self) -> (u64, u16) {
(self.ticks, self.counter)
}
}
const UUID_SIZE: usize = 16;
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Uuid {
data: [u8; UUID_SIZE]
}
impl Uuid {
#[inline]
pub const fn nil() -> Self {
Self::from_bytes([0; UUID_SIZE])
}
#[inline]
pub const fn from_bytes(data: [u8; UUID_SIZE]) -> Self {
Self { data }
}
#[inline]
pub const fn as_bytes(&self) -> &[u8] {
&self.data
}
#[inline]
pub const fn bytes(&self) -> [u8; UUID_SIZE] {
self.data
}
#[inline]
pub const fn is_version(&self, version: Version) -> bool {
(self.data[6] >> 4) == version as u8
}
#[inline]
pub const fn is_variant(&self) -> bool {
(self.data[8] & 0xc0) == 0x80
}
pub const fn v1(timestamp: Timestamp, mac: [u8; 6]) -> Self {
let time_low = (timestamp.ticks & 0xFFFF_FFFF) as u32;
let time_mid = ((timestamp.ticks >> 32) & 0xFFFF) as u16;
let time_high_and_version = (((timestamp.ticks >> 48) & 0x0FFF) as u16) | (1 << 12);
Self::from_bytes([
(time_low >> 24) as u8,
(time_low >> 16) as u8,
(time_low >> 8) as u8,
time_low as u8,
(time_mid >> 8) as u8,
time_mid as u8,
(time_high_and_version >> 8) as u8,
time_high_and_version as u8,
(((timestamp.counter & 0x3F00) >> 8) as u8) | 0x80,
(timestamp.counter & 0xFF) as u8,
mac[0],
mac[1],
mac[2],
mac[3],
mac[4],
mac[5]
])
}
#[cfg(feature = "osrng")]
pub fn v4() -> Self {
let mut bytes = [0; UUID_SIZE];
if let Err(error) = getrandom::getrandom(&mut bytes[..]) {
panic!("OS RNG is not available for use: {}", error)
}
Self::from_bytes(bytes).set_variant().set_version(Version::Random)
}
#[cfg(feature = "prng")]
#[inline]
pub fn prng() -> Self {
static RANDOM: wy::AtomicRandom = wy::AtomicRandom::new(9);
let right = u128::from(RANDOM.gen());
let left = u128::from(RANDOM.gen());
Self::from_bytes(((left << 64) | right).to_ne_bytes()).set_variant().set_version(Version::Random)
}
#[cfg(feature = "sha1")]
pub fn v5(namespace: Uuid, name: &[u8]) -> Self {
let mut sha1 = sha1::Sha1::new();
sha1.update(&namespace.data);
sha1.update(name);
let sha1 = sha1.digest().bytes();
Self::from_bytes([
sha1[0], sha1[1], sha1[2], sha1[3], sha1[4], sha1[5], sha1[6], sha1[7],
sha1[8], sha1[9], sha1[10], sha1[11], sha1[12], sha1[13], sha1[14], sha1[15],
]).set_variant().set_version(Version::Sha1)
}
#[inline]
pub const fn set_variant(mut self) -> Self {
self.data[8] = (self.data[8] & 0x3f) | 0x80;
self
}
#[inline]
pub const fn set_version(mut self, version: Version) -> Self {
self.data[6] = (self.data[6] & 0x0f) | ((version as u8) << 4);
self
}
pub fn parse_ascii_bytes(input: &[u8]) -> Result<Self, ParseError> {
if input.len() == StrBuf::capacity() {
let mut input = input.split(|byt| *byt == SEP);
let time_low = input.next().unwrap();
if time_low.len() != 8 {
return Err(ParseError::InvalidGroupLen(1, time_low.len()));
}
let time_mid = input.next().unwrap();
if time_mid.len() != 4 {
return Err(ParseError::InvalidGroupLen(2, time_mid.len()));
}
let time_hi_version = input.next().unwrap();
if time_hi_version.len() != 4 {
return Err(ParseError::InvalidGroupLen(3, time_hi_version.len()));
}
let clock_seq = input.next().unwrap();
if clock_seq.len() != 4 {
return Err(ParseError::InvalidGroupLen(4, clock_seq.len()));
}
let node = input.next().unwrap();
if node.len() != 12 {
return Err(ParseError::InvalidGroupLen(5, node.len()));
}
Ok(Self::from_bytes([
hex_to_byte(time_low, 0, 0)?,
hex_to_byte(time_low, 2, 0)?,
hex_to_byte(time_low, 4, 0)?,
hex_to_byte(time_low, 6, 0)?,
hex_to_byte(time_mid, 0, 9)?,
hex_to_byte(time_mid, 2, 9)?,
hex_to_byte(time_hi_version, 0, 14)?,
hex_to_byte(time_hi_version, 2, 14)?,
hex_to_byte(clock_seq, 0, 19)?,
hex_to_byte(clock_seq, 2, 19)?,
hex_to_byte(node, 0, 24)?,
hex_to_byte(node, 2, 24)?,
hex_to_byte(node, 4, 24)?,
hex_to_byte(node, 6, 24)?,
hex_to_byte(node, 8, 24)?,
hex_to_byte(node, 10, 24)?,
]))
} else if input.len() == StrBuf::capacity() - 4 {
Ok(Self::from_bytes([
hex_to_byte(input, 0, 0)?,
hex_to_byte(input, 2, 0)?,
hex_to_byte(input, 4, 0)?,
hex_to_byte(input, 6, 0)?,
hex_to_byte(input, 8, 0)?,
hex_to_byte(input, 10, 0)?,
hex_to_byte(input, 12, 0)?,
hex_to_byte(input, 14, 0)?,
hex_to_byte(input, 16, 0)?,
hex_to_byte(input, 18, 0)?,
hex_to_byte(input, 20, 0)?,
hex_to_byte(input, 22, 0)?,
hex_to_byte(input, 24, 0)?,
hex_to_byte(input, 26, 0)?,
hex_to_byte(input, 28, 0)?,
hex_to_byte(input, 30, 0)?,
]))
} else {
Err(ParseError::InvalidLength(input.len()))
}
}
#[inline(always)]
pub fn parse_str(input: &str) -> Result<Self, ParseError> {
Self::parse_ascii_bytes(input.as_bytes())
}
#[inline]
pub const fn to_str(&self) -> StrBuf {
let storage = [
byte_to_hex(self.data[0], 1),
byte_to_hex(self.data[0], 0),
byte_to_hex(self.data[1], 1),
byte_to_hex(self.data[1], 0),
byte_to_hex(self.data[2], 1),
byte_to_hex(self.data[2], 0),
byte_to_hex(self.data[3], 1),
byte_to_hex(self.data[3], 0),
SEP,
byte_to_hex(self.data[4], 1),
byte_to_hex(self.data[4], 0),
byte_to_hex(self.data[5], 1),
byte_to_hex(self.data[5], 0),
SEP,
byte_to_hex(self.data[6], 1),
byte_to_hex(self.data[6], 0),
byte_to_hex(self.data[7], 1),
byte_to_hex(self.data[7], 0),
SEP,
byte_to_hex(self.data[8], 1),
byte_to_hex(self.data[8], 0),
byte_to_hex(self.data[9], 1),
byte_to_hex(self.data[9], 0),
SEP,
byte_to_hex(self.data[10], 1),
byte_to_hex(self.data[10], 0),
byte_to_hex(self.data[11], 1),
byte_to_hex(self.data[11], 0),
byte_to_hex(self.data[12], 1),
byte_to_hex(self.data[12], 0),
byte_to_hex(self.data[13], 1),
byte_to_hex(self.data[13], 0),
byte_to_hex(self.data[14], 1),
byte_to_hex(self.data[14], 0),
byte_to_hex(self.data[15], 1),
byte_to_hex(self.data[15], 0),
];
unsafe {
StrBuf::from_storage(storage, StrBuf::capacity() as u8)
}
}
}
impl fmt::Display for Uuid {
#[inline(always)]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str(self.to_str().as_str())
}
}
impl Default for Uuid {
#[inline(always)]
fn default() -> Self {
Self::nil()
}
}
impl AsRef<[u8]> for Uuid {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl core::str::FromStr for Uuid {
type Err = ParseError;
#[inline(always)]
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::parse_ascii_bytes(input.as_bytes())
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ParseError {
InvalidLength(usize),
InvalidGroupLen(u8, usize),
InvalidByte(u8, usize)
}
impl fmt::Display for ParseError {
#[inline(always)]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::InvalidLength(len) => fmt.write_fmt(format_args!("Invalid length {}", len)),
ParseError::InvalidGroupLen(idx, len) => fmt.write_fmt(format_args!("Group {} has unexpected length {}", idx, len)),
ParseError::InvalidByte(byte, pos) => fmt.write_fmt(format_args!("Invalid character '{:x}' at position {}", byte, pos)),
}
}
}
#[cfg(test)]
mod tests {
use crate::byte_to_hex;
#[test]
fn should_convert_byte_to_hex() {
assert_eq!([byte_to_hex(254, 1), byte_to_hex(254, 0)], *b"fe");
assert_eq!([byte_to_hex(255, 1), byte_to_hex(255, 0)], *b"ff");
assert_eq!([byte_to_hex(1, 1), byte_to_hex(1, 0)], *b"01");
assert_eq!([byte_to_hex(15, 1), byte_to_hex(15, 0)], *b"0f");
assert_eq!([byte_to_hex(0, 1), byte_to_hex(0, 0)], *b"00");
}
}