securerandom 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ae0b7af17b7e1ae5e5538a9faf771856d7052e570dd2edb7f3876b44ff73caa
4
- data.tar.gz: 99bbfb8ac5dfa0f2089e4b8ab18a9dcf3628bb1b2564778b14045056e6473879
3
+ metadata.gz: 1e5fca1a0e74f7d256340e2b2e12196ed97adc8ebc512697473d56c110fe682c
4
+ data.tar.gz: 19d45327f812eede26a7a1432381e8c43d114ca0268ea1697b727db5c94c5ca9
5
5
  SHA512:
6
- metadata.gz: 5a6b61eecc2cf9681ea54abc84909635dca596e1ff453c5af84e75e87b042345e5d9b55f30a99149c61f7407e9238993dbc836efc9feb03067cb0538770cfa5d
7
- data.tar.gz: c458cecffdea575d9eea0ec41341520d1465d359dda792b8448149a5c32b6e76ccf8f85074c919a97d916dfe379e41f97d461e5dd242a155708808cf99dc7e07
6
+ metadata.gz: e36e2adb4f23bf29d52a519e68e89dd5c947803399fd493bdab26c452e38142f11d7af03ef436f70c6f7a3c8881cee75b179167b5376556dfc91e4ab3f8ccc46
7
+ data.tar.gz: 0e86593665d73afc22c91f6964df05f6134dc3b75b443932dde93ee38d296c692478b456a95110e042c4ff7dc54ccefb6cad84847dcd8a3e97d47736a6f25a6c
@@ -1,17 +1,24 @@
1
- name: build
1
+ name: test
2
2
 
3
3
  on: [push, pull_request]
4
4
 
5
5
  jobs:
6
- build:
6
+ ruby-versions:
7
+ uses: ruby/actions/.github/workflows/ruby_versions.yml@master
8
+ with:
9
+ engine: cruby
10
+ min_version: 2.6
11
+
12
+ test:
13
+ needs: ruby-versions
7
14
  name: build (${{ matrix.ruby }} / ${{ matrix.os }})
8
15
  strategy:
9
16
  matrix:
10
- ruby: [ head, '3.0', '2.7', '2.6' ]
17
+ ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
11
18
  os: [ ubuntu-latest, macos-latest, windows-latest ]
12
19
  runs-on: ${{ matrix.os }}
13
20
  steps:
14
- - uses: actions/checkout@v3
21
+ - uses: actions/checkout@v4
15
22
  - name: Set up Ruby
16
23
  uses: ruby/setup-ruby@v1
17
24
  with:
@@ -174,6 +174,125 @@ module Random::Formatter
174
174
  "%08x-%04x-%04x-%04x-%04x%08x" % ary
175
175
  end
176
176
 
177
+ alias uuid_v4 uuid
178
+
179
+ # Generate a random v7 UUID (Universally Unique IDentifier).
180
+ #
181
+ # require 'random/formatter'
182
+ #
183
+ # Random.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e"
184
+ # Random.uuid_v7 # => "0188d4c3-16fe-744f-86af-38fa04c62bb5"
185
+ # Random.uuid_v7 # => "0188d4c3-1af8-764f-b049-c204ce0afa23"
186
+ # Random.uuid_v7 # => "0188d4c3-1e74-7085-b14f-ef6415dc6f31"
187
+ # # |<--sorted-->| |<----- random ---->|
188
+ #
189
+ # # or
190
+ # prng = Random.new
191
+ # prng.uuid_v7 # => "0188ca51-5e72-7950-a11d-def7ff977c98"
192
+ #
193
+ # The version 7 UUID starts with the least significant 48 bits of a 64 bit
194
+ # Unix timestamp (milliseconds since the epoch) and fills the remaining bits
195
+ # with random data, excluding the version and variant bits.
196
+ #
197
+ # This allows version 7 UUIDs to be sorted by creation time. Time ordered
198
+ # UUIDs can be used for better database index locality of newly inserted
199
+ # records, which may have a significant performance benefit compared to random
200
+ # data inserts.
201
+ #
202
+ # The result contains 74 random bits (9.25 random bytes).
203
+ #
204
+ # Note that this method cannot be made reproducable with Kernel#srand, which
205
+ # can only affect the random bits. The sorted bits will still be based on
206
+ # Process.clock_gettime.
207
+ #
208
+ # See draft-ietf-uuidrev-rfc4122bis[https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/]
209
+ # for details of UUIDv7.
210
+ #
211
+ # ==== Monotonicity
212
+ #
213
+ # UUIDv7 has millisecond precision by default, so multiple UUIDs created
214
+ # within the same millisecond are not issued in monotonically increasing
215
+ # order. To create UUIDs that are time-ordered with sub-millisecond
216
+ # precision, up to 12 bits of additional timestamp may added with
217
+ # +extra_timestamp_bits+. The extra timestamp precision comes at the expense
218
+ # of random bits. Setting <tt>extra_timestamp_bits: 12</tt> provides ~244ns
219
+ # of precision, but only 62 random bits (7.75 random bytes).
220
+ #
221
+ # prng = Random.new
222
+ # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 12) }
223
+ # # =>
224
+ # ["0188d4c7-13da-74f9-8b53-22a786ffdd5a",
225
+ # "0188d4c7-13da-753b-83a5-7fb9b2afaeea",
226
+ # "0188d4c7-13da-754a-88ea-ac0baeedd8db",
227
+ # "0188d4c7-13da-7557-83e1-7cad9cda0d8d"]
228
+ # # |<--- sorted --->| |<-- random --->|
229
+ #
230
+ # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 8) }
231
+ # # =>
232
+ # ["0188d4c7-3333-7a95-850a-de6edb858f7e",
233
+ # "0188d4c7-3333-7ae8-842e-bc3a8b7d0cf9", # <- out of order
234
+ # "0188d4c7-3333-7ae2-995a-9f135dc44ead", # <- out of order
235
+ # "0188d4c7-3333-7af9-87c3-8f612edac82e"]
236
+ # # |<--- sorted -->||<---- random --->|
237
+ #
238
+ # Any rollbacks of the system clock will break monotonicity. UUIDv7 is based
239
+ # on UTC, which excludes leap seconds and can rollback the clock. To avoid
240
+ # this, the system clock can synchronize with an NTP server configured to use
241
+ # a "leap smear" approach. NTP or PTP will also be needed to synchronize
242
+ # across distributed nodes.
243
+ #
244
+ # Counters and other mechanisms for stronger guarantees of monotonicity are
245
+ # not implemented. Applications with stricter requirements should follow
246
+ # {Section 6.2}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-07.html#monotonicity_counters]
247
+ # of the specification.
248
+ #
249
+ def uuid_v7(extra_timestamp_bits: 0)
250
+ case (extra_timestamp_bits = Integer(extra_timestamp_bits))
251
+ when 0 # min timestamp precision
252
+ ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
253
+ rand = random_bytes(10)
254
+ rand.setbyte(0, rand.getbyte(0) & 0x0f | 0x70) # version
255
+ rand.setbyte(2, rand.getbyte(2) & 0x3f | 0x80) # variant
256
+ "%08x-%04x-%s" % [
257
+ (ms & 0x0000_ffff_ffff_0000) >> 16,
258
+ (ms & 0x0000_0000_0000_ffff),
259
+ rand.unpack("H4H4H12").join("-")
260
+ ]
261
+
262
+ when 12 # max timestamp precision
263
+ ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
264
+ .divmod(1_000_000)
265
+ extra_bits = ns * 4096 / 1_000_000
266
+ rand = random_bytes(8)
267
+ rand.setbyte(0, rand.getbyte(0) & 0x3f | 0x80) # variant
268
+ "%08x-%04x-7%03x-%s" % [
269
+ (ms & 0x0000_ffff_ffff_0000) >> 16,
270
+ (ms & 0x0000_0000_0000_ffff),
271
+ extra_bits,
272
+ rand.unpack("H4H12").join("-")
273
+ ]
274
+
275
+ when (0..12) # the generic version is slower than the special cases above
276
+ rand_a, rand_b1, rand_b2, rand_b3 = random_bytes(10).unpack("nnnN")
277
+ rand_mask_bits = 12 - extra_timestamp_bits
278
+ ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
279
+ .divmod(1_000_000)
280
+ "%08x-%04x-%04x-%04x-%04x%08x" % [
281
+ (ms & 0x0000_ffff_ffff_0000) >> 16,
282
+ (ms & 0x0000_0000_0000_ffff),
283
+ 0x7000 |
284
+ ((ns * (1 << extra_timestamp_bits) / 1_000_000) << rand_mask_bits) |
285
+ rand_a & ((1 << rand_mask_bits) - 1),
286
+ 0x8000 | (rand_b1 & 0x3fff),
287
+ rand_b2,
288
+ rand_b3
289
+ ]
290
+
291
+ else
292
+ raise ArgumentError, "extra_timestamp_bits must be in 0..12"
293
+ end
294
+ end
295
+
177
296
  private def gen_random(n)
178
297
  self.bytes(n)
179
298
  end
@@ -226,11 +345,13 @@ module Random::Formatter
226
345
  #
227
346
  # The argument _n_ specifies the length, in characters, of the alphanumeric
228
347
  # string to be generated.
348
+ # The argument _chars_ specifies the character list which the result is
349
+ # consist of.
229
350
  #
230
351
  # If _n_ is not specified or is nil, 16 is assumed.
231
352
  # It may be larger in the future.
232
353
  #
233
- # The result may contain A-Z, a-z and 0-9.
354
+ # The result may contain A-Z, a-z and 0-9, unless _chars_ is specified.
234
355
  #
235
356
  # require 'random/formatter'
236
357
  #
@@ -238,8 +359,13 @@ module Random::Formatter
238
359
  # # or
239
360
  # prng = Random.new
240
361
  # prng.alphanumeric(10) #=> "i6K93NdqiH"
241
- def alphanumeric(n=nil)
362
+ #
363
+ # Random.alphanumeric(4, chars: [*"0".."9"]) #=> "2952"
364
+ # # or
365
+ # prng = Random.new
366
+ # prng.alphanumeric(10, chars: [*"!".."/"]) #=> ",.,++%/''."
367
+ def alphanumeric(n = nil, chars: ALPHANUMERIC)
242
368
  n = 16 if n.nil?
243
- choose(ALPHANUMERIC, n)
369
+ choose(chars, n)
244
370
  end
245
371
  end
data/lib/securerandom.rb CHANGED
@@ -39,6 +39,9 @@ require 'random/formatter'
39
39
  # +NotImplementedError+ is raised.
40
40
 
41
41
  module SecureRandom
42
+
43
+ VERSION = "0.3.0"
44
+
42
45
  class << self
43
46
  def bytes(n)
44
47
  return gen_random(n)
@@ -47,17 +50,6 @@ module SecureRandom
47
50
  private
48
51
 
49
52
  def gen_random_openssl(n)
50
- @pid = 0 unless defined?(@pid)
51
- pid = $$
52
- unless @pid == pid
53
- now = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
54
- OpenSSL::Random.random_add([now, @pid, pid].join(""), 0.0)
55
- seed = Random.urandom(16)
56
- if (seed)
57
- OpenSSL::Random.random_add(seed, 16)
58
- end
59
- @pid = pid
60
- end
61
53
  return OpenSSL::Random.random_bytes(n)
62
54
  end
63
55
 
data/securerandom.gemspec CHANGED
@@ -1,6 +1,13 @@
1
+ name = File.basename(__FILE__, ".gemspec")
2
+ version = ["lib", Array.new(name.count("-")+1).join("/")].find do |dir|
3
+ break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
4
+ /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
5
+ end rescue nil
6
+ end
7
+
1
8
  Gem::Specification.new do |spec|
2
- spec.name = "securerandom"
3
- spec.version = "0.2.2"
9
+ spec.name = name
10
+ spec.version = version
4
11
  spec.authors = ["Tanaka Akira"]
5
12
  spec.email = ["akr@fsij.org"]
6
13
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: securerandom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tanaka Akira
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-14 00:00:00.000000000 Z
11
+ date: 2023-11-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Interface for secure random number generator.
14
14
  email:
@@ -52,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  requirements: []
55
- rubygems_version: 3.4.0.dev
55
+ rubygems_version: 3.5.0.dev
56
56
  signing_key:
57
57
  specification_version: 4
58
58
  summary: Interface for secure random number generator.