# # Copyright (c) Daniel Sheffield 2023 # All rights reserved # # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY from pytest import fixture, mark import app.rest.hash_util as hash_util import base32_lib as b32 from app.rest.hash_util import( hash_to_hex, hash_to_bytes, bytes_to_base32, bytes_to_base64, bytes_to_base85, bytes_to_hash, bytes_to_hex, normalize_bytes, normalize_hash, normalize_hex, base85_to_hash, base85_to_bytes, base85_to_hex, base32_to_bytes, base32_to_hash, base32_to_hex, base64_to_bytes, base64_to_hash, base64_to_hex, hash_to_base32, hash_to_base85, hash_to_base64, hex_to_base32, hex_to_base85, hex_to_base64, hex_to_bytes, hex_to_hash, ) def to_unsigned(_hash: int) -> int: return _hash & hash_util.DIGEST_SIZE_BITMASK def digest_size(): for g in ( two_bytes, three_bytes, four_bytes): yield g def two_bytes(): hash_util.DIGEST_SIZE_BYTES = 2 hash_util.DIGEST_SIZE_NIBBLES = 4 hash_util.DIGEST_SIZE_BITMASK = 0xffff hash_util.DIGEST_SIZE_SIGNED_TO_UNSIGNED_BIT = 0x10000 hash_util.DIGEST_SIZE_SIGNED_TO_UNSIGNED_BITMASK = 0x1ffff return hash_util.DIGEST_SIZE_BYTES def three_bytes(): hash_util.DIGEST_SIZE_BYTES = 3 hash_util.DIGEST_SIZE_NIBBLES = 6 hash_util.DIGEST_SIZE_BITMASK = 0xffffff hash_util.DIGEST_SIZE_SIGNED_TO_UNSIGNED_BIT = 0x1000000 hash_util.DIGEST_SIZE_SIGNED_TO_UNSIGNED_BITMASK = 0x1ffffff return hash_util.DIGEST_SIZE_BYTES def four_bytes(): hash_util.DIGEST_SIZE_BYTES = 4 hash_util.DIGEST_SIZE_NIBBLES = 8 hash_util.DIGEST_SIZE_BITMASK = 0xffffffff hash_util.DIGEST_SIZE_SIGNED_TO_UNSIGNED_BIT = 0x100000000 hash_util.DIGEST_SIZE_SIGNED_TO_UNSIGNED_BITMASK = 0x1ffffffff return hash_util.DIGEST_SIZE_BYTES @fixture def two_byte_bitmask(): return 0xffff def hash_codes(): for _hash in [ -7583489610679606711, 1421958803217889556, 10, ]: _bytes = b'\00' * 8 + _hash.to_bytes(8, byteorder='big', signed=True)[-8:] _hex = _bytes.hex() print(_hash, _hex, _bytes) yield _hash, _hex, _bytes # PYTHONHASHSEED=0 def digest_size_bitmask(digest_size_bits): mask = 0x0 for _ in range(digest_size_bits): mask = mask << 1 | 0x1 return mask def digest_size_force_unsigned_bitmask(digest_size): mask = 0x1 for _ in range(8*digest_size): mask = mask << 1 | 0x1 return mask def test_three_byte_assertions(): digest_size = three_bytes() assert hash_to_base64(-7583489610679606711) == 'k6JJ' assert bytes_to_base64(b'\x96\xc2\x08`\xcd\x93\xa2I') == 'k6JJ' assert hex_to_base64('96c20860cd93a249') == 'k6JJ' assert base64_to_hex('k6JJ') == '93a249' assert base64_to_bytes('aa') == b'\x00\x00i' assert base64_to_hex('aa') == '000069' assert base64_to_hash('aa') == 105 @mark.parametrize('_hash, _hex', [ (_hash, _hex) for _hash, _hex, *_ in hash_codes() ]) @mark.parametrize('digest_size_gen', digest_size()) def test_hash_to_hex(_hash, _hex, digest_size_gen): digest_size = digest_size_gen() assert hash_to_hex(_hash) == _hex[-digest_size*2:] for i in range(1, digest_size*2+1): # test hex is zero padded assert hash_to_hex(_hash & digest_size_bitmask(i*4)) == _hex[-i:].zfill(digest_size*2) @mark.parametrize('_hash, _bytes', [ (_hash, _bytes) for _hash, _, _bytes, *_ in hash_codes() ]) @mark.parametrize('digest_size_gen', digest_size()) def test_hash_to_base32(_hash, _bytes, digest_size_gen): digest_size = digest_size_gen() norm = hash_to_base32(_hash) assert norm == b32.encode( to_unsigned(int.from_bytes( _bytes[-digest_size:], byteorder='big', signed=False) ) ).upper().zfill(digest_size*8//5+1) assert len(norm) == digest_size*8//5 +1 for i in range(1, digest_size+1): norm = hash_to_base32(_hash & digest_size_bitmask(i*8)) # test base32 is zero padded assert norm == b32.encode( to_unsigned(int.from_bytes( (b'\x00'*digest_size + _bytes[-i:])[-digest_size:], byteorder='big', signed=False) ) ).upper().zfill(digest_size*8//5+1) assert len(norm) == digest_size*8//5 +1 @mark.parametrize('_hash, _hex', [ (_hash, _hex) for _hash, _hex, *_ in hash_codes() ]) @mark.parametrize('digest_size_gen', digest_size()) def test_hex_to_hash(_hash, _hex, digest_size_gen): digest_size = digest_size_gen() assert hex_to_hash(_hex) == _hash & digest_size_bitmask(digest_size*8) for i in range(1, digest_size*2+1): # test non-zero padded hex _input = _hex[-i:] assert digest_size*2 == i or len(_input) < digest_size*2 assert hex_to_hash(_input) == _hash & digest_size_bitmask(i*4) # test zero padded hex _input = _hex[-i:].zfill(digest_size*2) assert len(_input) == digest_size*2 assert hex_to_hash(_input) == _hash & digest_size_bitmask(i*4) @mark.parametrize('_hex, _bytes', [ (_hex, _bytes) for _, _hex, _bytes, *_ in hash_codes() ]) @mark.parametrize('digest_size_gen', digest_size()) def test_hex_to_bytes(_hex, _bytes, digest_size_gen): digest_size = digest_size_gen() assert hex_to_bytes(_hex) == _bytes[-digest_size:] for i in range(1, digest_size*2+1): # test non-zero padded hex _input = _hex[-i:] assert digest_size*2 == i or len(_input) < digest_size*2 if i % 2: if i > 2: assert hex_to_bytes(_input) == (b'\x00'*digest_size + bytes([ _bytes[-i//2] & 0x0f, *_bytes[-i//2+1:] ]))[-digest_size:] else: assert hex_to_bytes(_input) == (b'\x00'*digest_size + bytes([ _bytes[-i//2] & 0x0f ]))[-digest_size:] else: assert hex_to_bytes(_input) == (b'\x00'*digest_size + _bytes[-i//2:])[-digest_size:] # test zero padded hex _input = _hex[-i:].zfill(digest_size*2) assert len(_input) == digest_size*2 if i % 2: if i > 2: assert hex_to_bytes(_input) == (b'\x00'*digest_size + bytes([ _bytes[-i//2] & 0x0f, *_bytes[-i//2+1:] ]))[-digest_size:] else: assert hex_to_bytes(_input) == (b'\x00'*digest_size + bytes([ _bytes[-i//2] & 0x0f ]))[-digest_size:] else: assert hex_to_bytes(_input) == (b'\x00'*digest_size + _bytes[-i//2:])[-digest_size:] @mark.parametrize('_hash, _bytes', [ (_hash, _bytes) for _hash, _, _bytes, *_ in hash_codes() ]) @mark.parametrize('digest_size_gen', digest_size()) def test_bytes_to_hash(_hash, _bytes, digest_size_gen): digest_size = digest_size_gen() assert bytes_to_hash(_bytes) == _hash & digest_size_bitmask(digest_size*8) for i in range(1, digest_size+1): # test non-zero padded bytes _input = _bytes[-i:] assert digest_size == i or len(_input) < digest_size assert bytes_to_hash(_input) == _hash & digest_size_bitmask(i*8) # test zero padded bytes _input = (b'\x00'*digest_size + _bytes[-i:])[-digest_size:] assert len(_input) == digest_size assert bytes_to_hash(_input) == _hash & digest_size_bitmask(i*8) @mark.parametrize('_hash, _bytes', [ (_hash, _bytes) for _hash, _, _bytes, *_ in hash_codes() ]) @mark.parametrize('digest_size_gen', digest_size()) def test_base32_to_hash(_hash, _bytes, digest_size_gen): digest_size = digest_size_gen() _base32 = b32.encode( to_unsigned(int.from_bytes( _bytes[-digest_size:], byteorder='big', signed=False) ) ).upper().zfill(digest_size*8//5+1) norm = base32_to_hash(_base32) assert norm == _hash & digest_size_bitmask(digest_size*8) for i in range(1, digest_size+1): # test non-zero padded base32 _base32 = b32.encode( to_unsigned(int.from_bytes( (b'\x00'*digest_size + _bytes[-i:])[-digest_size:], byteorder='big', signed=False) ) ).upper() norm = base32_to_hash(_base32) assert norm == _hash & digest_size_bitmask(i*8) # test zero padded base32 _base32 = b32.encode( to_unsigned(int.from_bytes( (b'\x00'*digest_size + _bytes[-i:])[-digest_size:], byteorder='big', signed=False) ) ).upper().zfill(digest_size*8//5+1) norm = base32_to_hash(_base32) assert norm == _hash & digest_size_bitmask(i*8) @mark.parametrize('_hex', [ _hex for _, _hex, *_ in hash_codes() ]) @mark.parametrize('digest_size_gen', digest_size()) def test_normalize_hex(_hex, digest_size_gen): digest_size = digest_size_gen() assert normalize_hex(_hex) == _hex[-digest_size*2:] assert len(normalize_hex(_hex)) == digest_size*2 # test commutivity for i in range(1, digest_size*2+1): assert i == digest_size*2 or _hex[-i:] != _hex[-i:].zfill(digest_size*2) assert normalize_hex(_hex[-i:]) == _hex[-i:].zfill(digest_size*2) assert len(normalize_hex(_hex[-i:])) == digest_size*2 @mark.parametrize('_bytes', [ _bytes for _, _, _bytes, *_ in hash_codes() ]) @mark.parametrize('digest_size_gen', digest_size()) def test_normalize_bytes(_bytes, digest_size_gen): digest_size = digest_size_gen() assert normalize_bytes(_bytes) == _bytes[-digest_size:] assert len(normalize_bytes(_bytes)) == digest_size # test commutivity for i in range(1, digest_size+1): assert i == digest_size or _bytes[-i:] != (b'\x00'*digest_size + _bytes[-i:])[-digest_size:] assert normalize_bytes(_bytes[-i:]) == (b'\x00'*digest_size + _bytes[-i:])[-digest_size:] assert len(normalize_bytes(_bytes[-i:])) == digest_size @mark.parametrize('_hash', [ _hash for _hash, *_ in hash_codes() ]) @mark.parametrize('digest_size_gen', digest_size()) def test_normalize_hash(_hash, digest_size_gen): digest_size = digest_size_gen() norm = normalize_hash(_hash) assert norm == _hash & digest_size_bitmask(digest_size*8) # test commutivity for i in range(1, digest_size+1): norm = normalize_hash(_hash & digest_size_bitmask(i*8)) assert norm == _hash & digest_size_bitmask(i*8) assert norm == norm & digest_size_bitmask(i*8)