UUID module, currently supporting UUID v5
parent
62dae93140
commit
ca1c08a6d7
@ -0,0 +1,356 @@
|
||||
use std::{
|
||||
error,
|
||||
fmt::{self, Write},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct HexBytes<'a, E = Lowercase> {
|
||||
inner: &'a [u8],
|
||||
_marker: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<'a, E> HexBytes<'a, E> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn new(bytes: &'a [u8]) -> Self {
|
||||
Self {
|
||||
inner: bytes,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_inner(self) -> &'a [u8] {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Encode> fmt::Debug for HexBytes<'a, E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list()
|
||||
.entries(self.inner.iter().copied().map(HexByte::<E>::new))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Encode> fmt::Display for HexBytes<'a, E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for &byte in self.inner.iter() {
|
||||
fmt::Display::fmt(&HexByte::<E>::new(byte), f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct HexByte<E> {
|
||||
inner: u8,
|
||||
_marker: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> HexByte<E> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn new(byte: u8) -> Self {
|
||||
Self {
|
||||
inner: byte,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_inner(self) -> u8 {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Encode> fmt::Debug for HexByte<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let (b0, b1) = <E as Encode>::byte_to_hex(self.inner);
|
||||
f.write_str("0x")
|
||||
.and_then(|_| f.write_char(char::from(b0)))
|
||||
.and_then(|_| f.write_char(char::from(b1)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Encode> fmt::Display for HexByte<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let (b0, b1) = <E as Encode>::byte_to_hex(self.inner);
|
||||
f.write_char(char::from(b0))
|
||||
.and_then(|_| f.write_char(char::from(b1)))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Encode {
|
||||
fn byte_to_hex(byte: u8) -> (u8, u8);
|
||||
}
|
||||
|
||||
pub struct Lowercase;
|
||||
|
||||
impl Encode for Lowercase {
|
||||
#[inline]
|
||||
fn byte_to_hex(byte: u8) -> (u8, u8) {
|
||||
byte_to_hex_lower(byte)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Uppercase;
|
||||
|
||||
impl Encode for Uppercase {
|
||||
#[inline]
|
||||
fn byte_to_hex(byte: u8) -> (u8, u8) {
|
||||
byte_to_hex_upper(byte)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the given byte to its lowercase hexadecimal representation. The first byte returned
|
||||
/// encodes the most significant 4 bits, and the second byte encodes the least significant 4 bits.
|
||||
///
|
||||
/// ```
|
||||
/// # use libshire::hex::byte_to_hex_lower;
|
||||
/// assert_eq!(byte_to_hex_lower(15), (b'0', b'f'));
|
||||
/// assert_eq!(byte_to_hex_lower(139), (b'8', b'b'));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn byte_to_hex_lower(byte: u8) -> (u8, u8) {
|
||||
(
|
||||
nybble_to_hex_lower(byte >> 4),
|
||||
nybble_to_hex_lower(byte & 0xF),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn nybble_to_hex_lower(nybble: u8) -> u8 {
|
||||
match nybble {
|
||||
0..=9 => 0x30 + nybble,
|
||||
_ => 0x57 + nybble,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn byte_to_hex_upper(byte: u8) -> (u8, u8) {
|
||||
(
|
||||
nybble_to_hex_upper(byte >> 4),
|
||||
nybble_to_hex_upper(byte & 0xF),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn nybble_to_hex_upper(nybble: u8) -> u8 {
|
||||
match nybble {
|
||||
0..=9 => 0x30 + nybble,
|
||||
_ => 0x37 + nybble,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hex_to_be_byte_array<const N: usize>(hex: &str) -> Result<[u8; N], ArrayParseError> {
|
||||
let mut iter = hex.chars().rev();
|
||||
let mut buf = [0u8; N];
|
||||
let mut bytes = 0usize;
|
||||
|
||||
while let Some(ch0) = iter.next() {
|
||||
bytes += 1;
|
||||
if bytes > N {
|
||||
return Err(ArrayParseError::TooLong(N))
|
||||
}
|
||||
|
||||
match iter.next() {
|
||||
Some(ch1) => {
|
||||
buf[N - bytes] = hex_to_byte(ch1, ch0)?;
|
||||
},
|
||||
None => {
|
||||
buf[N - bytes] = hex_to_nybble(ch0)?;
|
||||
return Ok(buf);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hex_to_byte(ch0: char, ch1: char) -> Result<u8, ParseError> {
|
||||
Ok((hex_to_nybble(ch0)? << 4) | hex_to_nybble(ch1)?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hex_to_nybble(ch: char) -> Result<u8, ParseError> {
|
||||
match ch {
|
||||
'0'..='9' => Ok((ch as u8) - 0x30),
|
||||
'A'..='F' => Ok((ch as u8) - 0x37),
|
||||
'a'..='f' => Ok((ch as u8) - 0x57),
|
||||
ch => Err(ParseError::BadChar(ch)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseError {
|
||||
BadChar(char),
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::BadChar(ch) => write!(f, "bad hex chararcter '{}'", ch),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for ParseError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ArrayParseError {
|
||||
BadHex(ParseError),
|
||||
TooLong(usize),
|
||||
}
|
||||
|
||||
impl fmt::Display for ArrayParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::BadHex(err) => fmt::Display::fmt(err, f),
|
||||
Self::TooLong(max_len) => write!(f, "hex string exceeds maximum allowed length {}", max_len),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for ArrayParseError {}
|
||||
|
||||
impl From<ParseError> for ArrayParseError {
|
||||
fn from(err: ParseError) -> Self {
|
||||
Self::BadHex(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_hex_bytes_debug() {
|
||||
assert_eq!(
|
||||
format!("{:?}", HexBytes::<Lowercase>::new(&[0x87, 0xe1, 0x8f, 0xaa, 0x88, 0x8d, 0x43, 0x4e, 0xf2, 0xb2, 0x5d, 0xe1, 0xa5, 0x1b, 0xa0, 0x94])),
|
||||
"[0x87, 0xe1, 0x8f, 0xaa, 0x88, 0x8d, 0x43, 0x4e, 0xf2, 0xb2, 0x5d, 0xe1, 0xa5, 0x1b, 0xa0, 0x94]"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
format!("{:?}", HexBytes::<Uppercase>::new(&[0x87, 0xe1, 0x8f, 0xaa, 0x88, 0x8d, 0x43, 0x4e, 0xf2, 0xb2, 0x5d, 0xe1, 0xa5, 0x1b, 0xa0, 0x94])),
|
||||
"[0x87, 0xE1, 0x8F, 0xAA, 0x88, 0x8D, 0x43, 0x4E, 0xF2, 0xB2, 0x5D, 0xE1, 0xA5, 0x1B, 0xA0, 0x94]"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hex_bytes_display() {
|
||||
assert_eq!(
|
||||
HexBytes::<Lowercase>::new(&[0x87, 0xe1, 0x8f, 0xaa, 0x88, 0x8d, 0x43, 0x4e, 0xf2, 0xb2, 0x5d, 0xe1, 0xa5, 0x1b, 0xa0, 0x94]).to_string(),
|
||||
"87e18faa888d434ef2b25de1a51ba094"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
HexBytes::<Uppercase>::new(&[0x87, 0xe1, 0x8f, 0xaa, 0x88, 0x8d, 0x43, 0x4e, 0xf2, 0xb2, 0x5d, 0xe1, 0xa5, 0x1b, 0xa0, 0x94]).to_string(),
|
||||
"87E18FAA888D434EF2B25DE1A51BA094"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nybble_to_hex_lower() {
|
||||
assert_eq!(nybble_to_hex_lower(0), b'0');
|
||||
assert_eq!(nybble_to_hex_lower(1), b'1');
|
||||
assert_eq!(nybble_to_hex_lower(2), b'2');
|
||||
assert_eq!(nybble_to_hex_lower(3), b'3');
|
||||
assert_eq!(nybble_to_hex_lower(4), b'4');
|
||||
assert_eq!(nybble_to_hex_lower(5), b'5');
|
||||
assert_eq!(nybble_to_hex_lower(6), b'6');
|
||||
assert_eq!(nybble_to_hex_lower(7), b'7');
|
||||
assert_eq!(nybble_to_hex_lower(8), b'8');
|
||||
assert_eq!(nybble_to_hex_lower(9), b'9');
|
||||
assert_eq!(nybble_to_hex_lower(10), b'a');
|
||||
assert_eq!(nybble_to_hex_lower(11), b'b');
|
||||
assert_eq!(nybble_to_hex_lower(12), b'c');
|
||||
assert_eq!(nybble_to_hex_lower(13), b'd');
|
||||
assert_eq!(nybble_to_hex_lower(14), b'e');
|
||||
assert_eq!(nybble_to_hex_lower(15), b'f');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nybble_to_hex_upper() {
|
||||
assert_eq!(nybble_to_hex_upper(0), b'0');
|
||||
assert_eq!(nybble_to_hex_upper(1), b'1');
|
||||
assert_eq!(nybble_to_hex_upper(2), b'2');
|
||||
assert_eq!(nybble_to_hex_upper(3), b'3');
|
||||
assert_eq!(nybble_to_hex_upper(4), b'4');
|
||||
assert_eq!(nybble_to_hex_upper(5), b'5');
|
||||
assert_eq!(nybble_to_hex_upper(6), b'6');
|
||||
assert_eq!(nybble_to_hex_upper(7), b'7');
|
||||
assert_eq!(nybble_to_hex_upper(8), b'8');
|
||||
assert_eq!(nybble_to_hex_upper(9), b'9');
|
||||
assert_eq!(nybble_to_hex_upper(10), b'A');
|
||||
assert_eq!(nybble_to_hex_upper(11), b'B');
|
||||
assert_eq!(nybble_to_hex_upper(12), b'C');
|
||||
assert_eq!(nybble_to_hex_upper(13), b'D');
|
||||
assert_eq!(nybble_to_hex_upper(14), b'E');
|
||||
assert_eq!(nybble_to_hex_upper(15), b'F');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hex_to_nybble() {
|
||||
assert_eq!(hex_to_nybble('0').unwrap(), 0x0);
|
||||
assert_eq!(hex_to_nybble('1').unwrap(), 0x1);
|
||||
assert_eq!(hex_to_nybble('2').unwrap(), 0x2);
|
||||
assert_eq!(hex_to_nybble('3').unwrap(), 0x3);
|
||||
assert_eq!(hex_to_nybble('4').unwrap(), 0x4);
|
||||
assert_eq!(hex_to_nybble('5').unwrap(), 0x5);
|
||||
assert_eq!(hex_to_nybble('6').unwrap(), 0x6);
|
||||
assert_eq!(hex_to_nybble('7').unwrap(), 0x7);
|
||||
assert_eq!(hex_to_nybble('8').unwrap(), 0x8);
|
||||
assert_eq!(hex_to_nybble('9').unwrap(), 0x9);
|
||||
assert_eq!(hex_to_nybble('a').unwrap(), 0xa);
|
||||
assert_eq!(hex_to_nybble('b').unwrap(), 0xb);
|
||||
assert_eq!(hex_to_nybble('c').unwrap(), 0xc);
|
||||
assert_eq!(hex_to_nybble('d').unwrap(), 0xd);
|
||||
assert_eq!(hex_to_nybble('e').unwrap(), 0xe);
|
||||
assert_eq!(hex_to_nybble('f').unwrap(), 0xf);
|
||||
assert_eq!(hex_to_nybble('A').unwrap(), 0xa);
|
||||
assert_eq!(hex_to_nybble('B').unwrap(), 0xb);
|
||||
assert_eq!(hex_to_nybble('C').unwrap(), 0xc);
|
||||
assert_eq!(hex_to_nybble('D').unwrap(), 0xd);
|
||||
assert_eq!(hex_to_nybble('E').unwrap(), 0xe);
|
||||
assert_eq!(hex_to_nybble('F').unwrap(), 0xf);
|
||||
|
||||
assert!(matches!(hex_to_nybble('g'), Err(ParseError::BadChar('g'))));
|
||||
assert!(matches!(hex_to_nybble('G'), Err(ParseError::BadChar('G'))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hex_to_be_byte_array() {
|
||||
assert_eq!(hex_to_be_byte_array("").unwrap(), []);
|
||||
assert_eq!(hex_to_be_byte_array("").unwrap(), [0x00]);
|
||||
assert_eq!(hex_to_be_byte_array("").unwrap(), [0x00, 0x00]);
|
||||
|
||||
assert_eq!(
|
||||
hex_to_be_byte_array("d90058decebf").unwrap(),
|
||||
[0xd9, 0x00, 0x58, 0xde, 0xce, 0xbf]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_to_be_byte_array("D90058DECEBF").unwrap(),
|
||||
[0xd9, 0x00, 0x58, 0xde, 0xce, 0xbf]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_to_be_byte_array("90058DECEBF").unwrap(),
|
||||
[0x09, 0x00, 0x58, 0xde, 0xce, 0xbf]
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
hex_to_be_byte_array::<5>("d90058decebf"),
|
||||
Err(ArrayParseError::TooLong(5))
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
pub mod convert;
|
||||
pub mod either;
|
||||
pub mod hex;
|
||||
pub mod strings;
|
||||
pub mod uuid;
|
||||
|
||||
@ -0,0 +1,470 @@
|
||||
use std::{error, fmt, str};
|
||||
|
||||
use crate::hex::{self, HexBytes};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct Uuid([u8; 16]);
|
||||
|
||||
// UUID ANATOMY
|
||||
//
|
||||
// Offset | Field
|
||||
// -------+---------------------------
|
||||
// 0 | time_low
|
||||
// 1 |
|
||||
// 2 |
|
||||
// 3 |
|
||||
// -------+---------------------------
|
||||
// 4 | time_mid
|
||||
// 5 |
|
||||
// -------+---------------------------
|
||||
// 6 | time_hi_and_version
|
||||
// 7 |
|
||||
// -------+---------------------------
|
||||
// 8 | clock_seq_hi_and_reserved
|
||||
// -------+---------------------------
|
||||
// 9 | clock_seq_low
|
||||
// -------+---------------------------
|
||||
// 10 | node
|
||||
// 11 |
|
||||
// 12 |
|
||||
// 13 |
|
||||
// 14 |
|
||||
// 15 |
|
||||
|
||||
impl Uuid {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
pub fn new_v5<T>(namespace: Uuid, name: &T) -> Result<Self, UuidV5Error>
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
const UUID_VERSION: u8 = 5;
|
||||
|
||||
let hash = sha1(namespace.to_bytes(), <T as AsRef<[u8]>>::as_ref(name))?;
|
||||
|
||||
let mut buf = [0u8; 16];
|
||||
buf.copy_from_slice(&hash[..16]);
|
||||
buf[6] = (buf[6] & 0xF) | (UUID_VERSION << 4);
|
||||
buf[8] = (buf[8] & 0x3F) | 0x80;
|
||||
|
||||
Ok(Self::from_bytes(buf))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn to_bytes(self) -> [u8; 16] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for Uuid {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let groups = {
|
||||
let mut groups_iter = s.split('-');
|
||||
let mut groups_buf = [""; 5];
|
||||
let mut num_groups = 0;
|
||||
for (i, group) in groups_iter.by_ref().take(5).enumerate() {
|
||||
groups_buf[i] = group;
|
||||
num_groups += 1;
|
||||
}
|
||||
if num_groups < 5 {
|
||||
return Err(ParseError::NotEnoughGroups(num_groups));
|
||||
}
|
||||
if groups_iter.next().is_some() {
|
||||
return Err(ParseError::TooManyGroups);
|
||||
}
|
||||
groups_buf
|
||||
};
|
||||
|
||||
let mut buf = [0u8; 16];
|
||||
|
||||
buf[..4].copy_from_slice(
|
||||
&hex::hex_to_be_byte_array::<4>(groups[0]).map_err(ParseError::BadTimeLow)?,
|
||||
);
|
||||
buf[4..6].copy_from_slice(
|
||||
&hex::hex_to_be_byte_array::<2>(groups[1]).map_err(ParseError::BadTimeMid)?,
|
||||
);
|
||||
buf[6..8].copy_from_slice(
|
||||
&hex::hex_to_be_byte_array::<2>(groups[2]).map_err(ParseError::BadTimeHi)?,
|
||||
);
|
||||
buf[8..10].copy_from_slice(
|
||||
&hex::hex_to_be_byte_array::<2>(groups[3]).map_err(ParseError::BadClockSeq)?,
|
||||
);
|
||||
buf[10..].copy_from_slice(
|
||||
&hex::hex_to_be_byte_array::<6>(groups[4]).map_err(ParseError::BadNode)?,
|
||||
);
|
||||
|
||||
Ok(Self::from_bytes(buf))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: UUIDs have a fixed-length string representation, so write a function which either creates
|
||||
// a string with that capacity or returns a string type with a compile-time known length.
|
||||
|
||||
impl fmt::Display for Uuid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}-{}-{}-{}-{}",
|
||||
HexBytes::<hex::Lowercase>::new(&self.0[..4]),
|
||||
HexBytes::<hex::Lowercase>::new(&self.0[4..6]),
|
||||
HexBytes::<hex::Lowercase>::new(&self.0[6..8]),
|
||||
HexBytes::<hex::Lowercase>::new(&self.0[8..10]),
|
||||
HexBytes::<hex::Lowercase>::new(&self.0[10..])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseError {
|
||||
NotEnoughGroups(usize),
|
||||
TooManyGroups,
|
||||
BadTimeLow(hex::ArrayParseError),
|
||||
BadTimeMid(hex::ArrayParseError),
|
||||
BadTimeHi(hex::ArrayParseError),
|
||||
BadClockSeq(hex::ArrayParseError),
|
||||
BadNode(hex::ArrayParseError),
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::NotEnoughGroups(groups) => {
|
||||
write!(f, "expected 5 groups of digits, found {}", groups)
|
||||
}
|
||||
Self::TooManyGroups => {
|
||||
write!(
|
||||
f,
|
||||
"found an unexpected extra group after the first 5 groups"
|
||||
)
|
||||
}
|
||||
Self::BadTimeLow(err) => {
|
||||
write!(f, "error decoding `time_low` (first) group: {}", err)
|
||||
}
|
||||
Self::BadTimeMid(err) => {
|
||||
write!(f, "error decoding `time_mid` (second) group: {}", err)
|
||||
}
|
||||
Self::BadTimeHi(err) => {
|
||||
write!(f, "error decoding `time_hi` (third) group: {}", err)
|
||||
}
|
||||
Self::BadClockSeq(err) => {
|
||||
write!(f, "error decoding `clock_seq` (fourth) group: {}", err)
|
||||
}
|
||||
Self::BadNode(err) => {
|
||||
write!(f, "error decoding `node` (fifth) group: {}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for ParseError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UuidV5Error {
|
||||
NameTooLong(usize),
|
||||
}
|
||||
|
||||
impl fmt::Display for UuidV5Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::NameTooLong(size) => write!(f, "uuid v5 name too long ({} bytes)", size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for UuidV5Error {}
|
||||
|
||||
fn sha1(namespace: [u8; 16], name: &[u8]) -> Result<[u8; 20], UuidV5Error> {
|
||||
let (mut h0, mut h1, mut h2, mut h3, mut h4) = (
|
||||
0x67452301u32,
|
||||
0xefcdab89u32,
|
||||
0x98badcfeu32,
|
||||
0x10325476u32,
|
||||
0xc3d2e1f0u32,
|
||||
);
|
||||
|
||||
// Break the namespace and name (and some additional trailing bytes) into 64-byte chunks and
|
||||
// update the state for each chunk.
|
||||
for chunk in Sha1ChunkIter::new(namespace, name)? {
|
||||
let mut words = [0u32; 80];
|
||||
|
||||
// Break the 64-byte chunk into 16 4-byte words, and store them as the first 16 values in
|
||||
// the `words` buffer.
|
||||
for (i, word) in words.iter_mut().take(16).enumerate() {
|
||||
*word = u32::from_be_bytes(chunk[(i * 4)..(i * 4 + 4)].try_into().unwrap());
|
||||
}
|
||||
|
||||
// Calculate the remaining 64 4-byte words.
|
||||
for i in 16..80 {
|
||||
words[i] = (words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16]).rotate_left(1);
|
||||
}
|
||||
|
||||
let (mut a, mut b, mut c, mut d, mut e) = (h0, h1, h2, h3, h4);
|
||||
|
||||
// 80-round main loop for the current chunk.
|
||||
for (i, word) in words.iter().copied().enumerate() {
|
||||
let (f, k) = match i {
|
||||
0..=19 => ((b & c) | (!b & d), 0x5a827999),
|
||||
20..=39 => (b ^ c ^ d, 0x6ed9eba1),
|
||||
40..=59 => ((b & c) | (b & d) | (c & d), 0x8f1bbcdc),
|
||||
_ => (b ^ c ^ d, 0xca62c1d6),
|
||||
};
|
||||
|
||||
let temp = (a.rotate_left(5))
|
||||
.wrapping_add(f)
|
||||
.wrapping_add(e)
|
||||
.wrapping_add(k)
|
||||
.wrapping_add(word);
|
||||
|
||||
e = d;
|
||||
d = c;
|
||||
c = b.rotate_left(30);
|
||||
b = a;
|
||||
a = temp;
|
||||
}
|
||||
|
||||
h0 = h0.wrapping_add(a);
|
||||
h1 = h1.wrapping_add(b);
|
||||
h2 = h2.wrapping_add(c);
|
||||
h3 = h3.wrapping_add(d);
|
||||
h4 = h4.wrapping_add(e);
|
||||
}
|
||||
|
||||
// Copy the 5 4-byte hash values into a single 20-byte array, to be returned as the final hash
|
||||
// value.
|
||||
let mut hash = [0u8; 20];
|
||||
hash[..4].copy_from_slice(&h0.to_be_bytes());
|
||||
hash[4..8].copy_from_slice(&h1.to_be_bytes());
|
||||
hash[8..12].copy_from_slice(&h2.to_be_bytes());
|
||||
hash[12..16].copy_from_slice(&h3.to_be_bytes());
|
||||
hash[16..].copy_from_slice(&h4.to_be_bytes());
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
struct Sha1ChunkIter<'a> {
|
||||
namespace: [u8; 16],
|
||||
name: &'a [u8],
|
||||
message_len_bits: u64,
|
||||
namespace_added: bool,
|
||||
one_bit_added: bool,
|
||||
message_len_added: bool,
|
||||
}
|
||||
|
||||
impl<'a> Sha1ChunkIter<'a> {
|
||||
fn new(namespace: [u8; 16], name: &'a [u8]) -> Result<Self, UuidV5Error> {
|
||||
let message_len_bits = u64::try_from(name.len())
|
||||
.ok()
|
||||
.and_then(|len| len.checked_add(16))
|
||||
.and_then(|len| len.checked_mul(8))
|
||||
.ok_or(UuidV5Error::NameTooLong(name.len()))?;
|
||||
|
||||
Ok(Self {
|
||||
namespace,
|
||||
name,
|
||||
message_len_bits,
|
||||
namespace_added: false,
|
||||
one_bit_added: false,
|
||||
message_len_added: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Sha1ChunkIter<'a> {
|
||||
type Item = [u8; 64];
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.message_len_added {
|
||||
None
|
||||
} else {
|
||||
let mut chunk = [0u8; 64];
|
||||
let mut chunk_offset = 0;
|
||||
|
||||
// If the 16-byte namespace has not already appeared in a chunk, then we need to add it
|
||||
// to the current chunk.
|
||||
if !self.namespace_added {
|
||||
// Copy the namespace into the start of the current chunk.
|
||||
chunk[..16].copy_from_slice(&self.namespace);
|
||||
|
||||
// Since the 16-byte namespace is currently the only thing in the chunk, we can
|
||||
// just set the chunk offset to 16 rather than incrementing it by 16.
|
||||
chunk_offset = 16;
|
||||
|
||||
self.namespace_added = true;
|
||||
}
|
||||
|
||||
// Check whether there are any bytes of the name remaining that have not appeared in
|
||||
// any chunk.
|
||||
if !self.name.is_empty() {
|
||||
// Calculate how many bytes of the name to add to this chunk by taking the minimum
|
||||
// of the length of the remaining part of the name and the free space in the
|
||||
// current chunk. This will always be non-zero because a maximum of 16 bytes of the
|
||||
// chunk have been used at this point (to store the namespace), so there will
|
||||
// always be at least 48 bytes available to store a portion of the name.
|
||||
let name_slice_len = self.name.len().min(64 - chunk_offset);
|
||||
|
||||
// Copy the selected portion of the name into the chunk.
|
||||
chunk[chunk_offset..(chunk_offset + name_slice_len)]
|
||||
.copy_from_slice(&self.name[..name_slice_len]);
|
||||
|
||||
// Advance the chunk offset by the number of bytes we just wrote to it.
|
||||
chunk_offset += name_slice_len;
|
||||
|
||||
// Shrink the size of the name slice so the portion of the name we just added is no
|
||||
// longer included.
|
||||
self.name = &self.name[name_slice_len..];
|
||||
}
|
||||
|
||||
// Once we've written the entire name to the chunk, we now need to add the "1" bit
|
||||
// after the name, the zero-padding and the length of the hashed message in bits. Note
|
||||
// that this is a separate `if` statement rather than an `else` statement because `if`
|
||||
// statement above may have changed the value of `self.name`.
|
||||
if self.name.is_empty() {
|
||||
// If there's space in the chunk, add the byte 0x80 to the chunk in order to add a
|
||||
// "1" bit at the end of the message, as required by SHA-1.
|
||||
if !self.one_bit_added && chunk_offset < 64 {
|
||||
chunk[chunk_offset] = 0x80;
|
||||
chunk_offset += 1;
|
||||
self.one_bit_added = true;
|
||||
}
|
||||
|
||||
// If we've already added the "1" bit and there's space in the chunk, add the
|
||||
// message length to the end of the chunk.
|
||||
if self.one_bit_added && chunk_offset < 57 {
|
||||
chunk[56..].copy_from_slice(&self.message_len_bits.to_be_bytes());
|
||||
self.message_len_added = true;
|
||||
}
|
||||
}
|
||||
|
||||
Some(chunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Uuid;
|
||||
|
||||
#[test]
|
||||
fn test_uuid_display() {
|
||||
assert_eq!(
|
||||
Uuid::from_bytes([
|
||||
0x12, 0x3e, 0x45, 0x67, 0xe8, 0x9b, 0x12, 0xd3, 0xa4, 0x56, 0x42, 0x66, 0x14, 0x17,
|
||||
0x40, 0x00
|
||||
])
|
||||
.to_string(),
|
||||
"123e4567-e89b-12d3-a456-426614174000"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uuid_parse() {
|
||||
assert_eq!(
|
||||
"123e4567-e89b-12d3-a456-426614174000"
|
||||
.parse::<Uuid>()
|
||||
.unwrap(),
|
||||
Uuid::from_bytes([
|
||||
0x12, 0x3e, 0x45, 0x67, 0xe8, 0x9b, 0x12, 0xd3, 0xa4, 0x56, 0x42, 0x66, 0x14, 0x17,
|
||||
0x40, 0x00
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uuidv5() {
|
||||
let namespace = "123e4567-e89b-12d3-a456-426614174000".parse().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Uuid::new_v5(namespace, "hello").unwrap(),
|
||||
"971690ef-3543-5938-95a4-29c6b029d731".parse().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sha1() {
|
||||
use super::sha1;
|
||||
|
||||
assert_eq!(
|
||||
sha1(*b"abcdefghijklmnop", b"").unwrap(),
|
||||
[
|
||||
0x14, 0xf3, 0x99, 0x52, 0x88, 0xac, 0xd1, 0x89, 0xe6, 0xe5, 0x0a, 0x7a, 0xf4, 0x7e,
|
||||
0xe7, 0x09, 0x9a, 0xa6, 0x82, 0xb9
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sha1(*b"abcdefghijklmnop", b"hello").unwrap(),
|
||||
[
|
||||
0x35, 0x52, 0x6e, 0x86, 0xd8, 0x66, 0x5b, 0x7e, 0x91, 0x68, 0xf9, 0x94, 0x4d, 0xff,
|
||||
0x3b, 0x5e, 0xd4, 0xb9, 0x30, 0x1c
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sha1(
|
||||
*b"abcdefghijklmnop",
|
||||
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLM"
|
||||
)
|
||||
.unwrap(),
|
||||
[
|
||||
0x4e, 0xbd, 0x2c, 0x7f, 0xff, 0xa5, 0x75, 0xe0, 0xe0, 0xc4, 0xed, 0x50, 0x7b, 0x8b,
|
||||
0x3c, 0x93, 0xdc, 0x2d, 0xad, 0x0d
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sha1(
|
||||
*b"abcdefghijklmnop",
|
||||
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN"
|
||||
)
|
||||
.unwrap(),
|
||||
[
|
||||
0xb3, 0x5b, 0x3d, 0x3d, 0xaa, 0xa5, 0x92, 0xfd, 0x15, 0x49, 0xd3, 0xa9, 0x81, 0xad,
|
||||
0x85, 0x58, 0x03, 0xce, 0x01, 0x21
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sha1(
|
||||
*b"abcdefghijklmnop",
|
||||
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU"
|
||||
)
|
||||
.unwrap(),
|
||||
[
|
||||
0xae, 0x12, 0xd0, 0xa7, 0x54, 0x8b, 0xa5, 0x99, 0x41, 0x15, 0xfa, 0x04, 0x8f, 0xe1,
|
||||
0xcd, 0x7d, 0x91, 0xd8, 0x61, 0xc7,
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sha1(
|
||||
*b"abcdefghijklmnop",
|
||||
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV"
|
||||
)
|
||||
.unwrap(),
|
||||
[
|
||||
0x72, 0xd2, 0x58, 0xb5, 0x62, 0x4a, 0x55, 0x72, 0xd7, 0x55, 0x2d, 0xd9, 0xe2, 0x11,
|
||||
0x4d, 0x4a, 0xc3, 0x8b, 0xd7, 0x61
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sha1(
|
||||
*b"abcdefghijklmnop",
|
||||
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUW"
|
||||
)
|
||||
.unwrap(),
|
||||
[
|
||||
0xa1, 0x75, 0x25, 0xb5, 0xd3, 0x15, 0x31, 0x63, 0x18, 0xf4, 0x83, 0x5c, 0x05, 0xbb,
|
||||
0xf2, 0x5d, 0x8f, 0x89, 0x55, 0x13
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue