#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "http://www.rust-lang.org/favicon.ico",
html_root_url = "http://doc.rust-lang.org/uuid/")]
#![feature(core)]
#![cfg_attr(test, feature(test))]
#![cfg_attr(test, deny(warnings))]
#[cfg(test)]
extern crate test;
extern crate "rustc-serialize" as rustc_serialize;
extern crate rand;
use std::default::Default;
use std::fmt;
use std::hash;
use std::iter::repeat;
use std::mem::{transmute,transmute_copy};
use std::num::{FromStrRadix, Int};
use std::str::FromStr;
use rand::Rng;
use rustc_serialize::{Encoder, Encodable, Decoder, Decodable};
pub type UuidBytes = [u8; 16];
#[derive(PartialEq,Copy)]
pub enum UuidVersion {
Mac = 1,
Dce = 2,
Md5 = 3,
Random = 4,
Sha1 = 5,
}
#[derive(PartialEq,Copy)]
pub enum UuidVariant {
NCS,
RFC4122,
Microsoft,
Future,
}
#[allow(missing_copy_implementations)]
#[derive(Debug)]
pub struct Uuid {
bytes: UuidBytes
}
impl hash::Hash for Uuid {
fn hash<S: hash::Hasher>(&self, state: &mut S) {
self.bytes.hash(state)
}
}
#[derive(Copy)]
struct UuidFields {
data1: u32,
data2: u16,
data3: u16,
data4: [u8; 8]
}
#[allow(missing_docs)]
#[derive(Copy, Debug)]
pub enum ParseError {
InvalidLength(usize),
InvalidCharacter(char, usize),
InvalidGroups(usize),
InvalidGroupLength(usize, usize, usize),
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ParseError::InvalidLength(found) =>
write!(f, "Invalid length; expecting 32, 36 or 45 chars, \
found {}", found),
ParseError::InvalidCharacter(found, pos) =>
write!(f, "Invalid character; found `{}` (0x{:02x}) at \
offset {}", found, found as usize, pos),
ParseError::InvalidGroups(found) =>
write!(f, "Malformed; wrong number of groups: expected 1 \
or 5, found {}", found),
ParseError::InvalidGroupLength(group, found, expecting) =>
write!(f, "Malformed; length of group {} was {}, \
expecting {}", group, found, expecting),
}
}
}
#[allow(non_upper_case_globals)]
static UuidGroupLens: [usize; 5] = [8, 4, 4, 4, 12];
impl Uuid {
pub fn nil() -> Uuid {
Uuid{ bytes: [0; 16] }
}
pub fn new(v: UuidVersion) -> Option<Uuid> {
match v {
UuidVersion::Random => Some(Uuid::new_v4()),
_ => None
}
}
pub fn new_v4() -> Uuid {
let ub = rand::thread_rng().gen_iter::<u8>().take(16).collect::<Vec<_>>();
let mut uuid = Uuid{ bytes: [0; 16] };
copy_memory(&mut uuid.bytes, &ub);
uuid.set_variant(UuidVariant::RFC4122);
uuid.set_version(UuidVersion::Random);
uuid
}
pub fn from_fields(d1: u32, d2: u16, d3: u16, d4: &[u8]) -> Uuid {
let mut fields = UuidFields {
data1: 0,
data2: 0,
data3: 0,
data4: [0; 8]
};
fields.data1 = d1.to_be();
fields.data2 = d2.to_be();
fields.data3 = d3.to_be();
copy_memory(&mut fields.data4, d4);
unsafe {
transmute(fields)
}
}
pub fn from_bytes(b: &[u8]) -> Option<Uuid> {
if b.len() != 16 {
return None
}
let mut uuid = Uuid{ bytes: [0; 16] };
copy_memory(&mut uuid.bytes, b);
Some(uuid)
}
fn set_variant(&mut self, v: UuidVariant) {
self.bytes[8] = match v {
UuidVariant::NCS => self.bytes[8] & 0x7f, UuidVariant::RFC4122 => (self.bytes[8] & 0x3f) | 0x80, UuidVariant::Microsoft => (self.bytes[8] & 0x1f) | 0xc0, UuidVariant::Future => (self.bytes[8] & 0x1f) | 0xe0, }
}
pub fn get_variant(&self) -> Option<UuidVariant> {
match self.bytes[8] {
x if x & 0x80 == 0x00 => Some(UuidVariant::NCS),
x if x & 0xc0 == 0x80 => Some(UuidVariant::RFC4122),
x if x & 0xe0 == 0xc0 => Some(UuidVariant::Microsoft),
x if x & 0xe0 == 0xe0 => Some(UuidVariant::Future),
_ => None
}
}
fn set_version(&mut self, v: UuidVersion) {
self.bytes[6] = (self.bytes[6] & 0xF) | ((v as u8) << 4);
}
pub fn get_version_num(&self) -> usize {
(self.bytes[6] >> 4) as usize
}
pub fn get_version(&self) -> Option<UuidVersion> {
let v = self.bytes[6] >> 4;
match v {
1 => Some(UuidVersion::Mac),
2 => Some(UuidVersion::Dce),
3 => Some(UuidVersion::Md5),
4 => Some(UuidVersion::Random),
5 => Some(UuidVersion::Sha1),
_ => None
}
}
pub fn as_bytes<'a>(&'a self) -> &'a [u8] { &self.bytes }
pub fn to_simple_string(&self) -> String {
let mut s = repeat(0u8).take(32).collect::<Vec<_>>();
for i in 0..16 {
let digit = format!("{:02x}", self.bytes[i] as usize);
s[i*2+0] = digit.as_bytes()[0];
s[i*2+1] = digit.as_bytes()[1];
}
String::from_utf8(s).unwrap()
}
pub fn to_hyphenated_string(&self) -> String {
let mut uf: UuidFields;
unsafe {
uf = transmute_copy(&self.bytes);
}
uf.data1 = uf.data1.to_be();
uf.data2 = uf.data2.to_be();
uf.data3 = uf.data3.to_be();
let s = format!("{:08x}-{:04x}-{:04x}-{:02x}{:02x}-\
{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
uf.data1,
uf.data2, uf.data3,
uf.data4[0], uf.data4[1],
uf.data4[2], uf.data4[3], uf.data4[4],
uf.data4[5], uf.data4[6], uf.data4[7]);
s
}
pub fn to_urn_string(&self) -> String {
format!("urn:uuid:{}", self.to_hyphenated_string())
}
pub fn parse_str(us: &str) -> Result<Uuid, ParseError> {
let mut us = us.clone();
let orig_len = us.len();
if orig_len != 32 && orig_len != 36 && orig_len != 45 {
return Err(ParseError::InvalidLength(orig_len));
}
if us.starts_with("urn:uuid:") {
us = &us[9..orig_len];
}
for (i, c) in us.chars().enumerate() {
match c {
'0'...'9' | 'A'...'F' | 'a'...'f' | '-' => {},
_ => return Err(ParseError::InvalidCharacter(c, i)),
}
}
let hex_groups: Vec<&str> = us.split("-").collect();
let group_lens: Vec<usize> = hex_groups.iter().map(|&v| v.len()).collect();
match group_lens.len() {
1 => {
if group_lens[0] != 32 {
return Err(ParseError::InvalidLength(group_lens[0]));
}
},
5 => {
for (i, (&gl, &expected)) in
group_lens.iter().zip(UuidGroupLens.iter()).enumerate() {
if gl != expected {
return Err(ParseError::InvalidGroupLength(i, gl, expected))
}
}
},
_ => {
return Err(ParseError::InvalidGroups(group_lens.len()));
}
}
let vs: String = hex_groups.concat();
assert!(vs.len() == 32);
assert!(vs.chars().all(|c| c.is_digit(16)));
let mut ub = [0u8; 16];
for i in 0..16 {
ub[i] = FromStrRadix::from_str_radix(&vs[i*2 .. (i+1)*2],
16).unwrap();
}
Ok(Uuid::from_bytes(&mut ub).unwrap())
}
pub fn is_nil(&self) -> bool {
self.bytes.iter().all(|&b| b == 0)
}
}
fn copy_memory(dst: &mut [u8], src: &[u8]) {
for (slot, val) in dst.iter_mut().zip(src.iter()) {
*slot = *val;
}
}
impl Default for Uuid {
fn default() -> Uuid {
Uuid::nil()
}
}
impl Clone for Uuid {
fn clone(&self) -> Uuid {
Uuid { bytes: self.bytes }
}
}
impl FromStr for Uuid {
type Err = ParseError;
fn from_str(us: &str) -> Result<Uuid, ParseError> {
Uuid::parse_str(us)
}
}
impl fmt::Display for Uuid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_simple_string())
}
}
impl PartialEq for Uuid {
fn eq(&self, other: &Uuid) -> bool {
self.bytes == other.bytes
}
}
impl Eq for Uuid {}
impl Encodable for Uuid {
fn encode<E: Encoder>(&self, e: &mut E) -> Result<(), E::Error> {
e.emit_str(&self.to_hyphenated_string())
}
}
impl Decodable for Uuid {
fn decode<D: Decoder>(d: &mut D) -> Result<Uuid, D::Error> {
Ok(try!(d.read_str()).parse().unwrap())
}
}
impl rand::Rand for Uuid {
#[inline]
fn rand<R: rand::Rng>(rng: &mut R) -> Uuid {
let ub = rng.gen_iter::<u8>().take(16).collect::<Vec<_>>();
let mut uuid = Uuid{ bytes: [0; 16] };
copy_memory(&mut uuid.bytes, &ub);
uuid.set_variant(UuidVariant::RFC4122);
uuid.set_version(UuidVersion::Random);
uuid
}
}
#[cfg(test)]
mod tests {
use super::{Uuid, UuidVariant, UuidVersion};
use rand;
#[test]
fn test_nil() {
let nil = Uuid::nil();
let not_nil = Uuid::new_v4();
assert!(nil.is_nil());
assert!(!not_nil.is_nil());
}
#[test]
fn test_new() {
let uuid1 = Uuid::new(UuidVersion::Random).unwrap();
let s = uuid1.to_simple_string();
assert!(s.len() == 32);
assert!(uuid1.get_version().unwrap() == UuidVersion::Random);
assert!(Uuid::new(UuidVersion::Mac) == None);
assert!(Uuid::new(UuidVersion::Dce) == None);
assert!(Uuid::new(UuidVersion::Md5) == None);
assert!(Uuid::new(UuidVersion::Sha1) == None);
}
#[test]
fn test_new_v4() {
let uuid1 = Uuid::new_v4();
assert!(uuid1.get_version().unwrap() == UuidVersion::Random);
assert!(uuid1.get_variant().unwrap() == UuidVariant::RFC4122);
}
#[test]
fn test_get_version() {
let uuid1 = Uuid::new_v4();
assert!(uuid1.get_version().unwrap() == UuidVersion::Random);
assert!(uuid1.get_version_num() == 4);
}
#[test]
fn test_get_variant() {
let uuid1 = Uuid::new_v4();
let uuid2 = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
let uuid3 = Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").unwrap();
let uuid4 = Uuid::parse_str("936DA01F9ABD4d9dC0C702AF85C822A8").unwrap();
let uuid5 = Uuid::parse_str("F9168C5E-CEB2-4faa-D6BF-329BF39FA1E4").unwrap();
let uuid6 = Uuid::parse_str("f81d4fae-7dec-11d0-7765-00a0c91e6bf6").unwrap();
assert!(uuid1.get_variant().unwrap() == UuidVariant::RFC4122);
assert!(uuid2.get_variant().unwrap() == UuidVariant::RFC4122);
assert!(uuid3.get_variant().unwrap() == UuidVariant::RFC4122);
assert!(uuid4.get_variant().unwrap() == UuidVariant::Microsoft);
assert!(uuid5.get_variant().unwrap() == UuidVariant::Microsoft);
assert!(uuid6.get_variant().unwrap() == UuidVariant::NCS);
}
#[test]
fn test_parse_uuid_v4() {
use super::ParseError;
assert!(Uuid::parse_str("").is_err());
assert!(Uuid::parse_str("!").is_err());
assert!(Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45").is_err());
assert!(Uuid::parse_str("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4").is_err());
assert!(Uuid::parse_str("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4").is_err());
assert!(Uuid::parse_str("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4").is_err());
assert!(Uuid::parse_str("F9168C5E-CEB2-4faa").is_err());
assert!(Uuid::parse_str("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4").is_err());
assert!(Uuid::parse_str("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4").is_err());
assert!(Uuid::parse_str("01020304-1112-2122-3132-41424344").is_err());
assert!(Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c").is_err());
assert!(Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c88").is_err());
assert!(Uuid::parse_str("67e5504410b1426f9247bb680e5fe0cg8").is_err());
assert!(Uuid::parse_str("67e5504410b1426%9247bb680e5fe0c8").is_err());
assert!(Uuid::parse_str("00000000000000000000000000000000").is_ok());
assert!(Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").is_ok());
assert!(Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").is_ok());
assert!(Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").is_ok());
assert!(Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c8").is_ok());
assert!(Uuid::parse_str("01020304-1112-2122-3132-414243444546").is_ok());
assert!(Uuid::parse_str("urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8").is_ok());
let nil = Uuid::nil();
assert!(Uuid::parse_str("00000000000000000000000000000000").unwrap() == nil);
assert!(Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap() == nil);
let uuid_orig = Uuid::new_v4();
let orig_str = uuid_orig.to_string();
let uuid_out = Uuid::parse_str(&orig_str).unwrap();
assert!(uuid_orig == uuid_out);
let e = Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c").unwrap_err();
assert!(match e { ParseError::InvalidLength(n) => n==31, _ => false });
let e = Uuid::parse_str("67e550X410b1426f9247bb680e5fe0cd").unwrap_err();
assert!(match e { ParseError::InvalidCharacter(c, n) => c=='X' && n==6, _ => false });
let e = Uuid::parse_str("67e550-4105b1426f9247bb680e5fe0c").unwrap_err();
assert!(match e { ParseError::InvalidGroups(n) => n==2, _ => false });
let e = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4").unwrap_err();
assert!(match e { ParseError::InvalidGroupLength(g, n, e) => g==3 && n==5 && e==4, _ => false });
}
#[test]
fn test_to_simple_string() {
let uuid1 = Uuid::new_v4();
let s = uuid1.to_simple_string();
assert!(s.len() == 32);
assert!(s.chars().all(|c| c.is_digit(16)));
}
#[test]
fn test_to_string() {
let uuid1 = Uuid::new_v4();
let s = uuid1.to_string();
assert!(s.len() == 32);
assert!(s.chars().all(|c| c.is_digit(16)));
}
#[test]
fn test_to_hyphenated_string() {
let uuid1 = Uuid::new_v4();
let s = uuid1.to_hyphenated_string();
assert!(s.len() == 36);
assert!(s.chars().all(|c| c.is_digit(16) || c == '-'));
}
#[test]
fn test_to_urn_string() {
let uuid1 = Uuid::new_v4();
let ss = uuid1.to_urn_string();
let s = &ss[9..];
assert!(ss.starts_with("urn:uuid:"));
assert!(s.len() == 36);
assert!(s.chars().all(|c| c.is_digit(16) || c == '-'));
}
#[test]
fn test_to_str_matching() {
let uuid1 = Uuid::new_v4();
let hs = uuid1.to_hyphenated_string();
let ss = uuid1.to_string();
let hsn = hs.chars().filter(|&c| c != '-').collect::<String>();
assert!(hsn == ss);
}
#[test]
fn test_string_roundtrip() {
let uuid = Uuid::new_v4();
let hs = uuid.to_hyphenated_string();
let uuid_hs = Uuid::parse_str(&hs).unwrap();
assert!(uuid_hs == uuid);
let ss = uuid.to_string();
let uuid_ss = Uuid::parse_str(&ss).unwrap();
assert!(uuid_ss == uuid);
}
#[test]
fn test_compare() {
let uuid1 = Uuid::new_v4();
let uuid2 = Uuid::new_v4();
assert!(uuid1 == uuid1);
assert!(uuid2 == uuid2);
assert!(uuid1 != uuid2);
assert!(uuid2 != uuid1);
}
#[test]
fn test_from_fields() {
let d1: u32 = 0xa1a2a3a4;
let d2: u16 = 0xb1b2;
let d3: u16 = 0xc1c2;
let d4: Vec<u8> = vec!(0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8);
let u = Uuid::from_fields(d1, d2, d3, &d4);
let expected = "a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8".to_string();
let result = u.to_simple_string();
assert!(result == expected);
}
#[test]
fn test_from_bytes() {
let b = vec!( 0xa1, 0xa2, 0xa3, 0xa4, 0xb1, 0xb2, 0xc1, 0xc2,
0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8 );
let u = Uuid::from_bytes(&b).unwrap();
let expected = "a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8".to_string();
assert!(u.to_simple_string() == expected);
}
#[test]
fn test_as_bytes() {
let u = Uuid::new_v4();
let ub = u.as_bytes();
assert!(ub.len() == 16);
assert!(! ub.iter().all(|&b| b == 0));
}
#[test]
fn test_bytes_roundtrip() {
let b_in: [u8; 16] = [ 0xa1, 0xa2, 0xa3, 0xa4, 0xb1, 0xb2, 0xc1, 0xc2,
0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8 ];
let u = Uuid::from_bytes(&b_in).unwrap();
let b_out = u.as_bytes();
assert_eq!(&b_in, b_out);
}
#[test]
fn test_operator_eq() {
let u1 = Uuid::new_v4();
let u2 = u1.clone();
let u3 = Uuid::new_v4();
assert!(u1 == u1);
assert!(u1 == u2);
assert!(u2 == u1);
assert!(u1 != u3);
assert!(u3 != u1);
assert!(u2 != u3);
assert!(u3 != u2);
}
#[test]
fn test_rand_rand() {
let mut rng = rand::thread_rng();
let u: Uuid = rand::Rand::rand(&mut rng);
let ub = u.as_bytes();
assert!(ub.len() == 16);
assert!(! ub.iter().all(|&b| b == 0));
}
#[test]
fn test_serialize_round_trip() {
use rustc_serialize::json;
let u = Uuid::new_v4();
let s = json::encode(&u).unwrap();
let u2 = json::decode(&s).unwrap();
assert_eq!(u, u2);
}
#[test]
fn test_iterbytes_impl_for_uuid() {
use std::collections::HashSet;
let mut set = HashSet::new();
let id1 = Uuid::new_v4();
let id2 = Uuid::new_v4();
set.insert(id1.clone());
assert!(set.contains(&id1));
assert!(!set.contains(&id2));
}
}
#[cfg(test)]
mod bench {
extern crate test;
use self::test::Bencher;
use super::Uuid;
#[bench]
pub fn create_uuids(b: &mut Bencher) {
b.iter(|| {
Uuid::new_v4();
})
}
#[bench]
pub fn uuid_to_string(b: &mut Bencher) {
let u = Uuid::new_v4();
b.iter(|| {
u.to_string();
})
}
#[bench]
pub fn parse_str(b: &mut Bencher) {
let s = "urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4";
b.iter(|| {
Uuid::parse_str(s).unwrap();
})
}
}