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 @@
#include <stddef.h>
#include <stdint.h>
#include <ostream>
#include "base/rand_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
......@@ -16,43 +18,80 @@ namespace base {
namespace {
template <typename Char>
bool IsLowerHexDigit(Char c) {
constexpr bool IsLowerHexDigit(Char c) {
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>
bool IsValidGUIDInternal(StringPieceType guid, bool strict) {
std::string GetCanonicalGUIDInternal(StringPieceType input, bool strict) {
using CharType = typename StringPieceType::value_type;
const size_t kGUIDLength = 36U;
if (guid.length() != kGUIDLength)
return false;
constexpr size_t kGUIDLength = 36;
if (input.length() != kGUIDLength)
return std::string();
for (size_t i = 0; i < guid.length(); ++i) {
CharType current = guid[i];
if (i == 8 || i == 13 || i == 18 || i == 23) {
std::string lowercase_;
lowercase_.resize(kGUIDLength);
for (size_t i = 0; i < input.length(); ++i) {
CharType current = input[i];
if (IsHyphenPosition(i)) {
if (current != '-')
return false;
return std::string();
lowercase_[i] = '-';
} else {
if (strict ? !IsLowerHexDigit(current) : !IsHexDigit(current))
return false;
return std::string();
lowercase_[i] = ToLowerASCII(current);
}
}
return true;
return lowercase_;
}
} // namespace
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];
// Use base::RandBytes instead of crypto::RandBytes, because crypto calls the
// 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.
// 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:
sixteen_bytes[0] &= 0xffffffff'ffff0fffULL;
......@@ -63,28 +102,59 @@ std::string GenerateGUID() {
sixteen_bytes[1] &= 0x3fffffff'ffffffffULL;
sixteen_bytes[1] |= 0x80000000'00000000ULL;
return RandomDataToGUIDString(sixteen_bytes);
GUID guid;
guid.lowercase_ = RandomDataToGUIDString(sixteen_bytes);
return guid;
}
bool IsValidGUID(base::StringPiece guid) {
return IsValidGUIDInternal(guid, false /* strict */);
// static
GUID GUID::ParseCaseInsensitive(StringPiece input) {
GUID guid;
guid.lowercase_ = GetCanonicalGUIDInternal(input, /*strict=*/false);
return guid;
}
bool IsValidGUID(base::StringPiece16 guid) {
return IsValidGUIDInternal(guid, false /* strict */);
// static
GUID GUID::ParseCaseInsensitive(StringPiece16 input) {
GUID guid;
guid.lowercase_ = GetCanonicalGUIDInternal(input, /*strict=*/false);
return guid;
}
bool IsValidGUIDOutputString(base::StringPiece guid) {
return IsValidGUIDInternal(guid, true /* strict */);
// static
GUID GUID::ParseLowercase(StringPiece input) {
GUID guid;
guid.lowercase_ = GetCanonicalGUIDInternal(input, /*strict=*/true);
return guid;
}
std::string RandomDataToGUIDString(const uint64_t bytes[2]) {
return StringPrintf("%08x-%04x-%04x-%04x-%012llx",
static_cast<unsigned int>(bytes[0] >> 32),
static_cast<unsigned int>((bytes[0] >> 16) & 0x0000ffff),
static_cast<unsigned int>(bytes[0] & 0x0000ffff),
static_cast<unsigned int>(bytes[1] >> 48),
bytes[1] & 0x0000ffff'ffffffffULL);
// static
GUID GUID::ParseLowercase(StringPiece16 input) {
GUID guid;
guid.lowercase_ = GetCanonicalGUIDInternal(input, /*strict=*/true);
return guid;
}
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
......@@ -7,6 +7,7 @@
#include <stdint.h>
#include <iosfwd>
#include <string>
#include "base/base_export.h"
......@@ -15,32 +16,75 @@
namespace base {
// Generate a 128-bit random GUID in the form of version 4 as described in
// 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.
// DEPRECATED, use GUID::GenerateRandomV4() instead.
BASE_EXPORT std::string GenerateGUID();
// Returns true if the input string conforms to the version 4 GUID format.
// Note that this does NOT check if the hexadecimal values "a" through "f"
// are in lower case characters, as Version 4 RFC says onput they're
// 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);
// DEPRECATED, use GUID::ParseCaseInsensitive() and GUID::is_valid() instead.
BASE_EXPORT bool IsValidGUID(StringPiece input);
BASE_EXPORT bool IsValidGUID(StringPiece16 input);
// Returns true if the input string is valid version 4 GUID output string.
// This also checks if the hexadecimal values "a" through "f" are in lower
// case characters.
BASE_EXPORT bool IsValidGUIDOutputString(base::StringPiece guid);
// DEPRECATED, use GUID::ParseLowercase() and GUID::is_valid() instead.
BASE_EXPORT bool IsValidGUIDOutputString(StringPiece input);
// For unit testing purposes only. Do not use outside of tests.
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
#endif // BASE_GUID_H_
......@@ -14,34 +14,23 @@
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) {
uint64_t bytes[] = {0, 0};
std::string clientid = RandomDataToGUIDString(bytes);
static constexpr uint64_t kBytes[] = {0, 0};
const std::string clientid = RandomDataToGUIDString(kBytes);
EXPECT_EQ("00000000-0000-0000-0000-000000000000", clientid);
}
TEST(GUIDTest, GUIDGeneratesCorrectly) {
uint64_t bytes[] = {0x0123456789ABCDEFULL, 0xFEDCBA9876543210ULL};
std::string clientid = RandomDataToGUIDString(bytes);
static constexpr uint64_t kBytes[] = {0x0123456789ABCDEFULL,
0xFEDCBA9876543210ULL};
const std::string clientid = RandomDataToGUIDString(kBytes);
EXPECT_EQ("01234567-89ab-cdef-fedc-ba9876543210", clientid);
}
TEST(GUIDTest, GUIDCorrectlyFormatted) {
const int kIterations = 10;
for (int it = 0; it < kIterations; ++it) {
std::string guid = GenerateGUID();
TEST(GUIDTest, DeprecatedGUIDCorrectlyFormatted) {
constexpr int kIterations = 10;
for (int i = 0; i < kIterations; ++i) {
const std::string guid = GenerateGUID();
EXPECT_TRUE(IsValidGUID(guid));
EXPECT_TRUE(IsValidGUIDOutputString(guid));
EXPECT_TRUE(IsValidGUID(ToLowerASCII(guid)));
......@@ -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) {
const int kIterations = 10;
for (int it = 0; it < kIterations; ++it) {
std::string guid1 = GenerateGUID();
std::string guid2 = GenerateGUID();
EXPECT_EQ(36U, guid1.length());
EXPECT_EQ(36U, guid2.length());
constexpr int kIterations = 10;
for (int i = 0; i < kIterations; ++i) {
const GUID guid1 = GUID::GenerateRandomV4();
const GUID guid2 = GUID::GenerateRandomV4();
EXPECT_NE(guid1, guid2);
EXPECT_TRUE(IsGUIDv4(guid1));
EXPECT_TRUE(IsGUIDv4(guid2));
EXPECT_TRUE(guid1.is_valid());
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
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