#![warn(missing_docs)]
#![forbid(unsafe_code)]
use std::str::FromStr;
use thiserror::Error;
use zerocopy::AsBytes;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
#[repr(C)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, AsBytes, FromBytes, FromZeroes)]
#[cfg_attr(
feature = "mesh",
derive(mesh_protobuf::Protobuf),
mesh(package = "msguid")
)]
#[cfg_attr(feature = "inspect", derive(inspect::Inspect), inspect(display))]
#[allow(missing_docs)]
pub struct Guid {
#[cfg_attr(feature = "mesh", mesh(1))]
pub data1: u32,
#[cfg_attr(feature = "mesh", mesh(2))]
pub data2: u16,
#[cfg_attr(feature = "mesh", mesh(3))]
pub data3: u16,
#[cfg_attr(feature = "mesh", mesh(4))]
pub data4: [u8; 8],
}
impl Default for Guid {
fn default() -> Self {
Self::new_zeroed()
}
}
macro_rules! option_helper {
($e:expr) => {
match $e {
Some(v) => v,
None => return None,
}
};
}
macro_rules! result_helper {
($e:expr) => {
match $e {
Some(v) => v,
None => return Err(ParseError::Digit),
}
};
}
impl Guid {
pub fn new_random() -> Self {
let mut guid = Guid::default();
getrandom::getrandom(guid.as_bytes_mut()).expect("rng failure");
guid.data3 = guid.data3 & 0xfff | 0x4000;
guid.data4[0] = guid.data4[0] & 0x3f | 0x80;
guid
}
pub const fn from_static_str(value: &'static str) -> Guid {
match Self::parse(value.as_bytes()) {
Ok(guid) => guid,
Err(ParseError::Length) => panic!("Invalid GUID length."),
Err(ParseError::Format) => panic!("Invalid GUID format."),
Err(ParseError::Digit) => panic!("Invalid GUID digit."),
}
}
const fn parse(value: &[u8]) -> Result<Self, ParseError> {
let offset = if value.len() == 38 {
if value[0] != b'{' || value[37] != b'}' {
return Err(ParseError::Format);
}
1
} else if value.len() == 36 {
0
} else {
return Err(ParseError::Length);
};
if value[offset + 8] != b'-'
|| value[offset + 13] != b'-'
|| value[offset + 18] != b'-'
|| value[offset + 23] != b'-'
{
return Err(ParseError::Format);
}
Ok(Guid {
data1: result_helper!(u32_from_hex(value, offset)),
data2: result_helper!(u16_from_hex(value, offset + 9)),
data3: result_helper!(u16_from_hex(value, offset + 14)),
data4: [
result_helper!(u8_from_hex(value, offset + 19)),
result_helper!(u8_from_hex(value, offset + 21)),
result_helper!(u8_from_hex(value, offset + 24)),
result_helper!(u8_from_hex(value, offset + 26)),
result_helper!(u8_from_hex(value, offset + 28)),
result_helper!(u8_from_hex(value, offset + 30)),
result_helper!(u8_from_hex(value, offset + 32)),
result_helper!(u8_from_hex(value, offset + 34)),
],
})
}
pub const ZERO: Self = Self::from_static_str("00000000-0000-0000-0000-000000000000");
pub fn is_zero(&self) -> bool {
self == &Self::ZERO
}
}
impl std::fmt::Display for Guid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
self.data1,
self.data2,
self.data3,
self.data4[0],
self.data4[1],
self.data4[2],
self.data4[3],
self.data4[4],
self.data4[5],
self.data4[6],
self.data4[7],
)
}
}
impl std::fmt::Debug for Guid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum ParseError {
#[error("invalid GUID length")]
Length,
#[error("invalid GUID format")]
Format,
#[error("invalid GUID digit")]
Digit,
}
const fn char_to_hex(value: u8) -> Option<u8> {
Some(match value {
b'0'..=b'9' => value - b'0',
b'a'..=b'f' => 10 + value - b'a',
b'A'..=b'F' => 10 + value - b'A',
_ => return None,
})
}
const fn u8_from_hex(input: &[u8], index: usize) -> Option<u8> {
Some(
option_helper!(char_to_hex(input[index])) << 4
| option_helper!(char_to_hex(input[index + 1])),
)
}
const fn u16_from_hex(input: &[u8], index: usize) -> Option<u16> {
Some(
(option_helper!(u8_from_hex(input, index)) as u16) << 8
| (option_helper!(u8_from_hex(input, index + 2)) as u16),
)
}
const fn u32_from_hex(input: &[u8], index: usize) -> Option<u32> {
Some(
(option_helper!(u16_from_hex(input, index)) as u32) << 16
| (option_helper!(u16_from_hex(input, index + 4)) as u32),
)
}
impl FromStr for Guid {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s.as_bytes())
}
}
impl TryFrom<&[u8]> for Guid {
type Error = ParseError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Guid::parse(value)
}
}
impl From<Guid> for [u8; 16] {
fn from(value: Guid) -> Self {
value.as_bytes().try_into().unwrap()
}
}
mod windows {
#![cfg(windows)]
use super::Guid;
impl From<winapi::shared::guiddef::GUID> for Guid {
fn from(guid: winapi::shared::guiddef::GUID) -> Self {
Self {
data1: guid.Data1,
data2: guid.Data2,
data3: guid.Data3,
data4: guid.Data4,
}
}
}
impl From<Guid> for winapi::shared::guiddef::GUID {
fn from(guid: Guid) -> Self {
Self {
Data1: guid.data1,
Data2: guid.data2,
Data3: guid.data3,
Data4: guid.data4,
}
}
}
impl From<windows_sys::core::GUID> for Guid {
fn from(guid: windows_sys::core::GUID) -> Self {
Self {
data1: guid.data1,
data2: guid.data2,
data3: guid.data3,
data4: guid.data4,
}
}
}
impl From<Guid> for windows_sys::core::GUID {
fn from(guid: Guid) -> Self {
Self {
data1: guid.data1,
data2: guid.data2,
data3: guid.data3,
data4: guid.data4,
}
}
}
}
#[cfg(test)]
mod tests {
use super::Guid;
#[test]
fn test_display_guid() {
let guid = Guid {
data1: 0xcf127acc,
data2: 0xc960,
data3: 0x41e4,
data4: [0x9b, 0x1e, 0x51, 0x3e, 0x8a, 0x89, 0x14, 0x7d],
};
assert_eq!(format!("{}", &guid), "cf127acc-c960-41e4-9b1e-513e8a89147d");
}
#[test]
fn test_parse_guid() {
let guid = Guid {
data1: 0xcf127acc,
data2: 0xc960,
data3: 0x41e4,
data4: [0x9b, 0x1e, 0x51, 0x3e, 0x8a, 0x89, 0x14, 0x7d],
};
assert_eq!(
guid,
b"cf127acc-c960-41e4-9b1e-513e8a89147d"[..]
.try_into()
.expect("valid GUID")
);
assert_eq!(
guid,
b"{cf127acc-c960-41e4-9b1e-513e8a89147d}"[..]
.try_into()
.expect("valid braced GUID")
);
const TEST_GUID: Guid = Guid::from_static_str("cf127acc-c960-41e4-9b1e-513e8a89147d");
assert_eq!(guid, TEST_GUID);
const TEST_BRACED_GUID: Guid =
Guid::from_static_str("{cf127acc-c960-41e4-9b1e-513e8a89147d}");
assert_eq!(guid, TEST_BRACED_GUID);
}
}