root/lang/ruby/uuid/trunk/lib/uuid.rb @ 30603

Revision 30603, 7.6 kB (checked in by shyouhei, 6 years ago)

Identical UUIDs should have identical hash values.

Line 
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
27end
28
29# Pure ruby UUID generator, which is compatible with RFC4122
30UUID = Struct.new "UUID", :raw_bytes
31class UUID
32        # UUID epoch is 15th Oct. 1582
33        UNIXEpoch = 0x01B21DD213814000 # in 100-nanoseconds resolution
34
35        private_class_method :new
36
37        class << self
38                def mask ver, str # :nodoc
39                        ver = ver & 15
40                        v = str[6]
41                        v &= 0b0000_1111
42                        v |= ver << 4
43                        str[6] = v
44                        r = str[8]
45                        r &= 0b0011_1111
46                        r |= 0b1000_0000
47                        str[8] = r
48                        str
49                end
50
51                def prand # :nodoc:
52                        rand 0x100000000
53                end
54
55                private :mask, :prand
56
57                # UUID generation using SHA1. Recommended over create_md5.
58                # Namespace object is another UUID, some of them are pre-defined below.
59                def create_sha1 str, namespace
60                        sha1 = Digest::SHA1.new
61                        sha1.update namespace.raw_bytes
62                        sha1.update str
63                        sum = sha1.digest
64                        raw = mask 5, sum[0..15]
65                        ret = new raw
66                        ret.raw_bytes.freeze
67                        ret.freeze
68                        ret
69                end
70
71                # UUID generation using MD5 (for backward compat.)
72                def create_md5 str, namespace
73                        md5 = Digest::MD5.new
74                        md5.update namespace.raw_bytes
75                        md5.update str
76                        sum = md5.digest
77                        raw = mask 3, sum[0..16]
78                        ret = new raw
79                        ret.raw_bytes.freeze
80                        ret.freeze
81                        ret
82                end
83
84                # UUID  generation  using  random-number  generator.   From  it's  random
85                # nature, there's  no warranty that  the created ID is  really universaly
86                # unique.
87                def create_random
88                        rnd = [prand, prand, prand, prand].pack "N4"
89                        raw = mask 4, rnd
90                        ret = new raw
91                        ret.raw_bytes.freeze
92                        ret.freeze
93                        ret
94                end
95
96                def read_state fp                         # :nodoc:
97                        fp.rewind
98                        Marshal.load fp.read
99                end
100
101                def write_state fp, c, m  # :nodoc:
102                        fp.rewind
103                        str = Marshal.dump [c, m]
104                        fp.write str
105                end
106
107                private :read_state, :write_state
108                STATE_FILE = 'ruby-uuid'
109
110                # create  the "version  1" UUID  with current  system clock,  current UTC
111                # timestamp, and the IEEE 802 address (so-called MAC address).
112                #
113                # Speed notice: it's slow.  It writes  some data into hard drive on every
114                # invokation. If you want to speed  this up, try remounting tmpdir with a
115                # memory based filesystem  (such as tmpfs).  STILL slow?  then no way but
116                # rewrite it with c :)
117                def create clock=nil, time=Time.now, mac_addr=nil
118                        c = t = m = nil
119                        Dir.chdir Dir.tmpdir do
120                                unless FileTest.exist? STATE_FILE then
121                                        # Generate a pseudo MAC address because we have no pure-ruby way
122                                        # to know  the MAC  address of the  NIC this system  uses.  Note
123                                        # that cheating  with pseudo arresses here  is completely legal:
124                                        # see Section 4.5 of RFC4122 for details.
125                                        sha1 = Digest::SHA1.new
126                                        256.times do
127                                                r = [prand].pack "N"
128                                                sha1.update r
129                                        end
130                                        str = sha1.digest
131                                        r = rand 34 # 40-6
132                                        node = str[r, 6] || str
133                                        node[0] |= 0x01 # multicast bit
134                                        k = rand 0x40000
135                                        open STATE_FILE, 'w' do |fp|
136                                                fp.flock IO::LOCK_EX
137                                                write_state fp, k, node
138                                                fp.chmod 0o777 # must be world writable
139                                        end
140                                end
141                                open STATE_FILE, 'r+' do |fp|
142                                        fp.flock IO::LOCK_EX
143                                        c, m = read_state fp
144                                        c += 1 # important; increment here
145                                        write_state fp, c, m
146                                end
147                        end
148                        c = clock & 0b11_1111_1111_1111 if clock
149                        m = mac_addr if mac_addr
150                        time = Time.at time if time.is_a? Float
151                        case time
152                        when Time
153                                t = time.to_i * 10_000_000 + time.tv_usec * 10 + UNIXEpoch
154                        when Integer
155                                t = time + UNIXEpoch
156                        else
157                                raise TypeError, "cannot convert ``#{time}'' into Time."
158                        end
159
160                        tl = t & 0xFFFF_FFFF
161                        tm = t >> 32
162                        tm = tm & 0xFFFF
163                        th = t >> 48
164                        th = th & 0b0000_1111_1111_1111
165                        th = th | 0b0001_0000_0000_0000
166                        cl = c & 0b0000_0000_1111_1111
167                        ch = c & 0b0011_1111_0000_0000
168                        ch = ch >> 8
169                        ch = ch | 0b1000_0000
170                        pack tl, tm, th, ch, cl, m
171                end
172
173                # A  simple GUID  parser:  just ignores  unknown  characters and  convert
174                # hexadecimal dump into 16-octet object.
175                def parse obj
176                        str = obj.to_s.sub %r/\Aurn:uuid:/, ''
177                        str.gsub! %r/[^0-9A-Fa-f]/, ''
178                        raw = str[0..31].to_a.pack 'H*'
179                        ret = new raw
180                        ret.raw_bytes.freeze
181                        ret.freeze
182                        ret
183                end
184
185                # The 'primitive constructor' of this class
186                # Note UUID.pack(uuid.unpack) == uuid
187                def pack tl, tm, th, ch, cl, n
188                        raw = [tl, tm, th, ch, cl, n].pack "NnnCCa6"
189                        ret = new raw
190                        ret.raw_bytes.freeze
191                        ret.freeze
192                        ret
193                end
194        end
195
196        # The 'primitive deconstructor', or the dual to pack.
197        # Note UUID.pack(uuid.unpack) == uuid
198        def unpack
199                raw_bytes.unpack "NnnCCa6"
200        end
201
202        # The timestamp of this UUID.
203        # Throws RageError if that time exceeds UNIX time range
204        def time
205                a = unpack
206                tl = a[0]
207                tm = a[1]
208                th = a[2] & 0x0FFF
209                t = tl
210                t += tm << 32
211                t += th << 48
212                t -= UNIXEpoch
213                tv_sec = t / 10_000_000
214                t -= tv_sec * 10_000_000
215                tv_usec = t / 10
216                Time.at tv_sec, tv_usec
217        end
218
219        # The version of this UUID
220        def version
221                v = unpack[2] & 0b1111_0000_0000_0000
222                v >> 12
223        end
224
225        # The clock sequence of this UUID
226        def clock
227                a = unpack
228                ch = a[3] & 0b0001_1111
229                cl = a[4]
230                c = cl
231                c += ch << 8
232                c
233        end
234
235        # The IEEE 802 address in a hexadecimal format
236        def node
237                m = unpack[5].unpack 'C*'
238                '%02x%02x%02x%02x%02x%02x' % m
239        end
240        alias mac_address node
241        alias ieee802 node
242
243        # Generate the string representation (a.k.a GUID) of this UUID
244        def to_s
245                a = unpack
246                a[-1] = mac_address
247                "%08x-%04x-%04x-%02x%02x-%s" % a
248        end
249        alias guid to_s
250
251        # Convert into a RFC4122-comforming URN representation
252        def to_uri
253                "urn:uuid:" + self.to_s
254        end
255        alias urn to_uri
256
257        # Convert into 128-bit unsigned integer
258        # Typically a Bignum instance, but can be a Fixnum.
259        def to_int
260                tmp = self.raw_bytes.unpack "C*"
261                tmp.inject do |r, i|
262                        r * 256 | i
263                end
264        end
265        alias to_i to_int
266
267        # Two  UUIDs  are  said  to  be  equal if  and  only  if  their  (byte-order
268        # canonicalized) integer representations are equivallent.  Refer RFC4122 for
269        # details.
270        def == other
271                to_i == other.to_i
272        end
273        alias eql? ==
274
275        # Two identical UUIDs should have same hash
276        def hash
277                to_i
278        end
279
280        include Comparable
281        # UUIDs are comparable (don't know what benefits are there, though).
282        def <=> other
283                to_s <=> other.to_s
284        end
285
286        # Pre-defined UUID Namespaces described in RFC4122 Appendix C.
287        NameSpace_DNS = parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
288        NameSpace_URL = parse "6ba7b811-9dad-11d1-80b4-00c04fd430c8"
289        NameSpace_OID = parse "6ba7b812-9dad-11d1-80b4-00c04fd430c8"
290        NameSpace_X500 = parse "6ba7b814-9dad-11d1-80b4-00c04fd430c8"
291
292        # The Nil UUID in RFC4122 Section 4.1.7
293        Nil = parse "00000000-0000-0000-0000-000000000000"
294end
295
296
297# Local Variables:
298# mode: ruby
299# coding: utf-8
300# indent-tabs-mode: t
301# tab-width: 3
302# ruby-indent-level: 3
303# fill-column: 79
304# default-justification: full
305# End:
306# vi: ts=3 sw=3
Note: See TracBrowser for help on using the browser.