globeglitter 1.0.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.
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ - https://en.wikipedia.org/wiki/Surrogate_key
2
+
3
+ - https://devblogs.microsoft.com/oldnewthing/20190426-00/?p=102450
4
+ - https://devblogs.microsoft.com/oldnewthing/20190913-00/?p=102859
5
+
6
+
7
+ ## Non-UUID specs
8
+
9
+ - https://github.com/ulid/spec
10
+ - https://github.com/rs/xid
11
+ - https://github.com/boundary/flake
12
+ - https://github.com/segmentio/ksuid
13
+
14
+
15
+ ## Alternative Ruby Libraries
16
+
17
+ - `::SecureRandom::uuid` obviously
18
+ - https://github.com/sporkmonger/uuidtools
19
+ - https://github.com/assaf/uuid / https://www.rubydoc.info/gems/uuid/
20
+ - https://gist.github.com/brandur/1bddb5215540889983dc7e3a66ef4e41
21
+ - https://github.com/cassandra-rb/simple_uuid
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'memory_profiler'
5
+
6
+ $: << File.expand_path('../lib', __dir__)
7
+ result = MemoryProfiler.report do
8
+ require 'globeglitter'
9
+ 333.times { GlobeGlitter::random }
10
+ end.pretty_print
data/bin/benchmark ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'globeglitter'
5
+ require 'securerandom'
6
+ require 'benchmark/ips'
7
+
8
+ Benchmark.ips do |bm|
9
+ integer_buffer = ::Data::define(:lol, :rofl, :lmao) do
10
+ def self.new(msb, lsb, rofl: nil, lmao: nil) = self.allocate.tap { |gg|
11
+ gg.send(
12
+ :initialize,
13
+ **{
14
+ lol: ((msb << 64) | lsb),
15
+ rofl:,
16
+ lmao:,
17
+ }
18
+ )
19
+ }
20
+ end
21
+
22
+ io_buffer = ::Data::define(:lol, :rofl, :lmao) do
23
+ def self.new(msb, lsb, rofl: nil, lmao: nil) = self.allocate.tap { |gg|
24
+ gg.send(
25
+ :initialize,
26
+ **{
27
+ lol: ::IO::Buffer::new(size=16).tap {
28
+ _1.set_value(:U64, 0, msb)
29
+ _1.set_value(:U64, 8, lsb)
30
+ },
31
+ rofl:,
32
+ lmao:,
33
+ }
34
+ )
35
+ }
36
+ end
37
+
38
+ bm.report('Embedded buffer as `::Integer`') do
39
+ integer_buffer.new(
40
+ ::SecureRandom::random_number(0xFFFFFFFF_FFFFFFFF),
41
+ ::SecureRandom::random_number(0xFFFFFFFF_FFFFFFFF),
42
+ )
43
+ end
44
+ bm.report('Embedded buffer as `::IO::Buffer`') do
45
+ io_buffer.new(
46
+ ::SecureRandom::random_number(0xFFFFFFFF_FFFFFFFF),
47
+ ::SecureRandom::random_number(0xFFFFFFFF_FFFFFFFF),
48
+ )
49
+ end
50
+ end
51
+
52
+ Benchmark.ips do |bm|
53
+ bm.report 'SecureRandom::uuid random String' do
54
+ ::SecureRandom::uuid
55
+ end
56
+ bm.report 'SecureRandom::uuid random String to Integer' do
57
+ ::SecureRandom::uuid.gsub(?-, '').to_i(16)
58
+ end
59
+ bm.report 'GlobeGlitter random from SecureRandom::uuid' do
60
+ ::GlobeGlitter::new(::SecureRandom::uuid.gsub(?-, '').to_i(16))
61
+ end
62
+ bm.report 'GlobeGlitter random native' do
63
+ ::GlobeGlitter::random
64
+ end
65
+ bm.report 'GlobeGlitter random native to String' do
66
+ ::GlobeGlitter::random.to_s
67
+ end
68
+ end
69
+
70
+ # TODO: Benchmark `::IO::Buffer` vs `::Integer` immediates https://docs.ruby-lang.org/en/master/IO/Buffer.html
data/bin/profile ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'globeglitter'
5
+
6
+ require 'ruby-prof'
7
+
8
+
9
+ result = RubyProf.profile(:track_allocations => true) do
10
+ 1_234_56.times do
11
+ ::GlobeGlitter::random
12
+ end
13
+ end
14
+ printer = RubyProf::GraphPrinter.new(result)
15
+ printer.print(STDOUT, :min_percent => 2)
data/bin/repl ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "irb"
5
+ require "objspace"
6
+ require "globeglitter"
7
+
8
+ IRB.start(__FILE__)
data/bin/test-my-best ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require('bundler/setup')
4
+ require_relative('../lib/globeglitter')
5
+ require_relative('../TEST MY BEST/test-my-best')
@@ -0,0 +1,200 @@
1
+ require('securerandom') unless defined?(::SecureRandom)
2
+ require('socket') unless defined?(::Socket)
3
+
4
+ # Components for time-based UUIDs.
5
+ #
6
+ # See also:
7
+ # - https://pythonhosted.org/time-uuid/
8
+ module ::GlobeGlitter::CHRONO_DIVER; end
9
+ module ::GlobeGlitter::CHRONO_DIVER::PENDULUMS
10
+
11
+ # ITU-T Rec. X.667 sez —
12
+ #
13
+ # “The timestamp is a 60-bit value. For UUID version 1, this is
14
+ # represented by Coordinated Universal Time (UTC) as a count of 100-
15
+ # nanosecond intervals since 00:00:00.00, 15 October 1582 (the date of
16
+ # Gregorian reform to the Christian calendar).“
17
+ # “For systems that do not have UTC available, but do have the local
18
+ # time, they may use that instead of UTC, as long as they do so
19
+ # consistently throughout the system. However, this is not recommended
20
+ # since generating the UTC from local time only needs a time zone offset.”
21
+ # “Since a UUID is a fixed size and contains a time field,
22
+ # it is possible for values to rollover (around A.D. 3400,
23
+ # depending on the specific algorithm used).”
24
+ # “NT keeps time in FILETIME format which is 100ns ticks since Jan 1, 1601.
25
+ # UUIDs use time in 100ns ticks since Oct 15, 1582.
26
+ # The difference is 17 Days in Oct + 30 (Nov) + 31 (Dec) + 18 years and 5 leap days.”
27
+ #
28
+ # See also —
29
+ # - https://medium.com/swlh/should-i-use-date-time-or-datetime-in-ruby-and-rails-9372ad20ca4f
30
+ # - https://stackoverflow.com/questions/11835193/how-do-i-use-ruby-date-constants-gregorian-julian-england-and-even-italy
31
+ #
32
+ # TODO: Figure out how to handle date rollover.
33
+ private def current_time = (((::Time::now.utc - ::Time::new(1582, 10, 15)) * 1_000_000_000) / 100).to_i
34
+
35
+ # ITU-T Rec. X.667 sez —
36
+ #
37
+ # “[O]btain a 47-bit cryptographic quality random number and use it as the low 47 bits of the node ID,
38
+ # with the least significant bit of the first octet of the node ID set to one.
39
+ # This bit is the unicast/multicast bit, which will never be set in IEEE 802 addresses
40
+ # obtained from network cards. Hence, there can never be a conflict between UUIDs
41
+ # generated by machines with and without network cards.”
42
+ private def random_node = (::SecureRandom::random_number(0xFFFFFFFFFFFF) | (0b1 << 40))
43
+
44
+ # General background info: https://beej.us/guide/bgnet/html/
45
+ #
46
+ # Ruby exposes `hwaddr` in `::Socket::getifaddrs`, e.g. —
47
+ #
48
+ # irb> Socket::getifaddrs.map(&:addr)
49
+ # =>
50
+ # [#<Addrinfo: PACKET[protocol=0 lo hatype=772 HOST hwaddr=00:00:00:00:00:00]>,
51
+ # #<Addrinfo: PACKET[protocol=0 enp1s0 hatype=1 HOST hwaddr=00:e0:4c:18:42:69]>,
52
+ # #<Addrinfo: PACKET[protocol=0 wlp2s0 hatype=1 HOST hwaddr=80:19:34:6f:13:37]>,
53
+ # #<Addrinfo: 127.0.0.1>,
54
+ # #<Addrinfo: 172.16.0.226>,
55
+ # #<Addrinfo: 172.16.0.88>,
56
+ # #<Addrinfo: ::1>,
57
+ # #<Addrinfo: fe80::d9d7:4890:9ebc:486%wlp2s0>]
58
+ #
59
+ # …but there seems to be no way to get it in Ruby code without parsing the `::String` output
60
+ # of `::Addrinfo#inspect`, confirmed when we look at `raddrinfo.c` and see it constructing a `str`:
61
+ # https://github.com/ruby/ruby/blob/ea8a7287e2b96b9c24e5e89fe863e5bfa60bfdda/ext/socket/raddrinfo.c#L1375
62
+ #
63
+ # MAYBE: Figure out how to use ioctl to get this without `::String` parsing.
64
+ # - https://stackoverflow.com/a/1779758
65
+ # - https://medium.com/geckoboard-under-the-hood/how-to-build-a-network-stack-in-ruby-f73aeb1b661b
66
+ private def hardware_node = ::Socket::getifaddrs.map(&:addr).map(&:inspect_sockaddr).map! {
67
+ _1.match(/hwaddr=(?<hwaddr>\h\h:\h\h:\h\h:\h\h:\h\h:\h\h)/)&.captures&.pop&.gsub(?:, '')&.to_i(16)
68
+ }.compact.tap {
69
+ # Remove `00:00:00:00:00:00` loopback address.
70
+ # TODO: Better logic. There can be multiple loopback interfaces or no loopback interface.
71
+ # Maybe alter the `::Regexp` above and remove this `tap`/`delete` section entirely.
72
+ _1.delete(0)
73
+ }&.max {
74
+ # Choose the hwaddr that gives up the most entropy for a UUID,
75
+ # rather than always the first-enumerated hwaddr.
76
+ _1.bit_length
77
+ }
78
+
79
+ # Fall back to random node ID if a hardware ID is unavailable.
80
+ private def current_node = self.hardware_node || self.random_node
81
+
82
+ # ITU-T Rec. X.667 sez —
83
+ #
84
+ # “For UUID version 1, the clock sequence is used to help avoid
85
+ # duplicates that could arise when the clock is set backwards in time
86
+ # or if the node ID changes.
87
+ #
88
+ # If the clock is set backwards, or might have been set backwards
89
+ # (e.g., while the system was powered off), and the UUID generator can
90
+ # not be sure that no UUIDs were generated with timestamps larger than
91
+ # the value to which the clock was set, then the clock sequence has to
92
+ # be changed. If the previous value of the clock sequence is known, it
93
+ # can just be incremented; otherwise it should be set to a random or
94
+ # high-quality pseudo-random value.
95
+ #
96
+ # Similarly, if the node ID changes (e.g., because a network card has
97
+ # been moved between machines), setting the clock sequence to a random
98
+ # number minimizes the probability of a duplicate due to slight
99
+ # differences in the clock settings of the machines. If the value of
100
+ # clock sequence associated with the changed node ID were known, then
101
+ # the clock sequence could just be incremented, but that is unlikely.
102
+ #
103
+ # The clock sequence MUST be originally (i.e., once in the lifetime of
104
+ # a system) initialized to a random number to minimize the correlation
105
+ # across systems. This provides maximum protection against node
106
+ # identifiers that may move or switch from system to system rapidly.
107
+ # The initial value MUST NOT be correlated to the node identifier.”
108
+ #
109
+ # TODO: Pick a single random sequence at startup and increment from there,
110
+ # maybe in a dedicated `::Ractor`.
111
+ def clock_sequence = ::SecureRandom::random_number(0xFFFF)
112
+
113
+ # TODO: Take arguments to generate identifiers for specific time/seq/node.
114
+ def from_time = self::new(
115
+ current_time,
116
+ clock_sequence,
117
+ current_node,
118
+ structure: ::GlobeGlitter::STRUCTURE_ITU_T_REC_X_667,
119
+ rules: ::GlobeGlitter::RULES_TIME_GREGORIAN,
120
+ )
121
+ end
122
+
123
+ module ::GlobeGlitter::CHRONO_DIVER::FRAGMENT
124
+
125
+ # Getters for fields defined in the specification.
126
+ def time_low = self.bits127–96
127
+ def time_mid = self.bits95–80
128
+ def time_high_and_version = self.bits79–64
129
+ def clock_seq_high_and_reserved = self.bits63–56 & case self.structure
130
+ # This field is overlayed by the backward-masked `structure` a.k.a. """variant""",
131
+ # so we return a different number of bits from the chunk depending on that value.
132
+ when 0 then 0b01111111
133
+ when 1 then 0b00111111
134
+ when 2, 3 then 0b00011111
135
+ else 0b11111111 # Non-compliant structure. This should never happen.
136
+ end
137
+ def clock_seq_low = self.bits55–48
138
+ def node = self.bits47–0
139
+
140
+ # Setters for fields defined in the specification.
141
+ def time_low=(otra); self.bits127–96=(otra); end
142
+ def time_mid=(otra); self.bits95–80=(otra); end
143
+ def time_high=(otra); self.bits79–64=(((self.inner_spirit >> 64) & 0xF000) | (otra & 0x0FFF)); end
144
+ def time=(otra)
145
+ self.bits79–64=(((self.inner_spirit >> 64) & 0x00000000_0000F000) | (otra & 0xFFFFFFFF_FFFF0FFF))
146
+ end
147
+ def clock_seq_high_and_reserved=(otra)
148
+ # This field is overlayed by the backward-masked `structure` a.k.a. """variant""",
149
+ # so we set a different number of bits from the chunk depending on that value
150
+ # along with saving the existing value.
151
+ self.bits63–56=(
152
+ (otra & case self.structure
153
+ when 0 then 0b01111111
154
+ when 1 then 0b00111111
155
+ when 2, 3 then 0b00011111
156
+ else 0b11111111 # Non-compliant structure. This should never happen.
157
+ end) | (case self.structure
158
+ when 0 then 0b00000000
159
+ when 1 then 0b10000000
160
+ when 2 then 0b11000000
161
+ when 3 then 0b11100000
162
+ else 0b00000000 # Non-compliant structure. This should never happen.
163
+ end)
164
+ )
165
+ end
166
+ def clock_seq_low=(otra)
167
+ self.bits55–48=(otra)
168
+ end
169
+ def node=(otra)
170
+ self.bits47–0=(otra)
171
+ end
172
+
173
+ # Getter for Base-16 `::String` output where these two fields are combined.
174
+ def clock_seq = (self.clock_seq_high_and_reserved << 8) | self.clock_seq_low
175
+ def clock_seq=(otra)
176
+ self.with(inner_spirit: (
177
+ (self.inner_spirit & 0xFFFFFFFF_FFFFFFFF_0000FFFF_FFFFFFFF) |
178
+ (
179
+ case self.structure
180
+ when 0 then 0b00000000
181
+ when 1 then 0b10000000
182
+ when 2 then 0b11000000
183
+ when 3 then 0b11100000
184
+ end << 56
185
+ ) | (otra << 48)
186
+ ))
187
+ end
188
+
189
+ # Construct the full timestamp from the split chunk contents.
190
+ def time = (self.bits127–96 << 32) | (self.bits95–80 << 16) | (self.bits79–64 & 0x0FFF)
191
+
192
+ # ITU-T Rec. X.667 sez —
193
+ #
194
+ # “The timestamp is a 60-bit value. For UUID version 1, this is
195
+ # represented by Coordinated Universal Time (UTC) as a count of 100-
196
+ # nanosecond intervals since 00:00:00.00, 15 October 1582 (the date of
197
+ # Gregorian reform to the Christian calendar).“
198
+ def to_time = ::Time::new(1582, 10, 15).utc + (self.time / 10000000)
199
+
200
+ end
@@ -0,0 +1,24 @@
1
+ require('forwardable') unless defined?(::Forwardable)
2
+ require('comparable') unless defined?(::Comparable)
3
+
4
+ ::GlobeGlitter::FIRST_RESOLUTION = ::Module::new do
5
+
6
+ #extend(::Forwardable)
7
+ #def_instance_delegators(:inner_spirit, :to_i, :eql?)
8
+
9
+ # https://devblogs.microsoft.com/oldnewthing/20190426-00/?p=102450
10
+ # https://devblogs.microsoft.com/oldnewthing/20190913-00/?p=102859
11
+ # https://bornsql.ca/blog/how-sql-server-stores-data-types-guid/
12
+ COMPARATOR_UUID = 1
13
+ include(::Comparable)
14
+ def <=>(otra, comparator: COMPARATOR_UUID)
15
+ case otra
16
+ when ::GlobeGlitter then
17
+ case comparator
18
+ when COMPARATOR_UUID then self.inner_spirit.<=>(otra.inner_spirit)
19
+ #else
20
+ end
21
+ else self.inner_spirit.<=>(::GlobeGlitter::try_convert(otra))
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,162 @@
1
+ # Bitslicing components.
2
+ module ::GlobeGlitter::INNER_SPIRIT
3
+
4
+ # NOTE: These method names are based on big-endian representation of our buffer, i.e. MSB <-> LSB.
5
+ #
6
+ # ITU-T Rec. X.667 sez —
7
+ # “This Recommendation | International Standard specifies a sequence of octets for a UUID
8
+ # using the terms first and last. The first octet is also called "octet 15" and the last octet "octet 0".
9
+ # The bits within a UUID are also numbered as "bit 127" to "bit 0", with bit 127 as the most-
10
+ # significant bit of octet 15 and bit 0 as the least significant bit of octet 0.”
11
+ #
12
+ # I am going to 1-index the boundaries in these helper methods because I think the resulting numbers
13
+ # are easier to remember, but our structure is otherwise identical to what's described in the quote.
14
+ def bits127–96 = self.inner_spirit.get_value(self.structure.eql?(self.class::STRUCTURE_MICROSOFT) ? :u32 : :U32, 0)
15
+ def bits95–80 = self.inner_spirit.get_value(self.structure.eql?(self.class::STRUCTURE_MICROSOFT) ? :u16 : :U16, 4)
16
+ def bits79–64 = self.inner_spirit.get_value(self.structure.eql?(self.class::STRUCTURE_MICROSOFT) ? :u16 : :U16, 6)
17
+ def bits63–56 = self.inner_spirit.get_value(:U8, 8)
18
+ def bits55–48 = self.inner_spirit.get_value(:U8, 9)
19
+ def bits47–0 = (self.inner_spirit.get_value(:U16, 10) << 32) | self.inner_spirit.get_value(:U32, 12)
20
+
21
+ def bits127–96=(otra); self.inner_spirit.set_value(self.structure.eql?(self.class::STRUCTURE_MICROSOFT) ? :u32 : :U32, 0, otra); end
22
+ def bits95–80=(otra); self.inner_spirit.set_value(self.structure.eql?(self.class::STRUCTURE_MICROSOFT) ? :u16 : :U16, 4, otra); end
23
+ def bits79–64=(otra); self.inner_spirit.set_value(self.structure.eql?(self.class::STRUCTURE_MICROSOFT) ? :u16 : :U16, 6, otra); end
24
+ def bits63–56=(otra); self.inner_spirit.set_value(:U8, 8, otra); end
25
+ def bits55–48=(otra); self.inner_spirit.set_value(:U8, 9, otra); end
26
+ def bits47–0=(otra)
27
+ self.inner_spirit.set_value(:U16, 10, otra >> 32)
28
+ self.inner_spirit.set_value(:U32, 12, otra & 0xFFFFFFFF)
29
+ end
30
+
31
+ # ITU-T Rec. X.667 sez —
32
+ #
33
+ # “The version number is in the most significant 4 bits of the time
34
+ # stamp (bits 4 through 7 of the time_hi_and_version field).”
35
+ #
36
+ # “The following table lists the currently-defined versions for this UUID structure.”
37
+ #
38
+ # Msb0 Msb1 Msb2 Msb3 Version Description
39
+ #
40
+ # 0 0 0 1 1 The time-based version
41
+ # specified in this document.
42
+ #
43
+ # 0 0 1 0 2 DCE Security version, with
44
+ # embedded POSIX UIDs.
45
+ #
46
+ # 0 0 1 1 3 The name-based version
47
+ # specified in this document
48
+ # that uses MD5 hashing.
49
+ #
50
+ # 0 1 0 0 4 The randomly or pseudo-
51
+ # randomly generated version
52
+ # specified in this document.
53
+ #
54
+ # 0 1 0 1 5 The name-based version
55
+ # specified in this document
56
+ # that uses SHA-1 hashing.
57
+ #
58
+ # “The version is more accurately a sub-type; again, we retain the term for compatibility.”
59
+ def rules = (self.to_i >> 76) & 0xF
60
+ # NOTE: Assignment methods in Ruby can only return their argument, so we name this `replace` instead.
61
+ def replace_rules(otra)
62
+ raise ::ArgumentError::new("invalid version #{otra.to_s}") unless otra.is_a?(::Integer) and otra.between?(1, 8)
63
+ return self.with(rules: otra).tap {
64
+ _1.inner_spirit.set_value(:U8, 7, ((otra << 0xF) | (self.inner_spirit.get_value(:U8, 7) & 0xF)))
65
+ }
66
+ end
67
+ # This is just straight-up the same thing as "version" in the UUID specification,
68
+ # but I don't want to call it that because it's a terrible ambiguous word
69
+ # for anybody unfamiliar with the minutae of the specs.
70
+ # We should still provide it as `#version` because why not??
71
+ alias_method(:version, :rules)
72
+
73
+ # ITU-T Rec. X.667 sez —
74
+ #
75
+ # “The variant field determines the layout of the UUID.
76
+ # That is, the interpretation of all other bits in the UUID depends on the setting
77
+ # of the bits in the variant field. As such, it could more accurately be called a type field;
78
+ # we retain the original term for compatibility.
79
+ # The variant field consists of a variable number of the most significant bits of octet 8 of the UUID.
80
+ #
81
+ # “The following table lists the contents of the variant field, where
82
+ # the letter "x" indicates a "don't-care" value.
83
+ #
84
+ # Msb0 Msb1 Msb2 Description
85
+ #
86
+ # 0 x x Reserved, NCS backward compatibility.
87
+ #
88
+ # 1 0 x The structure specified in this document.
89
+ #
90
+ # 1 1 0 Reserved, Microsoft Corporation backward compatibility
91
+ #
92
+ # 1 1 1 Reserved for future definition.
93
+ #
94
+ #
95
+ # NOTE: Some libraries (like `java.util.UUID`) specify the variant value as if it were not backwards-masked:
96
+ # https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/util/UUID.html#variant()
97
+ #
98
+ # I think it makes more sense for it to count upward like `version` rather than use the raw bit value.
99
+ def structure
100
+ # Can't use getter for this since the getter return value will rely on this structure.
101
+ clock_seq_high_and_reserved = self.inner_spirit.get_value(:U8, 8)
102
+ # The structure is masked backwards, but with a variable number of bits,
103
+ # so we can't just swap it and mask.
104
+ case
105
+ when (clock_seq_high_and_reserved >> 7).zero? then 0
106
+ when (clock_seq_high_and_reserved >> 6).eql?(0b10) then 1
107
+ when (clock_seq_high_and_reserved >> 5).eql?(0b110) then 2
108
+ when (clock_seq_high_and_reserved >> 5).eql?(0b111) then 3
109
+ end
110
+ end
111
+
112
+ # NOTE: Assignment methods in Ruby can only return their argument, so we name this `replace` instead.
113
+ def replace_structure(otra)
114
+ raise ::ArgumentError::new("invalid structure #{otra.to_s}") unless otra.respond_to?(:<) and otra.<(4)
115
+ return self.with(structure: otra).tap {
116
+ _1.inner_spirit.set_value(:U8, 9,
117
+ (
118
+ (case otra
119
+ when 0 then 0b00000000
120
+ when 1 then 0b10000000
121
+ when 2 then 0b11000000
122
+ when 3 then 0b11100000
123
+ else raise ::ArgumentError::new("invalid structure #{otra.to_s}")
124
+ end) |
125
+ (_1.inner_spirit.get_value(:U8, 9) & case otra
126
+ when 0 then 0b01111111
127
+ when 1 then 0b00111111
128
+ when 2, 3 then 0b00011111
129
+ else raise ::ArgumentError::new("invalid structure #{otra.to_s}")
130
+ end)
131
+ )
132
+ )
133
+ }
134
+ end
135
+
136
+ # Return `::Integer` representation of the contents of our embedded buffer.
137
+ # ITU-T Rec. X.667 sez —
138
+ # “A UUID can be represented as a single integer value. To obtain the single integer value of the UUID,
139
+ # the 16 octets of the binary representation shall be treated as an unsigned integer encoding with
140
+ # the most significant bit of the integer encoding as the most significant bit (bit 7) of the first
141
+ # of the sixteen octets (octet 15) and the least significant bit as the least significant bit (bit 0)
142
+ # of the last of the sixteen octets (octet 0).”
143
+ # “UUIDs forming a component of an OID are represented in ASN.1 value notation
144
+ # as the decimal representation of their integer value.”
145
+ def to_i = (self.inner_spirit.get_value(:U64, 0) << 64) | self.inner_spirit.get_value(:U64, 8)
146
+
147
+ # ITU-T Rec. X.667 sez —
148
+ # “An alternative URN format [alternative to `"urn:uuid:<hex-string>"`] is available,
149
+ # but is not recommended for URNs generated using UUIDs.
150
+ # This alternative format uses the single integer value of the UUID, and represents the UUID
151
+ # `f81d4fae-7dec-11d0-a765-00a0c91e6bf6` as `urn:oid:2.25.329800735698586629295641978511506172918`.”
152
+ #
153
+ # Explicitly call `#to_i#to_s` to avoid `RangeError: bignum out of char range`.
154
+ def to_oid = ::String::new("urn:oid:2.25.".concat(self.to_i.to_s), encoding: ::Encoding::US_ASCII).-@
155
+
156
+ # Compare to dotNET `Guid.ToByteArray` https://learn.microsoft.com/en-us/dotnet/api/system.guid.tobytearray
157
+ # Match the naming of `::String#bytes` since we behave identically.
158
+ def bytes = self.inner_spirit.values
159
+ # Also keep the name `values` because why not lol
160
+ def values = self.inner_spirit.values
161
+
162
+ end
File without changes
@@ -0,0 +1,64 @@
1
+ # `::String`-printing components.
2
+ module ::GlobeGlitter::SAY_YEEEAHH
3
+
4
+ # SAY YEEEAHH
5
+ # NOTE: Built-in Ruby classes emit `US-ASCII` as their `#to_s`, thus so shall we.
6
+ # Some relevant discussions:
7
+ # - In https://bugs.ruby-lang.org/issues/7752 `naruse` sez —
8
+ # “On current policy, strings which always include only US-ASCII characters are US-ASCII.
9
+ # If there is a practical issue, I may change the policy in the future.
10
+ # Note that US-ASCII string is faster than UTF-8 on getting length or index access.”
11
+ # - `::Time`: https://bugs.ruby-lang.org/issues/6820
12
+ # - `::Integer`: https://bugs.ruby-lang.org/issues/15876
13
+ # - `::BigDecimal`: https://bugs.ruby-lang.org/issues/17011
14
+ def to_s(base=16)
15
+ case base
16
+ when 2 then self.to_i.to_s(2).rjust(128, ?0)
17
+ when 16 then
18
+ # ITU-T Rec. X.667 sez —
19
+ #
20
+ # “Each field is treated as an integer and has its value printed as a
21
+ # zero-filled hexadecimal digit string with the most significant digit first.
22
+ # The hexadecimal values "a" through "f" are output as lower case characters.”
23
+ ::Array[
24
+ # TODO: Handle swapping for String representation of MS-style GUIDs
25
+ self.bits127–96.to_s(16).rjust(8, ?0),
26
+ self.bits95–80.to_s(16).rjust(4, ?0),
27
+ self.bits79–64.to_s(16).rjust(4, ?0),
28
+ ((self.bits63–56 << 8) | self.bits55–48).to_s(16).rjust(4, ?0),
29
+ self.bits47–0.to_s(16).rjust(12, ?0),
30
+ ].join(?-).encode!(::Encoding::US_ASCII).-@
31
+ else
32
+ # Compare to `::Integer#to_s` behavior:
33
+ # irb> 333.to_s(666)
34
+ # (irb):in `to_s': invalid radix 666 (ArgumentError)
35
+ raise ::ArgumentError::new("invalid radix #{base.to_s}")
36
+ end
37
+ end
38
+
39
+ # In Microsoft-land, GUIDs were ALL-CAPS hex packaged between curly braces
40
+ def to_guid = ::String::new("{#{self.to_s(16).upcase}}", encoding: ::Encoding::US_ASCII)
41
+
42
+ def inspect = ::String::new("#<#{self.class.name} #{self.to_s}>", encoding: ::Encoding::US_ASCII)
43
+
44
+ # ITU-T Rec. X.667 sez —
45
+ #
46
+ # “A UUID can be used as the primary integer value of a Joint UUID arc using the single integer value of the UUID.
47
+ # The hexadecimal representation of the UUID can also be used as a non-integer Unicode label for the arc.
48
+ # EXAMPLE — The following is an example of the use of a UUID to form an IRI/URI value:
49
+ # "oid:/UUID/f81d4fae-7dec-11d0-a765-00a0c91e6bf6"”
50
+ def to_oid_s = ::String::new("oid:/UUID/".concat(self.to_s(base=16)), encoding: ::Encoding::US_ASCII).-@
51
+
52
+ # ITU-T Rec. X.667 sez —
53
+ #
54
+ # “The string representation of a UUID is fully compatible with the URN syntax.
55
+ # When converting from a bit-oriented, in-memory representation of a UUID into a URN,
56
+ # care must be taken to strictly adhere to the byte order issues
57
+ # mentioned in the string representation section.”
58
+ # “The following is an example of the string representation of a UUID as a URN:
59
+ # urn:inner_spirit:f81d4fae-7dec-11d0-a765-00a0c91e6bf6”
60
+ def to_urn = ::String::new("urn:uuid:".concat(self.to_s(base=16)), encoding: ::Encoding::US_ASCII).-@
61
+
62
+ # TODO: `#to_clsid` https://www.w3.org/Addressing/clsid-scheme
63
+
64
+ end