| 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) |
|---|
| 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php |
|---|
| 3 | """UUID (universally unique identifiers) as specified in RFC 4122. |
|---|
| 4 | |
|---|
| 5 | This module provides the UUID class and the functions uuid1(), uuid3(), |
|---|
| 6 | uuid4(), uuid5() for generating version 1, 3, 4, and 5 UUIDs respectively. |
|---|
| 7 | |
|---|
| 8 | This module works with Python 2.3 or higher.""" |
|---|
| 9 | |
|---|
| 10 | __author__ = 'Ka-Ping Yee <ping@zesty.ca>' |
|---|
| 11 | __date__ = '$Date: 2005/11/30 11:51:58 $'.split()[1].replace('/', '-') |
|---|
| 12 | __version__ = '$Revision: 1.10 $' |
|---|
| 13 | |
|---|
| 14 | RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [ |
|---|
| 15 | 'reserved for NCS compatibility', 'specified in RFC 4122', |
|---|
| 16 | 'reserved for Microsoft compatibility', 'reserved for future definition'] |
|---|
| 17 | |
|---|
| 18 | class UUID(object): |
|---|
| 19 | """Instances of the UUID class represent UUIDs as specified in RFC 4122. |
|---|
| 20 | Converting a UUID to a string using str() produces a string in the form |
|---|
| 21 | "{12345678-1234-1234-1234-123456789abc}". The UUID constructor accepts |
|---|
| 22 | a similar string (braces and hyphens optional), or six integer arguments |
|---|
| 23 | (with 32-bit, 16-bit, 16-bit, 8-bit, 8-bit, and 48-bit values |
|---|
| 24 | respectively). UUID objects have the following attributes: |
|---|
| 25 | |
|---|
| 26 | bytes gets or sets the UUID as a 16-byte string |
|---|
| 27 | |
|---|
| 28 | urn gets the UUID as a URN as specified in RFC 4122 |
|---|
| 29 | |
|---|
| 30 | variant gets or sets the UUID variant as one of the constants |
|---|
| 31 | RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE |
|---|
| 32 | |
|---|
| 33 | version gets or sets the UUID version number (1 through 5) |
|---|
| 34 | """ |
|---|
| 35 | |
|---|
| 36 | def __init__(self, *args): |
|---|
| 37 | """Create a UUID either from a string representation in hexadecimal |
|---|
| 38 | or from six integers (32-bit time_low, 16-bit time_mid, 16-bit |
|---|
| 39 | time_hi_ver, 8-bit clock_hi_res, 8-bit clock_low, 48-bit node).""" |
|---|
| 40 | if len(args) == 1: |
|---|
| 41 | digits = args[0].replace('urn:', '').replace('uuid:', '') |
|---|
| 42 | digits = digits.replace('{', '').replace('}', '').replace('-', '') |
|---|
| 43 | assert len(digits) == 32, ValueError('badly formed UUID string') |
|---|
| 44 | time_low = int(digits[:8], 16) |
|---|
| 45 | time_mid = int(digits[8:12], 16) |
|---|
| 46 | time_hi_ver = int(digits[12:16], 16) |
|---|
| 47 | clock_hi_res = int(digits[16:18], 16) |
|---|
| 48 | clock_low = int(digits[18:20], 16) |
|---|
| 49 | node = int(digits[20:32], 16) |
|---|
| 50 | else: |
|---|
| 51 | (time_low, time_mid, time_hi_ver, |
|---|
| 52 | clock_hi_res, clock_low, node) = args |
|---|
| 53 | assert 0 <= time_low < 0x100000000, ValueError('time_low out of range') |
|---|
| 54 | assert 0 <= time_mid < 1<<16, ValueError('time_mid out of range') |
|---|
| 55 | assert 0 <= time_hi_ver < 1<<16, ValueError('time_hi_ver out of range') |
|---|
| 56 | assert 0 <= clock_hi_res < 1<<8, ValueError('clock_hi_res out of range') |
|---|
| 57 | assert 0 <= clock_low < 1<<8, ValueError('clock_low out of range') |
|---|
| 58 | assert 0 <= node < 0x1000000000000, ValueError('node out of range') |
|---|
| 59 | self.time_low = time_low |
|---|
| 60 | self.time_mid = time_mid |
|---|
| 61 | self.time_hi_ver = time_hi_ver |
|---|
| 62 | self.clock_hi_res = clock_hi_res |
|---|
| 63 | self.clock_low = clock_low |
|---|
| 64 | self.node = node |
|---|
| 65 | |
|---|
| 66 | def __cmp__(self, other): |
|---|
| 67 | return cmp(self.bytes, getattr(other, 'bytes', other)) |
|---|
| 68 | |
|---|
| 69 | def __str__(self): |
|---|
| 70 | return '{%08x-%04x-%04x-%02x%02x-%012x}' % ( |
|---|
| 71 | self.time_low, self.time_mid, self.time_hi_ver, |
|---|
| 72 | self.clock_hi_res, self.clock_low, self.node) |
|---|
| 73 | |
|---|
| 74 | def __repr__(self): |
|---|
| 75 | return 'UUID(%r)' % str(self) |
|---|
| 76 | |
|---|
| 77 | def get_bytes(self): |
|---|
| 78 | def byte(n): |
|---|
| 79 | return chr(n & 0xff) |
|---|
| 80 | |
|---|
| 81 | return (byte(self.time_low >> 24) + byte(self.time_low >> 16) + |
|---|
| 82 | byte(self.time_low >> 8) + byte(self.time_low) + |
|---|
| 83 | byte(self.time_mid >> 8) + byte(self.time_mid) + |
|---|
| 84 | byte(self.time_hi_ver >> 8) + byte(self.time_hi_ver) + |
|---|
| 85 | byte(self.clock_hi_res) + byte(self.clock_low) + |
|---|
| 86 | byte(self.node >> 40) + byte(self.node >> 32) + |
|---|
| 87 | byte(self.node >> 24) + byte(self.node >> 16) + |
|---|
| 88 | byte(self.node >> 8) + byte(self.node)) |
|---|
| 89 | |
|---|
| 90 | def set_bytes(self, bytes): |
|---|
| 91 | values = map(ord, bytes) |
|---|
| 92 | self.time_low = ((values[0] << 24) + (values[1] << 16) + |
|---|
| 93 | (values[2] << 8) + values[3]) |
|---|
| 94 | self.time_mid = (values[4] << 8) + values[5] |
|---|
| 95 | self.time_hi_ver = (values[6] << 8) + values[7] |
|---|
| 96 | self.clock_hi_res = values[8] |
|---|
| 97 | self.clock_low = values[9] |
|---|
| 98 | self.node = ((values[10] << 40) + (values[11] << 32) + |
|---|
| 99 | (values[12] << 24) + (values[13] << 16) + |
|---|
| 100 | (values[14] << 8) + values[15]) |
|---|
| 101 | |
|---|
| 102 | bytes = property(get_bytes, set_bytes) |
|---|
| 103 | |
|---|
| 104 | def get_urn(self): |
|---|
| 105 | return 'urn:uuid:%08x-%04x-%04x-%02x%02x-%012x' % ( |
|---|
| 106 | self.time_low, self.time_mid, self.time_hi_ver, |
|---|
| 107 | self.clock_hi_res, self.clock_low, self.node) |
|---|
| 108 | |
|---|
| 109 | urn = property(get_urn) |
|---|
| 110 | |
|---|
| 111 | def get_variant(self): |
|---|
| 112 | if not self.clock_hi_res & 0x80: |
|---|
| 113 | return RESERVED_NCS |
|---|
| 114 | elif not self.clock_hi_res & 0x40: |
|---|
| 115 | return RFC_4122 |
|---|
| 116 | elif not self.clock_hi_res & 0x20: |
|---|
| 117 | return RESERVED_MICROSOFT |
|---|
| 118 | else: |
|---|
| 119 | return RESERVED_FUTURE |
|---|
| 120 | |
|---|
| 121 | def set_variant(self, variant): |
|---|
| 122 | if variant == RESERVED_NCS: |
|---|
| 123 | self.clock_hi_res &= 0x7f |
|---|
| 124 | elif variant == RFC_4122: |
|---|
| 125 | self.clock_hi_res &= 0x3f |
|---|
| 126 | self.clock_hi_res |= 0x80 |
|---|
| 127 | elif variant == RESERVED_MICROSOFT: |
|---|
| 128 | self.clock_hi_res &= 0x1f |
|---|
| 129 | self.clock_hi_res |= 0xc0 |
|---|
| 130 | elif variant == RESERVED_FUTURE: |
|---|
| 131 | self.clock_hi_res &= 0x1f |
|---|
| 132 | self.clock_hi_res |= 0xe0 |
|---|
| 133 | else: |
|---|
| 134 | raise ValueError('illegal variant identifier') |
|---|
| 135 | |
|---|
| 136 | variant = property(get_variant, set_variant) |
|---|
| 137 | |
|---|
| 138 | def get_version(self): |
|---|
| 139 | return self.time_hi_ver >> 12 |
|---|
| 140 | |
|---|
| 141 | def set_version(self, version): |
|---|
| 142 | assert 1 <= version <= 5, ValueError('illegal version number') |
|---|
| 143 | self.time_hi_ver &= 0x0fff |
|---|
| 144 | self.time_hi_ver |= (version << 12) |
|---|
| 145 | |
|---|
| 146 | version = property(get_version, set_version) |
|---|
| 147 | |
|---|
| 148 | def unixgetaddr(program): |
|---|
| 149 | """Get the hardware address on a Unix machine.""" |
|---|
| 150 | from os import popen |
|---|
| 151 | for line in popen(program): |
|---|
| 152 | words = line.lower().split() |
|---|
| 153 | if 'hwaddr' in words: |
|---|
| 154 | addr = words[words.index('hwaddr') + 1] |
|---|
| 155 | return int(addr.replace(':', ''), 16) |
|---|
| 156 | if 'ether' in words: |
|---|
| 157 | addr = words[words.index('ether') + 1] |
|---|
| 158 | return int(addr.replace(':', ''), 16) |
|---|
| 159 | |
|---|
| 160 | def wingetaddr(program): |
|---|
| 161 | """Get the hardware address on a Windows machine.""" |
|---|
| 162 | from os import popen |
|---|
| 163 | for line in popen(program + ' /all'): |
|---|
| 164 | if line.strip().lower().startswith('physical address'): |
|---|
| 165 | addr = line.split(':')[-1].strip() |
|---|
| 166 | return int(addr.replace('-', ''), 16) |
|---|
| 167 | |
|---|
| 168 | def getaddr(): |
|---|
| 169 | """Get the hardware address as a 48-bit integer.""" |
|---|
| 170 | from os.path import join, isfile |
|---|
| 171 | for dir in ['/sbin', '/usr/sbin', r'c:\windows', |
|---|
| 172 | r'c:\windows\system', r'c:\windows\system32']: |
|---|
| 173 | if isfile(join(dir, 'ifconfig')): |
|---|
| 174 | return unixgetaddr(join(dir, 'ifconfig')) |
|---|
| 175 | if isfile(join(dir, 'ipconfig.exe')): |
|---|
| 176 | return wingetaddr(join(dir, 'ipconfig.exe')) |
|---|
| 177 | |
|---|
| 178 | def uuid1(): |
|---|
| 179 | """Generate a UUID based on the time and hardware address.""" |
|---|
| 180 | from time import time |
|---|
| 181 | from random import randrange |
|---|
| 182 | nanoseconds = int(time() * 1e9) |
|---|
| 183 | # 0x01b21dd213814000 is the number of 100-ns intervals between the |
|---|
| 184 | # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. |
|---|
| 185 | timestamp = int(nanoseconds/100) + 0x01b21dd213814000 |
|---|
| 186 | clock = randrange(1<<16) # don't use stable storage |
|---|
| 187 | time_low = timestamp & (0x100000000 - 1) |
|---|
| 188 | time_mid = (timestamp >> 32) & 0xffff |
|---|
| 189 | time_hi_ver = (timestamp >> 48) & 0x0fff |
|---|
| 190 | clock_low = clock & 0xff |
|---|
| 191 | clock_hi_res = (clock >> 8) & 0x3f |
|---|
| 192 | node = getaddr() |
|---|
| 193 | uuid = UUID(time_low, time_mid, time_hi_ver, clock_low, clock_hi_res, node) |
|---|
| 194 | uuid.variant = RFC_4122 |
|---|
| 195 | uuid.version = 1 |
|---|
| 196 | return uuid |
|---|
| 197 | |
|---|
| 198 | def uuid3(namespace, name): |
|---|
| 199 | """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" |
|---|
| 200 | try: |
|---|
| 201 | from hashlib import md5 |
|---|
| 202 | except ImportError: |
|---|
| 203 | from md5 import md5 |
|---|
| 204 | uuid = UUID(0, 0, 0, 0, 0, 0) |
|---|
| 205 | uuid.bytes = md5(namespace.bytes + name).digest()[:16] |
|---|
| 206 | uuid.variant = RFC_4122 |
|---|
| 207 | uuid.version = 3 |
|---|
| 208 | return uuid |
|---|
| 209 | |
|---|
| 210 | def uuid4(): |
|---|
| 211 | """Generate a random UUID.""" |
|---|
| 212 | try: |
|---|
| 213 | from os import urandom |
|---|
| 214 | except: |
|---|
| 215 | from random import randrange |
|---|
| 216 | uuid = UUID(randrange(1<<32L), randrange(1<<16), randrange(1<<16), |
|---|
| 217 | randrange(1<<8), randrange(1<<8), randrange(1<<48L)) |
|---|
| 218 | else: |
|---|
| 219 | uuid = UUID(0, 0, 0, 0, 0, 0) |
|---|
| 220 | uuid.bytes = urandom(16) |
|---|
| 221 | uuid.variant = RFC_4122 |
|---|
| 222 | uuid.version = 4 |
|---|
| 223 | return uuid |
|---|
| 224 | |
|---|
| 225 | def uuid5(namespace, name): |
|---|
| 226 | """Generate a UUID from the SHA-1 hash of a namespace UUID and a name.""" |
|---|
| 227 | try: |
|---|
| 228 | from hashlib import sha1 |
|---|
| 229 | except ImportError: |
|---|
| 230 | from sha import sha as sha1 |
|---|
| 231 | uuid = UUID(0, 0, 0, 0, 0, 0) |
|---|
| 232 | uuid.bytes = sha1(namespace.bytes + name).digest()[:16] |
|---|
| 233 | uuid.variant = RFC_4122 |
|---|
| 234 | uuid.version = 5 |
|---|
| 235 | return uuid |
|---|
| 236 | |
|---|
| 237 | NAMESPACE_DNS = UUID('{6ba7b810-9dad-11d1-80b4-00c04fd430c8}') |
|---|
| 238 | NAMESPACE_URL = UUID('{6ba7b811-9dad-11d1-80b4-00c04fd430c8}') |
|---|
| 239 | NAMESPACE_OID = UUID('{6ba7b812-9dad-11d1-80b4-00c04fd430c8}') |
|---|
| 240 | NAMESPACE_X500 = UUID('{6ba7b814-9dad-11d1-80b4-00c04fd430c8}') |
|---|