@@ -85,6 +85,17 @@ class SafeUUID: |
85 | 85 | unknown = None |
86 | 86 |
|
87 | 87 |
|
| 88 | +_UINT_128_MAX = (1 << 128) - 1 |
| 89 | +# 128-bit mask to clear the variant and version bits of a UUID integral value |
| 90 | +_RFC_4122_CLEARFLAGS_MASK = ~((0xf000 << 64) | (0xc000 << 48)) |
| 91 | +# RFC 4122 variant bits and version bits to activate on a UUID integral value. |
| 92 | +_RFC_4122_VERSION_1_FLAGS = ((1 << 76) | (0x8000 << 48)) |
| 93 | +_RFC_4122_VERSION_3_FLAGS = ((3 << 76) | (0x8000 << 48)) |
| 94 | +_RFC_4122_VERSION_4_FLAGS = ((4 << 76) | (0x8000 << 48)) |
| 95 | +_RFC_4122_VERSION_5_FLAGS = ((5 << 76) | (0x8000 << 48)) |
| 96 | +_RFC_4122_VERSION_8_FLAGS = ((8 << 76) | (0x8000 << 48)) |
| 97 | + |
| 98 | + |
88 | 99 | class UUID: |
89 | 100 | """Instances of the UUID class represent UUIDs as specified in RFC 4122. |
90 | 101 | UUID objects are immutable, hashable, and usable as dictionary keys. |
@@ -174,57 +185,69 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, |
174 | 185 | if [hex, bytes, bytes_le, fields, int].count(None) != 4: |
175 | 186 | raise TypeError('one of the hex, bytes, bytes_le, fields, ' |
176 | 187 | 'or int arguments must be given') |
177 | | - if hex is not None: |
| 188 | + if int is not None: |
| 189 | + pass |
| 190 | + elif hex is not None: |
178 | 191 | hex = hex.replace('urn:', '').replace('uuid:', '') |
179 | 192 | hex = hex.strip('{}').replace('-', '') |
180 | 193 | if len(hex) != 32: |
181 | 194 | raise ValueError('badly formed hexadecimal UUID string') |
182 | 195 | int = int_(hex, 16) |
183 | | - if bytes_le is not None: |
| 196 | + elif bytes_le is not None: |
184 | 197 | if len(bytes_le) != 16: |
185 | 198 | raise ValueError('bytes_le is not a 16-char string') |
| 199 | + assert isinstance(bytes_le, bytes_), repr(bytes_le) |
186 | 200 | bytes = (bytes_le[4-1::-1] + bytes_le[6-1:4-1:-1] + |
187 | 201 | bytes_le[8-1:6-1:-1] + bytes_le[8:]) |
188 | | - if bytes is not None: |
| 202 | + int = int_.from_bytes(bytes) # big endian |
| 203 | + elif bytes is not None: |
189 | 204 | if len(bytes) != 16: |
190 | 205 | raise ValueError('bytes is not a 16-char string') |
191 | 206 | assert isinstance(bytes, bytes_), repr(bytes) |
192 | 207 | int = int_.from_bytes(bytes) # big endian |
193 | | - if fields is not None: |
| 208 | + elif fields is not None: |
194 | 209 | if len(fields) != 6: |
195 | 210 | raise ValueError('fields is not a 6-tuple') |
196 | 211 | (time_low, time_mid, time_hi_version, |
197 | 212 | clock_seq_hi_variant, clock_seq_low, node) = fields |
198 | | - if not 0 <= time_low < 1<<32: |
| 213 | + if not 0 <= time_low < (1 << 32): |
199 | 214 | raise ValueError('field 1 out of range (need a 32-bit value)') |
200 | | - if not 0 <= time_mid < 1<<16: |
| 215 | + if not 0 <= time_mid < (1 << 16): |
201 | 216 | raise ValueError('field 2 out of range (need a 16-bit value)') |
202 | | - if not 0 <= time_hi_version < 1<<16: |
| 217 | + if not 0 <= time_hi_version < (1 << 16): |
203 | 218 | raise ValueError('field 3 out of range (need a 16-bit value)') |
204 | | - if not 0 <= clock_seq_hi_variant < 1<<8: |
| 219 | + if not 0 <= clock_seq_hi_variant < (1 << 8): |
205 | 220 | raise ValueError('field 4 out of range (need an 8-bit value)') |
206 | | - if not 0 <= clock_seq_low < 1<<8: |
| 221 | + if not 0 <= clock_seq_low < (1 << 8): |
207 | 222 | raise ValueError('field 5 out of range (need an 8-bit value)') |
208 | | - if not 0 <= node < 1<<48: |
| 223 | + if not 0 <= node < (1 << 48): |
209 | 224 | raise ValueError('field 6 out of range (need a 48-bit value)') |
210 | 225 | clock_seq = (clock_seq_hi_variant << 8) | clock_seq_low |
211 | 226 | int = ((time_low << 96) | (time_mid << 80) | |
212 | 227 | (time_hi_version << 64) | (clock_seq << 48) | node) |
213 | | - if int is not None: |
214 | | - if not 0 <= int < 1<<128: |
215 | | - raise ValueError('int is out of range (need a 128-bit value)') |
| 228 | + if not 0 <= int <= _UINT_128_MAX: |
| 229 | + raise ValueError('int is out of range (need a 128-bit value)') |
216 | 230 | if version is not None: |
217 | 231 | if not 1 <= version <= 8: |
218 | 232 | raise ValueError('illegal version number') |
| 233 | + # clear the variant and the version number bits |
| 234 | + int &= _RFC_4122_CLEARFLAGS_MASK |
219 | 235 | # Set the variant to RFC 4122/9562. |
220 | | - int &= ~(0xc000 << 48) |
221 | | - int |= 0x8000 << 48 |
| 236 | + int |= 0x8000_0000_0000_0000 # (0x8000 << 48) |
222 | 237 | # Set the version number. |
223 | | - int &= ~(0xf000 << 64) |
224 | 238 | int |= version << 76 |
225 | 239 | object.__setattr__(self, 'int', int) |
226 | 240 | object.__setattr__(self, 'is_safe', is_safe) |
227 | 241 |
|
| 242 | + @classmethod |
| 243 | + def _from_int(cls, value): |
| 244 | + """Create a UUID from an integer *value*. Internal use only.""" |
| 245 | + assert 0 <= value <= _UINT_128_MAX, repr(value) |
| 246 | + self = object.__new__(cls) |
| 247 | + object.__setattr__(self, 'int', value) |
| 248 | + object.__setattr__(self, 'is_safe', SafeUUID.unknown) |
| 249 | + return self |
| 250 | + |
228 | 251 | def __getstate__(self): |
229 | 252 | d = {'int': self.int} |
230 | 253 | if self.is_safe != SafeUUID.unknown: |
@@ -700,24 +723,30 @@ def uuid3(namespace, name): |
700 | 723 | """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" |
701 | 724 | if isinstance(name, str): |
702 | 725 | name = bytes(name, "utf-8") |
703 | | - from hashlib import md5 |
704 | | - digest = md5( |
705 | | - namespace.bytes + name, |
706 | | - usedforsecurity=False |
707 | | - ).digest() |
708 | | - return UUID(bytes=digest[:16], version=3) |
| 726 | + import hashlib |
| 727 | + h = hashlib.md5(namespace.bytes + name, usedforsecurity=False) |
| 728 | + int_uuid_3 = int.from_bytes(h.digest()) |
| 729 | + int_uuid_3 &= _RFC_4122_CLEARFLAGS_MASK |
| 730 | + int_uuid_3 |= _RFC_4122_VERSION_3_FLAGS |
| 731 | + return UUID._from_int(int_uuid_3) |
709 | 732 |
|
710 | 733 | def uuid4(): |
711 | 734 | """Generate a random UUID.""" |
712 | | - return UUID(bytes=os.urandom(16), version=4) |
| 735 | + int_uuid_4 = int.from_bytes(os.urandom(16)) |
| 736 | + int_uuid_4 &= _RFC_4122_CLEARFLAGS_MASK |
| 737 | + int_uuid_4 |= _RFC_4122_VERSION_4_FLAGS |
| 738 | + return UUID._from_int(int_uuid_4) |
713 | 739 |
|
714 | 740 | def uuid5(namespace, name): |
715 | 741 | """Generate a UUID from the SHA-1 hash of a namespace UUID and a name.""" |
716 | 742 | if isinstance(name, str): |
717 | 743 | name = bytes(name, "utf-8") |
718 | | - from hashlib import sha1 |
719 | | - hash = sha1(namespace.bytes + name).digest() |
720 | | - return UUID(bytes=hash[:16], version=5) |
| 744 | + import hashlib |
| 745 | + h = hashlib.sha1(namespace.bytes + name, usedforsecurity=False) |
| 746 | + int_uuid_5 = int.from_bytes(h.digest()[:16]) |
| 747 | + int_uuid_5 &= _RFC_4122_CLEARFLAGS_MASK |
| 748 | + int_uuid_5 |= _RFC_4122_VERSION_5_FLAGS |
| 749 | + return UUID._from_int(int_uuid_5) |
721 | 750 |
|
722 | 751 | def uuid8(a=None, b=None, c=None): |
723 | 752 | """Generate a UUID from three custom blocks. |
@@ -740,7 +769,9 @@ def uuid8(a=None, b=None, c=None): |
740 | 769 | int_uuid_8 = (a & 0xffff_ffff_ffff) << 80 |
741 | 770 | int_uuid_8 |= (b & 0xfff) << 64 |
742 | 771 | int_uuid_8 |= c & 0x3fff_ffff_ffff_ffff |
743 | | - return UUID(int=int_uuid_8, version=8) |
| 772 | + # by construction, the variant and version bits are already cleared |
| 773 | + int_uuid_8 |= _RFC_4122_VERSION_8_FLAGS |
| 774 | + return UUID._from_int(int_uuid_8) |
744 | 775 |
|
745 | 776 | def main(): |
746 | 777 | """Run the uuid command line interface.""" |
|