| 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 | UUID = Struct.new "UUID", :raw_bytes |
|---|
| 31 | class 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" |
|---|
| 294 | end |
|---|
| 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 |
|---|