0001"""UUID (universally unique identifiers) as specified in RFC 4122.
0002
0003This module provides the UUID class and the functions uuid1(), uuid3(),
0004uuid4(), uuid5() for generating version 1, 3, 4, and 5 UUIDs respectively.
0005
0006This module works with Python 2.3 or higher."""
0007
0008__author__ = 'Ka-Ping Yee <ping@zesty.ca>'
0009__date__ = '$Date: 2005/11/30 11:51:58 $'.split()[1].replace('/', '-')
0010__version__ = '$Revision: 1.10 $'
0011
0012RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [
0013    'reserved for NCS compatibility', 'specified in RFC 4122',
0014    'reserved for Microsoft compatibility', 'reserved for future definition']
0015
0016class UUID(object):
0017    """Instances of the UUID class represent UUIDs as specified in RFC 4122.
0018    Converting a UUID to a string using str() produces a string in the form
0019    "{12345678-1234-1234-1234-123456789abc}".  The UUID constructor accepts
0020    a similar string (braces and hyphens optional), or six integer arguments
0021    (with 32-bit, 16-bit, 16-bit, 8-bit, 8-bit, and 48-bit values
0022    respectively).  UUID objects have the following attributes:
0023
0024        bytes       gets or sets the UUID as a 16-byte string
0025
0026        urn         gets the UUID as a URN as specified in RFC 4122
0027
0028        variant     gets or sets the UUID variant as one of the constants
0029                    RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE
0030
0031        version     gets or sets the UUID version number (1 through 5)
0032    """
0033
0034    def __init__(self, *args):
0035        """Create a UUID either from a string representation in hexadecimal
0036        or from six integers (32-bit time_low, 16-bit time_mid, 16-bit
0037        time_hi_ver, 8-bit clock_hi_res, 8-bit clock_low, 48-bit node)."""
0038        if len(args) == 1:
0039            digits = args[0].replace('urn:', '').replace('uuid:', '')
0040            digits = digits.replace('{', '').replace('}', '').replace('-', '')
0041            assert len(digits) == 32, ValueError('badly formed UUID string')
0042            time_low = int(digits[:8], 16)
0043            time_mid = int(digits[8:12], 16)
0044            time_hi_ver = int(digits[12:16], 16)
0045            clock_hi_res = int(digits[16:18], 16)
0046            clock_low = int(digits[18:20], 16)
0047            node = int(digits[20:32], 16)
0048        else:
0049            (time_low, time_mid, time_hi_ver,
0050             clock_hi_res, clock_low, node) = args
0051        assert 0 <= time_low < 0x100000000, ValueError('time_low out of range')
0052        assert 0 <= time_mid < 1<<16, ValueError('time_mid out of range')
0053        assert 0 <= time_hi_ver < 1<<16, ValueError('time_hi_ver out of range')
0054        assert 0 <= clock_hi_res < 1<<8, ValueError('clock_hi_res out of range')
0055        assert 0 <= clock_low < 1<<8, ValueError('clock_low out of range')
0056        assert 0 <= node < 0x1000000000000, ValueError('node out of range')
0057        self.time_low = time_low
0058        self.time_mid = time_mid
0059        self.time_hi_ver = time_hi_ver
0060        self.clock_hi_res = clock_hi_res
0061        self.clock_low = clock_low
0062        self.node = node
0063
0064    def __cmp__(self, other):
0065        return cmp(self.bytes, getattr(other, 'bytes', other))
0066
0067    def __str__(self):
0068        return '{%08x-%04x-%04x-%02x%02x-%012x}' % (
0069            self.time_low, self.time_mid, self.time_hi_ver,
0070            self.clock_hi_res, self.clock_low, self.node)
0071
0072    def __repr__(self):
0073        return 'UUID(%r)' % str(self)
0074
0075    def get_bytes(self):
0076        def byte(n):
0077            return chr(n & 0xff)
0078
0079        return (byte(self.time_low >> 24) + byte(self.time_low >> 16) +
0080                byte(self.time_low >> 8) + byte(self.time_low) +
0081                byte(self.time_mid >> 8) + byte(self.time_mid) +
0082                byte(self.time_hi_ver >> 8) + byte(self.time_hi_ver) +
0083                byte(self.clock_hi_res) + byte(self.clock_low) +
0084                byte(self.node >> 40) + byte(self.node >> 32) +
0085                byte(self.node >> 24) + byte(self.node >> 16) +
0086                byte(self.node >> 8) + byte(self.node))
0087
0088    def set_bytes(self, bytes):
0089        values = map(ord, bytes)
0090        self.time_low = ((values[0] << 24) + (values[1] << 16) +
0091                         (values[2] << 8) + values[3])
0092        self.time_mid = (values[4] << 8) + values[5]
0093        self.time_hi_ver = (values[6] << 8) + values[7]
0094        self.clock_hi_res = values[8]
0095        self.clock_low = values[9]
0096        self.node = ((values[10] << 40) + (values[11] << 32) +
0097                     (values[12] << 24) + (values[13] << 16) +
0098                     (values[14] << 8) + values[15])
0099
0100    bytes = property(get_bytes, set_bytes)
0101
0102    def get_urn(self):
0103        return 'urn:uuid:%08x-%04x-%04x-%02x%02x-%012x' % (
0104            self.time_low, self.time_mid, self.time_hi_ver,
0105            self.clock_hi_res, self.clock_low, self.node)
0106
0107    urn = property(get_urn)
0108
0109    def get_variant(self):
0110        if not self.clock_hi_res & 0x80:
0111            return RESERVED_NCS
0112        elif not self.clock_hi_res & 0x40:
0113            return RFC_4122
0114        elif not self.clock_hi_res & 0x20:
0115            return RESERVED_MICROSOFT
0116        else:
0117            return RESERVED_FUTURE
0118
0119    def set_variant(self, variant):
0120        if variant == RESERVED_NCS:
0121            self.clock_hi_res &= 0x7f
0122        elif variant == RFC_4122:
0123            self.clock_hi_res &= 0x3f
0124            self.clock_hi_res |= 0x80
0125        elif variant == RESERVED_MICROSOFT:
0126            self.clock_hi_res &= 0x1f
0127            self.clock_hi_res |= 0xc0
0128        elif variant == RESERVED_FUTURE:
0129            self.clock_hi_res &= 0x1f
0130            self.clock_hi_res |= 0xe0
0131        else:
0132            raise ValueError('illegal variant identifier')
0133
0134    variant = property(get_variant, set_variant)
0135
0136    def get_version(self):
0137        return self.time_hi_ver >> 12
0138
0139    def set_version(self, version):
0140        assert 1 <= version <= 5, ValueError('illegal version number')
0141        self.time_hi_ver &= 0x0fff
0142        self.time_hi_ver |= (version << 12)
0143
0144    version = property(get_version, set_version)
0145
0146def unixgetaddr(program):
0147    """Get the hardware address on a Unix machine."""
0148    from os import popen
0149    for line in popen(program):
0150        words = line.lower().split()
0151        if 'hwaddr' in words:
0152            addr = words[words.index('hwaddr') + 1]
0153            return int(addr.replace(':', ''), 16)
0154        if 'ether' in words:
0155            addr = words[words.index('ether') + 1]
0156            return int(addr.replace(':', ''), 16)
0157
0158def wingetaddr(program):
0159    """Get the hardware address on a Windows machine."""
0160    from os import popen
0161    for line in popen(program + ' /all'):
0162        if line.strip().lower().startswith('physical address'):
0163            addr = line.split(':')[-1].strip()
0164            return int(addr.replace('-', ''), 16)
0165
0166def getaddr():
0167    """Get the hardware address as a 48-bit integer."""
0168    from os.path import join, isfile
0169    for dir in ['/sbin', '/usr/sbin', r'c:\windows',
0170                r'c:\windows\system', r'c:\windows\system32']:
0171        if isfile(join(dir, 'ifconfig')):
0172            return unixgetaddr(join(dir, 'ifconfig'))
0173        if isfile(join(dir, 'ipconfig.exe')):
0174            return wingetaddr(join(dir, 'ipconfig.exe'))
0175
0176def uuid1():
0177    """Generate a UUID based on the time and hardware address."""
0178    from time import time
0179    from random import randrange
0180    nanoseconds = int(time() * 1e9)
0181    # 0x01b21dd213814000 is the number of 100-ns intervals between the
0182    # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
0183    timestamp = int(nanoseconds/100) + 0x01b21dd213814000
0184    clock = randrange(1<<16) # don't use stable storage
0185    time_low = timestamp & (0x100000000 - 1)
0186    time_mid = (timestamp >> 32) & 0xffff
0187    time_hi_ver = (timestamp >> 48) & 0x0fff
0188    clock_low = clock & 0xff
0189    clock_hi_res = (clock >> 8) & 0x3f
0190    node = getaddr()
0191    uuid = UUID(time_low, time_mid, time_hi_ver, clock_low, clock_hi_res, node)
0192    uuid.variant = RFC_4122
0193    uuid.version = 1
0194    return uuid
0195
0196def uuid3(namespace, name):
0197    """Generate a UUID from the MD5 hash of a namespace UUID and a name."""
0198    from md5 import md5
0199    uuid = UUID(0, 0, 0, 0, 0, 0)
0200    uuid.bytes = md5(namespace.bytes + name).digest()[:16]
0201    uuid.variant = RFC_4122
0202    uuid.version = 3
0203    return uuid
0204
0205def uuid4():
0206    """Generate a random UUID."""
0207    try:
0208        from os import urandom
0209    except:
0210        from random import randrange
0211        uuid = UUID(randrange(1<<32), randrange(1<<16), randrange(1<<16),
0212                    randrange(1<<8), randrange(1<<8), randrange(1<<48))
0213    else:
0214        uuid = UUID(0, 0, 0, 0, 0, 0)
0215        uuid.bytes = urandom(16)
0216    uuid.variant = RFC_4122
0217    uuid.version = 4
0218    return uuid
0219
0220def uuid5(namespace, name):
0221    """Generate a UUID from the SHA-1 hash of a namespace UUID and a name."""
0222    from sha import sha
0223    uuid = UUID(0, 0, 0, 0, 0, 0)
0224    uuid.bytes = sha(namespace.bytes + name).digest()[:16]
0225    uuid.variant = RFC_4122
0226    uuid.version = 5
0227    return uuid
0228
0229NAMESPACE_DNS = UUID('{6ba7b810-9dad-11d1-80b4-00c04fd430c8}')
0230NAMESPACE_URL = UUID('{6ba7b811-9dad-11d1-80b4-00c04fd430c8}')
0231NAMESPACE_OID = UUID('{6ba7b812-9dad-11d1-80b4-00c04fd430c8}')
0232NAMESPACE_X500 = UUID('{6ba7b814-9dad-11d1-80b4-00c04fd430c8}')