// Copyright 2013-2014 The Rust Project Developers. // Copyright 2018 The Uuid Project Developers. // // See the COPYRIGHT file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! [`Uuid`] parsing constructs and utilities. //! //! [`Uuid`]: ../struct.Uuid.html pub(crate) mod error; pub(crate) use self::error::Error; use crate::{adapter, Uuid}; /// Check if the length matches any of the given criteria lengths. fn len_matches_any(len: usize, crits: &[usize]) -> bool { for crit in crits { if len == *crit { return true; } } false } /// Check if the length matches any criteria lengths in the given range /// (inclusive). #[allow(dead_code)] fn len_matches_range(len: usize, min: usize, max: usize) -> bool { for crit in min..=max { if len == crit { return true; } } false } // Accumulated length of each hyphenated group in hex digits. const ACC_GROUP_LENS: [usize; 5] = [8, 12, 16, 20, 32]; // Length of each hyphenated group in hex digits. const GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; impl Uuid { /// Parses a `Uuid` from a string of hexadecimal digits with optional /// hyphens. /// /// Any of the formats generated by this module (simple, hyphenated, urn) /// are supported by this parsing function. pub fn parse_str(mut input: &str) -> Result { // Ensure length is valid for any of the supported formats let len = input.len(); if len == adapter::Urn::LENGTH && input.starts_with("urn:uuid:") { input = &input[9..]; } else if !len_matches_any( len, &[adapter::Hyphenated::LENGTH, adapter::Simple::LENGTH], ) { Err(Error::InvalidLength { expected: error::ExpectedLength::Any(&[ adapter::Hyphenated::LENGTH, adapter::Simple::LENGTH, ]), found: len, })?; } // `digit` counts only hexadecimal digits, `i_char` counts all chars. let mut digit = 0; let mut group = 0; let mut acc = 0; let mut buffer = [0u8; 16]; for (i_char, chr) in input.bytes().enumerate() { if digit as usize >= adapter::Simple::LENGTH && group != 4 { if group == 0 { Err(Error::InvalidLength { expected: error::ExpectedLength::Any(&[ adapter::Hyphenated::LENGTH, adapter::Simple::LENGTH, ]), found: len, })?; } Err(Error::InvalidGroupCount { expected: error::ExpectedLength::Any(&[1, 5]), found: group + 1, })?; } if digit % 2 == 0 { // First digit of the byte. match chr { // Calulate upper half. b'0'..=b'9' => acc = chr - b'0', b'a'..=b'f' => acc = chr - b'a' + 10, b'A'..=b'F' => acc = chr - b'A' + 10, // Found a group delimiter b'-' => { // TODO: remove the u8 cast // BODY: this only needed until we switch to // ParseError if ACC_GROUP_LENS[group] as u8 != digit { // Calculate how many digits this group consists of // in the input. let found = if group > 0 { // TODO: remove the u8 cast // BODY: this only needed until we switch to // ParseError digit - ACC_GROUP_LENS[group - 1] as u8 } else { digit }; Err(Error::InvalidGroupLength { expected: error::ExpectedLength::Exact( GROUP_LENS[group], ), found: found as usize, group, })?; } // Next group, decrement digit, it is incremented again // at the bottom. group += 1; digit -= 1; } _ => { Err(Error::InvalidCharacter { expected: "0123456789abcdefABCDEF-", found: input[i_char..].chars().next().unwrap(), index: i_char, urn: error::UrnPrefix::Optional, })?; } } } else { // Second digit of the byte, shift the upper half. acc *= 16; match chr { b'0'..=b'9' => acc += chr - b'0', b'a'..=b'f' => acc += chr - b'a' + 10, b'A'..=b'F' => acc += chr - b'A' + 10, b'-' => { // The byte isn't complete yet. let found = if group > 0 { // TODO: remove the u8 cast // BODY: this only needed until we switch to // ParseError digit - ACC_GROUP_LENS[group - 1] as u8 } else { digit }; Err(Error::InvalidGroupLength { expected: error::ExpectedLength::Exact( GROUP_LENS[group], ), found: found as usize, group, })?; } _ => { Err(Error::InvalidCharacter { expected: "0123456789abcdefABCDEF-", found: input[i_char..].chars().next().unwrap(), index: i_char, urn: error::UrnPrefix::Optional, })?; } } buffer[(digit / 2) as usize] = acc; } digit += 1; } // Now check the last group. // TODO: remove the u8 cast // BODY: this only needed until we switch to // ParseError if ACC_GROUP_LENS[4] as u8 != digit { Err(Error::InvalidGroupLength { expected: error::ExpectedLength::Exact(GROUP_LENS[4]), found: (digit as usize - ACC_GROUP_LENS[3]), group, })?; } Ok(Uuid::from_bytes(buffer)) } } #[cfg(test)] mod tests { use super::*; use crate::{adapter, std::string::ToString, test_util}; #[test] fn test_parse_uuid_v4() { const EXPECTED_UUID_LENGTHS: error::ExpectedLength = error::ExpectedLength::Any(&[ adapter::Hyphenated::LENGTH, adapter::Simple::LENGTH, ]); const EXPECTED_GROUP_COUNTS: error::ExpectedLength = error::ExpectedLength::Any(&[1, 5]); const EXPECTED_CHARS: &'static str = "0123456789abcdefABCDEF-"; // Invalid assert_eq!( Uuid::parse_str("").map_err(crate::Error::expect_parser), Err(Error::InvalidLength { expected: EXPECTED_UUID_LENGTHS, found: 0, }) ); assert_eq!( Uuid::parse_str("!").map_err(crate::Error::expect_parser), Err(Error::InvalidLength { expected: EXPECTED_UUID_LENGTHS, found: 1 }) ); assert_eq!( Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45") .map_err(crate::Error::expect_parser), Err(Error::InvalidLength { expected: EXPECTED_UUID_LENGTHS, found: 37, }) ); assert_eq!( Uuid::parse_str("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4") .map_err(crate::Error::expect_parser), Err(Error::InvalidLength { expected: EXPECTED_UUID_LENGTHS, found: 35 }) ); assert_eq!( Uuid::parse_str("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4") .map_err(crate::Error::expect_parser), Err(Error::InvalidCharacter { expected: EXPECTED_CHARS, found: 'G', index: 20, urn: error::UrnPrefix::Optional, }) ); assert_eq!( Uuid::parse_str("F9168C5E-CEB2F4faaFB6BFF329BF39FA1E4") .map_err(crate::Error::expect_parser), Err(Error::InvalidGroupCount { expected: EXPECTED_GROUP_COUNTS, found: 2 }) ); assert_eq!( Uuid::parse_str("F9168C5E-CEB2-4faaFB6BFF329BF39FA1E4") .map_err(crate::Error::expect_parser), Err(Error::InvalidGroupCount { expected: EXPECTED_GROUP_COUNTS, found: 3, }) ); assert_eq!( Uuid::parse_str("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4") .map_err(crate::Error::expect_parser), Err(Error::InvalidGroupCount { expected: EXPECTED_GROUP_COUNTS, found: 4, }) ); assert_eq!( Uuid::parse_str("F9168C5E-CEB2-4faa") .map_err(crate::Error::expect_parser), Err(Error::InvalidLength { expected: EXPECTED_UUID_LENGTHS, found: 18, }) ); assert_eq!( Uuid::parse_str("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4") .map_err(crate::Error::expect_parser), Err(Error::InvalidCharacter { expected: EXPECTED_CHARS, found: 'X', index: 18, urn: error::UrnPrefix::Optional, }) ); assert_eq!( Uuid::parse_str("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4") .map_err(crate::Error::expect_parser), Err(Error::InvalidGroupLength { expected: error::ExpectedLength::Exact(4), found: 3, group: 1, }) ); // (group, found, expecting) // assert_eq!( Uuid::parse_str("01020304-1112-2122-3132-41424344") .map_err(crate::Error::expect_parser), Err(Error::InvalidGroupLength { expected: error::ExpectedLength::Exact(12), found: 8, group: 4, }) ); assert_eq!( Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c") .map_err(crate::Error::expect_parser), Err(Error::InvalidLength { expected: EXPECTED_UUID_LENGTHS, found: 31, }) ); assert_eq!( Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c88") .map_err(crate::Error::expect_parser), Err(Error::InvalidLength { expected: EXPECTED_UUID_LENGTHS, found: 33, }) ); assert_eq!( Uuid::parse_str("67e5504410b1426f9247bb680e5fe0cg8") .map_err(crate::Error::expect_parser), Err(Error::InvalidLength { expected: EXPECTED_UUID_LENGTHS, found: 33, }) ); assert_eq!( Uuid::parse_str("67e5504410b1426%9247bb680e5fe0c8") .map_err(crate::Error::expect_parser), Err(Error::InvalidCharacter { expected: EXPECTED_CHARS, found: '%', index: 15, urn: error::UrnPrefix::Optional, }) ); assert_eq!( Uuid::parse_str("231231212212423424324323477343246663") .map_err(crate::Error::expect_parser), Err(Error::InvalidLength { expected: EXPECTED_UUID_LENGTHS, found: 36, }) ); // Valid assert!(Uuid::parse_str("00000000000000000000000000000000").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()); // Nil let nil = Uuid::nil(); assert_eq!( Uuid::parse_str("00000000000000000000000000000000").unwrap(), nil ); assert_eq!( Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(), nil ); // Round-trip let uuid_orig = test_util::new(); let orig_str = uuid_orig.to_string(); let uuid_out = Uuid::parse_str(&orig_str).unwrap(); assert_eq!(uuid_orig, uuid_out); // Test error reporting assert_eq!( Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c") .map_err(crate::Error::expect_parser), Err(Error::InvalidLength { expected: EXPECTED_UUID_LENGTHS, found: 31, }) ); assert_eq!( Uuid::parse_str("67e550X410b1426f9247bb680e5fe0cd") .map_err(crate::Error::expect_parser), Err(Error::InvalidCharacter { expected: EXPECTED_CHARS, found: 'X', index: 6, urn: error::UrnPrefix::Optional, }) ); assert_eq!( Uuid::parse_str("67e550-4105b1426f9247bb680e5fe0c") .map_err(crate::Error::expect_parser), Err(Error::InvalidGroupLength { expected: error::ExpectedLength::Exact(8), found: 6, group: 0, }) ); assert_eq!( Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4") .map_err(crate::Error::expect_parser), Err(Error::InvalidGroupLength { expected: error::ExpectedLength::Exact(4), found: 5, group: 3, }) ); } }