From 412aab200eb55996529095324c1028cd60dbb162 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Sun, 3 Jul 2022 16:22:22 +1000 Subject: [PATCH 1/2] Copy UUID implementation and tests from CPython --- python-stdlib/uuid/README.md | 8 + python-stdlib/uuid/test_uuid.py | 899 ++++++++++++++++++++++++++++++++ python-stdlib/uuid/uuid.py | 728 ++++++++++++++++++++++++++ 3 files changed, 1635 insertions(+) create mode 100644 python-stdlib/uuid/README.md create mode 100644 python-stdlib/uuid/test_uuid.py create mode 100644 python-stdlib/uuid/uuid.py diff --git a/python-stdlib/uuid/README.md b/python-stdlib/uuid/README.md new file mode 100644 index 000000000..dcfe45d81 --- /dev/null +++ b/python-stdlib/uuid/README.md @@ -0,0 +1,8 @@ +# UUID + +## Copied from CPython +UUID implementation and tests copied from CPython at the following versions: + +[uuid.py](https://github.com/python/cpython/blob/9bf7c2d638a582af2444bc864feba13ab8957b68/Lib/uuid.py): 9bf7c2d638a582af2444bc864feba13ab8957b68 + +[test_uuid.py](https://github.com/python/cpython/blob/91e33ac3d08a1c6004c469da2c0e2a97b5bdc53c/Lib/test/test_uuid.py): 91e33ac3d08a1c6004c469da2c0e2a97b5bdc53c diff --git a/python-stdlib/uuid/test_uuid.py b/python-stdlib/uuid/test_uuid.py new file mode 100644 index 000000000..411eec0f4 --- /dev/null +++ b/python-stdlib/uuid/test_uuid.py @@ -0,0 +1,899 @@ +import unittest +from test import support +from test.support import import_helper +import builtins +import contextlib +import copy +import enum +import io +import os +import pickle +import sys +import weakref +from unittest import mock + +py_uuid = import_helper.import_fresh_module('uuid', blocked=['_uuid']) +c_uuid = import_helper.import_fresh_module('uuid', fresh=['_uuid']) + +def importable(name): + try: + __import__(name) + return True + except: + return False + + +def mock_get_command_stdout(data): + def get_command_stdout(command, args): + return io.BytesIO(data.encode()) + return get_command_stdout + + +class BaseTestUUID: + uuid = None + + def test_safe_uuid_enum(self): + class CheckedSafeUUID(enum.Enum): + safe = 0 + unsafe = -1 + unknown = None + enum._test_simple_enum(CheckedSafeUUID, py_uuid.SafeUUID) + + def test_UUID(self): + equal = self.assertEqual + ascending = [] + for (string, curly, hex, bytes, bytes_le, fields, integer, urn, + time, clock_seq, variant, version) in [ + ('00000000-0000-0000-0000-000000000000', + '{00000000-0000-0000-0000-000000000000}', + '00000000000000000000000000000000', + b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', + b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', + (0, 0, 0, 0, 0, 0), + 0, + 'urn:uuid:00000000-0000-0000-0000-000000000000', + 0, 0, self.uuid.RESERVED_NCS, None), + ('00010203-0405-0607-0809-0a0b0c0d0e0f', + '{00010203-0405-0607-0809-0a0b0c0d0e0f}', + '000102030405060708090a0b0c0d0e0f', + b'\0\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\x0d\x0e\x0f', + b'\x03\x02\x01\0\x05\x04\x07\x06\x08\t\n\x0b\x0c\x0d\x0e\x0f', + (0x00010203, 0x0405, 0x0607, 8, 9, 0x0a0b0c0d0e0f), + 0x000102030405060708090a0b0c0d0e0f, + 'urn:uuid:00010203-0405-0607-0809-0a0b0c0d0e0f', + 0x607040500010203, 0x809, self.uuid.RESERVED_NCS, None), + ('02d9e6d5-9467-382e-8f9b-9300a64ac3cd', + '{02d9e6d5-9467-382e-8f9b-9300a64ac3cd}', + '02d9e6d59467382e8f9b9300a64ac3cd', + b'\x02\xd9\xe6\xd5\x94\x67\x38\x2e\x8f\x9b\x93\x00\xa6\x4a\xc3\xcd', + b'\xd5\xe6\xd9\x02\x67\x94\x2e\x38\x8f\x9b\x93\x00\xa6\x4a\xc3\xcd', + (0x02d9e6d5, 0x9467, 0x382e, 0x8f, 0x9b, 0x9300a64ac3cd), + 0x02d9e6d59467382e8f9b9300a64ac3cd, + 'urn:uuid:02d9e6d5-9467-382e-8f9b-9300a64ac3cd', + 0x82e946702d9e6d5, 0xf9b, self.uuid.RFC_4122, 3), + ('12345678-1234-5678-1234-567812345678', + '{12345678-1234-5678-1234-567812345678}', + '12345678123456781234567812345678', + b'\x12\x34\x56\x78'*4, + b'\x78\x56\x34\x12\x34\x12\x78\x56\x12\x34\x56\x78\x12\x34\x56\x78', + (0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678), + 0x12345678123456781234567812345678, + 'urn:uuid:12345678-1234-5678-1234-567812345678', + 0x678123412345678, 0x1234, self.uuid.RESERVED_NCS, None), + ('6ba7b810-9dad-11d1-80b4-00c04fd430c8', + '{6ba7b810-9dad-11d1-80b4-00c04fd430c8}', + '6ba7b8109dad11d180b400c04fd430c8', + b'\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8', + b'\x10\xb8\xa7\x6b\xad\x9d\xd1\x11\x80\xb4\x00\xc0\x4f\xd4\x30\xc8', + (0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00c04fd430c8), + 0x6ba7b8109dad11d180b400c04fd430c8, + 'urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8', + 0x1d19dad6ba7b810, 0xb4, self.uuid.RFC_4122, 1), + ('6ba7b811-9dad-11d1-80b4-00c04fd430c8', + '{6ba7b811-9dad-11d1-80b4-00c04fd430c8}', + '6ba7b8119dad11d180b400c04fd430c8', + b'\x6b\xa7\xb8\x11\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8', + b'\x11\xb8\xa7\x6b\xad\x9d\xd1\x11\x80\xb4\x00\xc0\x4f\xd4\x30\xc8', + (0x6ba7b811, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00c04fd430c8), + 0x6ba7b8119dad11d180b400c04fd430c8, + 'urn:uuid:6ba7b811-9dad-11d1-80b4-00c04fd430c8', + 0x1d19dad6ba7b811, 0xb4, self.uuid.RFC_4122, 1), + ('6ba7b812-9dad-11d1-80b4-00c04fd430c8', + '{6ba7b812-9dad-11d1-80b4-00c04fd430c8}', + '6ba7b8129dad11d180b400c04fd430c8', + b'\x6b\xa7\xb8\x12\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8', + b'\x12\xb8\xa7\x6b\xad\x9d\xd1\x11\x80\xb4\x00\xc0\x4f\xd4\x30\xc8', + (0x6ba7b812, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00c04fd430c8), + 0x6ba7b8129dad11d180b400c04fd430c8, + 'urn:uuid:6ba7b812-9dad-11d1-80b4-00c04fd430c8', + 0x1d19dad6ba7b812, 0xb4, self.uuid.RFC_4122, 1), + ('6ba7b814-9dad-11d1-80b4-00c04fd430c8', + '{6ba7b814-9dad-11d1-80b4-00c04fd430c8}', + '6ba7b8149dad11d180b400c04fd430c8', + b'\x6b\xa7\xb8\x14\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8', + b'\x14\xb8\xa7\x6b\xad\x9d\xd1\x11\x80\xb4\x00\xc0\x4f\xd4\x30\xc8', + (0x6ba7b814, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00c04fd430c8), + 0x6ba7b8149dad11d180b400c04fd430c8, + 'urn:uuid:6ba7b814-9dad-11d1-80b4-00c04fd430c8', + 0x1d19dad6ba7b814, 0xb4, self.uuid.RFC_4122, 1), + ('7d444840-9dc0-11d1-b245-5ffdce74fad2', + '{7d444840-9dc0-11d1-b245-5ffdce74fad2}', + '7d4448409dc011d1b2455ffdce74fad2', + b'\x7d\x44\x48\x40\x9d\xc0\x11\xd1\xb2\x45\x5f\xfd\xce\x74\xfa\xd2', + b'\x40\x48\x44\x7d\xc0\x9d\xd1\x11\xb2\x45\x5f\xfd\xce\x74\xfa\xd2', + (0x7d444840, 0x9dc0, 0x11d1, 0xb2, 0x45, 0x5ffdce74fad2), + 0x7d4448409dc011d1b2455ffdce74fad2, + 'urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2', + 0x1d19dc07d444840, 0x3245, self.uuid.RFC_4122, 1), + ('e902893a-9d22-3c7e-a7b8-d6e313b71d9f', + '{e902893a-9d22-3c7e-a7b8-d6e313b71d9f}', + 'e902893a9d223c7ea7b8d6e313b71d9f', + b'\xe9\x02\x89\x3a\x9d\x22\x3c\x7e\xa7\xb8\xd6\xe3\x13\xb7\x1d\x9f', + b'\x3a\x89\x02\xe9\x22\x9d\x7e\x3c\xa7\xb8\xd6\xe3\x13\xb7\x1d\x9f', + (0xe902893a, 0x9d22, 0x3c7e, 0xa7, 0xb8, 0xd6e313b71d9f), + 0xe902893a9d223c7ea7b8d6e313b71d9f, + 'urn:uuid:e902893a-9d22-3c7e-a7b8-d6e313b71d9f', + 0xc7e9d22e902893a, 0x27b8, self.uuid.RFC_4122, 3), + ('eb424026-6f54-4ef8-a4d0-bb658a1fc6cf', + '{eb424026-6f54-4ef8-a4d0-bb658a1fc6cf}', + 'eb4240266f544ef8a4d0bb658a1fc6cf', + b'\xeb\x42\x40\x26\x6f\x54\x4e\xf8\xa4\xd0\xbb\x65\x8a\x1f\xc6\xcf', + b'\x26\x40\x42\xeb\x54\x6f\xf8\x4e\xa4\xd0\xbb\x65\x8a\x1f\xc6\xcf', + (0xeb424026, 0x6f54, 0x4ef8, 0xa4, 0xd0, 0xbb658a1fc6cf), + 0xeb4240266f544ef8a4d0bb658a1fc6cf, + 'urn:uuid:eb424026-6f54-4ef8-a4d0-bb658a1fc6cf', + 0xef86f54eb424026, 0x24d0, self.uuid.RFC_4122, 4), + ('f81d4fae-7dec-11d0-a765-00a0c91e6bf6', + '{f81d4fae-7dec-11d0-a765-00a0c91e6bf6}', + 'f81d4fae7dec11d0a76500a0c91e6bf6', + b'\xf8\x1d\x4f\xae\x7d\xec\x11\xd0\xa7\x65\x00\xa0\xc9\x1e\x6b\xf6', + b'\xae\x4f\x1d\xf8\xec\x7d\xd0\x11\xa7\x65\x00\xa0\xc9\x1e\x6b\xf6', + (0xf81d4fae, 0x7dec, 0x11d0, 0xa7, 0x65, 0x00a0c91e6bf6), + 0xf81d4fae7dec11d0a76500a0c91e6bf6, + 'urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6', + 0x1d07decf81d4fae, 0x2765, self.uuid.RFC_4122, 1), + ('fffefdfc-fffe-fffe-fffe-fffefdfcfbfa', + '{fffefdfc-fffe-fffe-fffe-fffefdfcfbfa}', + 'fffefdfcfffefffefffefffefdfcfbfa', + b'\xff\xfe\xfd\xfc\xff\xfe\xff\xfe\xff\xfe\xff\xfe\xfd\xfc\xfb\xfa', + b'\xfc\xfd\xfe\xff\xfe\xff\xfe\xff\xff\xfe\xff\xfe\xfd\xfc\xfb\xfa', + (0xfffefdfc, 0xfffe, 0xfffe, 0xff, 0xfe, 0xfffefdfcfbfa), + 0xfffefdfcfffefffefffefffefdfcfbfa, + 'urn:uuid:fffefdfc-fffe-fffe-fffe-fffefdfcfbfa', + 0xffefffefffefdfc, 0x3ffe, self.uuid.RESERVED_FUTURE, None), + ('ffffffff-ffff-ffff-ffff-ffffffffffff', + '{ffffffff-ffff-ffff-ffff-ffffffffffff}', + 'ffffffffffffffffffffffffffffffff', + b'\xff'*16, + b'\xff'*16, + (0xffffffff, 0xffff, 0xffff, 0xff, 0xff, 0xffffffffffff), + 0xffffffffffffffffffffffffffffffff, + 'urn:uuid:ffffffff-ffff-ffff-ffff-ffffffffffff', + 0xfffffffffffffff, 0x3fff, self.uuid.RESERVED_FUTURE, None), + ]: + equivalents = [] + # Construct each UUID in several different ways. + for u in [self.uuid.UUID(string), self.uuid.UUID(curly), self.uuid.UUID(hex), + self.uuid.UUID(bytes=bytes), self.uuid.UUID(bytes_le=bytes_le), + self.uuid.UUID(fields=fields), self.uuid.UUID(int=integer), + self.uuid.UUID(urn)]: + # Test all conversions and properties of the UUID object. + equal(str(u), string) + equal(int(u), integer) + equal(u.bytes, bytes) + equal(u.bytes_le, bytes_le) + equal(u.fields, fields) + equal(u.time_low, fields[0]) + equal(u.time_mid, fields[1]) + equal(u.time_hi_version, fields[2]) + equal(u.clock_seq_hi_variant, fields[3]) + equal(u.clock_seq_low, fields[4]) + equal(u.node, fields[5]) + equal(u.hex, hex) + equal(u.int, integer) + equal(u.urn, urn) + equal(u.time, time) + equal(u.clock_seq, clock_seq) + equal(u.variant, variant) + equal(u.version, version) + equivalents.append(u) + + # Different construction methods should give the same UUID. + for u in equivalents: + for v in equivalents: + equal(u, v) + + # Bug 7380: "bytes" and "bytes_le" should give the same type. + equal(type(u.bytes), builtins.bytes) + equal(type(u.bytes_le), builtins.bytes) + + ascending.append(u) + + # Test comparison of UUIDs. + for i in range(len(ascending)): + for j in range(len(ascending)): + equal(i < j, ascending[i] < ascending[j]) + equal(i <= j, ascending[i] <= ascending[j]) + equal(i == j, ascending[i] == ascending[j]) + equal(i > j, ascending[i] > ascending[j]) + equal(i >= j, ascending[i] >= ascending[j]) + equal(i != j, ascending[i] != ascending[j]) + + # Test sorting of UUIDs (above list is in ascending order). + resorted = ascending[:] + resorted.reverse() + resorted.sort() + equal(ascending, resorted) + + def test_exceptions(self): + badvalue = lambda f: self.assertRaises(ValueError, f) + badtype = lambda f: self.assertRaises(TypeError, f) + + # Badly formed hex strings. + badvalue(lambda: self.uuid.UUID('')) + badvalue(lambda: self.uuid.UUID('abc')) + badvalue(lambda: self.uuid.UUID('1234567812345678123456781234567')) + badvalue(lambda: self.uuid.UUID('123456781234567812345678123456789')) + badvalue(lambda: self.uuid.UUID('123456781234567812345678z2345678')) + + # Badly formed bytes. + badvalue(lambda: self.uuid.UUID(bytes='abc')) + badvalue(lambda: self.uuid.UUID(bytes='\0'*15)) + badvalue(lambda: self.uuid.UUID(bytes='\0'*17)) + + # Badly formed bytes_le. + badvalue(lambda: self.uuid.UUID(bytes_le='abc')) + badvalue(lambda: self.uuid.UUID(bytes_le='\0'*15)) + badvalue(lambda: self.uuid.UUID(bytes_le='\0'*17)) + + # Badly formed fields. + badvalue(lambda: self.uuid.UUID(fields=(1,))) + badvalue(lambda: self.uuid.UUID(fields=(1, 2, 3, 4, 5))) + badvalue(lambda: self.uuid.UUID(fields=(1, 2, 3, 4, 5, 6, 7))) + + # Field values out of range. + badvalue(lambda: self.uuid.UUID(fields=(-1, 0, 0, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0x100000000, 0, 0, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, -1, 0, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0x10000, 0, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, -1, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0x10000, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, -1, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, 0x100, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, 0, -1, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, 0, 0x100, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, 0, 0, -1))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, 0, 0, 0x1000000000000))) + + # Version number out of range. + badvalue(lambda: self.uuid.UUID('00'*16, version=0)) + badvalue(lambda: self.uuid.UUID('00'*16, version=6)) + + # Integer value out of range. + badvalue(lambda: self.uuid.UUID(int=-1)) + badvalue(lambda: self.uuid.UUID(int=1<<128)) + + # Must supply exactly one of hex, bytes, fields, int. + h, b, f, i = '00'*16, b'\0'*16, (0, 0, 0, 0, 0, 0), 0 + self.uuid.UUID(h) + self.uuid.UUID(hex=h) + self.uuid.UUID(bytes=b) + self.uuid.UUID(bytes_le=b) + self.uuid.UUID(fields=f) + self.uuid.UUID(int=i) + + # Wrong number of arguments (positional). + badtype(lambda: self.uuid.UUID()) + badtype(lambda: self.uuid.UUID(h, b)) + badtype(lambda: self.uuid.UUID(h, b, b)) + badtype(lambda: self.uuid.UUID(h, b, b, f)) + badtype(lambda: self.uuid.UUID(h, b, b, f, i)) + + # Duplicate arguments. + for hh in [[], [('hex', h)]]: + for bb in [[], [('bytes', b)]]: + for bble in [[], [('bytes_le', b)]]: + for ii in [[], [('int', i)]]: + for ff in [[], [('fields', f)]]: + args = dict(hh + bb + bble + ii + ff) + if len(args) != 0: + badtype(lambda: self.uuid.UUID(h, **args)) + if len(args) != 1: + badtype(lambda: self.uuid.UUID(**args)) + + # Immutability. + u = self.uuid.UUID(h) + badtype(lambda: setattr(u, 'hex', h)) + badtype(lambda: setattr(u, 'bytes', b)) + badtype(lambda: setattr(u, 'bytes_le', b)) + badtype(lambda: setattr(u, 'fields', f)) + badtype(lambda: setattr(u, 'int', i)) + badtype(lambda: setattr(u, 'time_low', 0)) + badtype(lambda: setattr(u, 'time_mid', 0)) + badtype(lambda: setattr(u, 'time_hi_version', 0)) + badtype(lambda: setattr(u, 'time_hi_version', 0)) + badtype(lambda: setattr(u, 'clock_seq_hi_variant', 0)) + badtype(lambda: setattr(u, 'clock_seq_low', 0)) + badtype(lambda: setattr(u, 'node', 0)) + + # Comparison with a non-UUID object + badtype(lambda: u < object()) + badtype(lambda: u > object()) + + def test_getnode(self): + node1 = self.uuid.getnode() + self.assertTrue(0 < node1 < (1 << 48), '%012x' % node1) + + # Test it again to ensure consistency. + node2 = self.uuid.getnode() + self.assertEqual(node1, node2, '%012x != %012x' % (node1, node2)) + + def test_pickle_roundtrip(self): + def check(actual, expected): + self.assertEqual(actual, expected) + self.assertEqual(actual.is_safe, expected.is_safe) + + with support.swap_item(sys.modules, 'uuid', self.uuid): + for is_safe in self.uuid.SafeUUID: + u = self.uuid.UUID('d82579ce6642a0de7ddf490a7aec7aa5', + is_safe=is_safe) + check(copy.copy(u), u) + check(copy.deepcopy(u), u) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=proto): + check(pickle.loads(pickle.dumps(u, proto)), u) + + def test_unpickle_previous_python_versions(self): + def check(actual, expected): + self.assertEqual(actual, expected) + self.assertEqual(actual.is_safe, expected.is_safe) + + pickled_uuids = [ + # Python 2.7, protocol 0 + b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN' + b'tR(dS\'int\'\nL287307832597519156748809049798316161701L\nsb.', + # Python 2.7, protocol 1 + b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN' + b'tR}U\x03intL287307832597519156748809049798316161701L\nsb.', + # Python 2.7, protocol 2 + b'\x80\x02cuuid\nUUID\n)\x81}U\x03int\x8a\x11\xa5z\xecz\nI\xdf}' + b'\xde\xa0Bf\xcey%\xd8\x00sb.', + # Python 3.6, protocol 0 + b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN' + b'tR(dVint\nL287307832597519156748809049798316161701L\nsb.', + # Python 3.6, protocol 1 + b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN' + b'tR}X\x03\x00\x00\x00intL287307832597519156748809049798316161701L' + b'\nsb.', + # Python 3.6, protocol 2 + b'\x80\x02cuuid\nUUID\n)\x81}X\x03\x00\x00\x00int\x8a\x11\xa5z\xec' + b'z\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00sb.', + # Python 3.6, protocol 3 + b'\x80\x03cuuid\nUUID\n)\x81}X\x03\x00\x00\x00int\x8a\x11\xa5z\xec' + b'z\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00sb.', + # Python 3.6, protocol 4 + b'\x80\x04\x95+\x00\x00\x00\x00\x00\x00\x00\x8c\x04uuid\x8c\x04UUI' + b'D\x93)\x81}\x8c\x03int\x8a\x11\xa5z\xecz\nI\xdf}\xde\xa0Bf\xcey%' + b'\xd8\x00sb.', + # Python 3.7, protocol 0 + b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN' + b'tR(dVint\nL287307832597519156748809049798316161701L\nsVis_safe\n' + b'cuuid\nSafeUUID\n(NtRsb.', + # Python 3.7, protocol 1 + b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN' + b'tR}(X\x03\x00\x00\x00intL287307832597519156748809049798316161701' + b'L\nX\x07\x00\x00\x00is_safecuuid\nSafeUUID\n(NtRub.', + # Python 3.7, protocol 2 + b'\x80\x02cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z' + b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu' + b'id\nSafeUUID\nN\x85Rub.', + # Python 3.7, protocol 3 + b'\x80\x03cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z' + b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu' + b'id\nSafeUUID\nN\x85Rub.', + # Python 3.7, protocol 4 + b'\x80\x04\x95F\x00\x00\x00\x00\x00\x00\x00\x8c\x04uuid\x94\x8c' + b'\x04UUID\x93)\x81}(\x8c\x03int\x8a\x11\xa5z\xecz\nI\xdf}\xde\xa0' + b'Bf\xcey%\xd8\x00\x8c\x07is_safeh\x00\x8c\x08SafeUUID\x93N\x85Rub' + b'.', + ] + pickled_uuids_safe = [ + # Python 3.7, protocol 0 + b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN' + b'tR(dVint\nL287307832597519156748809049798316161701L\nsVis_safe\n' + b'cuuid\nSafeUUID\n(I0\ntRsb.', + # Python 3.7, protocol 1 + b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN' + b'tR}(X\x03\x00\x00\x00intL287307832597519156748809049798316161701' + b'L\nX\x07\x00\x00\x00is_safecuuid\nSafeUUID\n(K\x00tRub.', + # Python 3.7, protocol 2 + b'\x80\x02cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z' + b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu' + b'id\nSafeUUID\nK\x00\x85Rub.', + # Python 3.7, protocol 3 + b'\x80\x03cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z' + b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu' + b'id\nSafeUUID\nK\x00\x85Rub.', + # Python 3.7, protocol 4 + b'\x80\x04\x95G\x00\x00\x00\x00\x00\x00\x00\x8c\x04uuid\x94\x8c' + b'\x04UUID\x93)\x81}(\x8c\x03int\x8a\x11\xa5z\xecz\nI\xdf}\xde\xa0' + b'Bf\xcey%\xd8\x00\x8c\x07is_safeh\x00\x8c\x08SafeUUID\x93K\x00' + b'\x85Rub.', + ] + pickled_uuids_unsafe = [ + # Python 3.7, protocol 0 + b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN' + b'tR(dVint\nL287307832597519156748809049798316161701L\nsVis_safe\n' + b'cuuid\nSafeUUID\n(I-1\ntRsb.', + # Python 3.7, protocol 1 + b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN' + b'tR}(X\x03\x00\x00\x00intL287307832597519156748809049798316161701' + b'L\nX\x07\x00\x00\x00is_safecuuid\nSafeUUID\n(J\xff\xff\xff\xfftR' + b'ub.', + # Python 3.7, protocol 2 + b'\x80\x02cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z' + b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu' + b'id\nSafeUUID\nJ\xff\xff\xff\xff\x85Rub.', + # Python 3.7, protocol 3 + b'\x80\x03cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z' + b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu' + b'id\nSafeUUID\nJ\xff\xff\xff\xff\x85Rub.', + # Python 3.7, protocol 4 + b'\x80\x04\x95J\x00\x00\x00\x00\x00\x00\x00\x8c\x04uuid\x94\x8c' + b'\x04UUID\x93)\x81}(\x8c\x03int\x8a\x11\xa5z\xecz\nI\xdf}\xde\xa0' + b'Bf\xcey%\xd8\x00\x8c\x07is_safeh\x00\x8c\x08SafeUUID\x93J\xff' + b'\xff\xff\xff\x85Rub.', + ] + + u = self.uuid.UUID('d82579ce6642a0de7ddf490a7aec7aa5') + u_safe = self.uuid.UUID('d82579ce6642a0de7ddf490a7aec7aa5', + is_safe=self.uuid.SafeUUID.safe) + u_unsafe = self.uuid.UUID('d82579ce6642a0de7ddf490a7aec7aa5', + is_safe=self.uuid.SafeUUID.unsafe) + + with support.swap_item(sys.modules, 'uuid', self.uuid): + for pickled in pickled_uuids: + # is_safe was added in 3.7. When unpickling values from older + # versions, is_safe will be missing, so it should be set to + # SafeUUID.unknown. + check(pickle.loads(pickled), u) + for pickled in pickled_uuids_safe: + check(pickle.loads(pickled), u_safe) + for pickled in pickled_uuids_unsafe: + check(pickle.loads(pickled), u_unsafe) + + # bpo-32502: UUID1 requires a 48-bit identifier, but hardware identifiers + # need not necessarily be 48 bits (e.g., EUI-64). + def test_uuid1_eui64(self): + # Confirm that uuid.getnode ignores hardware addresses larger than 48 + # bits. Mock out each platform's *_getnode helper functions to return + # something just larger than 48 bits to test. This will cause + # uuid.getnode to fall back on uuid._random_getnode, which will + # generate a valid value. + too_large_getter = lambda: 1 << 48 + with mock.patch.multiple( + self.uuid, + _node=None, # Ignore any cached node value. + _GETTERS=[too_large_getter], + ): + node = self.uuid.getnode() + self.assertTrue(0 < node < (1 << 48), '%012x' % node) + + # Confirm that uuid1 can use the generated node, i.e., the that + # uuid.getnode fell back on uuid._random_getnode() rather than using + # the value from too_large_getter above. + try: + self.uuid.uuid1(node=node) + except ValueError: + self.fail('uuid1 was given an invalid node ID') + + def test_uuid1(self): + equal = self.assertEqual + + # Make sure uuid1() generates UUIDs that are actually version 1. + for u in [self.uuid.uuid1() for i in range(10)]: + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 1) + self.assertIn(u.is_safe, {self.uuid.SafeUUID.safe, + self.uuid.SafeUUID.unsafe, + self.uuid.SafeUUID.unknown}) + + # Make sure the generated UUIDs are actually unique. + uuids = {} + for u in [self.uuid.uuid1() for i in range(1000)]: + uuids[u] = 1 + equal(len(uuids.keys()), 1000) + + # Make sure the supplied node ID appears in the UUID. + u = self.uuid.uuid1(0) + equal(u.node, 0) + u = self.uuid.uuid1(0x123456789abc) + equal(u.node, 0x123456789abc) + u = self.uuid.uuid1(0xffffffffffff) + equal(u.node, 0xffffffffffff) + + # Make sure the supplied clock sequence appears in the UUID. + u = self.uuid.uuid1(0x123456789abc, 0) + equal(u.node, 0x123456789abc) + equal(((u.clock_seq_hi_variant & 0x3f) << 8) | u.clock_seq_low, 0) + u = self.uuid.uuid1(0x123456789abc, 0x1234) + equal(u.node, 0x123456789abc) + equal(((u.clock_seq_hi_variant & 0x3f) << 8) | + u.clock_seq_low, 0x1234) + u = self.uuid.uuid1(0x123456789abc, 0x3fff) + equal(u.node, 0x123456789abc) + equal(((u.clock_seq_hi_variant & 0x3f) << 8) | + u.clock_seq_low, 0x3fff) + + # bpo-29925: On Mac OS X Tiger, self.uuid.uuid1().is_safe returns + # self.uuid.SafeUUID.unknown + @support.requires_mac_ver(10, 5) + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') + def test_uuid1_safe(self): + if not self.uuid._has_uuid_generate_time_safe: + self.skipTest('requires uuid_generate_time_safe(3)') + + u = self.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, self.uuid.SafeUUID.unknown) + + @contextlib.contextmanager + def mock_generate_time_safe(self, safe_value): + """ + Mock uuid._generate_time_safe() to return a given *safe_value*. + """ + if os.name != 'posix': + self.skipTest('POSIX-only test') + self.uuid._load_system_functions() + f = self.uuid._generate_time_safe + if f is None: + self.skipTest('need uuid._generate_time_safe') + with mock.patch.object(self.uuid, '_generate_time_safe', + lambda: (f()[0], safe_value)): + yield + + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') + 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 self.mock_generate_time_safe(None): + u = self.uuid.uuid1() + self.assertEqual(u.is_safe, self.uuid.SafeUUID.unknown) + + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') + def test_uuid1_is_safe(self): + with self.mock_generate_time_safe(0): + u = self.uuid.uuid1() + self.assertEqual(u.is_safe, self.uuid.SafeUUID.safe) + + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') + def test_uuid1_is_unsafe(self): + with self.mock_generate_time_safe(-1): + u = self.uuid.uuid1() + self.assertEqual(u.is_safe, self.uuid.SafeUUID.unsafe) + + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') + def test_uuid1_bogus_return_value(self): + with self.mock_generate_time_safe(3): + u = self.uuid.uuid1() + self.assertEqual(u.is_safe, self.uuid.SafeUUID.unknown) + + def test_uuid1_time(self): + with mock.patch.object(self.uuid, '_has_uuid_generate_time_safe', False), \ + mock.patch.object(self.uuid, '_generate_time_safe', None), \ + mock.patch.object(self.uuid, '_last_timestamp', None), \ + mock.patch.object(self.uuid, 'getnode', return_value=93328246233727), \ + mock.patch('time.time_ns', return_value=1545052026752910643), \ + mock.patch('random.getrandbits', return_value=5317): # guaranteed to be random + u = self.uuid.uuid1() + self.assertEqual(u, self.uuid.UUID('a7a55b92-01fc-11e9-94c5-54e1acf6da7f')) + + with mock.patch.object(self.uuid, '_has_uuid_generate_time_safe', False), \ + mock.patch.object(self.uuid, '_generate_time_safe', None), \ + mock.patch.object(self.uuid, '_last_timestamp', None), \ + mock.patch('time.time_ns', return_value=1545052026752910643): + u = self.uuid.uuid1(node=93328246233727, clock_seq=5317) + self.assertEqual(u, self.uuid.UUID('a7a55b92-01fc-11e9-94c5-54e1acf6da7f')) + + def test_uuid3(self): + equal = self.assertEqual + + # Test some known version-3 UUIDs. + for u, v in [(self.uuid.uuid3(self.uuid.NAMESPACE_DNS, 'python.org'), + '6fa459ea-ee8a-3ca4-894e-db77e160355e'), + (self.uuid.uuid3(self.uuid.NAMESPACE_URL, 'http://python.org/'), + '9fe8e8c4-aaa8-32a9-a55c-4535a88b748d'), + (self.uuid.uuid3(self.uuid.NAMESPACE_OID, '1.3.6.1'), + 'dd1a1cef-13d5-368a-ad82-eca71acd4cd1'), + (self.uuid.uuid3(self.uuid.NAMESPACE_X500, 'c=ca'), + '658d3002-db6b-3040-a1d1-8ddd7d189a4d'), + ]: + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 3) + equal(u, self.uuid.UUID(v)) + equal(str(u), v) + + def test_uuid4(self): + equal = self.assertEqual + + # Make sure uuid4() generates UUIDs that are actually version 4. + for u in [self.uuid.uuid4() for i in range(10)]: + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 4) + + # Make sure the generated UUIDs are actually unique. + uuids = {} + for u in [self.uuid.uuid4() for i in range(1000)]: + uuids[u] = 1 + equal(len(uuids.keys()), 1000) + + def test_uuid5(self): + equal = self.assertEqual + + # Test some known version-5 UUIDs. + for u, v in [(self.uuid.uuid5(self.uuid.NAMESPACE_DNS, 'python.org'), + '886313e1-3b8a-5372-9b90-0c9aee199e5d'), + (self.uuid.uuid5(self.uuid.NAMESPACE_URL, 'http://python.org/'), + '4c565f0d-3f5a-5890-b41b-20cf47701c5e'), + (self.uuid.uuid5(self.uuid.NAMESPACE_OID, '1.3.6.1'), + '1447fa61-5277-5fef-a9b3-fbc6e44f4af3'), + (self.uuid.uuid5(self.uuid.NAMESPACE_X500, 'c=ca'), + 'cc957dd1-a972-5349-98cd-874190002798'), + ]: + equal(u.variant, self.uuid.RFC_4122) + equal(u.version, 5) + equal(u, self.uuid.UUID(v)) + equal(str(u), v) + + @support.requires_fork() + def testIssue8621(self): + # On at least some versions of OSX self.uuid.uuid4 generates + # the same sequence of UUIDs in the parent and any + # children started using fork. + fds = os.pipe() + pid = os.fork() + if pid == 0: + os.close(fds[0]) + value = self.uuid.uuid4() + os.write(fds[1], value.hex.encode('latin-1')) + os._exit(0) + + else: + os.close(fds[1]) + self.addCleanup(os.close, fds[0]) + parent_value = self.uuid.uuid4().hex + support.wait_process(pid, exitcode=0) + child_value = os.read(fds[0], 100).decode('latin-1') + + self.assertNotEqual(parent_value, child_value) + + def test_uuid_weakref(self): + # bpo-35701: check that weak referencing to a UUID object can be created + strong = self.uuid.uuid4() + weak = weakref.ref(strong) + self.assertIs(strong, weak()) + +class TestUUIDWithoutExtModule(BaseTestUUID, unittest.TestCase): + uuid = py_uuid + +@unittest.skipUnless(c_uuid, 'requires the C _uuid module') +class TestUUIDWithExtModule(BaseTestUUID, unittest.TestCase): + uuid = c_uuid + + +class BaseTestInternals: + _uuid = py_uuid + + def check_parse_mac(self, aix): + if not aix: + patch = mock.patch.multiple(self.uuid, + _MAC_DELIM=b':', + _MAC_OMITS_LEADING_ZEROES=False) + else: + patch = mock.patch.multiple(self.uuid, + _MAC_DELIM=b'.', + _MAC_OMITS_LEADING_ZEROES=True) + + with patch: + # Valid MAC addresses + if not aix: + tests = ( + (b'52:54:00:9d:0e:67', 0x5254009d0e67), + (b'12:34:56:78:90:ab', 0x1234567890ab), + ) + else: + # AIX format + tests = ( + (b'fe.ad.c.1.23.4', 0xfead0c012304), + ) + for mac, expected in tests: + self.assertEqual(self.uuid._parse_mac(mac), expected) + + # Invalid MAC addresses + for mac in ( + b'', + # IPv6 addresses with same length than valid MAC address + # (17 characters) + b'fe80::5054:ff:fe9', + b'123:2:3:4:5:6:7:8', + # empty 5rd field + b'52:54:00:9d::67', + # only 5 fields instead of 6 + b'52:54:00:9d:0e' + # invalid character 'x' + b'52:54:00:9d:0e:6x' + # dash separator + b'52-54-00-9d-0e-67', + ): + if aix: + mac = mac.replace(b':', b'.') + with self.subTest(mac=mac): + self.assertIsNone(self.uuid._parse_mac(mac)) + + def test_parse_mac(self): + self.check_parse_mac(False) + + def test_parse_mac_aix(self): + self.check_parse_mac(True) + + def test_find_under_heading(self): + data = '''\ +Name Mtu Network Address Ipkts Ierrs Opkts Oerrs Coll +en0 1500 link#2 fe.ad.c.1.23.4 1714807956 0 711348489 0 0 + 01:00:5e:00:00:01 +en0 1500 192.168.129 x071 1714807956 0 711348489 0 0 + 224.0.0.1 +en0 1500 192.168.90 x071 1714807956 0 711348489 0 0 + 224.0.0.1 +''' + + # The above data is from AIX - with '.' as _MAC_DELIM and strings + # shorter than 17 bytes (no leading 0). (_MAC_OMITS_LEADING_ZEROES=True) + with mock.patch.multiple(self.uuid, + _MAC_DELIM=b'.', + _MAC_OMITS_LEADING_ZEROES=True, + _get_command_stdout=mock_get_command_stdout(data)): + mac = self.uuid._find_mac_under_heading( + command='netstat', + args='-ian', + heading=b'Address', + ) + + self.assertEqual(mac, 0xfead0c012304) + + def test_find_under_heading_ipv6(self): + # bpo-39991: IPv6 address "fe80::5054:ff:fe9" looks like a MAC address + # (same string length) but must be skipped + data = '''\ +Name Mtu Network Address Ipkts Ierrs Idrop Opkts Oerrs Coll +vtnet 1500 52:54:00:9d:0e:67 10017 0 0 8174 0 0 +vtnet - fe80::%vtnet0 fe80::5054:ff:fe9 0 - - 4 - - +vtnet - 192.168.122.0 192.168.122.45 8844 - - 8171 - - +lo0 16384 lo0 260148 0 0 260148 0 0 +lo0 - ::1/128 ::1 193 - - 193 - - + ff01::1%lo0 + ff02::2:2eb7:74fa + ff02::2:ff2e:b774 + ff02::1%lo0 + ff02::1:ff00:1%lo +lo0 - fe80::%lo0/64 fe80::1%lo0 0 - - 0 - - + ff01::1%lo0 + ff02::2:2eb7:74fa + ff02::2:ff2e:b774 + ff02::1%lo0 + ff02::1:ff00:1%lo +lo0 - 127.0.0.0/8 127.0.0.1 259955 - - 259955 - - + 224.0.0.1 +''' + + with mock.patch.multiple(self.uuid, + _MAC_DELIM=b':', + _MAC_OMITS_LEADING_ZEROES=False, + _get_command_stdout=mock_get_command_stdout(data)): + mac = self.uuid._find_mac_under_heading( + command='netstat', + args='-ian', + heading=b'Address', + ) + + self.assertEqual(mac, 0x5254009d0e67) + + def test_find_mac_near_keyword(self): + # key and value are on the same line + data = ''' +fake Link encap:UNSPEC hwaddr 00-00 +cscotun0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 +eth0 Link encap:Ethernet HWaddr 12:34:56:78:90:ab +''' + + # The above data will only be parsed properly on non-AIX unixes. + with mock.patch.multiple(self.uuid, + _MAC_DELIM=b':', + _MAC_OMITS_LEADING_ZEROES=False, + _get_command_stdout=mock_get_command_stdout(data)): + mac = self.uuid._find_mac_near_keyword( + command='ifconfig', + args='', + keywords=[b'hwaddr'], + get_word_index=lambda x: x + 1, + ) + + self.assertEqual(mac, 0x1234567890ab) + + def check_node(self, node, requires=None): + if requires and node is None: + self.skipTest('requires ' + requires) + hex = '%012x' % node + if support.verbose >= 2: + print(hex, end=' ') + self.assertTrue(0 < node < (1 << 48), + "%s is not an RFC 4122 node ID" % hex) + + @unittest.skipUnless(_uuid._ifconfig_getnode in _uuid._GETTERS, + "ifconfig is not used for introspection on this platform") + def test_ifconfig_getnode(self): + node = self.uuid._ifconfig_getnode() + self.check_node(node, 'ifconfig') + + @unittest.skipUnless(_uuid._ip_getnode in _uuid._GETTERS, + "ip is not used for introspection on this platform") + def test_ip_getnode(self): + node = self.uuid._ip_getnode() + self.check_node(node, 'ip') + + @unittest.skipUnless(_uuid._arp_getnode in _uuid._GETTERS, + "arp is not used for introspection on this platform") + def test_arp_getnode(self): + node = self.uuid._arp_getnode() + self.check_node(node, 'arp') + + @unittest.skipUnless(_uuid._lanscan_getnode in _uuid._GETTERS, + "lanscan is not used for introspection on this platform") + def test_lanscan_getnode(self): + node = self.uuid._lanscan_getnode() + self.check_node(node, 'lanscan') + + @unittest.skipUnless(_uuid._netstat_getnode in _uuid._GETTERS, + "netstat is not used for introspection on this platform") + def test_netstat_getnode(self): + node = self.uuid._netstat_getnode() + self.check_node(node, 'netstat') + + def test_random_getnode(self): + node = self.uuid._random_getnode() + # The multicast bit, i.e. the least significant bit of first octet, + # must be set for randomly generated MAC addresses. See RFC 4122, + # $4.1.6. + self.assertTrue(node & (1 << 40), '%012x' % node) + self.check_node(node) + + node2 = self.uuid._random_getnode() + self.assertNotEqual(node2, node, '%012x' % node) + +class TestInternalsWithoutExtModule(BaseTestInternals, unittest.TestCase): + uuid = py_uuid + +@unittest.skipUnless(c_uuid, 'requires the C _uuid module') +class TestInternalsWithExtModule(BaseTestInternals, unittest.TestCase): + uuid = c_uuid + + @unittest.skipUnless(os.name == 'posix', 'requires Posix') + def test_unix_getnode(self): + if not importable('_uuid') and not importable('ctypes'): + self.skipTest("neither _uuid extension nor ctypes available") + try: # Issues 1481, 3581: _uuid_generate_time() might be None. + node = self.uuid._unix_getnode() + except TypeError: + self.skipTest('requires uuid_generate_time') + self.check_node(node, 'unix') + + @unittest.skipUnless(os.name == 'nt', 'requires Windows') + def test_windll_getnode(self): + node = self.uuid._windll_getnode() + self.check_node(node) + + +if __name__ == '__main__': + unittest.main() diff --git a/python-stdlib/uuid/uuid.py b/python-stdlib/uuid/uuid.py new file mode 100644 index 000000000..5ae0a3e5f --- /dev/null +++ b/python-stdlib/uuid/uuid.py @@ -0,0 +1,728 @@ +r"""UUID objects (universally unique identifiers) according to RFC 4122. + +This module provides immutable UUID objects (class UUID) and the functions +uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5 +UUIDs as specified in RFC 4122. + +If all you want is a unique ID, you should probably call uuid1() or uuid4(). +Note that uuid1() may compromise privacy since it creates a UUID containing +the computer's network address. uuid4() creates a random UUID. + +Typical usage: + + >>> import uuid + + # make a UUID based on the host ID and current time + >>> uuid.uuid1() # doctest: +SKIP + UUID('a8098c1a-f86e-11da-bd1a-00112444be1e') + + # make a UUID using an MD5 hash of a namespace UUID and a name + >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org') + UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e') + + # make a random UUID + >>> uuid.uuid4() # doctest: +SKIP + UUID('16fd2706-8baf-433b-82eb-8c7fada847da') + + # make a UUID using a SHA-1 hash of a namespace UUID and a name + >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org') + UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d') + + # make a UUID from a string of hex digits (braces and hyphens ignored) + >>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}') + + # convert a UUID to a string of hex digits in standard form + >>> str(x) + '00010203-0405-0607-0809-0a0b0c0d0e0f' + + # get the raw 16 bytes of the UUID + >>> x.bytes + b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f' + + # make a UUID from a 16-byte string + >>> uuid.UUID(bytes=x.bytes) + UUID('00010203-0405-0607-0809-0a0b0c0d0e0f') +""" + +import os +import sys + +from enum import Enum + + +__author__ = 'Ka-Ping Yee ' + +# The recognized platforms - known behaviors +if sys.platform in ('win32', 'darwin'): + _AIX = _LINUX = False +else: + import platform + _platform_system = platform.system() + _AIX = _platform_system == 'AIX' + _LINUX = _platform_system == 'Linux' + +_MAC_DELIM = b':' +_MAC_OMITS_LEADING_ZEROES = False +if _AIX: + _MAC_DELIM = b'.' + _MAC_OMITS_LEADING_ZEROES = True + +RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [ + 'reserved for NCS compatibility', 'specified in RFC 4122', + 'reserved for Microsoft compatibility', 'reserved for future definition'] + +int_ = int # The built-in int type +bytes_ = bytes # The built-in bytes type + + +class SafeUUID(Enum): + safe = 0 + unsafe = -1 + unknown = None + + +class UUID: + """Instances of the UUID class represent UUIDs as specified in RFC 4122. + UUID objects are immutable, hashable, and usable as dictionary keys. + Converting a UUID to a string with str() yields something in the form + '12345678-1234-1234-1234-123456789abc'. The UUID constructor accepts + five possible forms: a similar string of hexadecimal digits, or a tuple + of six integer fields (with 32-bit, 16-bit, 16-bit, 8-bit, 8-bit, and + 48-bit values respectively) as an argument named 'fields', or a string + of 16 bytes (with all the integer fields in big-endian order) as an + argument named 'bytes', or a string of 16 bytes (with the first three + fields in little-endian order) as an argument named 'bytes_le', or a + single 128-bit integer as an argument named 'int'. + + UUIDs have these read-only attributes: + + bytes the UUID as a 16-byte string (containing the six + integer fields in big-endian byte order) + + bytes_le the UUID as a 16-byte string (with time_low, time_mid, + and time_hi_version in little-endian byte order) + + fields a tuple of the six integer fields of the UUID, + which are also available as six individual attributes + and two derived attributes: + + time_low the first 32 bits of the UUID + time_mid the next 16 bits of the UUID + time_hi_version the next 16 bits of the UUID + clock_seq_hi_variant the next 8 bits of the UUID + clock_seq_low the next 8 bits of the UUID + node the last 48 bits of the UUID + + time the 60-bit timestamp + clock_seq the 14-bit sequence number + + hex the UUID as a 32-character hexadecimal string + + int the UUID as a 128-bit integer + + urn the UUID as a URN as specified in RFC 4122 + + variant the UUID variant (one of the constants RESERVED_NCS, + RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE) + + version the UUID version number (1 through 5, meaningful only + when the variant is RFC_4122) + + is_safe An enum indicating whether the UUID has been generated in + a way that is safe for multiprocessing applications, via + uuid_generate_time_safe(3). + """ + + __slots__ = ('int', 'is_safe', '__weakref__') + + def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, + int=None, version=None, + *, is_safe=SafeUUID.unknown): + r"""Create a UUID from either a string of 32 hexadecimal digits, + a string of 16 bytes as the 'bytes' argument, a string of 16 bytes + in little-endian order as the 'bytes_le' argument, a tuple of six + integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version, + 8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as + the 'fields' argument, or a single 128-bit integer as the 'int' + argument. When a string of hex digits is given, curly braces, + hyphens, and a URN prefix are all optional. For example, these + expressions all yield the same UUID: + + UUID('{12345678-1234-5678-1234-567812345678}') + UUID('12345678123456781234567812345678') + UUID('urn:uuid:12345678-1234-5678-1234-567812345678') + UUID(bytes='\x12\x34\x56\x78'*4) + UUID(bytes_le='\x78\x56\x34\x12\x34\x12\x78\x56' + + '\x12\x34\x56\x78\x12\x34\x56\x78') + UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678)) + UUID(int=0x12345678123456781234567812345678) + + Exactly one of 'hex', 'bytes', 'bytes_le', 'fields', or 'int' must + be given. The 'version' argument is optional; if given, the resulting + UUID will have its variant and version set according to RFC 4122, + overriding the given 'hex', 'bytes', 'bytes_le', 'fields', or 'int'. + + is_safe is an enum exposed as an attribute on the instance. It + indicates whether the UUID has been generated in a way that is safe + for multiprocessing applications, via uuid_generate_time_safe(3). + """ + + if [hex, bytes, bytes_le, fields, int].count(None) != 4: + raise TypeError('one of the hex, bytes, bytes_le, fields, ' + 'or int arguments must be given') + if hex is not None: + hex = hex.replace('urn:', '').replace('uuid:', '') + hex = hex.strip('{}').replace('-', '') + if len(hex) != 32: + raise ValueError('badly formed hexadecimal UUID string') + int = int_(hex, 16) + if bytes_le is not None: + if len(bytes_le) != 16: + raise ValueError('bytes_le is not a 16-char string') + bytes = (bytes_le[4-1::-1] + bytes_le[6-1:4-1:-1] + + bytes_le[8-1:6-1:-1] + bytes_le[8:]) + if bytes is not None: + if len(bytes) != 16: + raise ValueError('bytes is not a 16-char string') + assert isinstance(bytes, bytes_), repr(bytes) + int = int_.from_bytes(bytes, byteorder='big') + if fields is not None: + if len(fields) != 6: + raise ValueError('fields is not a 6-tuple') + (time_low, time_mid, time_hi_version, + clock_seq_hi_variant, clock_seq_low, node) = fields + if not 0 <= time_low < 1<<32: + raise ValueError('field 1 out of range (need a 32-bit value)') + if not 0 <= time_mid < 1<<16: + raise ValueError('field 2 out of range (need a 16-bit value)') + if not 0 <= time_hi_version < 1<<16: + raise ValueError('field 3 out of range (need a 16-bit value)') + if not 0 <= clock_seq_hi_variant < 1<<8: + raise ValueError('field 4 out of range (need an 8-bit value)') + if not 0 <= clock_seq_low < 1<<8: + raise ValueError('field 5 out of range (need an 8-bit value)') + if not 0 <= node < 1<<48: + raise ValueError('field 6 out of range (need a 48-bit value)') + clock_seq = (clock_seq_hi_variant << 8) | clock_seq_low + int = ((time_low << 96) | (time_mid << 80) | + (time_hi_version << 64) | (clock_seq << 48) | node) + if int is not None: + if not 0 <= int < 1<<128: + raise ValueError('int is out of range (need a 128-bit value)') + if version is not None: + if not 1 <= version <= 5: + raise ValueError('illegal version number') + # Set the variant to RFC 4122. + int &= ~(0xc000 << 48) + int |= 0x8000 << 48 + # Set the version number. + int &= ~(0xf000 << 64) + int |= version << 76 + object.__setattr__(self, 'int', int) + object.__setattr__(self, 'is_safe', is_safe) + + def __getstate__(self): + d = {'int': self.int} + if self.is_safe != SafeUUID.unknown: + # is_safe is a SafeUUID instance. Return just its value, so that + # it can be un-pickled in older Python versions without SafeUUID. + d['is_safe'] = self.is_safe.value + return d + + def __setstate__(self, state): + object.__setattr__(self, 'int', state['int']) + # is_safe was added in 3.7; it is also omitted when it is "unknown" + object.__setattr__(self, 'is_safe', + SafeUUID(state['is_safe']) + if 'is_safe' in state else SafeUUID.unknown) + + def __eq__(self, other): + if isinstance(other, UUID): + return self.int == other.int + return NotImplemented + + # Q. What's the value of being able to sort UUIDs? + # A. Use them as keys in a B-Tree or similar mapping. + + def __lt__(self, other): + if isinstance(other, UUID): + return self.int < other.int + return NotImplemented + + def __gt__(self, other): + if isinstance(other, UUID): + return self.int > other.int + return NotImplemented + + def __le__(self, other): + if isinstance(other, UUID): + return self.int <= other.int + return NotImplemented + + def __ge__(self, other): + if isinstance(other, UUID): + return self.int >= other.int + return NotImplemented + + def __hash__(self): + return hash(self.int) + + def __int__(self): + return self.int + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, str(self)) + + def __setattr__(self, name, value): + raise TypeError('UUID objects are immutable') + + def __str__(self): + hex = '%032x' % self.int + return '%s-%s-%s-%s-%s' % ( + hex[:8], hex[8:12], hex[12:16], hex[16:20], hex[20:]) + + @property + def bytes(self): + return self.int.to_bytes(16, 'big') + + @property + def bytes_le(self): + bytes = self.bytes + return (bytes[4-1::-1] + bytes[6-1:4-1:-1] + bytes[8-1:6-1:-1] + + bytes[8:]) + + @property + def fields(self): + return (self.time_low, self.time_mid, self.time_hi_version, + self.clock_seq_hi_variant, self.clock_seq_low, self.node) + + @property + def time_low(self): + return self.int >> 96 + + @property + def time_mid(self): + return (self.int >> 80) & 0xffff + + @property + def time_hi_version(self): + return (self.int >> 64) & 0xffff + + @property + def clock_seq_hi_variant(self): + return (self.int >> 56) & 0xff + + @property + def clock_seq_low(self): + return (self.int >> 48) & 0xff + + @property + def time(self): + return (((self.time_hi_version & 0x0fff) << 48) | + (self.time_mid << 32) | self.time_low) + + @property + def clock_seq(self): + return (((self.clock_seq_hi_variant & 0x3f) << 8) | + self.clock_seq_low) + + @property + def node(self): + return self.int & 0xffffffffffff + + @property + def hex(self): + return '%032x' % self.int + + @property + def urn(self): + return 'urn:uuid:' + str(self) + + @property + def variant(self): + if not self.int & (0x8000 << 48): + return RESERVED_NCS + elif not self.int & (0x4000 << 48): + return RFC_4122 + elif not self.int & (0x2000 << 48): + return RESERVED_MICROSOFT + else: + return RESERVED_FUTURE + + @property + def version(self): + # The version bits are only meaningful for RFC 4122 UUIDs. + if self.variant == RFC_4122: + return int((self.int >> 76) & 0xf) + + +def _get_command_stdout(command, *args): + import io, os, shutil, subprocess + + try: + path_dirs = os.environ.get('PATH', os.defpath).split(os.pathsep) + path_dirs.extend(['/sbin', '/usr/sbin']) + executable = shutil.which(command, path=os.pathsep.join(path_dirs)) + 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) + if not proc: + return None + stdout, stderr = proc.communicate() + return io.BytesIO(stdout) + except (OSError, subprocess.SubprocessError): + return None + + +# For MAC (a.k.a. IEEE 802, or EUI-48) addresses, the second least significant +# bit of the first octet signifies whether the MAC address is universally (0) +# or locally (1) administered. Network cards from hardware manufacturers will +# always be universally administered to guarantee global uniqueness of the MAC +# address, but any particular machine may have other interfaces which are +# locally administered. An example of the latter is the bridge interface to +# the Touch Bar on MacBook Pros. +# +# This bit works out to be the 42nd bit counting from 1 being the least +# significant, or 1<<41. We'll prefer universally administered MAC addresses +# over locally administered ones since the former are globally unique, but +# we'll return the first of the latter found if that's all the machine has. +# +# See https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local + +def _is_universal(mac): + return not (mac & (1 << 41)) + + +def _find_mac_near_keyword(command, args, keywords, get_word_index): + """Searches a command's output for a MAC address near a keyword. + + Each line of words in the output is case-insensitively searched for + any of the given keywords. Upon a match, get_word_index is invoked + to pick a word from the line, given the index of the match. For + example, lambda i: 0 would get the first word on the line, while + lambda i: i - 1 would get the word preceding the keyword. + """ + stdout = _get_command_stdout(command, args) + if stdout is None: + return None + + first_local_mac = None + for line in stdout: + words = line.lower().rstrip().split() + for i in range(len(words)): + if words[i] in keywords: + try: + word = words[get_word_index(i)] + mac = int(word.replace(_MAC_DELIM, b''), 16) + 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 + else: + if _is_universal(mac): + return mac + first_local_mac = first_local_mac or mac + return first_local_mac or None + + +def _parse_mac(word): + # Accept 'HH:HH:HH:HH:HH:HH' MAC address (ex: '52:54:00:9d:0e:67'), + # but reject IPv6 address (ex: 'fe80::5054:ff:fe9' or '123:2:3:4:5:6:7:8'). + # + # 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 + parts = word.split(_MAC_DELIM) + if len(parts) != 6: + return + if _MAC_OMITS_LEADING_ZEROES: + # (Only) on AIX the macaddr value given is not prefixed by 0, e.g. + # en0 1500 link#2 fa.bc.de.f7.62.4 110854824 0 160133733 0 0 + # not + # en0 1500 link#2 fa.bc.de.f7.62.04 110854824 0 160133733 0 0 + if not all(1 <= len(part) <= 2 for part in parts): + return + hexstr = b''.join(part.rjust(2, b'0') for part in parts) + else: + if not all(len(part) == 2 for part in parts): + return + hexstr = b''.join(parts) + try: + return int(hexstr, 16) + except ValueError: + return + + +def _find_mac_under_heading(command, args, heading): + """Looks for a MAC address under a heading in a command's output. + + The first line of words in the output is searched for the given + heading. Words at the same word index as the heading in subsequent + lines are then examined to see if they look like MAC addresses. + """ + stdout = _get_command_stdout(command, args) + if stdout is None: + return None + + keywords = stdout.readline().rstrip().split() + try: + column_index = keywords.index(heading) + except ValueError: + return None + + first_local_mac = None + for line in stdout: + words = line.rstrip().split() + try: + word = words[column_index] + except IndexError: + continue + + mac = _parse_mac(word) + if mac is None: + continue + if _is_universal(mac): + return mac + if first_local_mac is None: + first_local_mac = mac + + return first_local_mac + + +# The following functions call external programs to 'get' a macaddr value to +# be used as basis for an uuid +def _ifconfig_getnode(): + """Get the hardware address on Unix by running ifconfig.""" + # This works on Linux ('' or '-a'), Tru64 ('-av'), but not all Unixes. + keywords = (b'hwaddr', b'ether', b'address:', b'lladdr') + for args in ('', '-a', '-av'): + mac = _find_mac_near_keyword('ifconfig', args, keywords, lambda i: i+1) + if mac: + return mac + return None + +def _ip_getnode(): + """Get the hardware address on Unix by running ip.""" + # This works on Linux with iproute2. + mac = _find_mac_near_keyword('ip', 'link', [b'link/ether'], lambda i: i+1) + if mac: + return mac + return None + +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). + mac = _find_mac_near_keyword('arp', '-an', [os.fsencode(ip_addr)], lambda i: -1) + if mac: + return mac + + # This works on OpenBSD + mac = _find_mac_near_keyword('arp', '-an', [os.fsencode(ip_addr)], lambda i: i+1) + if mac: + return mac + + # This works on Linux, FreeBSD and NetBSD + mac = _find_mac_near_keyword('arp', '-an', [os.fsencode('(%s)' % ip_addr)], + lambda i: i+2) + # Return None instead of 0. + if mac: + return mac + return None + +def _lanscan_getnode(): + """Get the hardware address on Unix by running lanscan.""" + # This might work on HP-UX. + return _find_mac_near_keyword('lanscan', '-ai', [b'lan0'], lambda i: 0) + +def _netstat_getnode(): + """Get the hardware address on Unix by running netstat.""" + # This works on AIX and might work on Tru64 UNIX. + return _find_mac_under_heading('netstat', '-ian', b'Address') + +def _ipconfig_getnode(): + """[DEPRECATED] Get the hardware address on Windows.""" + # bpo-40501: UuidCreateSequential() is now the only supported approach + return _windll_getnode() + +def _netbios_getnode(): + """[DEPRECATED] Get the hardware address on Windows.""" + # bpo-40501: UuidCreateSequential() is now the only supported approach + return _windll_getnode() + + +# Import optional C extension at toplevel, to help disabling it when testing +try: + import _uuid + _generate_time_safe = getattr(_uuid, "generate_time_safe", None) + _UuidCreate = getattr(_uuid, "UuidCreate", None) + _has_uuid_generate_time_safe = _uuid.has_uuid_generate_time_safe +except ImportError: + _uuid = None + _generate_time_safe = None + _UuidCreate = None + _has_uuid_generate_time_safe = None + + +def _load_system_functions(): + """[DEPRECATED] Platform-specific functions loaded at import time""" + + +def _unix_getnode(): + """Get the hardware address on Unix using the _uuid extension module.""" + if _generate_time_safe: + uuid_time, _ = _generate_time_safe() + return UUID(bytes=uuid_time).node + +def _windll_getnode(): + """Get the hardware address on Windows using the _uuid extension module.""" + if _UuidCreate: + uuid_bytes = _UuidCreate() + return UUID(bytes_le=uuid_bytes).node + +def _random_getnode(): + """Get a random node ID.""" + # RFC 4122, $4.1.6 says "For systems with no IEEE address, a randomly or + # pseudo-randomly generated value may be used; see Section 4.5. The + # multicast bit must be set in such addresses, in order that they will + # never conflict with addresses obtained from network cards." + # + # The "multicast bit" of a MAC address is defined to be "the least + # significant bit of the first octet". This works out to be the 41st bit + # counting from 1 being the least significant bit, or 1<<40. + # + # See https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast + import random + return random.getrandbits(48) | (1 << 40) + + +# _OS_GETTERS, when known, are targeted for a specific OS or platform. +# The order is by 'common practice' on the specified platform. +# Note: 'posix' and 'windows' _OS_GETTERS are prefixed by a dll/dlload() method +# which, when successful, means none of these "external" methods are called. +# _GETTERS is (also) used by test_uuid.py to SkipUnless(), e.g., +# @unittest.skipUnless(_uuid._ifconfig_getnode in _uuid._GETTERS, ...) +if _LINUX: + _OS_GETTERS = [_ip_getnode, _ifconfig_getnode] +elif sys.platform == 'darwin': + _OS_GETTERS = [_ifconfig_getnode, _arp_getnode, _netstat_getnode] +elif sys.platform == 'win32': + # bpo-40201: _windll_getnode will always succeed, so these are not needed + _OS_GETTERS = [] +elif _AIX: + _OS_GETTERS = [_netstat_getnode] +else: + _OS_GETTERS = [_ifconfig_getnode, _ip_getnode, _arp_getnode, + _netstat_getnode, _lanscan_getnode] +if os.name == 'posix': + _GETTERS = [_unix_getnode] + _OS_GETTERS +elif os.name == 'nt': + _GETTERS = [_windll_getnode] + _OS_GETTERS +else: + _GETTERS = _OS_GETTERS + +_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. + """ + global _node + if _node is not None: + return _node + + for getter in _GETTERS + [_random_getnode]: + try: + _node = getter() + except: + continue + if (_node is not None) and (0 <= _node < (1 << 48)): + return _node + assert False, '_random_getnode() returned invalid value: {}'.format(_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.""" + + # 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 _generate_time_safe is not None and node is clock_seq is None: + uuid_time, safely_generated = _generate_time_safe() + try: + is_safe = SafeUUID(safely_generated) + except ValueError: + is_safe = SafeUUID.unknown + return UUID(bytes=uuid_time, is_safe=is_safe) + + global _last_timestamp + import time + nanoseconds = time.time_ns() + # 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 = 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.""" + from hashlib import md5 + digest = md5( + namespace.bytes + bytes(name, "utf-8"), + usedforsecurity=False + ).digest() + return UUID(bytes=digest[: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') +NAMESPACE_URL = UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8') +NAMESPACE_OID = UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8') +NAMESPACE_X500 = UUID('6ba7b814-9dad-11d1-80b4-00c04fd430c8') From dd2079dbb2b049a2a134ddd6a28299ab9e2a0c4a Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Wed, 6 Jul 2022 17:23:56 +1000 Subject: [PATCH 2/2] Basic UUID functionality - for each uuidX method - appears to be working --- python-stdlib/uuid/uuid.py | 362 ++----------------------------------- 1 file changed, 18 insertions(+), 344 deletions(-) diff --git a/python-stdlib/uuid/uuid.py b/python-stdlib/uuid/uuid.py index 5ae0a3e5f..b76cd3b74 100644 --- a/python-stdlib/uuid/uuid.py +++ b/python-stdlib/uuid/uuid.py @@ -47,25 +47,6 @@ import os import sys -from enum import Enum - - -__author__ = 'Ka-Ping Yee ' - -# The recognized platforms - known behaviors -if sys.platform in ('win32', 'darwin'): - _AIX = _LINUX = False -else: - import platform - _platform_system = platform.system() - _AIX = _platform_system == 'AIX' - _LINUX = _platform_system == 'Linux' - -_MAC_DELIM = b':' -_MAC_OMITS_LEADING_ZEROES = False -if _AIX: - _MAC_DELIM = b'.' - _MAC_OMITS_LEADING_ZEROES = True RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [ 'reserved for NCS compatibility', 'specified in RFC 4122', @@ -75,12 +56,6 @@ bytes_ = bytes # The built-in bytes type -class SafeUUID(Enum): - safe = 0 - unsafe = -1 - unknown = None - - class UUID: """Instances of the UUID class represent UUIDs as specified in RFC 4122. UUID objects are immutable, hashable, and usable as dictionary keys. @@ -133,11 +108,10 @@ class UUID: uuid_generate_time_safe(3). """ - __slots__ = ('int', 'is_safe', '__weakref__') - + def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, int=None, version=None, - *, is_safe=SafeUUID.unknown): + *): r"""Create a UUID from either a string of 32 hexadecimal digits, a string of 16 bytes as the 'bytes' argument, a string of 16 bytes in little-endian order as the 'bytes_le' argument, a tuple of six @@ -185,7 +159,7 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, if len(bytes) != 16: raise ValueError('bytes is not a 16-char string') assert isinstance(bytes, bytes_), repr(bytes) - int = int_.from_bytes(bytes, byteorder='big') + int = int_.from_bytes(bytes, 'big') if fields is not None: if len(fields) != 6: raise ValueError('fields is not a 6-tuple') @@ -219,22 +193,6 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, int &= ~(0xf000 << 64) int |= version << 76 object.__setattr__(self, 'int', int) - object.__setattr__(self, 'is_safe', is_safe) - - def __getstate__(self): - d = {'int': self.int} - if self.is_safe != SafeUUID.unknown: - # is_safe is a SafeUUID instance. Return just its value, so that - # it can be un-pickled in older Python versions without SafeUUID. - d['is_safe'] = self.is_safe.value - return d - - def __setstate__(self, state): - object.__setattr__(self, 'int', state['int']) - # is_safe was added in 3.7; it is also omitted when it is "unknown" - object.__setattr__(self, 'is_safe', - SafeUUID(state['is_safe']) - if 'is_safe' in state else SafeUUID.unknown) def __eq__(self, other): if isinstance(other, UUID): @@ -356,246 +314,21 @@ def version(self): return int((self.int >> 76) & 0xf) -def _get_command_stdout(command, *args): - import io, os, shutil, subprocess - - try: - path_dirs = os.environ.get('PATH', os.defpath).split(os.pathsep) - path_dirs.extend(['/sbin', '/usr/sbin']) - executable = shutil.which(command, path=os.pathsep.join(path_dirs)) - 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) - if not proc: - return None - stdout, stderr = proc.communicate() - return io.BytesIO(stdout) - except (OSError, subprocess.SubprocessError): - return None - - -# For MAC (a.k.a. IEEE 802, or EUI-48) addresses, the second least significant -# bit of the first octet signifies whether the MAC address is universally (0) -# or locally (1) administered. Network cards from hardware manufacturers will -# always be universally administered to guarantee global uniqueness of the MAC -# address, but any particular machine may have other interfaces which are -# locally administered. An example of the latter is the bridge interface to -# the Touch Bar on MacBook Pros. -# -# This bit works out to be the 42nd bit counting from 1 being the least -# significant, or 1<<41. We'll prefer universally administered MAC addresses -# over locally administered ones since the former are globally unique, but -# we'll return the first of the latter found if that's all the machine has. -# -# See https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local - -def _is_universal(mac): - return not (mac & (1 << 41)) - - -def _find_mac_near_keyword(command, args, keywords, get_word_index): - """Searches a command's output for a MAC address near a keyword. - - Each line of words in the output is case-insensitively searched for - any of the given keywords. Upon a match, get_word_index is invoked - to pick a word from the line, given the index of the match. For - example, lambda i: 0 would get the first word on the line, while - lambda i: i - 1 would get the word preceding the keyword. - """ - stdout = _get_command_stdout(command, args) - if stdout is None: - return None - - first_local_mac = None - for line in stdout: - words = line.lower().rstrip().split() - for i in range(len(words)): - if words[i] in keywords: - try: - word = words[get_word_index(i)] - mac = int(word.replace(_MAC_DELIM, b''), 16) - 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 - else: - if _is_universal(mac): - return mac - first_local_mac = first_local_mac or mac - return first_local_mac or None - - -def _parse_mac(word): - # Accept 'HH:HH:HH:HH:HH:HH' MAC address (ex: '52:54:00:9d:0e:67'), - # but reject IPv6 address (ex: 'fe80::5054:ff:fe9' or '123:2:3:4:5:6:7:8'). - # - # 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 - parts = word.split(_MAC_DELIM) - if len(parts) != 6: - return - if _MAC_OMITS_LEADING_ZEROES: - # (Only) on AIX the macaddr value given is not prefixed by 0, e.g. - # en0 1500 link#2 fa.bc.de.f7.62.4 110854824 0 160133733 0 0 - # not - # en0 1500 link#2 fa.bc.de.f7.62.04 110854824 0 160133733 0 0 - if not all(1 <= len(part) <= 2 for part in parts): - return - hexstr = b''.join(part.rjust(2, b'0') for part in parts) - else: - if not all(len(part) == 2 for part in parts): - return - hexstr = b''.join(parts) +def _micropython_get_mac(): + '''Error handling needed! network won't exist on ports/boards with no network access (unix). + Need to fall back to using _random_getnode() if this fails. + ''' + mac = b'' try: - return int(hexstr, 16) - except ValueError: - return + import network + wlan = network.WLAN() + mac = wlan.config('mac') + except ImportError: + from os import urandom + mac = urandom(6) + return int.from_bytes(mac, "little") -def _find_mac_under_heading(command, args, heading): - """Looks for a MAC address under a heading in a command's output. - - The first line of words in the output is searched for the given - heading. Words at the same word index as the heading in subsequent - lines are then examined to see if they look like MAC addresses. - """ - stdout = _get_command_stdout(command, args) - if stdout is None: - return None - - keywords = stdout.readline().rstrip().split() - try: - column_index = keywords.index(heading) - except ValueError: - return None - - first_local_mac = None - for line in stdout: - words = line.rstrip().split() - try: - word = words[column_index] - except IndexError: - continue - - mac = _parse_mac(word) - if mac is None: - continue - if _is_universal(mac): - return mac - if first_local_mac is None: - first_local_mac = mac - - return first_local_mac - - -# The following functions call external programs to 'get' a macaddr value to -# be used as basis for an uuid -def _ifconfig_getnode(): - """Get the hardware address on Unix by running ifconfig.""" - # This works on Linux ('' or '-a'), Tru64 ('-av'), but not all Unixes. - keywords = (b'hwaddr', b'ether', b'address:', b'lladdr') - for args in ('', '-a', '-av'): - mac = _find_mac_near_keyword('ifconfig', args, keywords, lambda i: i+1) - if mac: - return mac - return None - -def _ip_getnode(): - """Get the hardware address on Unix by running ip.""" - # This works on Linux with iproute2. - mac = _find_mac_near_keyword('ip', 'link', [b'link/ether'], lambda i: i+1) - if mac: - return mac - return None - -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). - mac = _find_mac_near_keyword('arp', '-an', [os.fsencode(ip_addr)], lambda i: -1) - if mac: - return mac - - # This works on OpenBSD - mac = _find_mac_near_keyword('arp', '-an', [os.fsencode(ip_addr)], lambda i: i+1) - if mac: - return mac - - # This works on Linux, FreeBSD and NetBSD - mac = _find_mac_near_keyword('arp', '-an', [os.fsencode('(%s)' % ip_addr)], - lambda i: i+2) - # Return None instead of 0. - if mac: - return mac - return None - -def _lanscan_getnode(): - """Get the hardware address on Unix by running lanscan.""" - # This might work on HP-UX. - return _find_mac_near_keyword('lanscan', '-ai', [b'lan0'], lambda i: 0) - -def _netstat_getnode(): - """Get the hardware address on Unix by running netstat.""" - # This works on AIX and might work on Tru64 UNIX. - return _find_mac_under_heading('netstat', '-ian', b'Address') - -def _ipconfig_getnode(): - """[DEPRECATED] Get the hardware address on Windows.""" - # bpo-40501: UuidCreateSequential() is now the only supported approach - return _windll_getnode() - -def _netbios_getnode(): - """[DEPRECATED] Get the hardware address on Windows.""" - # bpo-40501: UuidCreateSequential() is now the only supported approach - return _windll_getnode() - - -# Import optional C extension at toplevel, to help disabling it when testing -try: - import _uuid - _generate_time_safe = getattr(_uuid, "generate_time_safe", None) - _UuidCreate = getattr(_uuid, "UuidCreate", None) - _has_uuid_generate_time_safe = _uuid.has_uuid_generate_time_safe -except ImportError: - _uuid = None - _generate_time_safe = None - _UuidCreate = None - _has_uuid_generate_time_safe = None - - -def _load_system_functions(): - """[DEPRECATED] Platform-specific functions loaded at import time""" - - -def _unix_getnode(): - """Get the hardware address on Unix using the _uuid extension module.""" - if _generate_time_safe: - uuid_time, _ = _generate_time_safe() - return UUID(bytes=uuid_time).node - -def _windll_getnode(): - """Get the hardware address on Windows using the _uuid extension module.""" - if _UuidCreate: - uuid_bytes = _UuidCreate() - return UUID(bytes_le=uuid_bytes).node - def _random_getnode(): """Get a random node ID.""" # RFC 4122, $4.1.6 says "For systems with no IEEE address, a randomly or @@ -612,55 +345,6 @@ def _random_getnode(): return random.getrandbits(48) | (1 << 40) -# _OS_GETTERS, when known, are targeted for a specific OS or platform. -# The order is by 'common practice' on the specified platform. -# Note: 'posix' and 'windows' _OS_GETTERS are prefixed by a dll/dlload() method -# which, when successful, means none of these "external" methods are called. -# _GETTERS is (also) used by test_uuid.py to SkipUnless(), e.g., -# @unittest.skipUnless(_uuid._ifconfig_getnode in _uuid._GETTERS, ...) -if _LINUX: - _OS_GETTERS = [_ip_getnode, _ifconfig_getnode] -elif sys.platform == 'darwin': - _OS_GETTERS = [_ifconfig_getnode, _arp_getnode, _netstat_getnode] -elif sys.platform == 'win32': - # bpo-40201: _windll_getnode will always succeed, so these are not needed - _OS_GETTERS = [] -elif _AIX: - _OS_GETTERS = [_netstat_getnode] -else: - _OS_GETTERS = [_ifconfig_getnode, _ip_getnode, _arp_getnode, - _netstat_getnode, _lanscan_getnode] -if os.name == 'posix': - _GETTERS = [_unix_getnode] + _OS_GETTERS -elif os.name == 'nt': - _GETTERS = [_windll_getnode] + _OS_GETTERS -else: - _GETTERS = _OS_GETTERS - -_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. - """ - global _node - if _node is not None: - return _node - - for getter in _GETTERS + [_random_getnode]: - try: - _node = getter() - except: - continue - if (_node is not None) and (0 <= _node < (1 << 48)): - return _node - assert False, '_random_getnode() returned invalid value: {}'.format(_node) - - _last_timestamp = None def uuid1(node=None, clock_seq=None): @@ -669,21 +353,12 @@ def uuid1(node=None, clock_seq=None): address. If 'clock_seq' is given, it is used as the sequence number; otherwise a random 14-bit sequence number is chosen.""" - # 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 _generate_time_safe is not None and node is clock_seq is None: - uuid_time, safely_generated = _generate_time_safe() - try: - is_safe = SafeUUID(safely_generated) - except ValueError: - is_safe = SafeUUID.unknown - return UUID(bytes=uuid_time, is_safe=is_safe) - global _last_timestamp import time nanoseconds = time.time_ns() # 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. + # TODO mst This timestamp will need to be adjusted (MicroPython Epoch is different) timestamp = nanoseconds // 100 + 0x01b21dd213814000 if _last_timestamp is not None and timestamp <= _last_timestamp: timestamp = _last_timestamp + 1 @@ -697,7 +372,7 @@ def uuid1(node=None, clock_seq=None): clock_seq_low = clock_seq & 0xff clock_seq_hi_variant = (clock_seq >> 8) & 0x3f if node is None: - node = getnode() + node = _micropython_get_mac() return UUID(fields=(time_low, time_mid, time_hi_version, clock_seq_hi_variant, clock_seq_low, node), version=1) @@ -705,8 +380,7 @@ def uuid3(namespace, name): """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" from hashlib import md5 digest = md5( - namespace.bytes + bytes(name, "utf-8"), - usedforsecurity=False + namespace.bytes + bytes(name, "utf-8") ).digest() return UUID(bytes=digest[:16], version=3)