import hashlib
from itertools import izip_longest
# Big Endian bits
BE_MUL = tuple(2**(7-n) for n in range(8))
def grouper(n, iterable, fillvalue=None):
"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return izip_longest(fillvalue=fillvalue, *args)
class PackageFileBloomFilter(object):
def __init__(self, hash_function_name="sha384", records=None):
super(PackageFileBloomFilter, self).__init__()
self.hash_function_name = hash_function_name
self.hasher = getattr(hashlib, hash_function_name)
self.bloom_pools = tuple(set() for x in self.hasher().digest())
def _item_hashed_value(self, filename):
h = self.hasher()
h.update(filename)
return map(ord, h.digest())
def __contains__(self, item):
return self.check(filename)
def check(self, filename):
hashed_value = self._item_hashed_value(filename)
for bucket, item in zip(self.bloom_pools, hashed_value):
if item not in bucket:
return False
return True
def add(self, filename):
if not isinstance(filename, unicode):
raise ValueError("Filenames must be unicode.")
if self.hasher is None:
raise ValueError("This bloom filter is read-only.")
hashed_value = self._item_hashed_value(filename)
all_in_set = True
for bucket, item in zip(self.bloom_pools, hashed_value):
# Hopefully, one of the buckets doesn't have it set.
all_in_set = all_in_set and item in bucket
bucket.add(item)
if all_in_set:
warnings.warn("Collision in bloom filter!")
def read(self, mdns_records):
self.hasher = None
self.bloom_pools = tuple(set() for x in self.hasher.digest())
def publish_to_avahi_service_file(self, destination_file, port_number):
MDNS_HEADER = """%h APT cache_apt-http._tcp{port_number:d}version=1,{hash_function_name!s}-bloom"""
MDNS_FOOTER = """"""
MDNS_RECORD_FORMAT = "octet-{pool_number:03x}={pool_str}"
hash_function_name = self.hash_function_name # For formatting.
with open(destination_file, "w") as f:
f.write(MDNS_HEADER.format(**locals()))
for pool_number, pool in enumerate(self.bloom_pools):
chars = list()
inside = lambda i: i in pool
for positions in grouper(8, range(256)):
bits = [(1 if inside(x) else 0) for x in positions]
chars.append(chr(sum((x*e) for e, x in zip(BE_MUL, bits))))
pool_str = "".join(chars).encode("base64").strip()
f.write(MDNS_RECORD_FORMAT.format(**locals()))
f.write(MDNS_FOOTER)
import os
pfbf = PackageFileBloomFilter()
for filename in os.listdir("/var/cache/apt/archives/"):
pfbf.add(filename.decode("utf8"))
pfbf.publish_to_avahi_service_file("/var/tmp/avahi-apt-http.service", 5000)