2018-05-11 00:51:49 -08:00

206 lines
7.6 KiB
Python

"""
Implementation of C# System.Guid
References:
https://referencesource.microsoft.com/#mscorlib/system/guid.cs
# TODO Add License
"""
from struct import Struct, pack
from math import log
# Constants for masking bytes
MAXINT64 = 0xFFFFFFFFFFFFFFFF
MAXINT16 = 0xFFFF
MAXINT8 = 0xFF
class Guid:
"""
A Global Unique Identifier (Guid) based on the one provided in C#'s System package
The Guid is stored as a tuple of length 11 for easy processing and comparisons
Attributes:
guid (tuple): A tuple containing the guid
"""
def __init__(self, *args):
"""
Initialize the Guid
Arguments:
Union[int, str, bytes, short(int will be masked to short)]:
The arguments can be in any of the following formats:
(bytes(len 16))
(int(len <= 4), short, short, bytes(len 8))
(int(len <= 8), int(len <= 8))
(int(len <= 16))
(str) - str must be formatted 00000000-0000-0000-0000-000000000000
"""
# Guid will be stored as a tuple len 11
if bool(args):
self.get_guid(args)
else:
self.guid = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
def get_guid(self, args) -> None:
s = Struct('I2H8B')
if len(args) == 1 and isinstance(args[0], bytes) and len(args[0]) == 16:
b = args[0]
guid = s.pack((int(b[3]) << 24) | (int(b[2]) << 16) | (int(b[1]) << 8) | b[0],
((int(b[5]) << 8) | b[4]),
(int(b[7]) << 8) | b[6],
*b[8:16]
)
self.guid = s.unpack(guid)
elif len(args) == 4 and all(isinstance(arg, int) for arg in args[0:3]) \
and isinstance(args[3], bytes) and len(args[3]) == 8:
guid = s.pack(args[0], args[1] & MAXINT16, args[2] & MAXINT16, *args[3])
self.guid = s.unpack(guid)
elif len(args) == 2 and all(isinstance(arg, int) for arg in args):
if all((self.bytes_needed(arg) <= 4) for arg in args):
int1, int2 = (args[0] << 32), (args[1] & 0xFFFFFFFF)
id_int = int1 | int2
b = pack('Q8x', id_int)
elif all((self.bytes_needed(arg) <= 8) for arg in args):
b = pack('QQ', args[0], args[1])
else:
raise TypeError('Arguments don\'t match any allowed configuration')
guid = s.pack((int(b[3]) << 24) | (int(b[2]) << 16) | (int(b[1]) << 8) | b[0],
((int(b[5]) << 8) | b[4]),
((int(b[7]) << 8) | b[6]),
*b[8:16]
)
self.guid = s.unpack(guid)
elif len(args) == 1 and isinstance(args[0], int):
if self.bytes_needed(args[0]) <= 8:
b = pack('Q8x', args[0])
else:
first = (args[0] >> 64) & MAXINT64
second = args[0] & MAXINT64
b = pack('QQ', first, second)
guid = s.pack((int(b[3]) << 24) | (int(b[2]) << 16) | (int(b[1]) << 8) | b[0],
((int(b[5]) << 8) | b[4]),
(int(b[7]) << 8) | b[6],
*b[8:16]
)
self.guid = s.unpack(guid)
elif len(args) == 1 and isinstance(args[0], str) and len(args[0]) == 36:
self.parse_string(args[0])
else:
raise TypeError('Arguments don\'t match any allowed configuration')
def parse_string(self, string):
if len(string) == 36:
if string[8] == '-' and string[13] == '-' and string[18] == '-' and string[23] == '-':
guid_str = string.split('-')
try:
self.guid = (int(guid_str[0], 16),
int(guid_str[1], 16),
int(guid_str[2], 16),
int(guid_str[3], 16) >> 8,
int(guid_str[3], 16) & MAXINT8,
int(guid_str[4], 16) >> 40,
int(guid_str[4], 16) >> 32,
int(guid_str[4], 16) >> 24,
int(guid_str[4], 16) >> 16,
int(guid_str[4], 16) >> 8,
int(guid_str[4], 16) & MAXINT8,
)
except ValueError:
raise TypeError('String is not formatted properly must be in format '
'00000000-0000-0000-0000-000000000000')
def to_string(self):
output = ''
output += self.hexs_to_chars(self.guid[0] >> 24, self.guid[0] >> 16)
output += self.hexs_to_chars(self.guid[0] >> 8, self.guid[0])
output += '-'
output += self.hexs_to_chars(self.guid[1] >> 8, self.guid[1])
output += '-'
output += self.hexs_to_chars(self.guid[2] >> 8, self.guid[2])
output += '-'
output += self.hexs_to_chars(self.guid[3], self.guid[4])
output += '-'
output += self.hexs_to_chars(self.guid[5], self.guid[6])
output += self.hexs_to_chars(self.guid[7], self.guid[8])
output += self.hexs_to_chars(self.guid[9], self.guid[10])
return output
def to_byte_array(self):
b = bytearray(16)
b[0] = self.guid[0] & MAXINT8
b[1] = (self.guid[0] >> 8) & MAXINT8
b[2] = (self.guid[0] >> 16) & MAXINT8
b[3] = (self.guid[0] >> 24) & MAXINT8
b[4] = self.guid[1] & MAXINT8
b[5] = (self.guid[1] >> 8) & MAXINT8
b[6] = self.guid[2] & MAXINT8
b[7] = (self.guid[2] >> 8) & MAXINT8
b[8] = self.guid[3] & MAXINT8
b[9] = self.guid[4] & MAXINT8
b[10] = self.guid[5] & MAXINT8
b[11] = self.guid[6] & MAXINT8
b[12] = self.guid[7] & MAXINT8
b[13] = self.guid[8] & MAXINT8
b[14] = self.guid[9] & MAXINT8
b[15] = self.guid[10] & MAXINT8
return b
def hexs_to_chars(self, _a, _b):
out = f'{self.hex_to_char(_a>>4)}{self.hex_to_char(_a)}{self.hex_to_char(_b>>4)}{self.hex_to_char(_b)}'
return out
@staticmethod
def bytes_needed(a):
if a == 0:
return 1
return int(log(a, 256)) + 1
@staticmethod
def hex_to_char(a: int):
a = a & 0xf
out = chr(a - 10 + 0x61 if a > 9 else a + 0x30)
return out
def __hash__(self):
t = self.guid
return t[0] ^ ((int(t[1]) << 16) | int(t[2])) ^ ((int(t[5]) << 24) | t[10])
def __eq__(self, other):
if other is None or not isinstance(other, Guid):
return False
if other.guid[0] != self.guid[0]:
return False
if other.guid[1] != self.guid[1]:
return False
if other.guid[2] != self.guid[2]:
return False
if other.guid[3] != self.guid[3]:
return False
if other.guid[4] != self.guid[4]:
return False
if other.guid[5] != self.guid[5]:
return False
if other.guid[6] != self.guid[6]:
return False
if other.guid[7] != self.guid[7]:
return False
if other.guid[8] != self.guid[8]:
return False
if other.guid[9] != self.guid[9]:
return False
if other.guid[10] != self.guid[10]:
return False
return True
def __bool__(self):
return not (self.guid == (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
def __str__(self):
return self.to_string()
def __repr__(self):
return f'<Guid hash={self.__hash__()}>'