diff --git a/Lib/_uuid1.py b/Lib/_uuid1.py new file mode 100644 index 00000000000000..f76db889417d4d --- /dev/null +++ b/Lib/_uuid1.py @@ -0,0 +1,321 @@ +import sys + + +bytes_ = bytes # The built-in bytes type + + +def _popen(command, *args): + import os, shutil, subprocess + executable = shutil.which(command) + if executable is None: + path = os.pathsep.join(('/sbin', '/usr/sbin')) + executable = shutil.which(command, path=path) + if executable is None: + return None + + # LC_ALL=C to ensure English output, stderr=DEVNULL to prevent output + # on stderr (Note: we don't have an example where the words we search + # for are actually localized, but in theory some system could do so.) + env = dict(os.environ) + env['LC_ALL'] = 'C' + proc = subprocess.Popen((executable,) + args, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + env=env) + return proc + + +def _find_mac(command, args, hw_identifiers, get_index): + try: + proc = _popen(command, *args.split()) + if not proc: + return + + with proc: + for line in proc.stdout: + words = line.lower().rstrip().split() + for i in range(len(words)): + if words[i] in hw_identifiers: + try: + word = words[get_index(i)] + mac = int(word.replace(b':', b''), 16) + if mac: + return mac + except (ValueError, IndexError): + # Virtual interfaces, such as those provided by + # VPNs, do not have a colon-delimited MAC address + # as expected, but a 16-byte HWAddr separated by + # dashes. These should be ignored in favor of a + # real MAC address + pass + except OSError: + pass + + +def _ifconfig_getnode(): + """Get the hardware address on Unix by running ifconfig.""" + # This works on Linux ('' or '-a'), Tru64 ('-av'), but not all Unixes. + for args in ('', '-a', '-av'): + mac = _find_mac('ifconfig', args, [b'hwaddr', b'ether'], lambda i: i+1) + if mac: + return mac + + +def _ip_getnode(): + """Get the hardware address on Unix by running ip.""" + # This works on Linux with iproute2. + mac = _find_mac('ip', 'link list', [b'link/ether'], lambda i: i+1) + if mac: + return mac + + +def _arp_getnode(): + """Get the hardware address on Unix by running arp.""" + import os, socket + try: + ip_addr = socket.gethostbyname(socket.gethostname()) + except OSError: + return None + + # Try getting the MAC addr from arp based on our IP address (Solaris). + return _find_mac('arp', '-an', [os.fsencode(ip_addr)], lambda i: -1) + + +def _lanscan_getnode(): + """Get the hardware address on Unix by running lanscan.""" + # This might work on HP-UX. + return _find_mac('lanscan', '-ai', [b'lan0'], lambda i: 0) + + +def _netstat_getnode(): + """Get the hardware address on Unix by running netstat.""" + # This might work on AIX, Tru64 UNIX. + try: + proc = _popen('netstat', '-ia') + if not proc: + return + + with proc: + words = proc.stdout.readline().rstrip().split() + try: + i = words.index(b'Address') + except ValueError: + return + for line in proc.stdout: + try: + words = line.rstrip().split() + word = words[i] + if len(word) == 17 and word.count(b':') == 5: + mac = int(word.replace(b':', b''), 16) + if mac: + return mac + except (ValueError, IndexError): + pass + except OSError: + pass + + +def _ipconfig_getnode(): + """Get the hardware address on Windows by running ipconfig.exe.""" + import os, re + dirs = ['', r'c:\windows\system32', r'c:\winnt\system32'] + try: + import ctypes + buffer = ctypes.create_string_buffer(300) + ctypes.windll.kernel32.GetSystemDirectoryA(buffer, 300) + dirs.insert(0, buffer.value.decode('mbcs')) + except: + pass + for dir in dirs: + try: + pipe = os.popen(os.path.join(dir, 'ipconfig') + ' /all') + except OSError: + continue + with pipe: + for line in pipe: + value = line.split(':')[-1].strip().lower() + if re.match('([0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value): + return int(value.replace('-', ''), 16) + + +def _netbios_getnode(): + """Get the hardware address on Windows using NetBIOS calls. + See http://support.microsoft.com/kb/118623 for details.""" + import win32wnet, netbios + ncb = netbios.NCB() + ncb.Command = netbios.NCBENUM + ncb.Buffer = adapters = netbios.LANA_ENUM() + adapters._pack() + if win32wnet.Netbios(ncb) != 0: + return + + adapters._unpack() + for i in range(adapters.length): + ncb.Reset() + ncb.Command = netbios.NCBRESET + ncb.Lana_num = ord(adapters.lana[i]) + if win32wnet.Netbios(ncb) != 0: + continue + ncb.Reset() + ncb.Command = netbios.NCBASTAT + ncb.Lana_num = ord(adapters.lana[i]) + ncb.Callname = '*'.ljust(16) + ncb.Buffer = status = netbios.ADAPTER_STATUS() + if win32wnet.Netbios(ncb) != 0: + continue + status._unpack() + bytes = status.adapter_address[:6] + if len(bytes) != 6: + continue + return int.from_bytes(bytes, 'big') + + +# Thanks to Thomas Heller for ctypes and for his help with its use here. + +# If ctypes is available, use it to find system routines for UUID generation. +# XXX This makes the module non-thread-safe! +_uuid_generate_time = _UuidCreate = None +try: + import ctypes, ctypes.util + + # The uuid_generate_* routines are provided by libuuid on at least + # Linux and FreeBSD, and provided by libc on Mac OS X. + _libnames = ['uuid'] + if not sys.platform.startswith('win'): + _libnames.append('c') + + for libname in _libnames: + try: + lib = ctypes.CDLL(ctypes.util.find_library(libname)) + except Exception: # pragma: nocover + continue + # Try to find the safe variety first. + if hasattr(lib, 'uuid_generate_time_safe'): + _uuid_generate_time = lib.uuid_generate_time_safe + # int uuid_generate_time_safe(uuid_t out); + break + elif hasattr(lib, 'uuid_generate_time'): # pragma: nocover + _uuid_generate_time = lib.uuid_generate_time + # void uuid_generate_time(uuid_t out); + _uuid_generate_time.restype = None + break + del _libnames + + # The uuid_generate_* functions are broken on MacOS X 10.5, as noted + # in issue #8621 the function generates the same sequence of values + # in the parent process and all children created using fork (unless + # those children use exec as well). + # + # Assume that the uuid_generate functions are broken from 10.5 onward, + # the test can be adjusted when a later version is fixed. + if sys.platform == 'darwin': + if int(os.uname().release.split('.')[0]) >= 9: + _uuid_generate_time = None + + # On Windows prior to 2000, UuidCreate gives a UUID containing the + # hardware address. On Windows 2000 and later, UuidCreate makes a + # random UUID and UuidCreateSequential gives a UUID containing the + # hardware address. These routines are provided by the RPC runtime. + # NOTE: at least on Tim's WinXP Pro SP2 desktop box, while the last + # 6 bytes returned by UuidCreateSequential are fixed, they don't appear + # to bear any relationship to the MAC address of any network device + # on the box. + try: + lib = ctypes.windll.rpcrt4 + except: + lib = None + _UuidCreate = getattr(lib, 'UuidCreateSequential', + getattr(lib, 'UuidCreate', None)) +except: + pass + + +def _bytes_to_node(rawbytes): + value = int.from_bytes(rawbytes, byteorder='big') + node = value & 0xffffffffffff + return node + + +def _unixdll_getnode(): + """Get the hardware address on Unix using ctypes.""" + _buffer = ctypes.create_string_buffer(16) + _uuid_generate_time(_buffer) + return _bytes_to_node(bytes_(_buffer.raw)) + + +def _windll_getnode(): + """Get the hardware address on Windows using ctypes.""" + _buffer = ctypes.create_string_buffer(16) + if _UuidCreate(_buffer) == 0: + return _bytes_to_node(bytes_(_buffer.raw)) + + +def _random_getnode(): + """Get a random node ID, with eighth bit set as suggested by RFC 4122.""" + import random + return random.getrandbits(48) | 0x010000000000 + + +def getnode(): + """Get the hardware address as a 48-bit positive integer. + + The first time this runs, it may launch a separate program, which could + be quite slow. If all attempts to obtain the hardware address fail, we + choose a random 48-bit number with its eighth bit set to 1 as recommended + in RFC 4122. + """ + if sys.platform == 'win32': + getters = [_windll_getnode, _netbios_getnode, _ipconfig_getnode] + else: + getters = [_unixdll_getnode, _ifconfig_getnode, _ip_getnode, + _arp_getnode, _lanscan_getnode, _netstat_getnode] + + for getter in getters + [_random_getnode]: + try: + node = getter() + except: + continue + if node is not None: + return node + + +_last_timestamp = None + + +def uuid1(UUID, SafeUUID, node, clock_seq): + # When the system provides a version-1 UUID generator, use it (but don't + # use UuidCreate here because its UUIDs don't conform to RFC 4122). + if _uuid_generate_time and node is clock_seq is None: + _buffer = ctypes.create_string_buffer(16) + safely_generated = _uuid_generate_time(_buffer) + try: + is_safe = SafeUUID(safely_generated) + except ValueError: + is_safe = SafeUUID.unknown + return UUID(bytes=bytes_(_buffer.raw), is_safe=is_safe) + + global _last_timestamp + import time + nanoseconds = int(time.time() * 1e9) + # 0x01b21dd213814000 is the number of 100-ns intervals between the + # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. + timestamp = int(nanoseconds/100) + 0x01b21dd213814000 + if _last_timestamp is not None and timestamp <= _last_timestamp: + timestamp = _last_timestamp + 1 + _last_timestamp = timestamp + + if clock_seq is None: + import random + clock_seq = random.getrandbits(14) # instead of stable storage + + time_low = timestamp & 0xffffffff + time_mid = (timestamp >> 32) & 0xffff + time_hi_version = (timestamp >> 48) & 0x0fff + clock_seq_low = clock_seq & 0xff + clock_seq_hi_variant = (clock_seq >> 8) & 0x3f + if node is None: + node = getnode() + + return UUID(fields=(time_low, time_mid, time_hi_version, + clock_seq_hi_variant, clock_seq_low, node), + version=1) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index cd0f81e7e14e8f..f7723805452093 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -6,6 +6,11 @@ import shutil import subprocess import uuid +try: + import _uuid1 +except ImportError: + _uuid1 = None + def importable(name): try: @@ -14,6 +19,7 @@ def importable(name): except: return False + class TestUUID(unittest.TestCase): def test_UUID(self): equal = self.assertEqual @@ -340,50 +346,6 @@ def test_uuid1(self): equal(((u.clock_seq_hi_variant & 0x3f) << 8) | u.clock_seq_low, 0x3fff) - requires_ugt = unittest.skipUnless(uuid._uuid_generate_time is not None, - 'requires uuid_generate_time_safe(3)') - - @requires_ugt - # bpo-29925: On Mac OS X Tiger, uuid.uuid1().is_safe returns - # uuid.SafeUUID.unknown - @support.requires_mac_ver(10, 5) - def test_uuid1_safe(self): - u = uuid.uuid1() - # uuid_generate_time_safe() may return 0 or -1 but what it returns is - # dependent on the underlying platform support. At least it cannot be - # unknown (unless I suppose the platform is buggy). - self.assertNotEqual(u.is_safe, uuid.SafeUUID.unknown) - - @requires_ugt - def test_uuid1_unknown(self): - # Even if the platform has uuid_generate_time_safe(), let's mock it to - # be uuid_generate_time() and ensure the safety is unknown. - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', None): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.unknown) - - @requires_ugt - def test_uuid1_is_safe(self): - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', lambda x: 0): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.safe) - - @requires_ugt - def test_uuid1_is_unsafe(self): - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', lambda x: -1): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.unsafe) - - @requires_ugt - def test_uuid1_bogus_return_value(self): - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', lambda x: 3): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.unknown) - def test_uuid3(self): equal = self.assertEqual @@ -457,7 +419,49 @@ def testIssue8621(self): self.assertNotEqual(parent_value, child_value) -class TestInternals(unittest.TestCase): +@unittest.skipUnless(_uuid1 is not None + and _uuid1._uuid_generate_time is not None, + 'requires uuid_generate_time_safe(3)') +class TestGenerateTimeInternals(unittest.TestCase): + # bpo-29925: On Mac OS X Tiger, uuid.uuid1().is_safe returns + # uuid.SafeUUID.unknown + @support.requires_mac_ver(10, 5) + def test_uuid1_safe(self): + u = uuid.uuid1() + # uuid_generate_time_safe() may return 0 or -1 but what it returns is + # dependent on the underlying platform support. At least it cannot be + # unknown (unless I suppose the platform is buggy). + self.assertNotEqual(u.is_safe, uuid.SafeUUID.unknown) + + def test_uuid1_unknown(self): + # Even if the platform has uuid_generate_time_safe(), let's mock it to + # be uuid_generate_time() and ensure the safety is unknown. + with unittest.mock.patch.object(_uuid1._uuid_generate_time, + 'restype', None): + u = uuid.uuid1() + self.assertEqual(u.is_safe, uuid.SafeUUID.unknown) + + def test_uuid1_is_safe(self): + with unittest.mock.patch.object(_uuid1._uuid_generate_time, + 'restype', lambda x: 0): + u = uuid.uuid1() + self.assertEqual(u.is_safe, uuid.SafeUUID.safe) + + def test_uuid1_is_unsafe(self): + with unittest.mock.patch.object(_uuid1._uuid_generate_time, + 'restype', lambda x: -1): + u = uuid.uuid1() + self.assertEqual(u.is_safe, uuid.SafeUUID.unsafe) + + def test_uuid1_bogus_return_value(self): + with unittest.mock.patch.object(_uuid1._uuid_generate_time, + 'restype', lambda x: 3): + u = uuid.uuid1() + self.assertEqual(u.is_safe, uuid.SafeUUID.unknown) + + +@unittest.skipUnless(_uuid1 is not None, 'need _uuid1 module') +class TestGetNodeInternals(unittest.TestCase): @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_find_mac(self): data = ''' @@ -473,7 +477,7 @@ def test_find_mac(self): return_value='/sbin/ifconfig'): with unittest.mock.patch.object(subprocess, 'Popen', return_value=popen): - mac = uuid._find_mac( + mac = _uuid1._find_mac( command='ifconfig', args='', hw_identifiers=[b'hwaddr'], @@ -497,42 +501,42 @@ def check_node(self, node, requires=None, network=False): @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_ifconfig_getnode(self): - node = uuid._ifconfig_getnode() + node = _uuid1._ifconfig_getnode() self.check_node(node, 'ifconfig', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_ip_getnode(self): - node = uuid._ip_getnode() + node = _uuid1._ip_getnode() self.check_node(node, 'ip', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_arp_getnode(self): - node = uuid._arp_getnode() + node = _uuid1._arp_getnode() self.check_node(node, 'arp', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_lanscan_getnode(self): - node = uuid._lanscan_getnode() + node = _uuid1._lanscan_getnode() self.check_node(node, 'lanscan', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_netstat_getnode(self): - node = uuid._netstat_getnode() + node = _uuid1._netstat_getnode() self.check_node(node, 'netstat', True) @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_ipconfig_getnode(self): - node = uuid._ipconfig_getnode() + node = _uuid1._ipconfig_getnode() self.check_node(node, 'ipconfig', True) @unittest.skipUnless(importable('win32wnet'), 'requires win32wnet') @unittest.skipUnless(importable('netbios'), 'requires netbios') def test_netbios_getnode(self): - node = uuid._netbios_getnode() + node = _uuid1._netbios_getnode() self.check_node(node, network=True) def test_random_getnode(self): - node = uuid._random_getnode() + node = _uuid1._random_getnode() # Least significant bit of first octet must be set. self.assertTrue(node & 0x010000000000, '%012x' % node) self.check_node(node) @@ -541,7 +545,7 @@ def test_random_getnode(self): @unittest.skipUnless(importable('ctypes'), 'requires ctypes') def test_unixdll_getnode(self): try: # Issues 1481, 3581: _uuid_generate_time() might be None. - node = uuid._unixdll_getnode() + node = _uuid1._unixdll_getnode() except TypeError: self.skipTest('requires uuid_generate_time') self.check_node(node) @@ -549,7 +553,7 @@ def test_unixdll_getnode(self): @unittest.skipUnless(os.name == 'nt', 'requires Windows') @unittest.skipUnless(importable('ctypes'), 'requires ctypes') def test_windll_getnode(self): - node = uuid._windll_getnode() + node = _uuid1._windll_getnode() self.check_node(node) diff --git a/Lib/uuid.py b/Lib/uuid.py index 15a81f5c18b66b..44ace632eda5cb 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -322,305 +322,28 @@ def version(self): if self.variant == RFC_4122: return int((self.int >> 76) & 0xf) -def _popen(command, *args): - import os, shutil, subprocess - executable = shutil.which(command) - if executable is None: - path = os.pathsep.join(('/sbin', '/usr/sbin')) - executable = shutil.which(command, path=path) - if executable is None: - return None - # LC_ALL=C to ensure English output, stderr=DEVNULL to prevent output - # on stderr (Note: we don't have an example where the words we search - # for are actually localized, but in theory some system could do so.) - env = dict(os.environ) - env['LC_ALL'] = 'C' - proc = subprocess.Popen((executable,) + args, - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, - env=env) - return proc - -def _find_mac(command, args, hw_identifiers, get_index): - try: - proc = _popen(command, *args.split()) - if not proc: - return - with proc: - for line in proc.stdout: - words = line.lower().rstrip().split() - for i in range(len(words)): - if words[i] in hw_identifiers: - try: - word = words[get_index(i)] - mac = int(word.replace(b':', b''), 16) - if mac: - return mac - except (ValueError, IndexError): - # Virtual interfaces, such as those provided by - # VPNs, do not have a colon-delimited MAC address - # as expected, but a 16-byte HWAddr separated by - # dashes. These should be ignored in favor of a - # real MAC address - pass - except OSError: - pass - -def _ifconfig_getnode(): - """Get the hardware address on Unix by running ifconfig.""" - # This works on Linux ('' or '-a'), Tru64 ('-av'), but not all Unixes. - for args in ('', '-a', '-av'): - mac = _find_mac('ifconfig', args, [b'hwaddr', b'ether'], lambda i: i+1) - if mac: - return mac - -def _ip_getnode(): - """Get the hardware address on Unix by running ip.""" - # This works on Linux with iproute2. - mac = _find_mac('ip', 'link list', [b'link/ether'], lambda i: i+1) - if mac: - return mac - -def _arp_getnode(): - """Get the hardware address on Unix by running arp.""" - import os, socket - try: - ip_addr = socket.gethostbyname(socket.gethostname()) - except OSError: - return None - - # Try getting the MAC addr from arp based on our IP address (Solaris). - return _find_mac('arp', '-an', [os.fsencode(ip_addr)], lambda i: -1) - -def _lanscan_getnode(): - """Get the hardware address on Unix by running lanscan.""" - # This might work on HP-UX. - return _find_mac('lanscan', '-ai', [b'lan0'], lambda i: 0) - -def _netstat_getnode(): - """Get the hardware address on Unix by running netstat.""" - # This might work on AIX, Tru64 UNIX. - try: - proc = _popen('netstat', '-ia') - if not proc: - return - with proc: - words = proc.stdout.readline().rstrip().split() - try: - i = words.index(b'Address') - except ValueError: - return - for line in proc.stdout: - try: - words = line.rstrip().split() - word = words[i] - if len(word) == 17 and word.count(b':') == 5: - mac = int(word.replace(b':', b''), 16) - if mac: - return mac - except (ValueError, IndexError): - pass - except OSError: - pass - -def _ipconfig_getnode(): - """Get the hardware address on Windows by running ipconfig.exe.""" - import os, re - dirs = ['', r'c:\windows\system32', r'c:\winnt\system32'] - try: - import ctypes - buffer = ctypes.create_string_buffer(300) - ctypes.windll.kernel32.GetSystemDirectoryA(buffer, 300) - dirs.insert(0, buffer.value.decode('mbcs')) - except: - pass - for dir in dirs: - try: - pipe = os.popen(os.path.join(dir, 'ipconfig') + ' /all') - except OSError: - continue - with pipe: - for line in pipe: - value = line.split(':')[-1].strip().lower() - if re.match('([0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value): - return int(value.replace('-', ''), 16) - -def _netbios_getnode(): - """Get the hardware address on Windows using NetBIOS calls. - See http://support.microsoft.com/kb/118623 for details.""" - import win32wnet, netbios - ncb = netbios.NCB() - ncb.Command = netbios.NCBENUM - ncb.Buffer = adapters = netbios.LANA_ENUM() - adapters._pack() - if win32wnet.Netbios(ncb) != 0: - return - adapters._unpack() - for i in range(adapters.length): - ncb.Reset() - ncb.Command = netbios.NCBRESET - ncb.Lana_num = ord(adapters.lana[i]) - if win32wnet.Netbios(ncb) != 0: - continue - ncb.Reset() - ncb.Command = netbios.NCBASTAT - ncb.Lana_num = ord(adapters.lana[i]) - ncb.Callname = '*'.ljust(16) - ncb.Buffer = status = netbios.ADAPTER_STATUS() - if win32wnet.Netbios(ncb) != 0: - continue - status._unpack() - bytes = status.adapter_address[:6] - if len(bytes) != 6: - continue - return int.from_bytes(bytes, 'big') - -# Thanks to Thomas Heller for ctypes and for his help with its use here. - -# If ctypes is available, use it to find system routines for UUID generation. -# XXX This makes the module non-thread-safe! -_uuid_generate_time = _UuidCreate = None -try: - import ctypes, ctypes.util - import sys - - # The uuid_generate_* routines are provided by libuuid on at least - # Linux and FreeBSD, and provided by libc on Mac OS X. - _libnames = ['uuid'] - if not sys.platform.startswith('win'): - _libnames.append('c') - for libname in _libnames: - try: - lib = ctypes.CDLL(ctypes.util.find_library(libname)) - except Exception: # pragma: nocover - continue - # Try to find the safe variety first. - if hasattr(lib, 'uuid_generate_time_safe'): - _uuid_generate_time = lib.uuid_generate_time_safe - # int uuid_generate_time_safe(uuid_t out); - break - elif hasattr(lib, 'uuid_generate_time'): # pragma: nocover - _uuid_generate_time = lib.uuid_generate_time - # void uuid_generate_time(uuid_t out); - _uuid_generate_time.restype = None - break - del _libnames - - # The uuid_generate_* functions are broken on MacOS X 10.5, as noted - # in issue #8621 the function generates the same sequence of values - # in the parent process and all children created using fork (unless - # those children use exec as well). - # - # Assume that the uuid_generate functions are broken from 10.5 onward, - # the test can be adjusted when a later version is fixed. - if sys.platform == 'darwin': - if int(os.uname().release.split('.')[0]) >= 9: - _uuid_generate_time = None - - # On Windows prior to 2000, UuidCreate gives a UUID containing the - # hardware address. On Windows 2000 and later, UuidCreate makes a - # random UUID and UuidCreateSequential gives a UUID containing the - # hardware address. These routines are provided by the RPC runtime. - # NOTE: at least on Tim's WinXP Pro SP2 desktop box, while the last - # 6 bytes returned by UuidCreateSequential are fixed, they don't appear - # to bear any relationship to the MAC address of any network device - # on the box. - try: - lib = ctypes.windll.rpcrt4 - except: - lib = None - _UuidCreate = getattr(lib, 'UuidCreateSequential', - getattr(lib, 'UuidCreate', None)) -except: - pass - -def _unixdll_getnode(): - """Get the hardware address on Unix using ctypes.""" - _buffer = ctypes.create_string_buffer(16) - _uuid_generate_time(_buffer) - return UUID(bytes=bytes_(_buffer.raw)).node - -def _windll_getnode(): - """Get the hardware address on Windows using ctypes.""" - _buffer = ctypes.create_string_buffer(16) - if _UuidCreate(_buffer) == 0: - return UUID(bytes=bytes_(_buffer.raw)).node - -def _random_getnode(): - """Get a random node ID, with eighth bit set as suggested by RFC 4122.""" - import random - return random.getrandbits(48) | 0x010000000000 _node = None -def getnode(): - """Get the hardware address as a 48-bit positive integer. - - The first time this runs, it may launch a separate program, which could - be quite slow. If all attempts to obtain the hardware address fail, we - choose a random 48-bit number with its eighth bit set to 1 as recommended - in RFC 4122. - """ +def getnode(): global _node if _node is not None: return _node - import sys - if sys.platform == 'win32': - getters = [_windll_getnode, _netbios_getnode, _ipconfig_getnode] - else: - getters = [_unixdll_getnode, _ifconfig_getnode, _ip_getnode, - _arp_getnode, _lanscan_getnode, _netstat_getnode] + import _uuid1 + _node = _uuid1.getnode() + return _node - for getter in getters + [_random_getnode]: - try: - _node = getter() - except: - continue - if _node is not None: - return _node - -_last_timestamp = None def uuid1(node=None, clock_seq=None): """Generate a UUID from a host ID, sequence number, and the current time. If 'node' is not given, getnode() is used to obtain the hardware address. If 'clock_seq' is given, it is used as the sequence number; otherwise a random 14-bit sequence number is chosen.""" + import _uuid1 + return _uuid1.uuid1(UUID, SafeUUID, node, clock_seq) - # When the system provides a version-1 UUID generator, use it (but don't - # use UuidCreate here because its UUIDs don't conform to RFC 4122). - if _uuid_generate_time and node is clock_seq is None: - _buffer = ctypes.create_string_buffer(16) - safely_generated = _uuid_generate_time(_buffer) - try: - is_safe = SafeUUID(safely_generated) - except ValueError: - is_safe = SafeUUID.unknown - return UUID(bytes=bytes_(_buffer.raw), is_safe=is_safe) - - global _last_timestamp - import time - nanoseconds = int(time.time() * 1e9) - # 0x01b21dd213814000 is the number of 100-ns intervals between the - # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. - timestamp = int(nanoseconds/100) + 0x01b21dd213814000 - if _last_timestamp is not None and timestamp <= _last_timestamp: - timestamp = _last_timestamp + 1 - _last_timestamp = timestamp - if clock_seq is None: - import random - clock_seq = random.getrandbits(14) # instead of stable storage - time_low = timestamp & 0xffffffff - time_mid = (timestamp >> 32) & 0xffff - time_hi_version = (timestamp >> 48) & 0x0fff - clock_seq_low = clock_seq & 0xff - clock_seq_hi_variant = (clock_seq >> 8) & 0x3f - if node is None: - node = getnode() - return UUID(fields=(time_low, time_mid, time_hi_version, - clock_seq_hi_variant, clock_seq_low, node), version=1) def uuid3(namespace, name): """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" @@ -628,16 +351,19 @@ def uuid3(namespace, name): hash = md5(namespace.bytes + bytes(name, "utf-8")).digest() return UUID(bytes=hash[:16], version=3) + def uuid4(): """Generate a random UUID.""" return UUID(bytes=os.urandom(16), version=4) + def uuid5(namespace, name): """Generate a UUID from the SHA-1 hash of a namespace UUID and a name.""" from hashlib import sha1 hash = sha1(namespace.bytes + bytes(name, "utf-8")).digest() return UUID(bytes=hash[:16], version=5) + # The following standard UUIDs are for use with uuid3() or uuid5(). NAMESPACE_DNS = UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8') diff --git a/Misc/NEWS.d/next/Library/2017-09-28-12-01-56.bpo-11063.neO7aq.rst b/Misc/NEWS.d/next/Library/2017-09-28-12-01-56.bpo-11063.neO7aq.rst new file mode 100644 index 00000000000000..5981af8f68cf5b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-09-28-12-01-56.bpo-11063.neO7aq.rst @@ -0,0 +1,3 @@ +uuid module: Move getnode() and uuid1() implementations to a new _uuid1 +submodule which is now loaded on demand to reduce side effects on "import +uuid".