class TestPacketList(unittest.TestCase): def setUp(self): self.contact_list = ContactList(nicks=['Alice', 'Bob']) self.settings = Settings() self.onion_pub_key = nick_to_pub_key('Alice') packet = Packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, MESSAGE, self.contact_list.get_contact_by_address_or_nick('Alice'), self.settings) self.packet_list = PacketList(self.settings, self.contact_list) self.packet_list.packets = [packet] def test_packet_list_iterates_over_contact_objects(self): for p in self.packet_list: self.assertIsInstance(p, Packet) def test_len_returns_number_of_contacts(self): self.assertEqual(len(self.packet_list), 1) def test_has_packet(self): self.assertTrue(self.packet_list.has_packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, MESSAGE)) self.assertFalse(self.packet_list.has_packet(self.onion_pub_key, ORIGIN_USER_HEADER, MESSAGE)) def test_get_packet(self): packet = self.packet_list.get_packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, MESSAGE) self.assertEqual(packet.onion_pub_key, self.onion_pub_key) self.assertEqual(packet.origin, ORIGIN_CONTACT_HEADER) self.assertEqual(packet.type, MESSAGE) packet = self.packet_list.get_packet(self.onion_pub_key, ORIGIN_CONTACT_HEADER, MESSAGE) self.assertEqual(packet.onion_pub_key, self.onion_pub_key) self.assertEqual(packet.origin, ORIGIN_CONTACT_HEADER) self.assertEqual(packet.type, MESSAGE)
def access_logs(window: Union['TxWindow', 'RxWindow'], contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', master_key: 'MasterKey', msg_to_load: int = 0, export: bool = False) -> None: """\ Load 'msg_to_load' last messages from log database and display or export them. The default value of zero for `msg_to_load` means all messages for the window will be retrieved from the log database. """ file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs' packet_list = PacketList(settings, contact_list) message_list = [] # type: List[MsgTuple] group_msg_id = b'' check_log_file_exists(file_name) message_log = MessageLog(file_name, master_key.master_key) for log_entry in message_log: onion_pub_key, timestamp, origin, assembly_packet \ = separate_headers(log_entry, [ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH, ORIGIN_HEADER_LENGTH]) if window.type == WIN_TYPE_CONTACT and onion_pub_key != window.uid: continue packet = packet_list.get_packet(onion_pub_key, origin, MESSAGE, log_access=True) try: packet.add_packet(assembly_packet) except SoftError: continue if not packet.is_complete: continue group_msg_id = add_complete_message_to_message_list( timestamp, onion_pub_key, group_msg_id, packet, message_list, window) message_log.close_database() print_logs(message_list[-msg_to_load:], export, msg_to_load, window, contact_list, group_list, settings)
def remove_logs(contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', master_key: 'MasterKey', selector: bytes) -> None: """\ Remove log entries for selector (public key of an account/group ID). If the selector is a public key, all messages (both the private conversation and any associated group messages) sent to and received from the associated contact are removed. If the selector is a group ID, only messages for the group matching that group ID are removed. """ ensure_dir(DIR_USER_DATA) file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs' temp_name = file_name + TEMP_SUFFIX packet_list = PacketList(settings, contact_list) entries_to_keep = [] # type: List[bytes] removed = False contact = len(selector) == ONION_SERVICE_PUBLIC_KEY_LENGTH check_log_file_exists(file_name) message_log = MessageLog(file_name, master_key.master_key) for log_entry in message_log: onion_pub_key, _, origin, assembly_packet = separate_headers( log_entry, [ ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH, ORIGIN_HEADER_LENGTH ]) if contact: if onion_pub_key == selector: removed = True else: entries_to_keep.append(log_entry) else: # Group packet = packet_list.get_packet(onion_pub_key, origin, MESSAGE, log_access=True) try: packet.add_packet(assembly_packet, log_entry) except SoftError: continue if not packet.is_complete: continue removed = check_packet_fate(entries_to_keep, packet, removed, selector) message_log.close_database() message_log_temp = MessageLog(temp_name, master_key.master_key) for log_entry in entries_to_keep: message_log_temp.insert_log_entry(log_entry) message_log_temp.close_database() os.replace(temp_name, file_name) try: name = contact_list.get_nick_by_pub_key( selector) if contact else group_list.get_group_by_id(selector).name except StopIteration: name = pub_key_to_short_address(selector) if contact else b58encode( selector) action = "Removed" if removed else "Found no" win_type = "contact" if contact else "group" raise SoftError(f"{action} log entries for {win_type} '{name}'.")
def remove_logs(contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', master_key: 'MasterKey', selector: bytes) -> None: """\ Remove log entries for selector (public key of an account/group ID). If the selector is a public key, all messages (both the private conversation and any associated group messages) sent to and received from the associated contact are removed. If the selector is a group ID, only messages for group determined by that group ID are removed. """ ensure_dir(DIR_USER_DATA) file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs' temp_name = f'{file_name}_temp' log_file = get_logfile(file_name) packet_list = PacketList(settings, contact_list) ct_to_keep = [] # type: List[bytes] removed = False contact = len(selector) == ONION_SERVICE_PUBLIC_KEY_LENGTH for ct in iter(lambda: log_file.read(LOG_ENTRY_LENGTH), b''): plaintext = auth_and_decrypt(ct, master_key.master_key, database=file_name) onion_pub_key, _, origin, assembly_packet = separate_headers( plaintext, [ ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH, ORIGIN_HEADER_LENGTH ]) if contact: if onion_pub_key == selector: removed = True else: ct_to_keep.append(ct) else: # Group packet = packet_list.get_packet(onion_pub_key, origin, MESSAGE, log_access=True) try: packet.add_packet(assembly_packet, ct) except FunctionReturn: continue if not packet.is_complete: continue _, header, message = separate_headers( packet.assemble_message_packet(), [WHISPER_FIELD_LENGTH, MESSAGE_HEADER_LENGTH]) if header == PRIVATE_MESSAGE_HEADER: ct_to_keep.extend(packet.log_ct_list) packet.clear_assembly_packets() elif header == GROUP_MESSAGE_HEADER: group_id, _ = separate_header(message, GROUP_ID_LENGTH) if group_id == selector: removed = True else: ct_to_keep.extend(packet.log_ct_list) packet.clear_assembly_packets() log_file.close() if os.path.isfile(temp_name): os.remove(temp_name) with open(temp_name, 'wb+') as f: if ct_to_keep: f.write(b''.join(ct_to_keep)) os.remove(file_name) os.rename(temp_name, file_name) try: name = contact_list.get_contact_by_pub_key(selector).nick \ if contact else group_list.get_group_by_id(selector).name except StopIteration: name = pub_key_to_short_address(selector) \ if contact else b58encode(selector) action = "Removed" if removed else "Found no" win_type = "contact" if contact else "group" raise FunctionReturn(f"{action} log entries for {win_type} '{name}'.")
def access_logs(window: Union['TxWindow', 'RxWindow'], contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', master_key: 'MasterKey', msg_to_load: int = 0, export: bool = False) -> None: """\ Load 'msg_to_load' last messages from log database and display or export them. The default value of zero for `msg_to_load` means all messages for the window will be retrieved from the log database. """ file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs' log_file = get_logfile(file_name) packet_list = PacketList(settings, contact_list) message_log = [] # type: List[MsgTuple] group_msg_id = b'' for ct in iter(lambda: log_file.read(LOG_ENTRY_LENGTH), b''): plaintext = auth_and_decrypt(ct, master_key.master_key, database=file_name) onion_pub_key, timestamp, origin, assembly_packet = separate_headers( plaintext, [ ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH, ORIGIN_HEADER_LENGTH ]) if window.type == WIN_TYPE_CONTACT and onion_pub_key != window.uid: continue packet = packet_list.get_packet(onion_pub_key, origin, MESSAGE, log_access=True) try: packet.add_packet(assembly_packet) except FunctionReturn: continue if not packet.is_complete: continue whisper_byte, header, message = separate_headers( packet.assemble_message_packet(), [WHISPER_FIELD_LENGTH, MESSAGE_HEADER_LENGTH]) whisper = bytes_to_bool(whisper_byte) if header == PRIVATE_MESSAGE_HEADER and window.type == WIN_TYPE_CONTACT: message_log.append( (bytes_to_timestamp(timestamp), message.decode(), onion_pub_key, packet.origin, whisper, False)) elif header == GROUP_MESSAGE_HEADER and window.type == WIN_TYPE_GROUP: purp_group_id, message = separate_header(message, GROUP_ID_LENGTH) if window.group is not None and purp_group_id != window.group.group_id: continue purp_msg_id, message = separate_header(message, GROUP_MSG_ID_LENGTH) if packet.origin == ORIGIN_USER_HEADER: if purp_msg_id == group_msg_id: continue group_msg_id = purp_msg_id message_log.append( (bytes_to_timestamp(timestamp), message.decode(), onion_pub_key, packet.origin, whisper, False)) log_file.close() print_logs(message_log[-msg_to_load:], export, msg_to_load, window, contact_list, group_list, settings)