Commit 16ab8cf3 authored by Daniel Hosseinian's avatar Daniel Hosseinian Committed by Commit Bot

Introduce base::GUID class

Currently, GUIDs are returned as and stored in std::string. A dedicated
base::GUID class would introduce greater type safety.

Mark existing GUID functions as deprecated.

Add tests for the new class.

Bug: 1026195
Change-Id: I15d0c8a5907291d48d2e1ddf029390170e81993b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2200456
Commit-Queue: Daniel Hosseinian <dhoss@chromium.org>
Reviewed-by: default avatarGabriel Charette <gab@chromium.org>
Reviewed-by: default avatarMikel Astiz <mastiz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#827030}
parent 7e85f3d6
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <ostream>
#include "base/rand_util.h" #include "base/rand_util.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
...@@ -16,43 +18,80 @@ namespace base { ...@@ -16,43 +18,80 @@ namespace base {
namespace { namespace {
template <typename Char> template <typename Char>
bool IsLowerHexDigit(Char c) { constexpr bool IsLowerHexDigit(Char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
} }
constexpr bool IsHyphenPosition(size_t i) {
return i == 8 || i == 13 || i == 18 || i == 23;
}
// Returns a canonical GUID string given that `input` is validly formatted
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, such that x is a hexadecimal digit.
// If `strict`, x must be a lower-case hexadecimal digit.
template <typename StringPieceType> template <typename StringPieceType>
bool IsValidGUIDInternal(StringPieceType guid, bool strict) { std::string GetCanonicalGUIDInternal(StringPieceType input, bool strict) {
using CharType = typename StringPieceType::value_type; using CharType = typename StringPieceType::value_type;
const size_t kGUIDLength = 36U; constexpr size_t kGUIDLength = 36;
if (guid.length() != kGUIDLength) if (input.length() != kGUIDLength)
return false; return std::string();
for (size_t i = 0; i < guid.length(); ++i) { std::string lowercase_;
CharType current = guid[i]; lowercase_.resize(kGUIDLength);
if (i == 8 || i == 13 || i == 18 || i == 23) { for (size_t i = 0; i < input.length(); ++i) {
CharType current = input[i];
if (IsHyphenPosition(i)) {
if (current != '-') if (current != '-')
return false; return std::string();
lowercase_[i] = '-';
} else { } else {
if (strict ? !IsLowerHexDigit(current) : !IsHexDigit(current)) if (strict ? !IsLowerHexDigit(current) : !IsHexDigit(current))
return false; return std::string();
lowercase_[i] = ToLowerASCII(current);
} }
} }
return true; return lowercase_;
} }
} // namespace } // namespace
std::string GenerateGUID() { std::string GenerateGUID() {
GUID guid = GUID::GenerateRandomV4();
return guid.AsLowercaseString();
}
bool IsValidGUID(StringPiece input) {
return !GetCanonicalGUIDInternal(input, /*strict=*/false).empty();
}
bool IsValidGUID(StringPiece16 input) {
return !GetCanonicalGUIDInternal(input, /*strict=*/false).empty();
}
bool IsValidGUIDOutputString(StringPiece input) {
return !GetCanonicalGUIDInternal(input, /*strict=*/true).empty();
}
std::string RandomDataToGUIDString(const uint64_t bytes[2]) {
return StringPrintf(
"%08x-%04x-%04x-%04x-%012llx", static_cast<uint32_t>(bytes[0] >> 32),
static_cast<uint32_t>((bytes[0] >> 16) & 0x0000ffff),
static_cast<uint32_t>(bytes[0] & 0x0000ffff),
static_cast<uint32_t>(bytes[1] >> 48), bytes[1] & 0x0000ffff'ffffffffULL);
}
// static
GUID GUID::GenerateRandomV4() {
uint64_t sixteen_bytes[2]; uint64_t sixteen_bytes[2];
// Use base::RandBytes instead of crypto::RandBytes, because crypto calls the // Use base::RandBytes instead of crypto::RandBytes, because crypto calls the
// base version directly, and to prevent the dependency from base/ to crypto/. // base version directly, and to prevent the dependency from base/ to crypto/.
base::RandBytes(&sixteen_bytes, sizeof(sixteen_bytes)); RandBytes(&sixteen_bytes, sizeof(sixteen_bytes));
// Set the GUID to version 4 as described in RFC 4122, section 4.4. // Set the GUID to version 4 as described in RFC 4122, section 4.4.
// The format of GUID version 4 must be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx, // The format of GUID version 4 must be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
// where y is one of [8, 9, A, B]. // where y is one of [8, 9, a, b].
// Clear the version bits and set the version to 4: // Clear the version bits and set the version to 4:
sixteen_bytes[0] &= 0xffffffff'ffff0fffULL; sixteen_bytes[0] &= 0xffffffff'ffff0fffULL;
...@@ -63,28 +102,59 @@ std::string GenerateGUID() { ...@@ -63,28 +102,59 @@ std::string GenerateGUID() {
sixteen_bytes[1] &= 0x3fffffff'ffffffffULL; sixteen_bytes[1] &= 0x3fffffff'ffffffffULL;
sixteen_bytes[1] |= 0x80000000'00000000ULL; sixteen_bytes[1] |= 0x80000000'00000000ULL;
return RandomDataToGUIDString(sixteen_bytes); GUID guid;
guid.lowercase_ = RandomDataToGUIDString(sixteen_bytes);
return guid;
} }
bool IsValidGUID(base::StringPiece guid) { // static
return IsValidGUIDInternal(guid, false /* strict */); GUID GUID::ParseCaseInsensitive(StringPiece input) {
GUID guid;
guid.lowercase_ = GetCanonicalGUIDInternal(input, /*strict=*/false);
return guid;
} }
bool IsValidGUID(base::StringPiece16 guid) { // static
return IsValidGUIDInternal(guid, false /* strict */); GUID GUID::ParseCaseInsensitive(StringPiece16 input) {
GUID guid;
guid.lowercase_ = GetCanonicalGUIDInternal(input, /*strict=*/false);
return guid;
} }
bool IsValidGUIDOutputString(base::StringPiece guid) { // static
return IsValidGUIDInternal(guid, true /* strict */); GUID GUID::ParseLowercase(StringPiece input) {
GUID guid;
guid.lowercase_ = GetCanonicalGUIDInternal(input, /*strict=*/true);
return guid;
} }
std::string RandomDataToGUIDString(const uint64_t bytes[2]) { // static
return StringPrintf("%08x-%04x-%04x-%04x-%012llx", GUID GUID::ParseLowercase(StringPiece16 input) {
static_cast<unsigned int>(bytes[0] >> 32), GUID guid;
static_cast<unsigned int>((bytes[0] >> 16) & 0x0000ffff), guid.lowercase_ = GetCanonicalGUIDInternal(input, /*strict=*/true);
static_cast<unsigned int>(bytes[0] & 0x0000ffff), return guid;
static_cast<unsigned int>(bytes[1] >> 48), }
bytes[1] & 0x0000ffff'ffffffffULL);
GUID::GUID() = default;
GUID::GUID(const GUID& other) = default;
GUID& GUID::operator=(const GUID& other) = default;
const std::string& GUID::AsLowercaseString() const {
return lowercase_;
}
bool GUID::operator==(const GUID& other) const {
return AsLowercaseString() == other.AsLowercaseString();
}
bool GUID::operator!=(const GUID& other) const {
return !(*this == other);
}
std::ostream& operator<<(std::ostream& out, const GUID& guid) {
return out << guid.AsLowercaseString();
} }
} // namespace base } // namespace base
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <stdint.h> #include <stdint.h>
#include <iosfwd>
#include <string> #include <string>
#include "base/base_export.h" #include "base/base_export.h"
...@@ -15,32 +16,75 @@ ...@@ -15,32 +16,75 @@
namespace base { namespace base {
// Generate a 128-bit random GUID in the form of version 4 as described in // DEPRECATED, use GUID::GenerateRandomV4() instead.
// RFC 4122, section 4.4.
// The format of GUID version 4 must be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
// where y is one of [8, 9, A, B].
// The hexadecimal values "a" through "f" are output as lower case characters.
//
// A cryptographically secure random source will be used, but consider using
// UnguessableToken for greater type-safety if GUID format is unnecessary.
BASE_EXPORT std::string GenerateGUID(); BASE_EXPORT std::string GenerateGUID();
// Returns true if the input string conforms to the version 4 GUID format. // DEPRECATED, use GUID::ParseCaseInsensitive() and GUID::is_valid() instead.
// Note that this does NOT check if the hexadecimal values "a" through "f" BASE_EXPORT bool IsValidGUID(StringPiece input);
// are in lower case characters, as Version 4 RFC says onput they're BASE_EXPORT bool IsValidGUID(StringPiece16 input);
// case insensitive. (Use IsValidGUIDOutputString for checking if the
// given string is valid output string)
BASE_EXPORT bool IsValidGUID(base::StringPiece guid);
BASE_EXPORT bool IsValidGUID(base::StringPiece16 guid);
// Returns true if the input string is valid version 4 GUID output string. // DEPRECATED, use GUID::ParseLowercase() and GUID::is_valid() instead.
// This also checks if the hexadecimal values "a" through "f" are in lower BASE_EXPORT bool IsValidGUIDOutputString(StringPiece input);
// case characters.
BASE_EXPORT bool IsValidGUIDOutputString(base::StringPiece guid);
// For unit testing purposes only. Do not use outside of tests. // For unit testing purposes only. Do not use outside of tests.
BASE_EXPORT std::string RandomDataToGUIDString(const uint64_t bytes[2]); BASE_EXPORT std::string RandomDataToGUIDString(const uint64_t bytes[2]);
class BASE_EXPORT GUID {
public:
// Generate a 128-bit random GUID in the form of version 4. see RFC 4122,
// section 4.4. The format of GUID version 4 must be
// xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx, where y is one of [8, 9, a, b]. The
// hexadecimal values "a" through "f" are output as lower case characters.
// A cryptographically secure random source will be used, but consider using
// UnguessableToken for greater type-safety if GUID format is unnecessary.
static GUID GenerateRandomV4();
// Returns a valid GUID if the input string conforms to the GUID format, and
// an invalid GUID otherwise. Note that this does NOT check if the hexadecimal
// values "a" through "f" are in lower case characters.
static GUID ParseCaseInsensitive(StringPiece input);
static GUID ParseCaseInsensitive(StringPiece16 input);
// Similar to ParseCaseInsensitive(), but all hexadecimal values "a" through
// "f" must be lower case characters.
static GUID ParseLowercase(StringPiece input);
static GUID ParseLowercase(StringPiece16 input);
// Constructs an invalid GUID.
GUID();
GUID(const GUID& other);
GUID& operator=(const GUID& other);
bool is_valid() const { return !lowercase_.empty(); }
// Returns the GUID in a lowercase string format if it is valid, and an empty
// string otherwise. The returned value is guaranteed to be parsed by
// ParseLowercase().
//
// NOTE: While AsLowercaseString() is currently a trivial getter, callers
// should not treat it as such. When the internal type of base::GUID changes,
// this will be a non-trivial converter. See the TODO above `lowercase_` for
// more context.
const std::string& AsLowercaseString() const;
// Invalid GUIDs are equal.
bool operator==(const GUID& other) const;
bool operator!=(const GUID& other) const;
private:
// TODO(crbug.com/1026195): Consider using a different internal type.
// Most existing representations of GUIDs in the codebase use std::string,
// so matching the internal type will avoid inefficient string conversions
// during the migration to base::GUID.
//
// The lowercase form of the GUID. Empty for invalid GUIDs.
std::string lowercase_;
};
// Stream operator so GUID objects can be used in logging statements.
BASE_EXPORT std::ostream& operator<<(std::ostream& out, const GUID& guid);
} // namespace base } // namespace base
#endif // BASE_GUID_H_ #endif // BASE_GUID_H_
...@@ -14,34 +14,23 @@ ...@@ -14,34 +14,23 @@
namespace base { namespace base {
namespace {
bool IsGUIDv4(const std::string& guid) {
// The format of GUID version 4 must be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
// where y is one of [8, 9, A, B].
return IsValidGUID(guid) && guid[14] == '4' &&
(guid[19] == '8' || guid[19] == '9' || guid[19] == 'A' ||
guid[19] == 'a' || guid[19] == 'B' || guid[19] == 'b');
}
} // namespace
TEST(GUIDTest, GUIDGeneratesAllZeroes) { TEST(GUIDTest, GUIDGeneratesAllZeroes) {
uint64_t bytes[] = {0, 0}; static constexpr uint64_t kBytes[] = {0, 0};
std::string clientid = RandomDataToGUIDString(bytes); const std::string clientid = RandomDataToGUIDString(kBytes);
EXPECT_EQ("00000000-0000-0000-0000-000000000000", clientid); EXPECT_EQ("00000000-0000-0000-0000-000000000000", clientid);
} }
TEST(GUIDTest, GUIDGeneratesCorrectly) { TEST(GUIDTest, GUIDGeneratesCorrectly) {
uint64_t bytes[] = {0x0123456789ABCDEFULL, 0xFEDCBA9876543210ULL}; static constexpr uint64_t kBytes[] = {0x0123456789ABCDEFULL,
std::string clientid = RandomDataToGUIDString(bytes); 0xFEDCBA9876543210ULL};
const std::string clientid = RandomDataToGUIDString(kBytes);
EXPECT_EQ("01234567-89ab-cdef-fedc-ba9876543210", clientid); EXPECT_EQ("01234567-89ab-cdef-fedc-ba9876543210", clientid);
} }
TEST(GUIDTest, GUIDCorrectlyFormatted) { TEST(GUIDTest, DeprecatedGUIDCorrectlyFormatted) {
const int kIterations = 10; constexpr int kIterations = 10;
for (int it = 0; it < kIterations; ++it) { for (int i = 0; i < kIterations; ++i) {
std::string guid = GenerateGUID(); const std::string guid = GenerateGUID();
EXPECT_TRUE(IsValidGUID(guid)); EXPECT_TRUE(IsValidGUID(guid));
EXPECT_TRUE(IsValidGUIDOutputString(guid)); EXPECT_TRUE(IsValidGUIDOutputString(guid));
EXPECT_TRUE(IsValidGUID(ToLowerASCII(guid))); EXPECT_TRUE(IsValidGUID(ToLowerASCII(guid)));
...@@ -49,17 +38,113 @@ TEST(GUIDTest, GUIDCorrectlyFormatted) { ...@@ -49,17 +38,113 @@ TEST(GUIDTest, GUIDCorrectlyFormatted) {
} }
} }
TEST(GUIDTest, DeprecatedGUIDBasicUniqueness) {
constexpr int kIterations = 10;
for (int i = 0; i < kIterations; ++i) {
const std::string guid_str1 = GenerateGUID();
const std::string guid_str2 = GenerateGUID();
EXPECT_EQ(36U, guid_str1.length());
EXPECT_EQ(36U, guid_str2.length());
EXPECT_NE(guid_str1, guid_str2);
const GUID guid1 = GUID::ParseCaseInsensitive(guid_str1);
EXPECT_TRUE(guid1.is_valid());
const GUID guid2 = GUID::ParseCaseInsensitive(guid_str2);
EXPECT_TRUE(guid2.is_valid());
}
}
namespace {
// The format of GUID version 4 must be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
// where y is one of [8, 9, a, b].
bool IsValidV4(const GUID& guid) {
const std::string& lowercase = guid.AsLowercaseString();
return guid.is_valid() && lowercase[14] == '4' &&
(lowercase[19] == '8' || lowercase[19] == '9' ||
lowercase[19] == 'a' || lowercase[19] == 'b');
}
} // namespace
TEST(GUIDTest, GUIDBasicUniqueness) { TEST(GUIDTest, GUIDBasicUniqueness) {
const int kIterations = 10; constexpr int kIterations = 10;
for (int it = 0; it < kIterations; ++it) { for (int i = 0; i < kIterations; ++i) {
std::string guid1 = GenerateGUID(); const GUID guid1 = GUID::GenerateRandomV4();
std::string guid2 = GenerateGUID(); const GUID guid2 = GUID::GenerateRandomV4();
EXPECT_EQ(36U, guid1.length());
EXPECT_EQ(36U, guid2.length());
EXPECT_NE(guid1, guid2); EXPECT_NE(guid1, guid2);
EXPECT_TRUE(IsGUIDv4(guid1)); EXPECT_TRUE(guid1.is_valid());
EXPECT_TRUE(IsGUIDv4(guid2)); EXPECT_TRUE(IsValidV4(guid1));
EXPECT_TRUE(guid2.is_valid());
EXPECT_TRUE(IsValidV4(guid2));
} }
} }
namespace {
void TestGUIDValidity(StringPiece input, bool case_insensitive, bool strict) {
SCOPED_TRACE(input);
{
const GUID guid = GUID::ParseCaseInsensitive(input);
EXPECT_EQ(case_insensitive, guid.is_valid());
}
{
const GUID guid = GUID::ParseLowercase(input);
EXPECT_EQ(strict, guid.is_valid());
}
}
} // namespace
TEST(GUIDTest, Validity) {
// Empty GUID is invalid.
EXPECT_FALSE(GUID().is_valid());
enum Parsability { kDoesntParse, kParsesCaseInsensitiveOnly, kAlwaysParses };
static constexpr struct {
StringPiece input;
Parsability parsability;
} kGUIDValidity[] = {
{"invalid", kDoesntParse},
{"0123456789ab-cdef-fedc-ba98-76543210", kDoesntParse},
{"0123456789abcdeffedcba9876543210", kDoesntParse},
{"01234567-89Zz-ZzZz-ZzZz-Zz9876543210", kDoesntParse},
{"DEADBEEFDEADBEEFDEADBEEFDEADBEEF", kDoesntParse},
{"deadbeefWdeadXbeefYdeadZbeefdeadbeef", kDoesntParse},
{"XXXdeadbeefWdeadXbeefYdeadZbeefdeadbeefXXX", kDoesntParse},
{"01234567-89aB-cDeF-fEdC-bA9876543210", kParsesCaseInsensitiveOnly},
{"DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF", kParsesCaseInsensitiveOnly},
{"00000000-0000-0000-0000-000000000000", kAlwaysParses},
{"deadbeef-dead-beef-dead-beefdeadbeef", kAlwaysParses},
};
for (const auto& validity : kGUIDValidity) {
const bool case_insensitive = validity.parsability != kDoesntParse;
const bool strict = validity.parsability == kAlwaysParses;
TestGUIDValidity(validity.input, case_insensitive, strict);
}
}
TEST(GUIDTest, Equality) {
static constexpr uint64_t kBytes[] = {0xDEADBEEFDEADBEEFULL,
0xDEADBEEFDEADBEEFULL};
const std::string clientid = RandomDataToGUIDString(kBytes);
static constexpr char kExpectedCanonicalStr[] =
"deadbeef-dead-beef-dead-beefdeadbeef";
ASSERT_EQ(kExpectedCanonicalStr, clientid);
const GUID from_lower = GUID::ParseCaseInsensitive(ToLowerASCII(clientid));
EXPECT_EQ(kExpectedCanonicalStr, from_lower.AsLowercaseString());
const GUID from_upper = GUID::ParseCaseInsensitive(ToUpperASCII(clientid));
EXPECT_EQ(kExpectedCanonicalStr, from_upper.AsLowercaseString());
EXPECT_EQ(from_lower, from_upper);
// Invalid GUIDs are equal.
EXPECT_EQ(GUID(), GUID());
}
} // namespace base } // namespace base
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment