ruby-uuid 0.0.1

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.
Files changed (3) hide show
  1. data/lib/uuid.rb +312 -0
  2. data/test/test_uuid.rb +153 -0
  3. metadata +48 -0
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright(c) 2005 URABE, Shyouhei.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this code, to deal in the code without restriction, including without
6
+ # limitation the rights to use, copy, modify, merge, publish, distribute,
7
+ # sublicense, and/or sell copies of the code, and to permit persons to whom the
8
+ # code is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be
11
+ # included in all copies or substantial portions of the code.
12
+ #
13
+ # THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE CODE OR THE USE OR OTHER DEALINGS IN THE
19
+ # CODE.
20
+
21
+ %w[
22
+ digest/md5
23
+ digest/sha1
24
+ tmpdir
25
+ ].each do |f|
26
+ require f
27
+ end
28
+
29
+ # Pure ruby UUID generator, which is compatible with RFC4122
30
+ class UUID
31
+ # UUID epoch is 15th Oct. 1582
32
+ UNIXEpoch = 0x01B21DD213814000 # in 100-nanoseconds resolution
33
+
34
+ private_class_method :new
35
+
36
+ private
37
+ def initialize str
38
+ tmp = str.unpack "C*"
39
+ @num = tmp.inject do |r, i|
40
+ r * 256 | i
41
+ end
42
+ @num.freeze
43
+ self.freeze
44
+ end
45
+
46
+ public
47
+
48
+ def raw_bytes
49
+ ret = String.new
50
+ tmp = @num
51
+ 16.times do |i|
52
+ x, y = tmp.divmod 256
53
+ ret << y
54
+ tmp = x
55
+ end
56
+ ret.reverse!
57
+ ret
58
+ end
59
+
60
+ class << self
61
+ def mask ver, str # :nodoc:
62
+ ver = ver & 15
63
+ v = str[6].ord
64
+ v &= 0b0000_1111
65
+ v |= ver << 4
66
+ str[6] = v.chr
67
+ r = str[8].ord
68
+ r &= 0b0011_1111
69
+ r |= 0b1000_0000
70
+ str[8] = r.chr
71
+ str
72
+ end
73
+
74
+ def prand # :nodoc:
75
+ rand 0x100000000
76
+ end
77
+
78
+ private :mask, :prand
79
+
80
+ # UUID generation using SHA1. Recommended over create_md5.
81
+ # Namespace object is another UUID, some of them are pre-defined below.
82
+ def create_sha1 str, namespace
83
+ sha1 = Digest::SHA1.new
84
+ sha1.update namespace.raw_bytes
85
+ sha1.update str
86
+ sum = sha1.digest
87
+ raw = mask 5, sum[0..15]
88
+ new raw
89
+ end
90
+
91
+ # UUID generation using MD5 (for backward compat.)
92
+ def create_md5 str, namespace
93
+ md5 = Digest::MD5.new
94
+ md5.update namespace.raw_bytes
95
+ md5.update str
96
+ sum = md5.digest
97
+ raw = mask 3, sum[0..16]
98
+ new raw
99
+ end
100
+
101
+ # UUID generation using random-number generator. From it's random
102
+ # nature, there's no warranty that the created ID is really universaly
103
+ # unique.
104
+ def create_random
105
+ rnd = [prand, prand, prand, prand].pack "N4"
106
+ raw = mask 4, rnd
107
+ new raw
108
+ end
109
+
110
+ def read_state fp # :nodoc:
111
+ fp.rewind
112
+ Marshal.load fp.read
113
+ end
114
+
115
+ def write_state fp, c, m # :nodoc:
116
+ fp.rewind
117
+ str = Marshal.dump [c, m]
118
+ fp.write str
119
+ end
120
+
121
+ private :read_state, :write_state
122
+ STATE_FILE = 'ruby-uuid'
123
+
124
+ # create the "version 1" UUID with current system clock, current UTC
125
+ # timestamp, and the IEEE 802 address (so-called MAC address).
126
+ #
127
+ # Speed notice: it's slow. It writes some data into hard drive on every
128
+ # invokation. If you want to speed this up, try remounting tmpdir with a
129
+ # memory based filesystem (such as tmpfs). STILL slow? then no way but
130
+ # rewrite it with c :)
131
+ def create clock=nil, time=Time.now, mac_addr=nil
132
+ c = t = m = nil
133
+ Dir.chdir Dir.tmpdir do
134
+ unless FileTest.exist? STATE_FILE then
135
+ # Generate a pseudo MAC address because we have no pure-ruby way
136
+ # to know the MAC address of the NIC this system uses. Note
137
+ # that cheating with pseudo arresses here is completely legal:
138
+ # see Section 4.5 of RFC4122 for details.
139
+ sha1 = Digest::SHA1.new
140
+ 256.times do
141
+ r = [prand].pack "N"
142
+ sha1.update r
143
+ end
144
+ ary = sha1.digest.bytes.to_a
145
+ node = ary.last 6
146
+ node[0] |= 0x01 # multicast bit
147
+ node = node.pack "C*"
148
+ k = rand 0x40000
149
+ open STATE_FILE, 'w' do |fp|
150
+ fp.flock IO::LOCK_EX
151
+ write_state fp, k, node
152
+ fp.chmod 0o777 # must be world writable
153
+ end
154
+ end
155
+ open STATE_FILE, 'r+' do |fp|
156
+ fp.flock IO::LOCK_EX
157
+ c, m = read_state fp
158
+ c += 1 # important; increment here
159
+ write_state fp, c, m
160
+ end
161
+ end
162
+ c = clock & 0b11_1111_1111_1111 if clock
163
+ m = mac_addr if mac_addr
164
+ time = Time.at time if time.is_a? Float
165
+ case time
166
+ when Time
167
+ t = time.to_i * 10_000_000 + time.tv_usec * 10 + UNIXEpoch
168
+ when Integer
169
+ t = time + UNIXEpoch
170
+ else
171
+ raise TypeError, "cannot convert ``#{time}'' into Time."
172
+ end
173
+
174
+ tl = t & 0xFFFF_FFFF
175
+ tm = t >> 32
176
+ tm = tm & 0xFFFF
177
+ th = t >> 48
178
+ th = th & 0b0000_1111_1111_1111
179
+ th = th | 0b0001_0000_0000_0000
180
+ cl = c & 0b0000_0000_1111_1111
181
+ ch = c & 0b0011_1111_0000_0000
182
+ ch = ch >> 8
183
+ ch = ch | 0b1000_0000
184
+ pack tl, tm, th, ch, cl, m
185
+ end
186
+
187
+ # A simple GUID parser: just ignores unknown characters and convert
188
+ # hexadecimal dump into 16-octet object.
189
+ def parse obj
190
+ str = obj.to_s.sub %r/\Aurn:uuid:/, ''
191
+ str.gsub! %r/[^0-9A-Fa-f]/, ''
192
+ raw = [str[0..31]].pack 'H*'
193
+ new raw
194
+ end
195
+
196
+ # The 'primitive constructor' of this class
197
+ # Note UUID.pack(uuid.unpack) == uuid
198
+ def pack tl, tm, th, ch, cl, n
199
+ raw = [tl, tm, th, ch, cl, n].pack "NnnCCa6"
200
+ new raw
201
+ end
202
+ end
203
+
204
+ # The 'primitive deconstructor', or the dual to pack.
205
+ # Note UUID.pack(uuid.unpack) == uuid
206
+ def unpack
207
+ raw_bytes.unpack "NnnCCa6"
208
+ end
209
+
210
+ # The timestamp of this UUID.
211
+ # Throws RageError if that time exceeds UNIX time range
212
+ def time
213
+ a = unpack
214
+ tl = a[0]
215
+ tm = a[1]
216
+ th = a[2] & 0x0FFF
217
+ t = tl
218
+ t += tm << 32
219
+ t += th << 48
220
+ t -= UNIXEpoch
221
+ tv_sec = t / 10_000_000
222
+ t -= tv_sec * 10_000_000
223
+ tv_usec = t / 10
224
+ Time.at tv_sec, tv_usec
225
+ end
226
+
227
+ # The version of this UUID
228
+ def version
229
+ v = unpack[2] & 0b1111_0000_0000_0000
230
+ v >> 12
231
+ end
232
+
233
+ # The clock sequence of this UUID
234
+ def clock
235
+ a = unpack
236
+ ch = a[3] & 0b0001_1111
237
+ cl = a[4]
238
+ c = cl
239
+ c += ch << 8
240
+ c
241
+ end
242
+
243
+ # The IEEE 802 address in a hexadecimal format
244
+ def node
245
+ m = unpack[5].unpack 'C*'
246
+ '%02x%02x%02x%02x%02x%02x' % m
247
+ end
248
+ alias mac_address node
249
+ alias ieee802 node
250
+
251
+ # Generate the string representation (a.k.a GUID) of this UUID
252
+ def to_s
253
+ a = unpack
254
+ a[-1] = mac_address
255
+ "%08x-%04x-%04x-%02x%02x-%s" % a
256
+ end
257
+ alias guid to_s
258
+
259
+ # Convert into a RFC4122-comforming URN representation
260
+ def to_uri
261
+ "urn:uuid:" + self.to_s
262
+ end
263
+ alias urn to_uri
264
+ alias inspect to_uri
265
+
266
+ # Convert into 128-bit unsigned integer
267
+ # Typically a Bignum instance, but can be a Fixnum.
268
+ def to_int
269
+ @num
270
+ end
271
+ alias to_i to_int
272
+
273
+ # Two UUIDs are said to be equal if and only if their (byte-order
274
+ # canonicalized) integer representations are equivallent. Refer RFC4122 for
275
+ # details.
276
+ def == other
277
+ to_i == other.to_i
278
+ end
279
+ alias eql? ==
280
+
281
+ # Two identical UUIDs should have same hash
282
+ def hash
283
+ to_i
284
+ end
285
+
286
+ include Comparable
287
+ # UUIDs are comparable (don't know what benefits are there, though).
288
+ def <=> other
289
+ to_s <=> other.to_s
290
+ end
291
+
292
+ # Pre-defined UUID Namespaces described in RFC4122 Appendix C.
293
+ NameSpace_DNS = parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
294
+ NameSpace_URL = parse "6ba7b811-9dad-11d1-80b4-00c04fd430c8"
295
+ NameSpace_OID = parse "6ba7b812-9dad-11d1-80b4-00c04fd430c8"
296
+ NameSpace_X500 = parse "6ba7b814-9dad-11d1-80b4-00c04fd430c8"
297
+
298
+ # The Nil UUID in RFC4122 Section 4.1.7
299
+ Nil = parse "00000000-0000-0000-0000-000000000000"
300
+ end
301
+
302
+
303
+ # Local Variables:
304
+ # mode: ruby
305
+ # coding: utf-8
306
+ # indent-tabs-mode: t
307
+ # tab-width: 3
308
+ # ruby-indent-level: 3
309
+ # fill-column: 79
310
+ # default-justification: full
311
+ # End:
312
+ # vi: ts=3 sw=3
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright(c) 2005 URABE, Shyouhei.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this code, to deal in the code without restriction, including without
6
+ # limitation the rights to use, copy, modify, merge, publish, distribute,
7
+ # sublicense, and/or sell copies of the code, and to permit persons to whom the
8
+ # code is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be
11
+ # included in all copies or substantial portions of the code.
12
+ #
13
+ # THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE CODE OR THE USE OR OTHER DEALINGS IN THE
19
+ # CODE.
20
+
21
+ require 'test/unit'
22
+ require 'uuid'
23
+
24
+ class TC_UUID < Test::Unit::TestCase
25
+ def test_v1
26
+ u1 = UUID.create
27
+ u2 = UUID.create
28
+ assert_not_equal u1, u2
29
+ end
30
+
31
+ def test_v1_repeatability
32
+ u1 = UUID.create 1, 2, "345678"
33
+ u2 = UUID.create 1, 2, "345678"
34
+ assert_equal u1, u2
35
+ end
36
+
37
+ def test_v3
38
+ u1 = UUID.create_md5 "foo", UUID::NameSpace_DNS
39
+ u2 = UUID.create_md5 "foo", UUID::NameSpace_DNS
40
+ u3 = UUID.create_md5 "foo", UUID::NameSpace_URL
41
+ assert_equal u1, u2
42
+ assert_not_equal u1, u3
43
+ end
44
+
45
+ def test_v5
46
+ u1 = UUID.create_sha1 "foo", UUID::NameSpace_DNS
47
+ u2 = UUID.create_sha1 "foo", UUID::NameSpace_DNS
48
+ u3 = UUID.create_sha1 "foo", UUID::NameSpace_URL
49
+ assert_equal u1, u2
50
+ assert_not_equal u1, u3
51
+ end
52
+
53
+ def test_v4
54
+ # This test is not perfect, because the random nature of version 4
55
+ # UUID it is not always true that the three objects below really
56
+ # differ. But in real life it's enough to say we're OK when this
57
+ # passes.
58
+ u1 = UUID.create_random
59
+ u2 = UUID.create_random
60
+ u3 = UUID.create_random
61
+ assert_not_equal u1.raw_bytes, u2.raw_bytes
62
+ assert_not_equal u1.raw_bytes, u3.raw_bytes
63
+ assert_not_equal u2.raw_bytes, u3.raw_bytes
64
+ end
65
+
66
+ def test_pack
67
+ u1 = UUID.pack 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4,
68
+ "\000\300O\3240\310"
69
+ assert_equal UUID::NameSpace_DNS, u1
70
+ end
71
+
72
+ def test_unpack
73
+ tl, tm, th, cl, ch, m = UUID::NameSpace_DNS.unpack
74
+ assert_equal 0x6ba7b810, tl
75
+ assert_equal 0x9dad, tm
76
+ assert_equal 0x11d1, th
77
+ assert_equal 0x80, cl
78
+ assert_equal 0xb4, ch
79
+ assert_equal "\000\300O\3240\310", m
80
+ end
81
+
82
+ def test_parse
83
+ u1 = UUID.pack 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4,
84
+ "\000\300O\3240\310"
85
+ u2 = UUID.parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
86
+ u3 = UUID.parse "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
87
+ assert_equal u1, u2
88
+ assert_equal u1, u3
89
+ end
90
+
91
+ def test_to_s
92
+ u1 = UUID.parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
93
+ assert_equal "6ba7b810-9dad-11d1-80b4-00c04fd430c8", u1.to_s
94
+ end
95
+
96
+ def test_to_i
97
+ u1 = UUID.parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
98
+ assert_equal 0x6ba7b8109dad11d180b400c04fd430c8, u1.to_i
99
+ end
100
+
101
+ def test_time
102
+ assert_raises(RangeError) do
103
+ UUID::Nil.time
104
+ end
105
+ 0.times do |i|
106
+ t = Time.at i, i
107
+ u = UUID.create 0, t
108
+ assert_equal t.tv_sec, u.time.tv_sec
109
+ assert_equal t.tv_usec, u.time.tv_usec
110
+ end
111
+ end
112
+
113
+ def test_version
114
+ assert_equal 0, UUID::Nil.version
115
+ assert_equal 1, UUID.create.version
116
+ 65535.times do
117
+ assert_equal 4, UUID.create_random.version
118
+ end
119
+ end
120
+
121
+ def test_clock
122
+ assert_equal 0, UUID::Nil.clock
123
+ 8191.times do |i| # clock is 14bit so 8191 suffice
124
+ u = UUID.create i
125
+ assert_equal i, u.clock
126
+ end
127
+ end
128
+
129
+ def test_equality
130
+ u1 = UUID.parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
131
+ u2 = UUID.parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
132
+ assert_equal u1.hash, u2.hash
133
+ case u1
134
+ when u2
135
+ assert_equal true, true # ok
136
+ else
137
+ flunk "u1 != u2"
138
+ end
139
+ end
140
+ end
141
+
142
+
143
+
144
+ # Local Variables:
145
+ # mode: ruby
146
+ # coding: utf-8
147
+ # indent-tabs-mode: t
148
+ # tab-width: 3
149
+ # ruby-indent-level: 3
150
+ # fill-column: 79
151
+ # default-justification: full
152
+ # End:
153
+ # vi: ts=3 sw=3
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-uuid
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - URABE Shyouhei
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-06 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: UUID generator/parser in <300 lines. It fully supports UUID versions
15
+ 1, 3, 4 and 5.
16
+ email: shyouhei@ruby-lang.org
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - test/test_uuid.rb
22
+ - lib/uuid.rb
23
+ homepage: http://raa.ruby-lang.org/project/ruby-uuid/
24
+ licenses: []
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ! '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 1.8.10
44
+ signing_key:
45
+ specification_version: 3
46
+ summary: Pure-ruby RFC4122 implementation
47
+ test_files:
48
+ - test/test_uuid.rb