def test_upload_seeds(test_context): (client_db_path, server_db) = test_context # Upload contacts from database client_proc = subprocess.run([ script_path("device/upload_seeds.py"), "-d", "-D", client_db_path, "-s", SERVER_URL, "-v", (START_TIME + timedelta(minutes=2 * EPOCH_LENGTH)).isoformat(), (START_TIME + timedelta(minutes=8 * EPOCH_LENGTH)).isoformat(), ]) assert client_proc.returncode == 0 # See if they arrived in the server database (epochs, seeds) = server_db.get_epoch_seeds_tuple() assert (epoch_from_time(START_TIME + timedelta(minutes=2 * EPOCH_LENGTH)) - 1 not in epochs) assert epoch_from_time(START_TIME + timedelta(minutes=2 * EPOCH_LENGTH)) in epochs assert epoch_from_time(START_TIME + timedelta(minutes=8 * EPOCH_LENGTH)) in epochs assert (epoch_from_time(START_TIME + timedelta(minutes=8 * EPOCH_LENGTH)) + 1 not in epochs) assert bytes.fromhex("deadbeef06") in seeds
def main(): print("## Test vectors computing EphID given a seed ##") for seed_str in [SEED0, SEED1]: seed = bytes.fromhex(seed_str) ephid = ephid_from_seed(seed) print(" - Seed:", seed.hex()) print(" - EphID:", ephid.hex()) print() print("\n## Test vectors computing epoch number ##") for time in [TIME0, TIME1, TIME2]: print(" - Time:", time.isoformat(" ")) print(" - Epoch Number:", epoch_from_time(time)) print() print("\n## Test vector hashed observed EphIDs ##") ephid = ephid_from_seed(bytes.fromhex(SEED1)) for time in [TIME0, TIME1, TIME2]: epoch = epoch_from_time(time) print(" - EphID:", ephid.hex()) print(" - Time:", time.isoformat(" ")) print(" - Epoch:", epoch) print(" - Hashed observation:", hashed_observation_from_ephid(ephid, epoch).hex()) print()
def test_delete_epoch_seeds(db_connection): time_out = datetime(2020, 4, 25, 20, 59, tzinfo=timezone.utc) time_in = datetime(2020, 4, 25, 21, 1, tzinfo=timezone.utc) db_connection.add_epoch_seed(epoch_from_time(time_out), "out") db_connection.add_epoch_seed(epoch_from_time(time_in), "in") all_records = db_connection.get_epoch_seeds() assert sum(1 for _ in all_records) == 2 db_connection.delete_expired_data( datetime(2020, 4, 25, 21, 00, tzinfo=timezone.utc)) all_records = db_connection.get_epoch_seeds() for (epoch, seed) in all_records: assert epoch == epoch_from_time(time_in) assert seed == b"in"
def _create_new_day_ephids(self): """Compute a new set of seeds and ephids for a new day""" # The ephids are required by the transmitter if not self.transmitter: return # Generate fresh seeds and store them seeds = [generate_new_seed() for _ in range(NUM_EPOCHS_PER_DAY)] ephids = [ephid_from_seed(seed) for seed in seeds] # Convert to epoch numbers first_epoch = epoch_from_time(self.start_of_today) with self.db.atomic(): # Verify the ids have not been already been created if self.db.get_epoch_seeds(first_epoch, first_epoch + 1): return # Store seeds and compute EphIDs for relative_epoch in range(0, NUM_EPOCHS_PER_DAY): self.db.add_epoch_ids( first_epoch + relative_epoch, seeds[relative_epoch], ephids[relative_epoch], )
def test_check_advance_day_firing(contact_tracer): with Replace("dp3t.protocols.unlinkable_db.datetime", test_datetime(**START_TIME_TOMORROW)): contact_tracer.check_advance_day(datetime(**START_TIME_TOMORROW)) epoch = epoch_from_time(datetime(**START_TIME_TOMORROW)) seeds = contact_tracer.db.get_epoch_seeds(epoch, epoch + 1) assert len(seeds) == 1
def get_tracing_information(self, first_contagious_time, last_contagious_time=None): """Return the seeds corresponding to the requested time range *Warning:* This function should not be used to retrieve tracing information for future epochs to limit the amount of linking information available to the server. Unfortunately, this class does not have a notion of the exact time, so it is up to the caller to verify this constraint. Args: first_contagious_time (:obj:`datetime.datetime`): The time from which we should start tracing last_contagious_time (:obj:`datatime.datatime`, optional): The last time for tracing. Default value: the beginning of the current day. Returns: epochs: the epochs seeds: the corresponding seeds Raises: ValueError: If the requested key is unavailable or if last_contagious_time is before first_contagious_time """ if last_contagious_time is None: last_contagious_time = self.start_of_today if last_contagious_time < first_contagious_time: raise ValueError( "Last_contagious_time should be after first_contagious_time") start_epoch = epoch_from_time(first_contagious_time) end_epoch = epoch_from_time(last_contagious_time) reported_epochs = range(start_epoch, end_epoch + 1) return reported_epochs, self.get_tracing_seeds_for_epochs( reported_epochs)
def test_context(): # Initialize client database (client_db_handle, client_db_path) = mkstemp() close(client_db_handle) client_db = ClientDatabase(client_db_path) # Add seeds to the client database for i in range(0, 10): e = epoch_from_time(START_TIME + timedelta(minutes=i * EPOCH_LENGTH)) client_db.add_epoch_ids(e, bytes.fromhex(f"deadbeef0{i}"), f"E{i}") close(client_db_handle) # subprocess.call(["/home/dds/src/epidose/utils/client-db-report.sh", client_db_path]) # Instantiate the back-end server (server_db_handle, server_db_path) = mkstemp() close(server_db_handle) server_proc = subprocess.Popen([ script_path("back_end/ha_server.py"), "-d", "-v", "-D", server_db_path ]) # Wait for server to come up count = 0 while True: try: res = requests.get(f"{SERVER_URL}/version") if res.ok: break except requests.exceptions.ConnectionError: pass sleep(0.1) count += 0.1 # Timeout after 5s if count > 5: raise TimeoutError server_db = ServerDatabase(server_db_path) yield (client_db_path, server_db) # Shutdown the server res = requests.get(f"{SERVER_URL}/shutdown") assert res.ok # Wait for the server to finish server_proc.wait() # Cleanup server_db.close() remove(server_db_path) remove(client_db_path)
def get_ephid_for_time(self, time): """Return the EphID corresponding to the requested time Args: time (:obj:`datetime.datetime`): The requested time Raises: ValueError: If the requested ephid is unavailable """ # Convert to epoch number epoch = epoch_from_time(time) ephid = self.db.get_epoch_ephid(epoch) if not ephid: raise ValueError("EphID not available, did you call next_day()?") return ephid
def add_observation(self, ephid, time, rssi=0): """Add ephID to list of observations. Time must correspond to the current day Args: ephID (byte array): the observed ephID time (:obj:`datatime.datetime`): time of observation rssi: the observation's received signal strength indicator Raises: ValueError: If time does not correspond to the current day """ if not time.date() == self.today: raise ValueError("Observation must correspond to current day") epoch = epoch_from_time(time) hashed_observation = hashed_observation_from_ephid(ephid, epoch) self.db.add_observation(day_timestamp(self.today), hashed_observation, rssi)
def next_day(self): """Setup seeds and EphIDs for the next day, and do housekeeping""" # Update current day self.start_of_today = self.start_of_today + timedelta(days=1) # Generate new EphIDs for new day self._create_new_day_ephids() # Remove old observations if self.receiver: last_retained_day = self.today - timedelta(days=RETENTION_PERIOD) self.db.delete_past_observations(day_timestamp(last_retained_day)) # Remove old seeds and ephids if self.transmitter: days_back = timedelta(days=RETENTION_PERIOD) last_valid_time = self.start_of_today - days_back last_retained_epoch = epoch_from_time(last_valid_time) self.db.delete_past_epoch_ids(last_retained_epoch)
def delete_expired_data(self, last_retained_day): """Delete contagious user data that has expired.""" last_retained_epoch = epoch_from_time(last_retained_day) query = ContagiousIds.delete().where( ContagiousIds.epoch < last_retained_epoch) query.execute()
def test_epoch_from_time(): epoch0 = epoch_from_time(TIME0) assert epoch0 == EPOCH0 epoch1 = epoch_from_time(TIME1) assert epoch1 == EPOCH1
def test_check_next_day_empty(contact_tracer): epoch = epoch_from_time(START_TIME + timedelta(days=1)) seeds = contact_tracer.db.get_epoch_seeds(epoch, epoch + 1) assert len(seeds) == 0
def test_seeds_initialized(contact_tracer): epoch = epoch_from_time(START_TIME) seeds = contact_tracer.db.get_epoch_seeds(epoch, epoch + 1) assert len(seeds) == 1
def test_no_seeds_for_receiver(): ct = ContactTracer(start_time=START_TIME, transmitter=False) epoch = epoch_from_time(START_TIME) seeds = ct.db.get_epoch_seeds(epoch, epoch + 1) assert len(seeds) == 0 ct.db.close()