forked from scylladb/scylladb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathUUID.hh
288 lines (247 loc) · 9.1 KB
/
UUID.hh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#pragma once
/*
* Copyright (C) 2015-present ScyllaDB
*/
/*
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
*/
// This class is the parts of java.util.UUID that we need
#include <stdint.h>
#include <cassert>
#include <array>
#include <iosfwd>
#include <compare>
#include <seastar/core/sstring.hh>
#include "bytes_fwd.hh"
#include "utils/assert.hh"
#include "utils/hashing.hh"
#include "utils/serialization.hh"
namespace utils {
class UUID {
private:
int64_t most_sig_bits;
int64_t least_sig_bits;
public:
constexpr UUID() noexcept : most_sig_bits(0), least_sig_bits(0) {}
constexpr UUID(int64_t most_sig_bits, int64_t least_sig_bits) noexcept
: most_sig_bits(most_sig_bits), least_sig_bits(least_sig_bits) {}
// May throw marshal_exception is failed to parse uuid string.
explicit UUID(const sstring& uuid_string) : UUID(std::string_view(uuid_string)) { }
explicit UUID(const char * s) : UUID(std::string_view(s)) {}
explicit UUID(std::string_view uuid_string);
int64_t get_most_significant_bits() const noexcept {
return most_sig_bits;
}
int64_t get_least_significant_bits() const noexcept {
return least_sig_bits;
}
int version() const noexcept {
return (most_sig_bits >> 12) & 0xf;
}
bool is_timestamp() const noexcept {
return version() == 1;
}
int64_t timestamp() const noexcept {
//if (version() != 1) {
// throw new UnsupportedOperationException("Not a time-based UUID");
//}
SCYLLA_ASSERT(is_timestamp());
return ((most_sig_bits & 0xFFF) << 48) |
(((most_sig_bits >> 16) & 0xFFFF) << 32) |
(((uint64_t)most_sig_bits) >> 32);
}
friend ::fmt::formatter<UUID>;
friend std::ostream& operator<<(std::ostream& out, const UUID& uuid);
bool operator==(const UUID& v) const noexcept = default;
// Please note that this comparator does not preserve timeuuid
// monotonicity. For this reason you should avoid using it for
// UUIDs that could store timeuuids, otherwise bugs like
// https://github.com/scylladb/scylla/issues/7729 may happen.
//
// For comparing timeuuids, you can use `timeuuid_tri_compare`
// functions from this file.
std::strong_ordering operator<=>(const UUID& v) const noexcept {
auto cmp = uint64_t(most_sig_bits) <=> uint64_t(v.most_sig_bits);
if (cmp != 0) {
return cmp;
}
return uint64_t(least_sig_bits) <=> uint64_t(v.least_sig_bits);
}
// nibble set to a non-zero value
bool is_null() const noexcept {
return !most_sig_bits && !least_sig_bits;
}
explicit operator bool() const noexcept {
return !is_null();
}
bytes serialize() const {
bytes b(bytes::initialized_later(), serialized_size());
auto i = b.begin();
serialize(i);
return b;
}
constexpr static size_t serialized_size() noexcept {
return 16;
}
template <typename CharOutputIterator>
void serialize(CharOutputIterator& out) const {
serialize_int64(out, most_sig_bits);
serialize_int64(out, least_sig_bits);
}
};
// Convert the uuid to a uint32_t using xor.
// It is useful to get a uint32_t number from the uuid.
uint32_t uuid_xor_to_uint32(const UUID& uuid);
inline constexpr UUID null_uuid() noexcept {
return UUID();
}
UUID make_random_uuid() noexcept;
// Read 8 most significant bytes of timeuuid from serialized bytes
inline uint64_t timeuuid_read_msb(const int8_t *b) noexcept {
// cast to unsigned to avoid sign-compliment during shift.
auto u64 = [](uint8_t i) -> uint64_t { return i; };
// Scylla and Cassandra use a standard UUID memory layout for MSB:
// 4 bytes 2 bytes 2 bytes
// time_low - time_mid - time_hi_and_version
//
// The storage format uses network byte order.
// Reorder bytes to allow for an integer compare.
return u64(b[6] & 0xf) << 56 | u64(b[7]) << 48 |
u64(b[4]) << 40 | u64(b[5]) << 32 |
u64(b[0]) << 24 | u64(b[1]) << 16 |
u64(b[2]) << 8 | u64(b[3]);
}
inline uint64_t uuid_read_lsb(const int8_t *b) noexcept {
auto u64 = [](uint8_t i) -> uint64_t { return i; };
return u64(b[8]) << 56 | u64(b[9]) << 48 |
u64(b[10]) << 40 | u64(b[11]) << 32 |
u64(b[12]) << 24 | u64(b[13]) << 16 |
u64(b[14]) << 8 | u64(b[15]);
}
// Compare two values of timeuuid type.
// Cassandra legacy requires:
// - using signed compare for least significant bits.
// - masking off UUID version during compare, to
// treat possible non-version-1 UUID the same way as UUID.
//
// To avoid breaking ordering in existing sstables, Scylla preserves
// Cassandra compare order.
//
inline std::strong_ordering timeuuid_tri_compare(const int8_t* o1, const int8_t* o2) noexcept {
auto timeuuid_read_lsb = [](const int8_t* o) -> uint64_t {
return uuid_read_lsb(o) ^ 0x8080808080808080;
};
auto res = timeuuid_read_msb(o1) <=> timeuuid_read_msb(o2);
if (res == 0) {
res = timeuuid_read_lsb(o1) <=> timeuuid_read_lsb(o2);
}
return res;
}
inline std::strong_ordering timeuuid_tri_compare(bytes_view o1, bytes_view o2) noexcept {
return timeuuid_tri_compare(o1.begin(), o2.begin());
}
inline std::strong_ordering timeuuid_tri_compare(const UUID& u1, const UUID& u2) noexcept {
std::array<int8_t, UUID::serialized_size()> buf1;
{
auto i = buf1.begin();
u1.serialize(i);
}
std::array<int8_t, UUID::serialized_size()> buf2;
{
auto i = buf2.begin();
u2.serialize(i);
}
return timeuuid_tri_compare(buf1.begin(), buf2.begin());
}
// Compare two values of UUID type, if they happen to be
// both of Version 1 (timeuuids).
//
// This function uses memory order for least significant bits,
// which is both faster and monotonic, so should be preferred
// to @timeuuid_tri_compare() used for all new features.
//
inline std::strong_ordering uuid_tri_compare_timeuuid(bytes_view o1, bytes_view o2) noexcept {
auto res = timeuuid_read_msb(o1.begin()) <=> timeuuid_read_msb(o2.begin());
if (res == 0) {
res = uuid_read_lsb(o1.begin()) <=> uuid_read_lsb(o2.begin());
}
return res;
}
template<typename Tag>
struct tagged_uuid {
utils::UUID id;
std::strong_ordering operator<=>(const tagged_uuid&) const noexcept = default;
explicit operator bool() const noexcept {
// The default constructor sets the id to nil, which is
// guaranteed to not match any valid id.
return bool(id);
}
static tagged_uuid create_random_id() noexcept { return tagged_uuid{utils::make_random_uuid()}; }
static constexpr tagged_uuid create_null_id() noexcept { return tagged_uuid{}; }
explicit constexpr tagged_uuid(const utils::UUID& uuid) noexcept : id(uuid) {}
constexpr tagged_uuid() = default;
const utils::UUID& uuid() const noexcept {
return id;
}
sstring to_sstring() const {
return fmt::to_string(id);
}
};
} // namespace utils
template<>
struct appending_hash<utils::UUID> {
template<typename Hasher>
void operator()(Hasher& h, const utils::UUID& id) const noexcept {
feed_hash(h, id.get_most_significant_bits());
feed_hash(h, id.get_least_significant_bits());
}
};
template<typename Tag>
struct appending_hash<utils::tagged_uuid<Tag>> {
template<typename Hasher>
void operator()(Hasher& h, const utils::tagged_uuid<Tag>& id) const noexcept {
appending_hash<utils::UUID>{}(h, id.uuid());
}
};
namespace std {
template<>
struct hash<utils::UUID> {
size_t operator()(const utils::UUID& id) const noexcept {
auto hilo = id.get_most_significant_bits()
^ id.get_least_significant_bits();
return size_t((hilo >> 32) ^ hilo);
}
};
template<typename Tag>
struct hash<utils::tagged_uuid<Tag>> {
size_t operator()(const utils::tagged_uuid<Tag>& id) const noexcept {
return hash<utils::UUID>()(id.id);
}
};
template<typename Tag>
std::ostream& operator<<(std::ostream& os, const utils::tagged_uuid<Tag>& id) {
return os << id.id;
}
} // namespace std
template <>
struct fmt::formatter<utils::UUID> : fmt::formatter<string_view> {
template <typename FormatContext>
auto format(const utils::UUID& id, FormatContext& ctx) const {
// This matches Java's UUID.toString() actual implementation. Note that
// that method's documentation suggest something completely different!
return fmt::format_to(ctx.out(),
"{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
((uint64_t)id.most_sig_bits >> 32),
((uint64_t)id.most_sig_bits >> 16 & 0xffff),
((uint64_t)id.most_sig_bits & 0xffff),
((uint64_t)id.least_sig_bits >> 48 & 0xffff),
((uint64_t)id.least_sig_bits & 0xffffffffffffLL));
}
};
template <typename Tag>
struct fmt::formatter<utils::tagged_uuid<Tag>> : fmt::formatter<string_view> {
template <typename FormatContext>
auto format(const utils::tagged_uuid<Tag>& id, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{}", id.id);
}
};