|
| 1 | +// Copyright 2025 Google LLC |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +#include "google/cloud/spanner/uuid.h" |
| 16 | +#include "google/cloud/internal/make_status.h" |
| 17 | +#include "absl/strings/str_format.h" |
| 18 | +#include "absl/strings/strip.h" |
| 19 | +#include <unordered_map> |
| 20 | + |
| 21 | +namespace google { |
| 22 | +namespace cloud { |
| 23 | +namespace spanner { |
| 24 | +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN |
| 25 | +namespace { |
| 26 | + |
| 27 | +// Helper function to parse a single hexadecimal block of a UUID. |
| 28 | +// A hexadecimal block is a 16-digit hexadecimal number, which is represented |
| 29 | +// as 8 bytes. |
| 30 | +StatusOr<std::uint64_t> ParseHexBlock(absl::string_view& str, |
| 31 | + absl::string_view original_str) { |
| 32 | + constexpr int kUuidNumberOfHexDigits = 32; |
| 33 | + constexpr int kMaxUuidBlockLength = 16; |
| 34 | + static auto const* char_to_hex = new std::unordered_map<char, std::uint8_t>( |
| 35 | + {{'0', 0x00}, {'1', 0x01}, {'2', 0x02}, {'3', 0x03}, {'4', 0x04}, |
| 36 | + {'5', 0x05}, {'6', 0x06}, {'7', 0x07}, {'8', 0x08}, {'9', 0x09}, |
| 37 | + {'a', 0x0a}, {'b', 0x0b}, {'c', 0x0c}, {'d', 0x0d}, {'e', 0x0e}, |
| 38 | + {'f', 0x0f}, {'A', 0x0a}, {'B', 0x0b}, {'C', 0x0c}, {'D', 0x0d}, |
| 39 | + {'E', 0x0e}, {'F', 0x0f}}); |
| 40 | + std::uint64_t block = 0; |
| 41 | + for (int j = 0; j < kMaxUuidBlockLength; ++j) { |
| 42 | + absl::ConsumePrefix(&str, "-"); |
| 43 | + if (str.empty()) { |
| 44 | + return internal::InvalidArgumentError( |
| 45 | + absl::StrFormat("UUID must contain %d hexadecimal digits: %s", |
| 46 | + kUuidNumberOfHexDigits, original_str), |
| 47 | + GCP_ERROR_INFO()); |
| 48 | + } |
| 49 | + auto it = char_to_hex->find(str[0]); |
| 50 | + if (it == char_to_hex->end()) { |
| 51 | + if (str[0] == '-') { |
| 52 | + return internal::InvalidArgumentError( |
| 53 | + absl::StrFormat("UUID cannot contain consecutive hyphens: %s", |
| 54 | + original_str), |
| 55 | + GCP_ERROR_INFO()); |
| 56 | + } |
| 57 | + |
| 58 | + return internal::InvalidArgumentError( |
| 59 | + absl::StrFormat("UUID contains invalid character (%c): %s", str[0], |
| 60 | + original_str), |
| 61 | + GCP_ERROR_INFO()); |
| 62 | + } |
| 63 | + block = (block << 4) + it->second; |
| 64 | + str.remove_prefix(1); |
| 65 | + } |
| 66 | + return block; |
| 67 | +} |
| 68 | +} // namespace |
| 69 | + |
| 70 | +Uuid::Uuid(absl::uint128 value) : uuid_(value) {} |
| 71 | + |
| 72 | +Uuid::Uuid(std::uint64_t high_bits, std::uint64_t low_bits) |
| 73 | + : Uuid(absl::MakeUint128(high_bits, low_bits)) {} |
| 74 | + |
| 75 | +std::pair<std::uint64_t, std::uint64_t> Uuid::As64BitPair() const { |
| 76 | + return std::make_pair(Uint128High64(uuid_), Uint128Low64(uuid_)); |
| 77 | +} |
| 78 | + |
| 79 | +// TODO(#15043): Refactor to handle all 128 bits at once instead of splitting |
| 80 | +// into a pair of unsigned 64-bit integers. |
| 81 | +Uuid::operator std::string() const { |
| 82 | + constexpr int kUuidStringLen = 36; |
| 83 | + constexpr int kChunkLength[] = {8, 4, 4, 4, 12}; |
| 84 | + auto to_hex = [](std::uint64_t v, int start_index, int end_index, char* out) { |
| 85 | + static constexpr char kHexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', |
| 86 | + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; |
| 87 | + for (int i = start_index; i >= end_index; --i) { |
| 88 | + *out++ = kHexChar[(v >> (i * 4)) & 0xf]; |
| 89 | + } |
| 90 | + return start_index - end_index + 1; |
| 91 | + }; |
| 92 | + |
| 93 | + std::string output; |
| 94 | + output.resize(kUuidStringLen); |
| 95 | + char* target = const_cast<char*>(output.data()); |
| 96 | + char* const last = &((output)[output.size()]); |
| 97 | + auto bits = Uint128High64(uuid_); |
| 98 | + int start = 16; |
| 99 | + for (auto length : kChunkLength) { |
| 100 | + int end = start - length; |
| 101 | + target += to_hex(bits, start - 1, end, target); |
| 102 | + // Only hyphens write to valid addresses. |
| 103 | + if (target < last) *(target++) = '-'; |
| 104 | + if (end == 0) { |
| 105 | + start = 16; |
| 106 | + bits = Uint128Low64(uuid_); |
| 107 | + } else { |
| 108 | + start = end; |
| 109 | + } |
| 110 | + } |
| 111 | + return output; |
| 112 | +} |
| 113 | + |
| 114 | +StatusOr<Uuid> MakeUuid(absl::string_view str) { |
| 115 | + absl::string_view original_str = str; |
| 116 | + // Check and remove optional braces |
| 117 | + if (absl::ConsumePrefix(&str, "{")) { |
| 118 | + if (!absl::ConsumeSuffix(&str, "}")) { |
| 119 | + return internal::InvalidArgumentError( |
| 120 | + absl::StrFormat("UUID missing closing '}': %s", original_str), |
| 121 | + GCP_ERROR_INFO()); |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + // Check for leading hyphen after stripping any surrounding braces. |
| 126 | + if (absl::StartsWith(str, "-")) { |
| 127 | + return internal::InvalidArgumentError( |
| 128 | + absl::StrFormat("UUID cannot begin with '-': %s", original_str), |
| 129 | + GCP_ERROR_INFO()); |
| 130 | + } |
| 131 | + |
| 132 | + // TODO(#15043): Refactor to parse all the bits at once. |
| 133 | + auto high_bits = ParseHexBlock(str, original_str); |
| 134 | + if (!high_bits.ok()) return std::move(high_bits).status(); |
| 135 | + auto low_bits = ParseHexBlock(str, original_str); |
| 136 | + if (!low_bits.ok()) return std::move(low_bits).status(); |
| 137 | + |
| 138 | + if (!str.empty()) { |
| 139 | + return internal::InvalidArgumentError( |
| 140 | + absl::StrFormat("Extra characters found after parsing UUID: %s", str), |
| 141 | + GCP_ERROR_INFO()); |
| 142 | + } |
| 143 | + |
| 144 | + return Uuid(absl::MakeUint128(*high_bits, *low_bits)); |
| 145 | +} |
| 146 | + |
| 147 | +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END |
| 148 | +} // namespace spanner |
| 149 | +} // namespace cloud |
| 150 | +} // namespace google |
0 commit comments