uuidx 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +46 -0
- data/LICENSE.txt +21 -0
- data/README.md +195 -0
- data/lib/uuid/errors.rb +11 -0
- data/lib/uuid/gem_version.rb +6 -0
- data/lib/uuid/version4.rb +36 -0
- data/lib/uuid/version6.rb +80 -0
- data/lib/uuid/version7.rb +69 -0
- data/lib/uuid/version8.rb +59 -0
- data/lib/uuid.rb +130 -0
- data/sig/uuid/errors.rbs +7 -0
- data/sig/uuid/version4.rbs +6 -0
- data/sig/uuid/version6.rbs +11 -0
- data/sig/uuid/version7.rbs +10 -0
- data/sig/uuid/version8.rbs +14 -0
- data/sig/uuid.rbs +30 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d2e41b677c8a6cb523605c6f10620796d1b5831ad511a32b49e8d98415629677
|
4
|
+
data.tar.gz: 8e8935a0b62e6538a5de355ec22119a0cba5242a3c76767bb720d5d13192431d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff313efefe5c2ccca58479e2e208cd98d636b74e5311f5e29de20faaf5207f47baf708c304c2e15275fa2a75b26d0f050f7c38174ddeaadcbf8e9449e9dd8a1c
|
7
|
+
data.tar.gz: 4def103fd92484499eef8474454eed8e7f6bc2a0e975e7f22deabe986d02f12d957b7bf2457b8d6c6101e66c4789cffaf3844812b6b0e973c95950e7b7abfbf5
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Unreleased
|
2
|
+
|
3
|
+
# Stable Releases
|
4
|
+
|
5
|
+
## 1.0.0 – ???
|
6
|
+
|
7
|
+
# Alpha Releases
|
8
|
+
|
9
|
+
## 0.9.0 – 2023-02-11
|
10
|
+
- Swap gem name to shorter uuidx
|
11
|
+
- Main documentation written
|
12
|
+
- Add type signatures
|
13
|
+
- Add monotonic batch support to simple API.
|
14
|
+
|
15
|
+
## 0.8.0 – 2023-02-10
|
16
|
+
- Add simple Uuid generation API
|
17
|
+
- Generator classes are now lock-free
|
18
|
+
- Remove UUID value type
|
19
|
+
|
20
|
+
## 0.7.0 – 2023-02-10
|
21
|
+
- Generator APIs now return opaque string values for UUIDs
|
22
|
+
- Add faster UUID v4 implementation
|
23
|
+
|
24
|
+
## 0.6.0 – 2023-02-04
|
25
|
+
- Convert generator modules to classes
|
26
|
+
- Fix clock ID references in UUID v7
|
27
|
+
- Pass tests on all supported versions of Ruby
|
28
|
+
|
29
|
+
## 0.5.0 – 2023-01-31
|
30
|
+
- Threading safety for clock sequence in UUID v6
|
31
|
+
- Clock drift detection in UUID v6
|
32
|
+
|
33
|
+
## 0.4.0 – 2023-01-26
|
34
|
+
- Add clock resolution verification methods
|
35
|
+
- UUID v6 and v7 are now thread-safe
|
36
|
+
|
37
|
+
## 0.3.0 – 2023-01-25
|
38
|
+
- New, faster design of UUID generator modules
|
39
|
+
- Removal of old implementations
|
40
|
+
|
41
|
+
## 0.2.0 – 2023-01-22
|
42
|
+
- UUID v6, v7, and v8 implemented quickly
|
43
|
+
- Some performance optimization done
|
44
|
+
|
45
|
+
## 0.1.0 – 2023-01-18
|
46
|
+
- Initial release without design considerations
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 Stephan Tarulli
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
<h1 align="center">uuidx</h1>
|
2
|
+
<p align="center">Fast Ruby implementations of UUID versions 4, 6, 7, and 8 🪪</p>
|
3
|
+
|
4
|
+
---
|
5
|
+
|
6
|
+
## What's in the box?
|
7
|
+
|
8
|
+
✅ Simple usage documentation written to get started fast. [30 seconds and you're off.](#usage)
|
9
|
+
|
10
|
+
📚 API documentation for the library. [Read the docs.](https://tinychameleon.github.io/uuidx/)
|
11
|
+
|
12
|
+
⚡ A reasonably fast, zero dependency implementation of the new UUID standards.
|
13
|
+
|
14
|
+
🤖 RBS types for your type checking wants. [Typed goodness.](./sig)
|
15
|
+
|
16
|
+
💎 Tests against Ruby 2.7, 3.0, 3.1, and 3.2.
|
17
|
+
|
18
|
+
🔒 MFA protection on gem owners.
|
19
|
+
|
20
|
+
---
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
### Using Bundler
|
25
|
+
|
26
|
+
Run `bundle add uuidx` to update your `Gemfile` and install the gem. You may
|
27
|
+
also add the gem to your `Gemfile` manually and then run `bundle install`.
|
28
|
+
|
29
|
+
### Manual
|
30
|
+
|
31
|
+
Use `gem install uuidx` to install the gem from [RubyGems](https://rubygems.org).
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
To get started using the default generators for UUID v4, v6, or v7 `require`
|
36
|
+
the library and call the associated method.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
require "uuidx"
|
40
|
+
|
41
|
+
Uuid.v4 # => "2b54639d-e43e-489f-9c64-30ecdcac3c95"
|
42
|
+
Uuid.v6 # => "1eda9761-9f6f-6414-8c5f-fd61f1239907"
|
43
|
+
Uuid.v7 # => "01863d24-6d1e-78ba-92ee-6e80c79c4e28"
|
44
|
+
```
|
45
|
+
|
46
|
+
These methods all use default generators and are thread-safe. However, if you
|
47
|
+
are using child processes (like Puma's clustered mode) you should also reset
|
48
|
+
the generators you are using when each child process starts.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# Puma example that resets all default generators.
|
52
|
+
on_worker_boot do
|
53
|
+
Uuid.reset_v4!
|
54
|
+
Uuid.reset_v6!
|
55
|
+
Uuid.reset_v7!
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
This way you will get thread-safe state access per process without requiring
|
60
|
+
IPC.
|
61
|
+
|
62
|
+
### Monotonicity & Batching
|
63
|
+
The simple API provided by `Uuid` also supports monotonic batches. Provide the
|
64
|
+
batch size and you will receive an array of UUID values back.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
Uuid.batch_v4(10) # => ["2b54639d-e43e-489f-9c64-30ecdcac3c95", ...]
|
68
|
+
Uuid.batch_v6(10) # => ["1eda9761-9f6f-6414-8c5f-fd61f1239907", ...]
|
69
|
+
Uuid.batch_v7(10) # => ["01863d24-6d1e-78ba-92ee-6e80c79c4e28", ...]
|
70
|
+
```
|
71
|
+
|
72
|
+
### Advanced Usage
|
73
|
+
If you require multiple generators you can drop below the simple API presented
|
74
|
+
above to create generators directly.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
v6 = Uuid::Version6.new
|
78
|
+
v6.generate # => "1eda9adc-2ed9-629e-9a02-4d2ccc87c569"
|
79
|
+
```
|
80
|
+
|
81
|
+
These generators are lock-free, so if you use them to obtain higher throughput
|
82
|
+
keep in mind that you must ensure they are never shared between threads.
|
83
|
+
|
84
|
+
For typical MRI Ruby workloads using multi-process worker strategies state will
|
85
|
+
be cloned and modified using copy-on-write and the thread safety provided by
|
86
|
+
the simple API is adequate.
|
87
|
+
|
88
|
+
#### UUID v8
|
89
|
+
UUID v8 is a special case of advanced usage that always requires you to build
|
90
|
+
a generator directly. It takes a single parameter to its constructor which must
|
91
|
+
be the class name of your UUID v8 definition.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
v8 = Uuid::Version8.new(MyV8Definition)
|
95
|
+
v8.generate # => "..."
|
96
|
+
```
|
97
|
+
|
98
|
+
The definition class should implement the methods `custom_a`, `custom_b`, and
|
99
|
+
`custom_c` in order to fill out the UUID data [according to the draft](https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-uuid-version-8).
|
100
|
+
|
101
|
+
See the [documentation for Version8](https://tinychameleon.github.io/uuidx/Uuid/Version8.html) for precise details.
|
102
|
+
|
103
|
+
#### Batching
|
104
|
+
Any custom UUID v8 generators can also participate in thread-safe batching by using
|
105
|
+
the `batch` method.
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
Uuid.batch(v8, 10) # => ["<a v8 uuid>", ...]
|
109
|
+
```
|
110
|
+
|
111
|
+
### Clock Resolution
|
112
|
+
If you have need to verify the clock resolution for UUID v6 or v7 you can call
|
113
|
+
the `verify_clock_resolution!` method on either class. A `ClockResolutionError`
|
114
|
+
is raised if the system has insufficient precision.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
begin
|
118
|
+
Uuid::Version6.verify_clock_resolution! # or Uuid::Version7
|
119
|
+
rescue Uuid::ClockResolutionError
|
120
|
+
# ...
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
The API documentation has details about what the clock resolution must be for
|
125
|
+
each of the UUID versions. See the
|
126
|
+
[Version 6](https://tinychameleon.github.io/uuidx/Uuid/Version6.html) and
|
127
|
+
[Version 7](https://tinychameleon.github.io/uuidx/Uuid/Version7.html)
|
128
|
+
documentation for details.
|
129
|
+
|
130
|
+
### A Note on Clock Timings
|
131
|
+
The API documentation contains specific details around how the implementations
|
132
|
+
deal with clock drift. See the
|
133
|
+
[Uuid](https://tinychameleon.github.io/uuidx/Uuid.html),
|
134
|
+
[Version 6](https://tinychameleon.github.io/uuidx/Uuid/Version6.html), and
|
135
|
+
[Version 7](https://tinychameleon.github.io/uuidx/Uuid/Version7.html)
|
136
|
+
documentation for more information.
|
137
|
+
|
138
|
+
## Performance
|
139
|
+
This performance data was captured using `benchmark/ips`. It uses the following
|
140
|
+
Ruby version
|
141
|
+
|
142
|
+
ruby 3.2.0 (2022-12-25 revision a528908271) [arm64-darwin22]
|
143
|
+
|
144
|
+
and is run on a Macbook Pro with an M1 Max CPU.
|
145
|
+
|
146
|
+
❯ bundle exec ruby test/benchmarks/simple_api.rb
|
147
|
+
Warming up --------------------------------------
|
148
|
+
stdlib 60.617k i/100ms
|
149
|
+
uuid4 106.927k i/100ms
|
150
|
+
uuid6 111.417k i/100ms
|
151
|
+
uuid7 103.551k i/100ms
|
152
|
+
Calculating -------------------------------------
|
153
|
+
stdlib 626.957k (± 0.3%) i/s - 3.152M in 5.027633s
|
154
|
+
uuid4 1.077M (± 1.2%) i/s - 5.453M in 5.065726s
|
155
|
+
uuid6 1.121M (± 0.2%) i/s - 5.682M in 5.067392s
|
156
|
+
uuid7 1.027M (± 1.3%) i/s - 5.178M in 5.041666s
|
157
|
+
|
158
|
+
Comparison:
|
159
|
+
uuid6: 1121344.6 i/s
|
160
|
+
uuid4: 1076662.4 i/s - 1.04x (± 0.00) slower
|
161
|
+
uuid7: 1027127.8 i/s - 1.09x (± 0.00) slower
|
162
|
+
stdlib: 626957.1 i/s - 1.79x (± 0.00) slower
|
163
|
+
|
164
|
+
|
165
|
+
As reported, the `stdlib` version of `SecureRandom.uuid` is at least 1.70x
|
166
|
+
slower than the simple API implementation of UUID v4.
|
167
|
+
|
168
|
+
These timings are good enough that they shouldn't get in the way of any web
|
169
|
+
frameworks.
|
170
|
+
|
171
|
+
## Development
|
172
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
173
|
+
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
174
|
+
prompt that will allow you to experiment.
|
175
|
+
|
176
|
+
To run tests across all supported Ruby versions on Debian and Alpine Linux run
|
177
|
+
`make test-all`. To run a specific version use `make test-[version]`;
|
178
|
+
for example, `make test-3.2.0`.
|
179
|
+
|
180
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
181
|
+
|
182
|
+
To release a new version:
|
183
|
+
|
184
|
+
1. ensure the documentation and changelog are ready
|
185
|
+
2. run `bundle exec rake rdoc` to generate the new documentation and commit it
|
186
|
+
3. update the version number in `lib/uuid/gem_version.rb`
|
187
|
+
4. run `bundle install` to update the `Gemfile.lock`
|
188
|
+
5. create a release commit with the version updates
|
189
|
+
6. run `bundle exec rake release` to tag and push the version to RubyGems
|
190
|
+
|
191
|
+
## Contributing
|
192
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tinychameleon/uuidx.
|
193
|
+
|
194
|
+
## License
|
195
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/uuid/errors.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module Uuid
|
6
|
+
# UUID Version 7 defined by the
|
7
|
+
# {RFC 4122 BIS-01 Draft}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-uuid-version-4].
|
8
|
+
#
|
9
|
+
# To construct a new UUID v4 value create a generator, then use #generate.
|
10
|
+
# g = Uuid::Version4.new
|
11
|
+
# g.generate # => "2b54639d-e43e-489f-9c64-30ecdcac3c95"
|
12
|
+
#
|
13
|
+
# The implementation will cache 1024 bytes of random data from +SecureRandom+ to facilitate faster construction.
|
14
|
+
class Version4
|
15
|
+
VERSION_VARIANT = (0x4 << 76) | (0x2 << 62) # :nodoc:
|
16
|
+
BUFFER_SIZE = 64 # :nodoc:
|
17
|
+
NEEDED_BYTES = BUFFER_SIZE * 16 # :nodoc:
|
18
|
+
UNPACK_FORMAT = "QQ" * BUFFER_SIZE # :nodoc:
|
19
|
+
RANDOM_AB_MASK = 0xffffffff_ffff0fff # :nodoc:
|
20
|
+
AB_SHIFT = 64 # :nodoc:
|
21
|
+
RANDOM_C_MASK = 0x3fffffff_ffffffff # :nodoc:
|
22
|
+
|
23
|
+
# Construct a UUID v4 generator.
|
24
|
+
def initialize
|
25
|
+
@pool = []
|
26
|
+
end
|
27
|
+
|
28
|
+
# Construct a UUID v4 value.
|
29
|
+
def generate
|
30
|
+
@pool = SecureRandom.bytes(NEEDED_BYTES).unpack(UNPACK_FORMAT) if @pool.empty?
|
31
|
+
ab, c = @pool.pop(2)
|
32
|
+
|
33
|
+
Uuid.format(VERSION_VARIANT | ((ab & RANDOM_AB_MASK) << AB_SHIFT) | (c & RANDOM_C_MASK))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module Uuid
|
6
|
+
# UUID Version 6 defined by the
|
7
|
+
# {RFC 4122 BIS-01 Draft}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-uuid-version-6].
|
8
|
+
#
|
9
|
+
# To construct a new UUID v6 value create a generator, then use #generate.
|
10
|
+
# g = Uuid::Version6.new
|
11
|
+
# g.generate # => "1eda9761-9f6f-6414-8c5f-fd61f1239907"
|
12
|
+
#
|
13
|
+
# The implementation will use +SecureRandom+ to populate the Node and Clock Sequence bits with a random value
|
14
|
+
# at module load time.
|
15
|
+
#
|
16
|
+
# Generation is thread-safe, but if you are using multi-process clusters you should call #reset! at the start
|
17
|
+
# of each process to reduce the chance of two processes generating the same value.
|
18
|
+
#
|
19
|
+
# If you have need to make sure that the clock resolution is sufficient for the v6 specification
|
20
|
+
# you can call ::verify_clock_resolution! and handle the ClockResolutionError as you see fit.
|
21
|
+
#
|
22
|
+
# begin
|
23
|
+
# Uuid::Version6.verify_clock_resolution!
|
24
|
+
# rescue Uuid::ClockResolutionError
|
25
|
+
# # ...
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# The necessary clock resolution for v6 is 100ns.
|
29
|
+
#
|
30
|
+
# ===== A Note on Clock Timings
|
31
|
+
# To combat clock drift, leap-second smearing, and other clock value changes that can appear without
|
32
|
+
# requiring additional compute cost this implementation always increments the clock sequence number.
|
33
|
+
class Version6
|
34
|
+
VERSION_VARIANT = (0x6 << 76) + (0x2 << 62) # :nodoc:
|
35
|
+
GREGORIAN_MICROSECOND_TENTHS = 122_192_928_000_000_000 # :nodoc:
|
36
|
+
CLOCK_SEQ_SHIFT = 48 # :nodoc:
|
37
|
+
CLOCK_SEQ_INCREMENT = 1 << CLOCK_SEQ_SHIFT # :nodoc:
|
38
|
+
CLOCK_SEQ_MASK = 0x3fff << CLOCK_SEQ_SHIFT # :nodoc:
|
39
|
+
TS_NS_FACTOR = 100 # :nodoc:
|
40
|
+
TS_LOW_MASK = 0xfff # :nodoc:
|
41
|
+
TS_HIGH_MID_MASK = 0xffffffff_ffff0000 # :nodoc:
|
42
|
+
TS_MASK_SHIFT = 4 # :nodoc:
|
43
|
+
TS_POSITIONAL_SHIFT = 64 # :nodoc:
|
44
|
+
NODE_ID_MASK = 0xffff_ffffffff # :nodoc:
|
45
|
+
NODE_ID_MC_BIT = 0x0100_00000000 # :nodoc:
|
46
|
+
|
47
|
+
# Construct a new UUID v6 generator.
|
48
|
+
def initialize
|
49
|
+
reset!
|
50
|
+
end
|
51
|
+
|
52
|
+
# Construct a UUID v6 value.
|
53
|
+
def generate
|
54
|
+
@clock_sequence = (@clock_sequence + CLOCK_SEQ_INCREMENT) & CLOCK_SEQ_MASK
|
55
|
+
|
56
|
+
ts = GREGORIAN_MICROSECOND_TENTHS + (Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) / TS_NS_FACTOR)
|
57
|
+
ts = ((ts << TS_MASK_SHIFT) & TS_HIGH_MID_MASK) | (ts & TS_LOW_MASK)
|
58
|
+
|
59
|
+
Uuid.format(VERSION_VARIANT | (ts << TS_POSITIONAL_SHIFT) | @clock_sequence | @node_id)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Reset the generator with a new random node ID and clock sequence.
|
63
|
+
#
|
64
|
+
# This method is not thread-safe and should only be called at application or child process start.
|
65
|
+
def reset!
|
66
|
+
@node_id = (SecureRandom.bytes(8).unpack1("Q") & NODE_ID_MASK) | NODE_ID_MC_BIT
|
67
|
+
@clock_sequence = SecureRandom.bytes(4).unpack1("L") << CLOCK_SEQ_SHIFT
|
68
|
+
end
|
69
|
+
|
70
|
+
# Verify that the clock resolution is capable of 100ns resolution.
|
71
|
+
#
|
72
|
+
# Raises ClockResolutionError when the clock resolution is insufficient.
|
73
|
+
def self.verify_clock_resolution!
|
74
|
+
ns_res = Process.clock_getres(Process::CLOCK_REALTIME, :nanosecond)
|
75
|
+
raise ClockResolutionError, "Detected #{ns_res}ns resolution, need <= 100ns" if ns_res > 100
|
76
|
+
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module Uuid
|
6
|
+
# UUID Version 7 defined by the
|
7
|
+
# {RFC 4122 BIS-01 Draft}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-uuid-version-7].
|
8
|
+
#
|
9
|
+
# To construct a new UUID v7 value create a generator, then use #generate.
|
10
|
+
# g = Uuid::Version7.new
|
11
|
+
# g.generate # => "01863d24-6d1e-78ba-92ee-6e80c79c4e28"
|
12
|
+
#
|
13
|
+
# The implementation will cache 640 bytes of random data from +SecureRandom+ to facilitate faster construction.
|
14
|
+
#
|
15
|
+
# If you have need to make sure that the clock resolution is sufficient for the v7 specification
|
16
|
+
# you can call ::verify_clock_resolution! and handle the ClockResolutionError as you see fit.
|
17
|
+
#
|
18
|
+
# begin
|
19
|
+
# Uuid::Version7.verify_clock_resolution!
|
20
|
+
# rescue Uuid::ClockResolutionError
|
21
|
+
# # ...
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# The necessary clock resolution for v6 is 1ms.
|
25
|
+
#
|
26
|
+
# ===== A Note on Clock Timings
|
27
|
+
# To combat clock drift, leap-second smearing, and other clock value changes that can appear without
|
28
|
+
# requiring additional compute cost this implementation relies on secure random data to have sufficient
|
29
|
+
# variation such that all generated UUIDs within 1ms have a low probability of collision.
|
30
|
+
#
|
31
|
+
# For a 0.001% chance of collision, the 74 bits of random data will require between 6.1×10^6 and 4.0×10^11
|
32
|
+
# UUIDs to be generated in 1ms.
|
33
|
+
class Version7
|
34
|
+
VERSION_VARIANT = (0x7 << 76) | (0x2 << 62) # :nodoc:
|
35
|
+
BUFFER_SIZE = 64 # :nodoc:
|
36
|
+
NEEDED_BYTES = BUFFER_SIZE * 10 # :nodoc:
|
37
|
+
UNPACK_FORMAT = "SQ" * BUFFER_SIZE # :nodoc:
|
38
|
+
TS_MASK = 0xffff_ffffffff # :nodoc:
|
39
|
+
RAND_A_MASK = 0xfff # :nodoc:
|
40
|
+
RAND_B_MASK = 0x3fffffff_ffffffff # :nodoc:
|
41
|
+
TS_SHIFT = 16 # :nodoc:
|
42
|
+
HIGH_SHIFT = 64 # :nodoc:
|
43
|
+
|
44
|
+
# Construct a UUID v7 generator.
|
45
|
+
def initialize
|
46
|
+
@pool = []
|
47
|
+
end
|
48
|
+
|
49
|
+
# Construct a UUID v7 value.
|
50
|
+
def generate
|
51
|
+
@pool = SecureRandom.bytes(NEEDED_BYTES).unpack(UNPACK_FORMAT) if @pool.empty?
|
52
|
+
a, b = @pool.pop(2)
|
53
|
+
ts = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) & TS_MASK
|
54
|
+
high = (ts << TS_SHIFT) | (a & RAND_A_MASK)
|
55
|
+
|
56
|
+
Uuid.format(VERSION_VARIANT | (high << HIGH_SHIFT) | (b & RAND_B_MASK))
|
57
|
+
end
|
58
|
+
|
59
|
+
# Verify that the clock resolution is capable of 1ms resolution.
|
60
|
+
#
|
61
|
+
# Raises ClockResolutionError when the clock resolution is insufficient.
|
62
|
+
def self.verify_clock_resolution!
|
63
|
+
ms_res = Process.clock_getres(Process::CLOCK_REALTIME, :millisecond)
|
64
|
+
raise ClockResolutionError, "Detected #{ms_res}ms resolution, need <= 1ms" if ms_res > 1
|
65
|
+
|
66
|
+
true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uuid
|
4
|
+
# UUID Version 8 defined by the
|
5
|
+
# {RFC 4122 BIS-01 Draft}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-uuid-version-8].
|
6
|
+
#
|
7
|
+
# Since UUID v8 is entirely custom to your application, first create a generator definition class.
|
8
|
+
# class MyGeneratorDefinition
|
9
|
+
# def custom_a
|
10
|
+
# 1
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# def custom_b
|
14
|
+
# 2
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def custom_c
|
18
|
+
# 3
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# The requirements for each method are:
|
23
|
+
#
|
24
|
+
# - +custom_a+ should generate a 48-bit value which will be the most significant 6 octets.
|
25
|
+
# - +custom_b+ should generate a 12-bit value which is placed between the version and variant bits.
|
26
|
+
# - +custom_c+ should generate a 62-bit value which acts as the remaining least significant octets.
|
27
|
+
#
|
28
|
+
# Then create a UUID v8 generator by passing in the class, and call #generate.
|
29
|
+
# g = Uuid::Version8.new(MyGeneratorDefinition)
|
30
|
+
# g.generate # => "00000000-0001-8002-8000-000000000003"
|
31
|
+
#
|
32
|
+
# The implementation will truncate the results of each generator module method so that they abide by the bit lengths
|
33
|
+
# of the UUID specification. The thread safety of UUID v8 depends entirely on your implementation.
|
34
|
+
#
|
35
|
+
# There is no default implementation of UUID v8.
|
36
|
+
class Version8
|
37
|
+
VERSION_VARIANT = (0x8 << 76) | (0x2 << 62) # :nodoc:
|
38
|
+
CUSTOM_A_MASK = 0xffff_ffffffff # :nodoc:
|
39
|
+
CUSTOM_B_MASK = 0xfff # :nodoc:
|
40
|
+
CUSTOM_C_MASK = 0x3fffffff_ffffffff # :nodoc:
|
41
|
+
A_SHIFT = 16 # :nodoc:
|
42
|
+
HIGH_SHIFT = 64 # :nodoc:
|
43
|
+
|
44
|
+
# Construct a UUID v8 generator.
|
45
|
+
def initialize(definition_class)
|
46
|
+
@definition = definition_class.new
|
47
|
+
end
|
48
|
+
|
49
|
+
# Construct a UUID v8 value.
|
50
|
+
def generate
|
51
|
+
a = @definition.custom_a & CUSTOM_A_MASK
|
52
|
+
b = @definition.custom_b & CUSTOM_B_MASK
|
53
|
+
high = (a << A_SHIFT) | b
|
54
|
+
c = @definition.custom_c & CUSTOM_C_MASK
|
55
|
+
|
56
|
+
Uuid.format(VERSION_VARIANT | (high << HIGH_SHIFT) | c)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/uuid.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require_relative "uuid/gem_version"
|
5
|
+
require_relative "uuid/errors"
|
6
|
+
require_relative "uuid/version4"
|
7
|
+
require_relative "uuid/version6"
|
8
|
+
require_relative "uuid/version7"
|
9
|
+
require_relative "uuid/version8"
|
10
|
+
|
11
|
+
# The Uuid module contains a simple API to generate v4, v6, and v7 UUIDs
|
12
|
+
# without needing to create generators manually.
|
13
|
+
#
|
14
|
+
# The simple API is exposed as a set of methods ::v4, ::v6, and ::v7 which
|
15
|
+
# handle thread-safety and generator creation.
|
16
|
+
#
|
17
|
+
# Uuid.v4 # => "2b54639d-e43e-489f-9c64-30ecdcac3c95"
|
18
|
+
# Uuid.v6 # => "1eda9761-9f6f-6414-8c5f-fd61f1239907"
|
19
|
+
# Uuid.v7 # => "01863d24-6d1e-78ba-92ee-6e80c79c4e28"
|
20
|
+
#
|
21
|
+
# See the Version4, Version6, and Version7 classes for details on how to create
|
22
|
+
# generators manually.
|
23
|
+
#
|
24
|
+
# ===== Monotonic Batching
|
25
|
+
# The simple API also provides thread-safe monotonic batch methods which expect
|
26
|
+
# an amount.
|
27
|
+
#
|
28
|
+
# Uuid.batch_v4(10) # => ["2b54639d-e43e-489f-9c64-30ecdcac3c95", ...]
|
29
|
+
# Uuid.batch_v6(10) # => ["1eda9761-9f6f-6414-8c5f-fd61f1239907", ...]
|
30
|
+
# Uuid.batch_v7(10) # => ["01863d24-6d1e-78ba-92ee-6e80c79c4e28", ...]
|
31
|
+
#
|
32
|
+
# Monotonicity has little meaning with UUID v4, but the batches are ordered for
|
33
|
+
# consistency.
|
34
|
+
#
|
35
|
+
# ===== A Note on Clock Timings
|
36
|
+
# This library uses the +Process::CLOCK_REALTIME+ clock ID to obtain the current
|
37
|
+
# time. While the specification allows for implementations to manipulate time
|
38
|
+
# values, this library does not. Any system-based smearing or drift will appear
|
39
|
+
# within the timestamp values.
|
40
|
+
#
|
41
|
+
# See the Version6 and Version7 documentation for manifestation details.
|
42
|
+
module Uuid
|
43
|
+
# The nil UUID as defined by
|
44
|
+
# {§5.10 of RFC 4122 BIS-01}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-nil-uuid].
|
45
|
+
#
|
46
|
+
# This UUID is written as +00000000-0000-0000-0000-000000000000+ and is less than all other UUIDs in comparisons.
|
47
|
+
# It does not act as +nil+.
|
48
|
+
def self.nil_uuid
|
49
|
+
"00000000-0000-0000-0000-000000000000"
|
50
|
+
end
|
51
|
+
|
52
|
+
# The maximum UUID as defined by
|
53
|
+
# {§5.10 of RFC 4122 BIS-01}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-max-uuid].
|
54
|
+
#
|
55
|
+
# This UUID is written as +ffffffff-ffff-ffff-ffff-ffffffffffff+ and is greater than all other UUIDs in comparisons.
|
56
|
+
def self.max_uuid
|
57
|
+
"ffffffff-ffff-ffff-ffff-ffffffffffff"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Generate a new UUID v4 value using the default generator.
|
61
|
+
def self.v4
|
62
|
+
@lock4.synchronize { @uuid4.generate }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Generate a new UUID v6 value using the default generator.
|
66
|
+
def self.v6
|
67
|
+
@lock6.synchronize { @uuid6.generate }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Generate a new UUID v7 value using the default generator.
|
71
|
+
def self.v7
|
72
|
+
@lock7.synchronize { @uuid7.generate }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Create a batch of UUID v4 values using the default generator.
|
76
|
+
def self.batch_v4(amount)
|
77
|
+
@lock4.synchronize { batch(@uuid4, amount) }
|
78
|
+
end
|
79
|
+
|
80
|
+
# Create a batch of UUID v6 values using the default generator.
|
81
|
+
def self.batch_v6(amount)
|
82
|
+
@lock6.synchronize { batch(@uuid6, amount) }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Create a batch of UUID v7 values using the default generator.
|
86
|
+
def self.batch_v7(amount)
|
87
|
+
@lock7.synchronize { batch(@uuid7, amount) }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Reset the UUID v4 default generator.
|
91
|
+
def self.reset_v4!
|
92
|
+
@lock4 = Mutex.new
|
93
|
+
@uuid4 = Version4.new
|
94
|
+
end
|
95
|
+
|
96
|
+
# Reset the UUID v6 default generator.
|
97
|
+
def self.reset_v6!
|
98
|
+
@lock6 = Mutex.new
|
99
|
+
@uuid6 = Version6.new
|
100
|
+
end
|
101
|
+
|
102
|
+
# Reset the UUID v7 default generator.
|
103
|
+
def self.reset_v7!
|
104
|
+
@lock7 = Mutex.new
|
105
|
+
@uuid7 = Version7.new
|
106
|
+
end
|
107
|
+
|
108
|
+
# Create a batch of UUIDs from a generator.
|
109
|
+
#
|
110
|
+
# This can be useful for custom Version8 generators.
|
111
|
+
def self.batch(generator, amount)
|
112
|
+
s = Set.new
|
113
|
+
s << generator.generate while s.length < amount
|
114
|
+
s.sort
|
115
|
+
end
|
116
|
+
|
117
|
+
reset_v4!
|
118
|
+
reset_v6!
|
119
|
+
reset_v7!
|
120
|
+
|
121
|
+
# :nodoc:
|
122
|
+
def self.format(value)
|
123
|
+
b = +value.to_s(16).rjust(32, "0")
|
124
|
+
b.insert(8, "-")
|
125
|
+
b.insert(13, "-")
|
126
|
+
b.insert(18, "-")
|
127
|
+
b.insert(23, "-")
|
128
|
+
b.freeze
|
129
|
+
end
|
130
|
+
end
|
data/sig/uuid/errors.rbs
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Uuid
|
2
|
+
class Version8
|
3
|
+
public
|
4
|
+
|
5
|
+
interface _GeneratorDefinition
|
6
|
+
def custom_a: () -> Integer
|
7
|
+
def custom_b: () -> Integer
|
8
|
+
def custom_c: () -> Integer
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize: [Definition < _GeneratorDefinition] (Definition) -> void
|
12
|
+
def generate: () -> uuid
|
13
|
+
end
|
14
|
+
end
|
data/sig/uuid.rbs
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# See the writing guide of rbs: https://github.com/ruby/rbs#guides
|
2
|
+
|
3
|
+
module Uuid
|
4
|
+
VERSION: String
|
5
|
+
|
6
|
+
type uuid = String
|
7
|
+
|
8
|
+
public
|
9
|
+
|
10
|
+
interface _Generator
|
11
|
+
def generate: () -> uuid
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.nil_uuid: () -> uuid
|
15
|
+
def self.max_uuid: () -> uuid
|
16
|
+
def self.v4: () -> uuid
|
17
|
+
def self.v6: () -> uuid
|
18
|
+
def self.v7: () -> uuid
|
19
|
+
def self.batch_v4: (Integer) -> Array[uuid]
|
20
|
+
def self.batch_v6: (Integer) -> Array[uuid]
|
21
|
+
def self.batch_v7: (Integer) -> Array[uuid]
|
22
|
+
def self.reset_v4!: () -> void
|
23
|
+
def self.reset_v6!: () -> void
|
24
|
+
def self.reset_v7!: () -> void
|
25
|
+
def self.batch: (_Generator, Integer) -> Array[uuid]
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def self.format: (Integer) -> uuid
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uuidx
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephan Tarulli
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-02-11 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: "A fast Ruby implementation of UUID versions 4, 6, 7, and 8 \U0001FAAA"
|
14
|
+
email:
|
15
|
+
- srt@tinychameleon.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- CHANGELOG.md
|
21
|
+
- LICENSE.txt
|
22
|
+
- README.md
|
23
|
+
- lib/uuid.rb
|
24
|
+
- lib/uuid/errors.rb
|
25
|
+
- lib/uuid/gem_version.rb
|
26
|
+
- lib/uuid/version4.rb
|
27
|
+
- lib/uuid/version6.rb
|
28
|
+
- lib/uuid/version7.rb
|
29
|
+
- lib/uuid/version8.rb
|
30
|
+
- sig/uuid.rbs
|
31
|
+
- sig/uuid/errors.rbs
|
32
|
+
- sig/uuid/version4.rbs
|
33
|
+
- sig/uuid/version6.rbs
|
34
|
+
- sig/uuid/version7.rbs
|
35
|
+
- sig/uuid/version8.rbs
|
36
|
+
homepage: https://github.com/tinychameleon/uuidx
|
37
|
+
licenses:
|
38
|
+
- MIT
|
39
|
+
metadata:
|
40
|
+
rubygems_mfa_required: 'true'
|
41
|
+
homepage_uri: https://github.com/tinychameleon/uuidx
|
42
|
+
source_code_uri: https://github.com/tinychameleon/uuidx
|
43
|
+
changelog_uri: https://github.com/tinychameleon/uuidx/blob/main/CHANGELOG.md
|
44
|
+
bug_tracker_uri: https://github.com/tinychameleon/uuidx/issues
|
45
|
+
documentation_uri: https://tinychameleon.github.io/uuidx/
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.7.0
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubygems_version: 3.4.3
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: "A fast Ruby implementation of UUID versions 4, 6, 7, and 8 \U0001FAAA"
|
65
|
+
test_files: []
|