ruby-uuid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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