From ca1c08a6d7c28cd99f2bb78e2a738b614c38bac3 Mon Sep 17 00:00:00 2001 From: Pantonshire Date: Wed, 25 May 2022 14:14:27 +0100 Subject: [PATCH] UUID module, currently supporting UUID v5 --- src/hex.rs | 356 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/uuid.rs | 470 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 828 insertions(+) create mode 100644 src/hex.rs create mode 100644 src/uuid.rs diff --git a/src/hex.rs b/src/hex.rs new file mode 100644 index 0000000..4717730 --- /dev/null +++ b/src/hex.rs @@ -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, +} + +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::::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::::new(byte), f)?; + } + Ok(()) + } +} + +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct HexByte { + inner: u8, + _marker: PhantomData, +} + +impl HexByte { + #[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 fmt::Debug for HexByte { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (b0, b1) = ::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 fmt::Display for HexByte { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (b0, b1) = ::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(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 { + Ok((hex_to_nybble(ch0)? << 4) | hex_to_nybble(ch1)?) +} + +#[inline] +fn hex_to_nybble(ch: char) -> Result { + 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 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::::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::::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::::new(&[0x87, 0xe1, 0x8f, 0xaa, 0x88, 0x8d, 0x43, 0x4e, 0xf2, 0xb2, 0x5d, 0xe1, 0xa5, 0x1b, 0xa0, 0x94]).to_string(), + "87e18faa888d434ef2b25de1a51ba094" + ); + + assert_eq!( + HexBytes::::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)) + )); + } +} diff --git a/src/lib.rs b/src/lib.rs index 60d0467..c2eec97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ pub mod convert; pub mod either; +pub mod hex; pub mod strings; +pub mod uuid; diff --git a/src/uuid.rs b/src/uuid.rs new file mode 100644 index 0000000..374601e --- /dev/null +++ b/src/uuid.rs @@ -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(namespace: Uuid, name: &T) -> Result + where + T: AsRef<[u8]> + ?Sized, + { + const UUID_VERSION: u8 = 5; + + let hash = sha1(namespace.to_bytes(), >::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 { + 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::::new(&self.0[..4]), + HexBytes::::new(&self.0[4..6]), + HexBytes::::new(&self.0[6..8]), + HexBytes::::new(&self.0[8..10]), + HexBytes::::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 { + 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 { + 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::() + .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 + ] + ); + } +}