Implement UUID CRC32 and byte order compatibility
This commit is contained in:
@@ -9,3 +9,8 @@ description = "Core value-type shims for the MetaCrate LibreMetaverse rewrite"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
getrandom = "0.4"
|
||||
md-5 = "0.10"
|
||||
uuid = { version = "1.18", features = ["v4"] }
|
||||
|
||||
91
crates/libremetaverse-types/src/byte_order.rs
Normal file
91
crates/libremetaverse-types/src/byte_order.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
//! Platform-independent byte-order helpers owned by `Utils`.
|
||||
|
||||
use crate::Error;
|
||||
|
||||
fn checked_range(pos: i32, width: usize, len: usize) -> Result<std::ops::Range<usize>, Error> {
|
||||
let start = usize::try_from(pos).map_err(|_| Error::IndexOutOfRange)?;
|
||||
let end = start.checked_add(width).ok_or(Error::IndexOutOfRange)?;
|
||||
if end > len {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
Ok(start..end)
|
||||
}
|
||||
|
||||
pub(crate) fn read_single_little_endian(src: &[u8], pos: i32) -> Result<f32, Error> {
|
||||
let range = checked_range(pos, 4, src.len())?;
|
||||
let bytes: [u8; 4] = src[range]
|
||||
.try_into()
|
||||
.expect("range has the requested width");
|
||||
Ok(f32::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub(crate) fn write_single_little_endian(
|
||||
dest: &mut [u8],
|
||||
pos: i32,
|
||||
value: f32,
|
||||
) -> Result<(), Error> {
|
||||
let range = checked_range(pos, 4, dest.len())?;
|
||||
dest[range].copy_from_slice(&value.to_le_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn read_double_little_endian(src: &[u8], pos: i32) -> Result<f64, Error> {
|
||||
let range = checked_range(pos, 8, src.len())?;
|
||||
let bytes: [u8; 8] = src[range]
|
||||
.try_into()
|
||||
.expect("range has the requested width");
|
||||
Ok(f64::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub(crate) fn write_double_little_endian(
|
||||
dest: &mut [u8],
|
||||
pos: i32,
|
||||
value: f64,
|
||||
) -> Result<(), Error> {
|
||||
let range = checked_range(pos, 8, dest.len())?;
|
||||
dest[range].copy_from_slice(&value.to_le_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::float_cmp)] // Exact bit-preserving round trips are the behavior under test.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn little_endian_float_golden_vectors_round_trip_at_offsets() {
|
||||
let mut bytes = [0xaa; 14];
|
||||
write_single_little_endian(&mut bytes, 1, -12.5).unwrap();
|
||||
assert_eq!(&bytes[1..5], &(-12.5_f32).to_bits().to_le_bytes());
|
||||
assert_eq!(
|
||||
read_single_little_endian(&bytes, 1).unwrap().to_bits(),
|
||||
(-12.5_f32).to_bits()
|
||||
);
|
||||
|
||||
write_double_little_endian(&mut bytes, 5, std::f64::consts::PI).unwrap();
|
||||
assert_eq!(&bytes[5..13], &std::f64::consts::PI.to_bits().to_le_bytes());
|
||||
assert_eq!(
|
||||
read_double_little_endian(&bytes, 5).unwrap().to_bits(),
|
||||
std::f64::consts::PI.to_bits()
|
||||
);
|
||||
assert_eq!(bytes[0], 0xaa);
|
||||
assert_eq!(bytes[13], 0xaa);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_order_helpers_reject_invalid_ranges() {
|
||||
assert_eq!(
|
||||
read_single_little_endian(&[0; 4], -1),
|
||||
Err(Error::IndexOutOfRange)
|
||||
);
|
||||
assert_eq!(
|
||||
read_double_little_endian(&[0; 7], 0),
|
||||
Err(Error::IndexOutOfRange)
|
||||
);
|
||||
assert_eq!(
|
||||
write_single_little_endian(&mut [0; 4], 1, 0.0),
|
||||
Err(Error::IndexOutOfRange)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ impl<T: std::io::Read + std::io::Write> ReadWrite for T {}
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Object {
|
||||
String(String),
|
||||
UUID(crate::UUID),
|
||||
}
|
||||
|
||||
impl From<String> for Object {
|
||||
|
||||
119
crates/libremetaverse-types/src/crc32.rs
Normal file
119
crates/libremetaverse-types/src/crc32.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
//! Native implementation of `LibreMetaverse`'s incremental CRC-32 accumulator.
|
||||
|
||||
#![allow(clippy::needless_pass_by_value)] // Public signature mirrors the mapped C# byte array.
|
||||
|
||||
use crate::Error;
|
||||
|
||||
/// Incremental reflected CRC-32 accumulator using polynomial `0xedb88320`.
|
||||
///
|
||||
/// `LibreMetaverse` exposes the running (not final-XORed) accumulator. A new
|
||||
/// value therefore starts at `0xffff_ffff`, and callers may apply a final XOR
|
||||
/// themselves if they need the conventional ISO-HDLC checksum.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct CRC32 {
|
||||
/// Current running CRC value.
|
||||
pub crc: u32,
|
||||
}
|
||||
|
||||
impl CRC32 {
|
||||
/// Creates an accumulator initialized to `0xffff_ffff`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This compatibility signature is infallible and always returns `Ok`.
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
Ok(Self { crc: u32::MAX })
|
||||
}
|
||||
|
||||
/// Incorporates one byte into the running accumulator.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This compatibility signature is infallible and always returns `Ok`.
|
||||
pub fn update_with_byte(&mut self, value: u8) -> Result<(), Error> {
|
||||
self.crc = update(self.crc, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates bytes in the half-open range `pos..length`.
|
||||
///
|
||||
/// The final argument intentionally preserves the pinned C# implementation:
|
||||
/// it is an exclusive end index, despite being named `length`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Error::IndexOutOfRange`] when an iterated index is outside
|
||||
/// `value`. As in C#, bytes before the invalid index remain incorporated.
|
||||
pub fn update_with_bytes_int32_int32(
|
||||
&mut self,
|
||||
value: Vec<u8>,
|
||||
pos: i32,
|
||||
length: i32,
|
||||
) -> Result<(), Error> {
|
||||
if pos >= length {
|
||||
return Ok(());
|
||||
}
|
||||
for index in pos..length {
|
||||
let index = usize::try_from(index).map_err(|_| Error::IndexOutOfRange)?;
|
||||
let byte = *value.get(index).ok_or(Error::IndexOutOfRange)?;
|
||||
self.crc = update(self.crc, byte);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn update(mut crc: u32, byte: u8) -> u32 {
|
||||
crc ^= u32::from(byte);
|
||||
for _ in 0..8 {
|
||||
let mask = 0_u32.wrapping_sub(crc & 1);
|
||||
crc = (crc >> 1) ^ (0xedb8_8320 & mask);
|
||||
}
|
||||
crc
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn crc32_matches_standard_and_incremental_golden_vectors() {
|
||||
let mut crc = CRC32::new().unwrap();
|
||||
crc.update_with_bytes_int32_int32(b"123456789".to_vec(), 0, 9)
|
||||
.unwrap();
|
||||
assert_eq!(crc.crc, 0x340b_c6d9);
|
||||
assert_eq!(crc.crc ^ u32::MAX, 0xcbf4_3926);
|
||||
|
||||
let mut incremental = CRC32::new().unwrap();
|
||||
for byte in b"123456789" {
|
||||
incremental.update_with_byte(*byte).unwrap();
|
||||
}
|
||||
assert_eq!(incremental, crc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_argument_is_an_exclusive_end_index() {
|
||||
let mut selected = CRC32::new().unwrap();
|
||||
selected
|
||||
.update_with_bytes_int32_int32(b"x123y".to_vec(), 1, 4)
|
||||
.unwrap();
|
||||
let mut direct = CRC32::new().unwrap();
|
||||
direct
|
||||
.update_with_bytes_int32_int32(b"123".to_vec(), 0, 3)
|
||||
.unwrap();
|
||||
assert_eq!(selected, direct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_end_index_preserves_csharp_partial_update_behavior() {
|
||||
let mut partial = CRC32::new().unwrap();
|
||||
assert_eq!(
|
||||
partial.update_with_bytes_int32_int32(b"12".to_vec(), 0, 3),
|
||||
Err(Error::IndexOutOfRange)
|
||||
);
|
||||
let mut expected = CRC32::new().unwrap();
|
||||
expected
|
||||
.update_with_bytes_int32_int32(b"12".to_vec(), 0, 2)
|
||||
.unwrap();
|
||||
assert_eq!(partial, expected);
|
||||
}
|
||||
}
|
||||
@@ -184,31 +184,11 @@ pub enum AttachmentPoint {
|
||||
}
|
||||
|
||||
/// C# type: `T:LibreMetaverse.CRC32`.
|
||||
pub struct CRC32 {
|
||||
/// C# member: `F:LibreMetaverse.CRC32.CRC`.
|
||||
pub crc: u32,
|
||||
}
|
||||
impl CRC32 {
|
||||
/// C# member: `M:LibreMetaverse.CRC32.#ctor`.
|
||||
pub fn new() -> Result<Self, crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.CRC32.#ctor")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.CRC32.Update(System.Byte)`.
|
||||
pub fn update_with_byte(&self, value: u8) -> Result<(), crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.CRC32.Update(System.Byte)")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.CRC32.Update(System.Byte[],System.Int32,System.Int32)`.
|
||||
pub fn update_with_bytes_int32_int32(
|
||||
&self,
|
||||
value: Vec<u8>,
|
||||
pos: i32,
|
||||
length: i32,
|
||||
) -> Result<(), crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.CRC32.Update(System.Byte[],System.Int32,System.Int32)",
|
||||
)
|
||||
}
|
||||
}
|
||||
/// C# member: `F:LibreMetaverse.CRC32.CRC`.
|
||||
/// C# member: `M:LibreMetaverse.CRC32.#ctor`.
|
||||
/// C# member: `M:LibreMetaverse.CRC32.Update(System.Byte)`.
|
||||
/// C# member: `M:LibreMetaverse.CRC32.Update(System.Byte[],System.Int32,System.Int32)`.
|
||||
pub use crate::crc32::CRC32;
|
||||
|
||||
/// C# type: `T:LibreMetaverse.CacheDictionary`2`.
|
||||
pub struct CacheDictionary<TKey, TValue> {
|
||||
@@ -2658,154 +2638,33 @@ impl TokenBucket {
|
||||
}
|
||||
|
||||
/// C# type: `T:LibreMetaverse.UUID`.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
pub struct UUID {
|
||||
bytes: [u8; 16],
|
||||
}
|
||||
impl UUID {
|
||||
/// C# member: `F:LibreMetaverse.UUID.Zero`.
|
||||
pub fn zero() -> libremetaverse_types::UUID {
|
||||
libremetaverse_types::unimplemented_api!("F:LibreMetaverse.UUID.Zero")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.#ctor(System.Byte[],System.Int32)`.
|
||||
pub fn new_with_bytes_int32(source: Vec<u8>, pos: i32) -> Result<Self, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.UUID.#ctor(System.Byte[],System.Int32)",
|
||||
)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.#ctor(System.Guid)`.
|
||||
pub fn new_with_guid(guid: libremetaverse_types::compat::Guid) -> Result<Self, crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.#ctor(System.Guid)")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.#ctor(System.String)`.
|
||||
pub fn new_with_string(val: String) -> Result<Self, crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.#ctor(System.String)")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.#ctor(System.UInt64)`.
|
||||
pub fn new_with_u_int64(val: u64) -> Result<Self, crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.#ctor(System.UInt64)")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.CRC`.
|
||||
pub fn crc(&mut self) -> Result<u32, crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.CRC")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.Combine(LibreMetaverse.UUID,LibreMetaverse.UUID)`.
|
||||
pub fn combine(
|
||||
first: libremetaverse_types::UUID,
|
||||
second: libremetaverse_types::UUID,
|
||||
) -> Result<libremetaverse_types::UUID, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.UUID.Combine(LibreMetaverse.UUID,LibreMetaverse.UUID)",
|
||||
)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.CompareTo(LibreMetaverse.UUID)`.
|
||||
pub fn compare_to(&self, id: libremetaverse_types::UUID) -> Result<i32, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.UUID.CompareTo(LibreMetaverse.UUID)",
|
||||
)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.Deconstruct(System.Guid@)`.
|
||||
pub fn deconstruct(
|
||||
&mut self,
|
||||
guid: &mut libremetaverse_types::compat::Guid,
|
||||
) -> Result<(), crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.Deconstruct(System.Guid@)")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.Equals(LibreMetaverse.UUID)`.
|
||||
pub fn equals_with_uuid(&self, other: libremetaverse_types::UUID) -> bool {
|
||||
libremetaverse_types::unimplemented_api!(
|
||||
"M:LibreMetaverse.UUID.Equals(LibreMetaverse.UUID)"
|
||||
)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.Equals(System.Object)`.
|
||||
pub fn equals_with_object(&self, obj: libremetaverse_types::compat::Object) -> bool {
|
||||
libremetaverse_types::unimplemented_api!("M:LibreMetaverse.UUID.Equals(System.Object)")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.FromBytes(System.Byte[],System.Int32)`.
|
||||
pub fn from_bytes(
|
||||
source: Vec<u8>,
|
||||
pos: i32,
|
||||
) -> Result<libremetaverse_types::UUID, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.UUID.FromBytes(System.Byte[],System.Int32)",
|
||||
)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.GetBytes`.
|
||||
pub fn get_bytes(&self) -> Result<Vec<u8>, crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.GetBytes")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.GetHashCode`.
|
||||
pub fn get_hash_code(&self) -> i32 {
|
||||
libremetaverse_types::unimplemented_api!("M:LibreMetaverse.UUID.GetHashCode")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.GetULong`.
|
||||
pub fn get_u_long(&self) -> Result<u64, crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.GetULong")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.Parse(System.String)`.
|
||||
pub fn parse(val: String) -> Result<libremetaverse_types::UUID, crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.Parse(System.String)")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.Random`.
|
||||
pub fn random() -> Result<libremetaverse_types::UUID, crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.Random")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.SecureRandom`.
|
||||
pub fn secure_random() -> Result<libremetaverse_types::UUID, crate::Error> {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.SecureRandom")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.ToBytes(System.Byte[],System.Int32)`.
|
||||
pub fn to_bytes(&self, dest: Vec<u8>, pos: i32) -> Result<(), crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.UUID.ToBytes(System.Byte[],System.Int32)",
|
||||
)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.ToString`.
|
||||
pub fn to_string(&self) -> String {
|
||||
libremetaverse_types::unimplemented_api!("M:LibreMetaverse.UUID.ToString")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.TryParse(System.String,LibreMetaverse.UUID@)`.
|
||||
pub fn try_parse(val: String, result: &mut libremetaverse_types::UUID) -> bool {
|
||||
libremetaverse_types::unimplemented_api!(
|
||||
"M:LibreMetaverse.UUID.TryParse(System.String,LibreMetaverse.UUID@)"
|
||||
)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.op_Equality(LibreMetaverse.UUID,LibreMetaverse.UUID)`.
|
||||
pub fn eq(left: libremetaverse_types::UUID, right: libremetaverse_types::UUID) -> bool {
|
||||
libremetaverse_types::unimplemented_api!(
|
||||
"M:LibreMetaverse.UUID.op_Equality(LibreMetaverse.UUID,LibreMetaverse.UUID)"
|
||||
)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.op_ExclusiveOr(LibreMetaverse.UUID,LibreMetaverse.UUID)`.
|
||||
pub fn bitxor(
|
||||
lhs: libremetaverse_types::UUID,
|
||||
rhs: libremetaverse_types::UUID,
|
||||
) -> libremetaverse_types::UUID {
|
||||
libremetaverse_types::unimplemented_api!(
|
||||
"M:LibreMetaverse.UUID.op_ExclusiveOr(LibreMetaverse.UUID,LibreMetaverse.UUID)"
|
||||
)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.op_Explicit(System.String)~LibreMetaverse.UUID`.
|
||||
pub fn try_from(val: String) -> libremetaverse_types::UUID {
|
||||
libremetaverse_types::unimplemented_api!(
|
||||
"M:LibreMetaverse.UUID.op_Explicit(System.String)~LibreMetaverse.UUID"
|
||||
)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.UUID.op_Inequality(LibreMetaverse.UUID,LibreMetaverse.UUID)`.
|
||||
pub fn ne(left: libremetaverse_types::UUID, right: libremetaverse_types::UUID) -> bool {
|
||||
libremetaverse_types::unimplemented_api!(
|
||||
"M:LibreMetaverse.UUID.op_Inequality(LibreMetaverse.UUID,LibreMetaverse.UUID)"
|
||||
)
|
||||
}
|
||||
/// C# member: `P:LibreMetaverse.UUID.Guid`.
|
||||
pub fn guid(&self) -> libremetaverse_types::compat::Guid {
|
||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.UUID.Guid")
|
||||
}
|
||||
/// Setter for C# member: `P:LibreMetaverse.UUID.Guid`.
|
||||
pub fn set_guid(&mut self, value: libremetaverse_types::compat::Guid) {
|
||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.UUID.Guid")
|
||||
}
|
||||
}
|
||||
/// C# member: `F:LibreMetaverse.UUID.Zero`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.#ctor(System.Byte[],System.Int32)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.#ctor(System.Guid)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.#ctor(System.String)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.#ctor(System.UInt64)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.CRC`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.Combine(LibreMetaverse.UUID,LibreMetaverse.UUID)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.CompareTo(LibreMetaverse.UUID)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.Deconstruct(System.Guid@)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.Equals(LibreMetaverse.UUID)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.Equals(System.Object)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.FromBytes(System.Byte[],System.Int32)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.GetBytes`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.GetHashCode`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.GetULong`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.Parse(System.String)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.Random`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.SecureRandom`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.ToBytes(System.Byte[],System.Int32)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.ToString`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.TryParse(System.String,LibreMetaverse.UUID@)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.op_Equality(LibreMetaverse.UUID,LibreMetaverse.UUID)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.op_ExclusiveOr(LibreMetaverse.UUID,LibreMetaverse.UUID)`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.op_Explicit(System.String)~LibreMetaverse.UUID`.
|
||||
/// C# member: `M:LibreMetaverse.UUID.op_Inequality(LibreMetaverse.UUID,LibreMetaverse.UUID)`.
|
||||
/// C# member: `P:LibreMetaverse.UUID.Guid`.
|
||||
pub use crate::uuid::UUID;
|
||||
|
||||
/// C# type: `T:LibreMetaverse.Utils`.
|
||||
pub struct Utils;
|
||||
@@ -3252,16 +3111,12 @@ impl Utils {
|
||||
libremetaverse_types::not_implemented("M:LibreMetaverse.Utils.RandomDouble")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.Utils.ReadDoubleLittleEndian(System.Byte[],System.Int32)`.
|
||||
pub fn read_double_little_endian(src: Vec<u8>, pos: i32) -> Result<f64, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.Utils.ReadDoubleLittleEndian(System.Byte[],System.Int32)",
|
||||
)
|
||||
pub fn read_double_little_endian(src: &[u8], pos: i32) -> Result<f64, crate::Error> {
|
||||
crate::byte_order::read_double_little_endian(src, pos)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32)`.
|
||||
pub fn read_single_little_endian(src: Vec<u8>, pos: i32) -> Result<f32, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32)",
|
||||
)
|
||||
pub fn read_single_little_endian(src: &[u8], pos: i32) -> Result<f32, crate::Error> {
|
||||
crate::byte_order::read_single_little_endian(src, pos)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.Utils.Round(System.Single)`.
|
||||
pub fn round(val: f32) -> Result<i32, crate::Error> {
|
||||
@@ -3510,23 +3365,19 @@ impl Utils {
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.Utils.WriteDoubleLittleEndian(System.Byte[],System.Int32,System.Double)`.
|
||||
pub fn write_double_little_endian(
|
||||
dest: Vec<u8>,
|
||||
dest: &mut [u8],
|
||||
pos: i32,
|
||||
value: f64,
|
||||
) -> Result<(), crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.Utils.WriteDoubleLittleEndian(System.Byte[],System.Int32,System.Double)",
|
||||
)
|
||||
crate::byte_order::write_double_little_endian(dest, pos, value)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single)`.
|
||||
pub fn write_single_little_endian(
|
||||
dest: Vec<u8>,
|
||||
dest: &mut [u8],
|
||||
pos: i32,
|
||||
value: f32,
|
||||
) -> Result<(), crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single)",
|
||||
)
|
||||
crate::byte_order::write_single_little_endian(dest, pos, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
extern crate self as libremetaverse_types;
|
||||
|
||||
mod byte_order;
|
||||
pub mod compat;
|
||||
mod crc32;
|
||||
mod generated;
|
||||
pub mod shim;
|
||||
mod uuid;
|
||||
|
||||
pub use generated::*;
|
||||
pub use shim::{Error, NotImplemented, not_implemented};
|
||||
|
||||
448
crates/libremetaverse-types/src/uuid.rs
Normal file
448
crates/libremetaverse-types/src/uuid.rs
Normal file
@@ -0,0 +1,448 @@
|
||||
//! Native, byte-order-explicit UUID compatibility type.
|
||||
|
||||
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the public compatibility map.
|
||||
#![allow(clippy::needless_pass_by_value)] // Owned arguments mirror mapped C# value parameters.
|
||||
|
||||
use crate::Error;
|
||||
use crate::compat::{Guid, Object};
|
||||
use md5::{Digest, Md5};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
|
||||
/// A 128-bit UUID stored in RFC/network byte order.
|
||||
///
|
||||
/// `System.Guid` uses a mixed-endian byte-array representation for its first
|
||||
/// three fields. Conversions to and from [`Guid`] explicitly perform that
|
||||
/// permutation; all protocol-facing byte APIs remain unambiguously big-endian.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct UUID(uuid::Uuid);
|
||||
|
||||
impl UUID {
|
||||
#[must_use]
|
||||
pub const fn zero() -> Self {
|
||||
Self(uuid::Uuid::nil())
|
||||
}
|
||||
|
||||
pub fn new_with_bytes_int32(source: Vec<u8>, pos: i32) -> Result<Self, Error> {
|
||||
Self::from_slice_at(&source, pos)
|
||||
}
|
||||
|
||||
pub fn new_with_guid(guid: Guid) -> Result<Self, Error> {
|
||||
Ok(Self(uuid::Uuid::from_bytes(guid_raw_to_network(guid.0))))
|
||||
}
|
||||
|
||||
pub fn new_with_string(val: String) -> Result<Self, Error> {
|
||||
if val.is_empty() {
|
||||
return Ok(Self::zero());
|
||||
}
|
||||
parse_guid_text(&val).map(Self).ok_or(Error::Argument)
|
||||
}
|
||||
|
||||
pub fn new_with_u_int64(val: u64) -> Result<Self, Error> {
|
||||
let mut bytes = [0_u8; 16];
|
||||
bytes[8..].copy_from_slice(&val.to_le_bytes());
|
||||
Ok(Self(uuid::Uuid::from_bytes(bytes)))
|
||||
}
|
||||
|
||||
pub fn crc(&mut self) -> Result<u32, Error> {
|
||||
let bytes = self.0.as_bytes();
|
||||
Ok((0..4)
|
||||
.map(|index| {
|
||||
let offset = index * 4;
|
||||
u32::from_be_bytes([
|
||||
bytes[offset],
|
||||
bytes[offset + 1],
|
||||
bytes[offset + 2],
|
||||
bytes[offset + 3],
|
||||
])
|
||||
})
|
||||
.fold(0_u32, u32::wrapping_add))
|
||||
}
|
||||
|
||||
pub fn combine(first: Self, second: Self) -> Result<Self, Error> {
|
||||
let mut digest = Md5::new();
|
||||
digest.update(first.0.as_bytes());
|
||||
digest.update(second.0.as_bytes());
|
||||
let bytes: [u8; 16] = digest.finalize().into();
|
||||
Ok(Self(uuid::Uuid::from_bytes(bytes)))
|
||||
}
|
||||
|
||||
pub fn compare_to(&self, id: Self) -> Result<i32, Error> {
|
||||
Ok(match self.cmp(&id) {
|
||||
Ordering::Less => -1,
|
||||
Ordering::Equal => 0,
|
||||
Ordering::Greater => 1,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deconstruct(&mut self, guid: &mut Guid) -> Result<(), Error> {
|
||||
*guid = self.guid();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn equals_with_uuid(&self, other: Self) -> bool {
|
||||
*self == other
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn equals_with_object(&self, obj: Object) -> bool {
|
||||
matches!(obj, Object::UUID(value) if *self == value)
|
||||
}
|
||||
|
||||
pub fn from_bytes(source: Vec<u8>, pos: i32) -> Result<Self, Error> {
|
||||
Self::from_slice_at(&source, pos)
|
||||
}
|
||||
|
||||
pub fn get_bytes(&self) -> Result<Vec<u8>, Error> {
|
||||
Ok(self.0.as_bytes().to_vec())
|
||||
}
|
||||
|
||||
/// Returns the hash produced by `System.Guid.GetHashCode` for the wrapped
|
||||
/// Guid value. This is deliberately distinct from Rust's randomized
|
||||
/// [`Hash`](std::hash::Hash) use in collections.
|
||||
#[must_use]
|
||||
pub fn get_hash_code(&self) -> i32 {
|
||||
let raw = network_to_guid_raw(*self.0.as_bytes());
|
||||
let a = i32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]);
|
||||
let b = i32::from(i16::from_le_bytes([raw[4], raw[5]]));
|
||||
let c = u16::from_le_bytes([raw[6], raw[7]]);
|
||||
a ^ ((b << 16) | i32::from(c)) ^ ((i32::from(raw[10])) << 24 | i32::from(raw[15]))
|
||||
}
|
||||
|
||||
pub fn get_u_long(&self) -> Result<u64, Error> {
|
||||
let bytes = self.0.as_bytes();
|
||||
Ok(u64::from_le_bytes([
|
||||
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
|
||||
]))
|
||||
}
|
||||
|
||||
pub fn parse(val: String) -> Result<Self, Error> {
|
||||
Self::new_with_string(val)
|
||||
}
|
||||
|
||||
pub fn random() -> Result<Self, Error> {
|
||||
Ok(Self(uuid::Uuid::new_v4()))
|
||||
}
|
||||
|
||||
pub fn secure_random() -> Result<Self, Error> {
|
||||
// Match the pinned implementation exactly: random bytes are first
|
||||
// interpreted in Guid.ToByteArray layout, then its raw indices 6 and 8
|
||||
// receive the version/variant masks.
|
||||
let mut raw = [0_u8; 16];
|
||||
getrandom::fill(&mut raw).map_err(|_| Error::InvalidOperation)?;
|
||||
raw[6] = (raw[6] & 0x0f) | 0x40;
|
||||
raw[8] = (raw[8] & 0x3f) | 0x80;
|
||||
Ok(Self(uuid::Uuid::from_bytes(guid_raw_to_network(raw))))
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self, dest: &mut [u8], pos: i32) -> Result<(), Error> {
|
||||
let start = usize::try_from(pos).map_err(|_| Error::IndexOutOfRange)?;
|
||||
let end = start.checked_add(16).ok_or(Error::IndexOutOfRange)?;
|
||||
let target = dest.get_mut(start..end).ok_or(Error::IndexOutOfRange)?;
|
||||
target.copy_from_slice(self.0.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::inherent_to_string_shadow_display)] // Required mapped C# member.
|
||||
pub fn to_string(&self) -> String {
|
||||
self.0.hyphenated().to_string()
|
||||
}
|
||||
|
||||
pub fn try_parse(val: String, result: &mut Self) -> bool {
|
||||
if val.is_empty() {
|
||||
*result = Self::zero();
|
||||
return false;
|
||||
}
|
||||
if let Some(value) = parse_guid_text(&val) {
|
||||
*result = Self(value);
|
||||
true
|
||||
} else {
|
||||
*result = Self::zero();
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::should_implement_trait)] // Required mapped operator name.
|
||||
pub fn eq(left: Self, right: Self) -> bool {
|
||||
left == right
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::should_implement_trait)] // Required mapped operator name.
|
||||
pub fn bitxor(lhs: Self, rhs: Self) -> Self {
|
||||
let mut bytes = [0_u8; 16];
|
||||
for ((output, left), right) in bytes.iter_mut().zip(lhs.0.as_bytes()).zip(rhs.0.as_bytes())
|
||||
{
|
||||
*output = left ^ right;
|
||||
}
|
||||
Self(uuid::Uuid::from_bytes(bytes))
|
||||
}
|
||||
|
||||
/// Preserves the mapped explicit conversion. Like the C# operator, invalid
|
||||
/// input cannot be represented in its return type and therefore panics.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when `val` is not accepted by `System.Guid` parsing rules.
|
||||
#[must_use]
|
||||
pub fn try_from(val: String) -> Self {
|
||||
Self::new_with_string(val).expect("invalid UUID string in explicit conversion")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn ne(left: Self, right: Self) -> bool {
|
||||
left != right
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn guid(&self) -> Guid {
|
||||
Guid(network_to_guid_raw(*self.0.as_bytes()))
|
||||
}
|
||||
|
||||
pub fn set_guid(&mut self, value: Guid) {
|
||||
self.0 = uuid::Uuid::from_bytes(guid_raw_to_network(value.0));
|
||||
}
|
||||
|
||||
fn from_slice_at(source: &[u8], pos: i32) -> Result<Self, Error> {
|
||||
let start = usize::try_from(pos).map_err(|_| Error::IndexOutOfRange)?;
|
||||
let end = start.checked_add(16).ok_or(Error::IndexOutOfRange)?;
|
||||
let bytes: [u8; 16] = source
|
||||
.get(start..end)
|
||||
.ok_or(Error::IndexOutOfRange)?
|
||||
.try_into()
|
||||
.expect("UUID range has sixteen bytes");
|
||||
Ok(Self(uuid::Uuid::from_bytes(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for UUID {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.hyphenated().fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UUID> for Object {
|
||||
fn from(value: UUID) -> Self {
|
||||
Self::UUID(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn guid_raw_to_network(raw: [u8; 16]) -> [u8; 16] {
|
||||
[
|
||||
raw[3], raw[2], raw[1], raw[0], raw[5], raw[4], raw[7], raw[6], raw[8], raw[9], raw[10],
|
||||
raw[11], raw[12], raw[13], raw[14], raw[15],
|
||||
]
|
||||
}
|
||||
|
||||
fn network_to_guid_raw(network: [u8; 16]) -> [u8; 16] {
|
||||
[
|
||||
network[3],
|
||||
network[2],
|
||||
network[1],
|
||||
network[0],
|
||||
network[5],
|
||||
network[4],
|
||||
network[7],
|
||||
network[6],
|
||||
network[8],
|
||||
network[9],
|
||||
network[10],
|
||||
network[11],
|
||||
network[12],
|
||||
network[13],
|
||||
network[14],
|
||||
network[15],
|
||||
]
|
||||
}
|
||||
|
||||
fn parse_guid_text(value: &str) -> Option<uuid::Uuid> {
|
||||
let value = value.trim();
|
||||
if let Ok(parsed) = uuid::Uuid::parse_str(value) {
|
||||
return Some(parsed);
|
||||
}
|
||||
if let Some(inner) = value
|
||||
.strip_prefix('(')
|
||||
.and_then(|inner| inner.strip_suffix(')'))
|
||||
.or_else(|| {
|
||||
value
|
||||
.strip_prefix('{')
|
||||
.and_then(|inner| inner.strip_suffix('}'))
|
||||
})
|
||||
&& let Ok(parsed) = uuid::Uuid::parse_str(inner)
|
||||
{
|
||||
return Some(parsed);
|
||||
}
|
||||
parse_guid_x_format(value)
|
||||
}
|
||||
|
||||
fn parse_guid_x_format(value: &str) -> Option<uuid::Uuid> {
|
||||
let compact: String = value
|
||||
.chars()
|
||||
.filter(|character| !character.is_whitespace())
|
||||
.collect();
|
||||
let outer = compact.strip_prefix('{')?.strip_suffix('}')?;
|
||||
let (leading, trailing) = outer.split_once(",{")?;
|
||||
let trailing = trailing.strip_suffix('}')?;
|
||||
let fields: Vec<&str> = leading.split(',').chain(trailing.split(',')).collect();
|
||||
if fields.len() != 11 {
|
||||
return None;
|
||||
}
|
||||
let a = u32::from_str_radix(strip_hex_prefix(fields[0])?, 16).ok()?;
|
||||
let b = u16::from_str_radix(strip_hex_prefix(fields[1])?, 16).ok()?;
|
||||
let c = u16::from_str_radix(strip_hex_prefix(fields[2])?, 16).ok()?;
|
||||
let mut bytes = [0_u8; 16];
|
||||
bytes[..4].copy_from_slice(&a.to_be_bytes());
|
||||
bytes[4..6].copy_from_slice(&b.to_be_bytes());
|
||||
bytes[6..8].copy_from_slice(&c.to_be_bytes());
|
||||
for (output, field) in bytes[8..].iter_mut().zip(&fields[3..]) {
|
||||
*output = u8::from_str_radix(strip_hex_prefix(field)?, 16).ok()?;
|
||||
}
|
||||
Some(uuid::Uuid::from_bytes(bytes))
|
||||
}
|
||||
|
||||
fn strip_hex_prefix(field: &str) -> Option<&str> {
|
||||
field
|
||||
.strip_prefix("0x")
|
||||
.or_else(|| field.strip_prefix("0X"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const NETWORK: [u8; 16] = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
|
||||
0x0f,
|
||||
];
|
||||
const TEXT: &str = "00010203-0405-0607-0809-0a0b0c0d0e0f";
|
||||
|
||||
#[test]
|
||||
fn network_bytes_string_and_guid_raw_bytes_are_unambiguous() {
|
||||
let id = UUID::new_with_bytes_int32(NETWORK.to_vec(), 0).unwrap();
|
||||
assert_eq!(id.to_string(), TEXT);
|
||||
assert_eq!(id.get_bytes().unwrap(), NETWORK);
|
||||
assert_eq!(
|
||||
id.guid().0,
|
||||
[
|
||||
0x03, 0x02, 0x01, 0x00, 0x05, 0x04, 0x07, 0x06, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
||||
0x0e, 0x0f,
|
||||
]
|
||||
);
|
||||
assert_eq!(UUID::new_with_guid(id.guid()).unwrap(), id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_offsets_and_malformed_inputs_match_contract() {
|
||||
assert_eq!(UUID::new_with_string(String::new()).unwrap(), UUID::zero());
|
||||
assert_eq!(
|
||||
UUID::parse(TEXT.replace('-', "")).unwrap().to_string(),
|
||||
TEXT
|
||||
);
|
||||
assert_eq!(UUID::parse(TEXT.to_uppercase()).unwrap().to_string(), TEXT);
|
||||
assert_eq!(
|
||||
UUID::parse(format!(" {TEXT}\t")).unwrap().to_string(),
|
||||
TEXT
|
||||
);
|
||||
assert_eq!(UUID::parse(format!("({TEXT})")).unwrap().to_string(), TEXT);
|
||||
assert_eq!(
|
||||
UUID::parse(format!("{{{TEXT}}}")).unwrap().to_string(),
|
||||
TEXT
|
||||
);
|
||||
assert_eq!(
|
||||
UUID::parse(
|
||||
"{0x00010203,0x0405,0x0607,{0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f}}".to_owned()
|
||||
)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
TEXT
|
||||
);
|
||||
assert_eq!(UUID::parse("bad".to_owned()), Err(Error::Argument));
|
||||
|
||||
let mut output = UUID::parse(TEXT.to_owned()).unwrap();
|
||||
assert!(!UUID::try_parse(String::new(), &mut output));
|
||||
assert_eq!(output, UUID::zero());
|
||||
assert!(!UUID::try_parse("bad".to_owned(), &mut output));
|
||||
assert_eq!(output, UUID::zero());
|
||||
|
||||
let mut with_offset = vec![0xff; 20];
|
||||
with_offset[2..18].copy_from_slice(&NETWORK);
|
||||
assert_eq!(UUID::from_bytes(with_offset, 2).unwrap().to_string(), TEXT);
|
||||
assert_eq!(
|
||||
UUID::from_bytes(vec![0; 15], 0),
|
||||
Err(Error::IndexOutOfRange)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crc_ulong_xor_and_combine_match_golden_values() {
|
||||
let mut id = UUID::parse(TEXT.to_owned()).unwrap();
|
||||
assert_eq!(id.crc().unwrap(), 0x181c_2024);
|
||||
assert_eq!(id.get_u_long().unwrap(), 0x0f0e_0d0c_0b0a_0908);
|
||||
assert_eq!(UUID::bitxor(id, id), UUID::zero());
|
||||
|
||||
let second = UUID::parse("ffffffff-ffff-ffff-ffff-ffffffffffff".to_owned()).unwrap();
|
||||
assert_eq!(
|
||||
UUID::combine(id, second).unwrap().to_string(),
|
||||
"1145ee5c-de6d-592f-027e-3daf0424653e"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destination_write_and_u64_constructor_preserve_exact_bytes() {
|
||||
let id = UUID::parse(TEXT.to_owned()).unwrap();
|
||||
let mut destination = [0xff; 20];
|
||||
id.to_bytes(&mut destination, 2).unwrap();
|
||||
assert_eq!(&destination[2..18], &NETWORK);
|
||||
assert_eq!(destination[0], 0xff);
|
||||
assert_eq!(destination[19], 0xff);
|
||||
assert_eq!(
|
||||
id.to_bytes(&mut destination, 5),
|
||||
Err(Error::IndexOutOfRange)
|
||||
);
|
||||
|
||||
let from_u64 = UUID::new_with_u_int64(0x0102_0304_0506_0708).unwrap();
|
||||
assert_eq!(
|
||||
from_u64.get_bytes().unwrap(),
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 6, 5, 4, 3, 2, 1]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_values_have_expected_uniqueness_and_bit_layout() {
|
||||
let random = UUID::random().unwrap();
|
||||
assert_ne!(random, UUID::zero());
|
||||
assert_eq!(random.0.get_version_num(), 4);
|
||||
assert_eq!(random.0.as_bytes()[8] & 0xc0, 0x80);
|
||||
|
||||
let secure = UUID::secure_random().unwrap();
|
||||
let raw = secure.guid().0;
|
||||
assert_eq!(raw[6] & 0xf0, 0x40);
|
||||
assert_eq!(raw[8] & 0xc0, 0x80);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comparison_hash_guid_mutation_and_mapped_operators_are_compatible() {
|
||||
let id = UUID::parse(TEXT.to_owned()).unwrap();
|
||||
let greater = UUID::parse("00010203-0405-0607-0809-0a0b0c0d0e10".to_owned()).unwrap();
|
||||
assert_eq!(id.compare_to(id).unwrap(), 0);
|
||||
assert_eq!(id.compare_to(greater).unwrap(), -1);
|
||||
assert_eq!(greater.compare_to(id).unwrap(), 1);
|
||||
assert_eq!(id.get_hash_code(), 0x0e04_040b);
|
||||
|
||||
assert!(UUID::eq(id, id));
|
||||
assert!(!UUID::ne(id, id));
|
||||
assert!(id.equals_with_object(Object::from(id)));
|
||||
assert!(!id.equals_with_object(Object::from(TEXT)));
|
||||
assert_eq!(UUID::try_from(TEXT.to_owned()), id);
|
||||
|
||||
let mut deconstructed = Guid([0xff; 16]);
|
||||
let mut mutable = id;
|
||||
mutable.deconstruct(&mut deconstructed).unwrap();
|
||||
assert_eq!(deconstructed, id.guid());
|
||||
mutable.set_guid(greater.guid());
|
||||
assert_eq!(mutable, greater);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user