Beispiel #1
0
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)
Beispiel #2
0
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)
Beispiel #3
0
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}'.")
Beispiel #4
0
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}'.")
Beispiel #5
0
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)