549 lines
24 KiB
Python
Executable File
549 lines
24 KiB
Python
Executable File
#!/usr/bin/env python3.7
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
TFC - Onion-routed, endpoint secure messaging system
|
|
Copyright (C) 2013-2020 Markus Ottela
|
|
|
|
This file is part of TFC.
|
|
|
|
TFC is free software: you can redistribute it and/or modify it under the terms
|
|
of the GNU General Public License as published by the Free Software Foundation,
|
|
either version 3 of the License, or (at your option) any later version.
|
|
|
|
TFC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with TFC. If not, see <https://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import base64
|
|
import os
|
|
import typing
|
|
import zlib
|
|
|
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
|
|
from src.common.crypto import blake2b, byte_padding, csprng, encrypt_and_sign
|
|
from src.common.encoding import bool_to_bytes, int_to_bytes, str_to_bytes
|
|
from src.common.exceptions import CriticalError, SoftError
|
|
from src.common.input import yes
|
|
from src.common.misc import split_byte_string
|
|
from src.common.output import m_print, phase, print_on_previous_line
|
|
from src.common.path import ask_path_gui
|
|
from src.common.statics import (ASSEMBLY_PACKET_LENGTH, COMMAND, COMMAND_DATAGRAM_HEADER, COMMAND_PACKET_QUEUE,
|
|
COMPRESSION_LEVEL, C_A_HEADER, C_E_HEADER, C_L_HEADER, C_S_HEADER, DONE, FILE,
|
|
FILE_DATAGRAM_HEADER, FILE_KEY_HEADER, FILE_PACKET_CTR_LENGTH, F_A_HEADER,
|
|
F_C_HEADER, F_E_HEADER, F_L_HEADER, F_S_HEADER, GROUP_MESSAGE_HEADER,
|
|
GROUP_MSG_ID_LENGTH, LOCAL_PUBKEY, MESSAGE, MESSAGE_DATAGRAM_HEADER,
|
|
MESSAGE_PACKET_QUEUE, M_A_HEADER, M_C_HEADER, M_E_HEADER, M_L_HEADER, M_S_HEADER,
|
|
PADDING_LENGTH, PRIVATE_MESSAGE_HEADER, RELAY_PACKET_QUEUE, TM_COMMAND_PACKET_QUEUE,
|
|
TM_FILE_PACKET_QUEUE, TM_MESSAGE_PACKET_QUEUE, WIN_TYPE_GROUP)
|
|
|
|
from src.transmitter.files import File
|
|
from src.transmitter.window_mock import MockWindow
|
|
from src.transmitter.user_input import UserInput
|
|
|
|
if typing.TYPE_CHECKING:
|
|
from multiprocessing import Queue
|
|
from src.common.db_keys import KeyList
|
|
from src.common.db_masterkey import MasterKey
|
|
from src.common.db_settings import Settings
|
|
from src.common.gateway import Gateway
|
|
from src.transmitter.windows import TxWindow
|
|
QueueDict = Dict[bytes, Queue[Any]]
|
|
log_queue_data = Tuple[Optional[bytes], bytes, Optional[bool], Optional[bool], MasterKey]
|
|
|
|
|
|
def queue_to_nc(packet: bytes,
|
|
nc_queue: 'Queue[bytes]',
|
|
) -> None:
|
|
"""Queue unencrypted command/exported file to Networked Computer.
|
|
|
|
This function queues unencrypted packets intended for Relay Program
|
|
on Networked Computer. These packets are processed in the order of
|
|
priority by the `sender_loop` process of src.transmitter.sender_loop
|
|
module.
|
|
"""
|
|
nc_queue.put(packet)
|
|
|
|
|
|
def queue_command(command: bytes,
|
|
settings: 'Settings',
|
|
queues: 'QueueDict'
|
|
) -> None:
|
|
"""Split command to assembly packets and queue them for sender_loop()."""
|
|
assembly_packets = split_to_assembly_packets(command, COMMAND)
|
|
|
|
queue_assembly_packets(assembly_packets, COMMAND, settings, queues)
|
|
|
|
|
|
def queue_message(user_input: 'UserInput',
|
|
window: Union['MockWindow', 'TxWindow'],
|
|
settings: 'Settings',
|
|
queues: 'QueueDict',
|
|
header: bytes = b'',
|
|
whisper: bool = False,
|
|
log_as_ph: bool = False
|
|
) -> None:
|
|
"""\
|
|
Prepend header to message, split the message into assembly packets,
|
|
and queue the assembly packets.
|
|
|
|
In this function the Transmitter Program adds the headers that allow
|
|
the recipient's Receiver Program to redirect the received message to
|
|
the correct window.
|
|
|
|
Each message packet starts with a 1 byte whisper-header that
|
|
determines whether the packet should be logged by the recipient. For
|
|
private messages no additional information aside the
|
|
PRIVATE_MESSAGE_HEADER -- that informs the Receiver Program to use
|
|
sender's window -- is required.
|
|
|
|
For group messages, the GROUP_MESSAGE_HEADER tells the Receiver
|
|
Program that the header is followed by two additional headers:
|
|
|
|
1) 4-byte Group ID that tells to what group the message was
|
|
intended to. If the Receiver Program has not whitelisted the
|
|
group ID, the group message will be ignored. The group ID
|
|
space was chosen so that the birthday bound is at 65536
|
|
because it's unlikely a user will ever have that many groups.
|
|
|
|
2) 16-byte group message ID. This random ID is not important for
|
|
the recipient. Instead, it is used by the sender's Receiver
|
|
Program to detect what group messages are copies sent to other
|
|
members of the group (these will be ignored from ephemeral and
|
|
persistent message log). The message ID space was chosen so
|
|
that the birthday bound is 2^64 (the same as the hash ratchet
|
|
counter space).
|
|
|
|
Once the headers are determined, the message is split into assembly
|
|
packets, that are then queued for encryption and transmission by the
|
|
`sender_loop` process.
|
|
"""
|
|
if not header:
|
|
if window.type == WIN_TYPE_GROUP and window.group is not None:
|
|
header = GROUP_MESSAGE_HEADER + window.group.group_id + os.urandom(GROUP_MSG_ID_LENGTH)
|
|
else:
|
|
header = PRIVATE_MESSAGE_HEADER
|
|
|
|
payload = bool_to_bytes(whisper) + header + user_input.plaintext.encode()
|
|
assembly_packets = split_to_assembly_packets(payload, MESSAGE)
|
|
|
|
queue_assembly_packets(assembly_packets, MESSAGE, settings, queues, window, log_as_ph)
|
|
|
|
|
|
def queue_file(window: 'TxWindow',
|
|
settings: 'Settings',
|
|
queues: 'QueueDict'
|
|
) -> None:
|
|
"""Ask file path and load file data.
|
|
|
|
In TFC there are two ways to send a file.
|
|
|
|
For traffic masking, the file is loaded and sent inside normal
|
|
messages using assembly packet headers dedicated for file
|
|
transmission. This transmission is much slower, so the File object
|
|
will determine metadata about the transmission's estimated transfer
|
|
time, number of packets and the name and size of file. This
|
|
information is inserted to the first assembly packet so that the
|
|
recipient can observe the transmission progress from file transfer
|
|
window.
|
|
|
|
When traffic masking is disabled, file transmission is much faster
|
|
as the file is only encrypted and transferred over serial once
|
|
before the Relay Program multi-casts the ciphertext to each
|
|
specified recipient. See the send_file docstring (below) for more
|
|
details.
|
|
"""
|
|
path = ask_path_gui("Select file to send...", settings, get_file=True)
|
|
|
|
if path.endswith(('tx_contacts', 'tx_groups', 'tx_keys', 'tx_login_data', 'tx_settings',
|
|
'rx_contacts', 'rx_groups', 'rx_keys', 'rx_login_data', 'rx_settings',
|
|
'tx_serial_settings.json', 'nc_serial_settings.json',
|
|
'rx_serial_settings.json', 'tx_onion_db')):
|
|
raise SoftError("Error: Can't send TFC database.", head_clear=True)
|
|
|
|
if not settings.traffic_masking:
|
|
send_file(path, settings, queues, window)
|
|
return
|
|
|
|
file = File(path, window, settings)
|
|
assembly_packets = split_to_assembly_packets(file.plaintext, FILE)
|
|
|
|
if settings.confirm_sent_files:
|
|
try:
|
|
if not yes(f"Send {file.name.decode()} ({file.size_hr}) to {window.type_print} {window.name} "
|
|
f"({len(assembly_packets)} packets, time: {file.time_hr})?"):
|
|
raise SoftError("File selection aborted.", head_clear=True)
|
|
except (EOFError, KeyboardInterrupt):
|
|
raise SoftError("File selection aborted.", head_clear=True)
|
|
|
|
queue_assembly_packets(assembly_packets, FILE, settings, queues, window, log_as_ph=True)
|
|
|
|
|
|
def send_file(path: str,
|
|
settings: 'Settings',
|
|
queues: 'QueueDict',
|
|
window: 'TxWindow'
|
|
) -> None:
|
|
"""Send file to window members in a single transmission.
|
|
|
|
This is the default mode for file transmission, used when traffic
|
|
masking is not enabled. The file is loaded and compressed before it
|
|
is encrypted. The encrypted file is then exported to Networked
|
|
Computer along with a list of Onion Service public keys (members in
|
|
window) of all recipients to whom the Relay Program will multi-cast
|
|
the file to.
|
|
|
|
Once the file ciphertext has been exported, this function will
|
|
multi-cast the file decryption key to each recipient inside an
|
|
automated key delivery message that uses a special FILE_KEY_HEADER
|
|
in place of standard PRIVATE_MESSAGE_HEADER. To know for which file
|
|
ciphertext the key is for, an identifier must be added to the key
|
|
delivery message. The identifier in this case is the BLAKE2b digest
|
|
of the ciphertext itself. The reason of using the digest as the
|
|
identifier is, it authenticates both the ciphertext and its origin.
|
|
To understand this, consider the following attack scenario:
|
|
|
|
Let the file ciphertext identifier be just a random 32-byte value "ID".
|
|
|
|
1) Alice sends Bob and Chuck (a malicious common peer) a file
|
|
ciphertext and identifier CT|ID (where | denotes concatenation).
|
|
|
|
2) Chuck who has compromised Bob's Networked Computer interdicts the
|
|
CT|ID from Alice.
|
|
|
|
3) Chuck decrypts CT in his end, makes edits to the plaintext PT to
|
|
create PT'.
|
|
|
|
4) Chuck re-encrypts PT' with the same symmetric key to produce CT'.
|
|
|
|
5) Chuck re-uses the ID and produces CT'|ID.
|
|
|
|
6) Chuck uploads the CT'|ID to Bob's Networked Computer and replaces
|
|
the interdicted CT|ID with it.
|
|
|
|
7) When Bob' Receiver Program receives the automated key delivery
|
|
message from Alice, his Receiver program uses the bundled ID to
|
|
identify the key is for CT'.
|
|
|
|
8) Bob's Receiver decrypts CT' using the newly received key and
|
|
obtains Chuck's PT', that appears to come from Alice.
|
|
|
|
Now, consider a situation where the ID is instead calculated
|
|
ID = BLAKE2b(CT), if Chuck edits the PT, the CT' will by definition
|
|
be different from CT, and the BLAKE2b digest will also be different.
|
|
In order to make Bob decrypt CT', Chuck needs to also change the
|
|
hash in Alice's key delivery message, which means Chuck needs to
|
|
create an existential forgery of the TFC message. Since the Poly1305
|
|
tag prevents this, the calculated ID is enough to authenticate the
|
|
ciphertext.
|
|
|
|
If Chuck attempts to send their own key delivery message, Chuck's
|
|
own Onion Service public key used to identify the TFC message key
|
|
(decryption key for the key delivery message) will be permanently
|
|
associated with the file hash, so if they inject a file CT, and Bob
|
|
has decided to enable file reception for Chuck, the file CT will
|
|
appear to come from Chuck, and not from Alice. From the perspective
|
|
of Bob, it's as if Chuck had dropped Alice's file and sent him
|
|
another file instead.
|
|
"""
|
|
if settings.traffic_masking:
|
|
raise SoftError("Error: Command is disabled during traffic masking.", head_clear=True)
|
|
|
|
name = path.split('/')[-1]
|
|
data = bytearray()
|
|
data.extend(str_to_bytes(name))
|
|
|
|
if not os.path.isfile(path):
|
|
raise SoftError("Error: File not found.", head_clear=True)
|
|
|
|
if os.path.getsize(path) == 0:
|
|
raise SoftError("Error: Target file is empty.", head_clear=True)
|
|
|
|
phase("Reading data")
|
|
with open(path, 'rb') as f:
|
|
data.extend(f.read())
|
|
phase(DONE)
|
|
print_on_previous_line(flush=True)
|
|
|
|
phase("Compressing data")
|
|
comp = bytes(zlib.compress(bytes(data), level=COMPRESSION_LEVEL))
|
|
phase(DONE)
|
|
print_on_previous_line(flush=True)
|
|
|
|
phase("Encrypting data")
|
|
file_key = csprng()
|
|
file_ct = encrypt_and_sign(comp, file_key)
|
|
ct_hash = blake2b(file_ct)
|
|
phase(DONE)
|
|
print_on_previous_line(flush=True)
|
|
|
|
phase("Exporting data")
|
|
no_contacts = int_to_bytes(len(window))
|
|
ser_contacts = b''.join([c.onion_pub_key for c in window])
|
|
file_packet = FILE_DATAGRAM_HEADER + no_contacts + ser_contacts + file_ct
|
|
queue_to_nc(file_packet, queues[RELAY_PACKET_QUEUE])
|
|
|
|
key_delivery_msg = base64.b85encode(ct_hash + file_key).decode()
|
|
for contact in window:
|
|
queue_message(user_input=UserInput(key_delivery_msg, MESSAGE),
|
|
window =MockWindow(contact.onion_pub_key, [contact]),
|
|
settings =settings,
|
|
queues =queues,
|
|
header =FILE_KEY_HEADER,
|
|
log_as_ph =True)
|
|
phase(DONE)
|
|
print_on_previous_line(flush=True)
|
|
m_print(f"Sent file '{name}' to {window.type_print} {window.name}.")
|
|
|
|
|
|
def split_to_assembly_packets(payload: bytes, p_type: str) -> List[bytes]:
|
|
"""Split payload to assembly packets.
|
|
|
|
Messages and commands are compressed to reduce transmission time.
|
|
Files directed to this function during traffic masking have been
|
|
compressed at an earlier point.
|
|
|
|
If the compressed message cannot be sent over one packet, it is
|
|
split into multiple assembly packets. Long messages are encrypted
|
|
with an inner layer of XChaCha20-Poly1305 to provide sender based
|
|
control over partially transmitted data. Regardless of packet size,
|
|
files always have an inner layer of encryption, and it is added
|
|
before the file data is passed to this function. Commands do not
|
|
need sender-based control, so they are only delivered with a hash
|
|
that makes integrity check easy.
|
|
|
|
First assembly packet in file transmission is prepended with an
|
|
8-byte packet counter header that tells the sender and receiver how
|
|
many packets the file transmission requires.
|
|
|
|
Each assembly packet is prepended with a header that tells the
|
|
Receiver Program if the packet is a short (single packet)
|
|
transmission or if it's the start packet, a continuation packet, or
|
|
the last packet of a multi-packet transmission.
|
|
"""
|
|
s_header = {MESSAGE: M_S_HEADER, FILE: F_S_HEADER, COMMAND: C_S_HEADER}[p_type]
|
|
l_header = {MESSAGE: M_L_HEADER, FILE: F_L_HEADER, COMMAND: C_L_HEADER}[p_type]
|
|
a_header = {MESSAGE: M_A_HEADER, FILE: F_A_HEADER, COMMAND: C_A_HEADER}[p_type]
|
|
e_header = {MESSAGE: M_E_HEADER, FILE: F_E_HEADER, COMMAND: C_E_HEADER}[p_type]
|
|
|
|
if p_type in [MESSAGE, COMMAND]:
|
|
payload = zlib.compress(payload, level=COMPRESSION_LEVEL)
|
|
|
|
if len(payload) < PADDING_LENGTH:
|
|
padded = byte_padding(payload)
|
|
packet_list = [s_header + padded]
|
|
|
|
else:
|
|
if p_type == MESSAGE:
|
|
msg_key = csprng()
|
|
payload = encrypt_and_sign(payload, msg_key)
|
|
payload += msg_key
|
|
|
|
elif p_type == FILE:
|
|
payload = bytes(FILE_PACKET_CTR_LENGTH) + payload
|
|
|
|
elif p_type == COMMAND:
|
|
payload += blake2b(payload)
|
|
|
|
padded = byte_padding(payload)
|
|
p_list = split_byte_string(padded, item_len=PADDING_LENGTH)
|
|
|
|
if p_type == FILE:
|
|
p_list[0] = int_to_bytes(len(p_list)) + p_list[0][FILE_PACKET_CTR_LENGTH:]
|
|
|
|
packet_list = ([l_header + p_list[0]] +
|
|
[a_header + p for p in p_list[1:-1]] +
|
|
[e_header + p_list[-1]])
|
|
|
|
return packet_list
|
|
|
|
|
|
def queue_assembly_packets(assembly_packet_list: List[bytes],
|
|
p_type: str,
|
|
settings: 'Settings',
|
|
queues: 'QueueDict',
|
|
window: Optional[Union['TxWindow', 'MockWindow']] = None,
|
|
log_as_ph: bool = False
|
|
) -> None:
|
|
"""Queue assembly packets for sender_loop().
|
|
|
|
This function is the last function on Transmitter Program's
|
|
`input_loop` process. It feeds the assembly packets to
|
|
multiprocessing queues along with metadata required for transmission
|
|
and message logging. The data put into these queues is read by the
|
|
`sender_loop` process in src.transmitter.sender_loop module.
|
|
"""
|
|
if p_type in [MESSAGE, FILE] and window is not None:
|
|
|
|
if settings.traffic_masking:
|
|
queue = queues[TM_MESSAGE_PACKET_QUEUE] if p_type == MESSAGE else queues[TM_FILE_PACKET_QUEUE]
|
|
for assembly_packet in assembly_packet_list:
|
|
queue.put((assembly_packet, window.log_messages, log_as_ph))
|
|
else:
|
|
queue = queues[MESSAGE_PACKET_QUEUE]
|
|
for c in window:
|
|
for assembly_packet in assembly_packet_list:
|
|
queue.put((assembly_packet, c.onion_pub_key, window.log_messages, log_as_ph, window.uid))
|
|
|
|
elif p_type == COMMAND:
|
|
queue = queues[TM_COMMAND_PACKET_QUEUE] if settings.traffic_masking else queues[COMMAND_PACKET_QUEUE]
|
|
for assembly_packet in assembly_packet_list:
|
|
queue.put(assembly_packet)
|
|
|
|
|
|
def send_packet(key_list: 'KeyList', # Key list object
|
|
gateway: 'Gateway', # Gateway object
|
|
log_queue: 'Queue[log_queue_data]', # Multiprocessing queue for logged messages
|
|
assembly_packet: bytes, # Padded plaintext assembly packet
|
|
onion_pub_key: Optional[bytes] = None, # Recipient v3 Onion Service address
|
|
log_messages: Optional[bool] = None, # When True, log the message assembly packet
|
|
log_as_ph: Optional[bool] = None # When True, log assembly packet as placeholder data
|
|
) -> None:
|
|
"""Encrypt and send assembly packet.
|
|
|
|
The assembly packets are encrypted using a symmetric message key.
|
|
TFC provides forward secrecy via a hash ratchet, meaning previous
|
|
message key is replaced by it's BLAKE2b hash. The preimage
|
|
resistance of the hash function prevents retrospective decryption of
|
|
ciphertexts in cases of physical compromise.
|
|
|
|
The hash ratchet state (the number of times initial message key has
|
|
been passed through BLAKE2b) is delivered to recipient inside the
|
|
hash ratchet counter. This counter is encrypted with a static
|
|
symmetric key called the header key.
|
|
|
|
The encrypted assembly packet and encrypted harac are prepended with
|
|
datagram headers that tell if the encrypted assembly packet is a
|
|
command or a message. Packets with MESSAGE_DATAGRAM_HEADER also
|
|
contain a second header, which is the public key of the recipient's
|
|
Onion Service. This allows the ciphertext to be requested from Relay
|
|
Program's server by the correct contact.
|
|
|
|
Once the encrypted_packet has been output, the hash ratchet advances
|
|
to the next state, and the assembly packet is pushed to log_queue,
|
|
which is read by the `log_writer_loop` process (that can be found
|
|
at src.common.db_logs). This approach prevents IO delays caused by
|
|
`input_loop` reading the log file from affecting the `sender_loop`
|
|
process, which could reveal schedule information under traffic
|
|
masking mode.
|
|
"""
|
|
if len(assembly_packet) != ASSEMBLY_PACKET_LENGTH:
|
|
raise CriticalError("Invalid assembly packet PT length.")
|
|
|
|
if onion_pub_key is None:
|
|
keyset = key_list.get_keyset(LOCAL_PUBKEY)
|
|
header = COMMAND_DATAGRAM_HEADER
|
|
else:
|
|
keyset = key_list.get_keyset(onion_pub_key)
|
|
header = MESSAGE_DATAGRAM_HEADER + onion_pub_key
|
|
|
|
harac_in_bytes = int_to_bytes(keyset.tx_harac)
|
|
encrypted_harac = encrypt_and_sign(harac_in_bytes, keyset.tx_hk)
|
|
encrypted_message = encrypt_and_sign(assembly_packet, keyset.tx_mk)
|
|
encrypted_packet = header + encrypted_harac + encrypted_message
|
|
gateway.write(encrypted_packet)
|
|
|
|
keyset.rotate_tx_mk()
|
|
|
|
log_queue.put((onion_pub_key, assembly_packet, log_messages, log_as_ph, key_list.master_key))
|
|
|
|
|
|
def cancel_packet(user_input: 'UserInput',
|
|
window: 'TxWindow',
|
|
settings: 'Settings',
|
|
queues: 'QueueDict'
|
|
) -> None:
|
|
"""Cancel sent message/file to contact/group.
|
|
|
|
In cases where the assembly packets have not yet been encrypted or
|
|
output to Networked Computer, the queued messages or files to active
|
|
window can be cancelled. Any single-packet message and file this
|
|
function removes from the queue/transfer buffer are unavailable to
|
|
recipient. However, in the case of multi-packet transmissions, if
|
|
only the last assembly packet is cancelled, the recipient might
|
|
obtain large enough section of the key that protects the inner
|
|
encryption layer to allow them to brute force the rest of the key,
|
|
and thus, decryption of the packet. There is simply no way to
|
|
prevent this kind of attack without making TFC proprietary and
|
|
re-writing it in a compiled language (which is very bad for users'
|
|
rights).
|
|
"""
|
|
header, p_type = dict(cm=(M_C_HEADER, 'messages'),
|
|
cf=(F_C_HEADER, 'files' ))[user_input.plaintext]
|
|
|
|
if settings.traffic_masking:
|
|
queue = queues[TM_MESSAGE_PACKET_QUEUE] if header == M_C_HEADER else queues[TM_FILE_PACKET_QUEUE]
|
|
else:
|
|
if header == F_C_HEADER:
|
|
raise SoftError("Files are only queued during traffic masking.", head_clear=True)
|
|
queue = queues[MESSAGE_PACKET_QUEUE]
|
|
|
|
cancel_pt = header + bytes(PADDING_LENGTH)
|
|
log_as_ph = False # Never log cancel assembly packets as placeholder data
|
|
|
|
cancel = False
|
|
if settings.traffic_masking:
|
|
cancel_traffic_masking_packet(cancel, cancel_pt, log_as_ph, p_type, queue)
|
|
|
|
else:
|
|
cancel_standard_packet(cancel, cancel_pt, log_as_ph, p_type, queue, window)
|
|
|
|
|
|
def cancel_standard_packet(cancel: bool,
|
|
cancel_pt: bytes,
|
|
log_as_ph: bool,
|
|
p_type: str,
|
|
queue: 'Queue[Any]',
|
|
window: 'TxWindow'
|
|
) -> None:
|
|
"""Cancel standard packet."""
|
|
p_buffer = []
|
|
while queue.qsize():
|
|
queue_data = queue.get()
|
|
window_uid = queue_data[4]
|
|
|
|
# Put messages unrelated to the active window into the buffer
|
|
if window_uid != window.uid:
|
|
p_buffer.append(queue_data)
|
|
else:
|
|
cancel = True
|
|
|
|
# Put cancel packets for each window contact to queue first
|
|
if cancel:
|
|
for c in window:
|
|
queue.put((cancel_pt, c.onion_pub_key, c.log_messages, log_as_ph, window.uid))
|
|
|
|
# Put buffered tuples back to the queue
|
|
for p in p_buffer:
|
|
queue.put(p)
|
|
if cancel:
|
|
message = f"Cancelled queued {p_type} to {window.type_print} {window.name}."
|
|
else:
|
|
message = f"No {p_type} queued for {window.type_print} {window.name}."
|
|
raise SoftError(message, head_clear=True)
|
|
|
|
|
|
def cancel_traffic_masking_packet(cancel: bool,
|
|
cancel_pt: bytes,
|
|
log_as_ph: bool,
|
|
p_type: str,
|
|
queue: 'Queue[Any]'
|
|
) -> None:
|
|
"""Cancel traffic masking packet."""
|
|
if queue.qsize():
|
|
cancel = True
|
|
|
|
# Get most recent log_messages setting status in queue
|
|
log_messages = False
|
|
while queue.qsize():
|
|
log_messages = queue.get()[1]
|
|
|
|
queue.put((cancel_pt, log_messages, log_as_ph))
|
|
|
|
m_print(f"Cancelled queues {p_type}." if cancel else f"No {p_type} to cancel.", head=1, tail=1)
|