blob: e1b1304309948819614cece402db021e0d3ef85a [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::util::{byte_to_ascii_hex_lower, parse_byte_from_ascii_str_at};
use crate::GuidFromStrError;
use core::fmt::{self, Display, Formatter};
use core::str::{self, FromStr};
#[cfg(feature = "serde")]
use {
serde::de::{self, Visitor},
serde::{Deserialize, Deserializer, Serialize, Serializer},
};
#[cfg(feature = "bytemuck")]
use bytemuck::{Pod, Zeroable};
/// Globally-unique identifier.
///
/// The format is defined in [RFC 4122]. However, unlike "normal" UUIDs
/// (such as those provided by the [`uuid`] crate), the first three
/// fields are little-endian. See also [Appendix A] of the UEFI
/// Specification.
///
/// This type is 4-byte aligned. The UEFI Specification says the GUID
/// type should be 8-byte aligned, but most C implementations have
/// 4-byte alignment, so we do the same here for compatibility.
///
/// [Appendix A]: https://uefi.org/specs/UEFI/2.10/Apx_A_GUID_and_Time_Formats.html
/// [RFC 4122]: https://datatracker.ietf.org/doc/html/rfc4122
/// [`uuid`]: https://docs.rs/uuid/latest/uuid
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[repr(C)]
pub struct Guid {
// Use `u32` rather than `[u8; 4]` here so that the natural
// alignment of the struct is four bytes. This is better for the end
// user than setting `repr(align(4))` because it doesn't prevent use
// of the type in a `repr(packed)` struct. For more discussion, see
// https://github.com/rust-lang/rfcs/pull/1358#issuecomment-217582887
time_low: u32,
time_mid: [u8; 2],
time_high_and_version: [u8; 2],
clock_seq_high_and_reserved: u8,
clock_seq_low: u8,
node: [u8; 6],
}
impl Guid {
/// GUID with all fields set to zero.
pub const ZERO: Self = Self {
time_low: 0,
time_mid: [0, 0],
time_high_and_version: [0, 0],
clock_seq_high_and_reserved: 0,
clock_seq_low: 0,
node: [0; 6],
};
/// Create a new GUID.
#[must_use]
pub const fn new(
time_low: [u8; 4],
time_mid: [u8; 2],
time_high_and_version: [u8; 2],
clock_seq_high_and_reserved: u8,
clock_seq_low: u8,
node: [u8; 6],
) -> Self {
Self {
time_low: u32::from_ne_bytes([
time_low[0],
time_low[1],
time_low[2],
time_low[3],
]),
time_mid: [time_mid[0], time_mid[1]],
time_high_and_version: [
time_high_and_version[0],
time_high_and_version[1],
],
clock_seq_high_and_reserved,
clock_seq_low,
node,
}
}
/// Create a version 4 GUID from provided random bytes.
///
/// See [RFC 4122 section 4.4][rfc] for the definition of a version
/// 4 GUID.
///
/// This constructor does not itself generate random bytes, but
/// instead expects the caller to provide suitably random bytes.
///
/// # Example
///
/// ```
/// use uguid::{Guid, Variant};
///
/// let guid = Guid::from_random_bytes([
/// 104, 192, 95, 215, 120, 33, 249, 1, 102, 21, 171, 84, 233, 204, 68, 176,
/// ]);
/// assert_eq!(guid.variant(), Variant::Rfc4122);
/// assert_eq!(guid.version(), 4);
/// ```
///
/// [rfc]: https://datatracker.ietf.org/doc/html/rfc4122#section-4.4
#[must_use]
pub const fn from_random_bytes(mut random_bytes: [u8; 16]) -> Self {
// Set the variant in byte 8: set bit 7, clear bit 6.
random_bytes[8] &= 0b1011_1111;
random_bytes[8] |= 0b1000_0000;
// Set the version in byte 7: set the most-significant-nibble to 4.
random_bytes[7] &= 0b0000_1111;
random_bytes[7] |= 0b0100_1111;
Self::from_bytes(random_bytes)
}
/// True if all bits are zero, false otherwise.
///
/// # Example
///
/// ```
/// use uguid::guid;
///
/// assert!(guid!("00000000-0000-0000-0000-000000000000").is_zero());
/// assert!(!guid!("308bbc16-a308-47e8-8977-5e5646c5291f").is_zero());
/// ```
#[must_use]
pub const fn is_zero(self) -> bool {
let b = self.to_bytes();
b[0] == 0
&& b[1] == 0
&& b[2] == 0
&& b[3] == 0
&& b[4] == 0
&& b[5] == 0
&& b[6] == 0
&& b[7] == 0
&& b[8] == 0
&& b[9] == 0
&& b[10] == 0
&& b[11] == 0
&& b[12] == 0
&& b[13] == 0
&& b[14] == 0
&& b[15] == 0
}
/// The little-endian low field of the timestamp.
#[must_use]
pub const fn time_low(self) -> [u8; 4] {
self.time_low.to_ne_bytes()
}
/// The little-endian middle field of the timestamp.
#[must_use]
pub const fn time_mid(self) -> [u8; 2] {
self.time_mid
}
/// The little-endian high field of the timestamp multiplexed with
/// the version number.
#[must_use]
pub const fn time_high_and_version(self) -> [u8; 2] {
self.time_high_and_version
}
/// The high field of the clock sequence multiplexed with the
/// variant.
#[must_use]
pub const fn clock_seq_high_and_reserved(self) -> u8 {
self.clock_seq_high_and_reserved
}
/// The low field of the clock sequence.
#[must_use]
pub const fn clock_seq_low(self) -> u8 {
self.clock_seq_low
}
/// The spatially unique node identifier.
#[must_use]
pub const fn node(self) -> [u8; 6] {
self.node
}
/// Get the GUID variant.
///
/// # Example
///
/// ```
/// use uguid::{guid, Variant};
///
/// assert_eq!(
/// guid!("308bbc16-a308-47e8-8977-5e5646c5291f").variant(),
/// Variant::Rfc4122
/// );
/// ```
#[must_use]
pub const fn variant(self) -> Variant {
// Get the 3 most significant bits of `clock_seq_high_and_reserved`.
let bits = (self.clock_seq_high_and_reserved & 0b1110_0000) >> 5;
if (bits & 0b100) == 0 {
Variant::ReservedNcs
} else if (bits & 0b010) == 0 {
Variant::Rfc4122
} else if (bits & 0b001) == 0 {
Variant::ReservedMicrosoft
} else {
Variant::ReservedFuture
}
}
/// Get the GUID version. This is a sub-type of the variant as
/// defined in [RFC4122].
///
/// [RFC4122]: https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.3
///
/// # Example
///
/// ```
/// use uguid::guid;
///
/// assert_eq!(guid!("308bbc16-a308-47e8-8977-5e5646c5291f").version(), 4);
/// ```
#[must_use]
pub const fn version(self) -> u8 {
(self.time_high_and_version[1] & 0b1111_0000) >> 4
}
/// Parse a GUID from a string.
///
/// This is functionally the same as [`Self::from_str`], but is
/// exposed separately to provide a `const` method for parsing.
pub const fn try_parse(s: &str) -> Result<Self, GuidFromStrError> {
// Treat input as ASCII.
let s = s.as_bytes();
if s.len() != 36 {
return Err(GuidFromStrError::Length);
}
let sep = b'-';
if s[8] != sep {
return Err(GuidFromStrError::Separator(8));
}
if s[13] != sep {
return Err(GuidFromStrError::Separator(13));
}
if s[18] != sep {
return Err(GuidFromStrError::Separator(18));
}
if s[23] != sep {
return Err(GuidFromStrError::Separator(23));
}
Ok(Self::from_bytes([
mtry!(parse_byte_from_ascii_str_at(s, 6)),
mtry!(parse_byte_from_ascii_str_at(s, 4)),
mtry!(parse_byte_from_ascii_str_at(s, 2)),
mtry!(parse_byte_from_ascii_str_at(s, 0)),
mtry!(parse_byte_from_ascii_str_at(s, 11)),
mtry!(parse_byte_from_ascii_str_at(s, 9)),
mtry!(parse_byte_from_ascii_str_at(s, 16)),
mtry!(parse_byte_from_ascii_str_at(s, 14)),
mtry!(parse_byte_from_ascii_str_at(s, 19)),
mtry!(parse_byte_from_ascii_str_at(s, 21)),
mtry!(parse_byte_from_ascii_str_at(s, 24)),
mtry!(parse_byte_from_ascii_str_at(s, 26)),
mtry!(parse_byte_from_ascii_str_at(s, 28)),
mtry!(parse_byte_from_ascii_str_at(s, 30)),
mtry!(parse_byte_from_ascii_str_at(s, 32)),
mtry!(parse_byte_from_ascii_str_at(s, 34)),
]))
}
/// Parse a GUID from a string, panicking on failure.
///
/// The input must be in "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
/// format, where each `x` is a hex digit (any of `0-9`, `a-f`, or
/// `A-F`).
///
/// This function is marked `track_caller` so that error messages
/// point directly to the invalid GUID string.
///
/// # Panics
///
/// This function will panic if the input is not in the format shown
/// above. In particular, it will panic if the input is not exactly
/// 36 bytes long, or if the input does not have separators at the
/// expected positions, or if any of the remaining characters are
/// not valid hex digits.
#[must_use]
#[track_caller]
pub const fn parse_or_panic(s: &str) -> Self {
match Self::try_parse(s) {
Ok(g) => g,
Err(GuidFromStrError::Length) => {
panic!("GUID string has wrong length (expected 36 bytes)");
}
Err(GuidFromStrError::Separator(_)) => {
panic!("GUID string is missing one or more separators (`-`)");
}
Err(GuidFromStrError::Hex(_)) => {
panic!("GUID string contains one or more invalid characters");
}
}
}
/// Create a GUID from a 16-byte array. No changes to byte order are made.
#[must_use]
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
Self::new(
[bytes[0], bytes[1], bytes[2], bytes[3]],
[bytes[4], bytes[5]],
[bytes[6], bytes[7]],
bytes[8],
bytes[9],
[
bytes[10], bytes[11], bytes[12], bytes[13], bytes[14],
bytes[15],
],
)
}
/// Convert to a 16-byte array.
#[must_use]
pub const fn to_bytes(self) -> [u8; 16] {
let time_low = self.time_low();
[
time_low[0],
time_low[1],
time_low[2],
time_low[3],
self.time_mid[0],
self.time_mid[1],
self.time_high_and_version[0],
self.time_high_and_version[1],
self.clock_seq_high_and_reserved,
self.clock_seq_low,
self.node[0],
self.node[1],
self.node[2],
self.node[3],
self.node[4],
self.node[5],
]
}
/// Convert to a lower-case hex ASCII string.
///
/// The output is in "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" format.
#[must_use]
pub const fn to_ascii_hex_lower(self) -> [u8; 36] {
let bytes = self.to_bytes();
let mut buf = [0; 36];
(buf[0], buf[1]) = byte_to_ascii_hex_lower(bytes[3]);
(buf[2], buf[3]) = byte_to_ascii_hex_lower(bytes[2]);
(buf[4], buf[5]) = byte_to_ascii_hex_lower(bytes[1]);
(buf[6], buf[7]) = byte_to_ascii_hex_lower(bytes[0]);
buf[8] = b'-';
(buf[9], buf[10]) = byte_to_ascii_hex_lower(bytes[5]);
(buf[11], buf[12]) = byte_to_ascii_hex_lower(bytes[4]);
buf[13] = b'-';
(buf[14], buf[15]) = byte_to_ascii_hex_lower(bytes[7]);
(buf[16], buf[17]) = byte_to_ascii_hex_lower(bytes[6]);
buf[18] = b'-';
(buf[19], buf[20]) = byte_to_ascii_hex_lower(bytes[8]);
(buf[21], buf[22]) = byte_to_ascii_hex_lower(bytes[9]);
buf[23] = b'-';
(buf[24], buf[25]) = byte_to_ascii_hex_lower(bytes[10]);
(buf[26], buf[27]) = byte_to_ascii_hex_lower(bytes[11]);
(buf[28], buf[29]) = byte_to_ascii_hex_lower(bytes[12]);
(buf[30], buf[31]) = byte_to_ascii_hex_lower(bytes[13]);
(buf[32], buf[33]) = byte_to_ascii_hex_lower(bytes[14]);
(buf[34], buf[35]) = byte_to_ascii_hex_lower(bytes[15]);
buf
}
}
impl Default for Guid {
fn default() -> Self {
Self::ZERO
}
}
impl Display for Guid {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let ascii = self.to_ascii_hex_lower();
// OK to unwrap since the ascii output is valid utf-8.
let s = str::from_utf8(&ascii).unwrap();
f.write_str(s)
}
}
impl FromStr for Guid {
type Err = GuidFromStrError;
/// Parse a GUID from a string, panicking on failure.
///
/// The input must be in "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
/// format, where each `x` is a hex digit (any of `0-9`, `a-f`, or
/// `A-F`).
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_parse(s)
}
}
#[cfg(feature = "serde")]
impl Serialize for Guid {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let ascii = self.to_ascii_hex_lower();
// OK to unwrap since the ascii output is valid utf-8.
let s = str::from_utf8(&ascii).unwrap();
serializer.serialize_str(s)
}
}
#[cfg(feature = "serde")]
struct DeserializerVisitor;
#[cfg(feature = "serde")]
impl<'de> Visitor<'de> for DeserializerVisitor {
type Value = Guid;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(
"a string in the format \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"",
)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Guid::try_parse(value).map_err(E::custom)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Guid {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(DeserializerVisitor)
}
}
/// Variant or type of GUID, as defined in [RFC4122].
///
/// [RFC4122]: https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.3
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Variant {
/// Reserved, NCS backward compatibility.
ReservedNcs,
/// The GUID variant described by RFC4122.
Rfc4122,
/// Reserved, Microsoft Corporation backward compatibility.
ReservedMicrosoft,
/// Reserved for future use.
ReservedFuture,
}