diff --git a/expedite/client/auth.py b/expedite/client/auth.py
index 1ce5c44..a3f443c 100644
--- a/expedite/client/auth.py
+++ b/expedite/client/auth.py
@@ -34,11 +34,26 @@
def derive_code(password: str = standard.client_pswd, salt: bytes = standard.client_salt) -> bytes:
+ """
+ Derive byte convertion from password and salt composition
+
+ :param password: Password provided by the clients
+ :param salt: Byte array for additional protection
+ :return: Cryptography HMAC code
+ """
kdfo = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=default_backend())
return kdfo.derive(password.encode())
def encr_bite(data: bytes = b"", code: bytes = standard.client_code, invc: bytes = standard.client_invc) -> bytes:
+ """
+ Encrypt file chunks using converted cryptography HMAC code
+
+ :param data: Chunks to read, encrypt and deliver
+ :param code: Generated cryptography HMAC code
+ :param invc: Initialization vector for added protection
+ :return: Encrypted bytes
+ """
cipher = Cipher(algorithms.AES(code), modes.CBC(invc), backend=default_backend())
encrob = cipher.encryptor()
padder = padding.PKCS7(algorithms.AES.block_size).padder()
@@ -47,6 +62,14 @@ def encr_bite(data: bytes = b"", code: bytes = standard.client_code, invc: bytes
def decr_bite(data: bytes = b"", code: bytes = standard.client_code, invc: bytes = standard.client_invc) -> bytes:
+ """
+ Decrypt file chunks using converted cryptography HMAC code
+
+ :param data: Chunks to collect, decrypt and save
+ :param code: Generated cryptography HMAC code
+ :param invc: Initialization vector for added protection
+ :return: Decrypted bytes
+ """
try:
cipher = Cipher(algorithms.AES(code), modes.CBC(invc), backend=default_backend())
decrob = cipher.decryptor()
@@ -58,6 +81,11 @@ def decr_bite(data: bytes = b"", code: bytes = standard.client_code, invc: bytes
def encr_metadata() -> bytes:
+ """
+ Encrypt metadata to deliver before file contents
+
+ :return: Encrypted bytes
+ """
standard.client_invc, standard.client_salt = os.urandom(16), os.urandom(16)
data = dumps({"call": "meta", "name": standard.client_filename, "size": standard.client_filesize, "chks": len(standard.client_bind)-1})
standard.client_code = derive_code(standard.client_pswd, standard.client_salt)
@@ -67,6 +95,12 @@ def encr_metadata() -> bytes:
def decr_metadata(pack: bytes = b"") -> tuple:
+ """
+ Decrypt metadata to collect before file contents
+
+ :param pack: Chunks to decrypt
+ :return: Decrypted chunks
+ """
standard.client_invc, standard.client_salt = pack[0:16], pack[16:32]
data = pack[32:]
standard.client_code = derive_code(standard.client_pswd, standard.client_salt)
diff --git a/expedite/client/base.py b/expedite/client/base.py
index 275f2e3..2c58013 100644
--- a/expedite/client/base.py
+++ b/expedite/client/base.py
@@ -29,14 +29,30 @@
def find_size() -> int:
+ """
+ Retrieve the file size using the file location
+
+ :return: Size of the intended file
+ """
return getsize(standard.client_file)
def find_name() -> str:
+ """
+ Retrieve the file name using the file location
+
+ :return: Name of the intended file
+ """
return basename(standard.client_file)
def ease_size(size: Union[int, float]) -> str:
+ """
+ Retrieve the file size in human-readable format
+
+ :param size: Size in byte count format
+ :return: Size in human-readable format
+ """
unitlist = ["B", "KB", "MB", "GB", "TB", "PB"]
indx, opsz = 0, size
if size == 0:
@@ -48,6 +64,11 @@ def ease_size(size: Union[int, float]) -> str:
def bite_file() -> list:
+ """
+ Retrieve the list of read ranges from chunk size
+
+ :return: List of read ranges
+ """
init, size, bite = 0, standard.client_filesize, []
while init < size:
bite.append(init)
@@ -60,6 +81,13 @@ def bite_file() -> list:
def read_file(init: int = 0, fina: int = 0) -> bytes:
+ """
+ Retrieve the chunk from the provided byte range
+
+ :param init: Starting byte index for reading
+ :param fina: Stopping byte index for reading
+ :return: Chunks to read
+ """
if exists(standard.client_file):
with open(standard.client_file, "rb") as file:
file.seek(init)
@@ -72,6 +100,12 @@ def read_file(init: int = 0, fina: int = 0) -> bytes:
def fuse_file(pack: bytes = b"") -> bool:
+ """
+ Create and join the chunks on the storage device
+
+ :param pack: Chunks to save
+ :return:
+ """
if not standard.client_fileinit:
mode, standard.client_fileinit = "wb", True
else:
diff --git a/expedite/client/bridge/main.py b/expedite/client/bridge/main.py
index 786848b..14837b9 100644
--- a/expedite/client/bridge/main.py
+++ b/expedite/client/bridge/main.py
@@ -31,7 +31,12 @@
from expedite.client.bridge.room import MainWindow
-def load_custom_font():
+def load_custom_font() -> None:
+ """
+ Populate the application database with custom fonts
+
+ :return:
+ """
fontlist = [
":font/font/sans-bold.ttf",
":font/font/sans-rlar.ttf",
@@ -42,7 +47,12 @@ def load_custom_font():
QFontDatabase.addApplicationFont(indx)
-def main():
+def main() -> None:
+ """
+ Start the worker module to start the transfer service
+
+ :return:
+ """
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
QApplication.setStyle("Fusion")
app = QApplication(sys.argv)
diff --git a/expedite/client/bridge/room.py b/expedite/client/bridge/room.py
index 097b167..83e19ec 100644
--- a/expedite/client/bridge/room.py
+++ b/expedite/client/bridge/room.py
@@ -49,7 +49,7 @@
collect_digest_checks,
collect_dropping_summon,
collect_metadata,
- collect_permission_to_join,
+ collect_connection_to_server,
collect_separation_from_mistaken_password,
deliver_confirmation,
deliver_connection_to_server,
@@ -221,7 +221,7 @@ async def maintain_connection(self):
if standard.client_plan in ["SEND", "RECV"]:
# If the purpose of the client is either DELIVERING or COLLECTING
if mesgdict["call"] == "okay":
- await collect_permission_to_join(mesgdict["iden"])
+ await collect_connection_to_server(mesgdict["iden"])
self.statarea.showMessage("Please share your acquired identity to begin interaction")
self.identity.setText(f"{mesgdict["iden"]}")
elif mesgdict["call"] in ["awry", "lone"]:
diff --git a/expedite/client/bridge/util.py b/expedite/client/bridge/util.py
index 0495510..612907a 100644
--- a/expedite/client/bridge/util.py
+++ b/expedite/client/bridge/util.py
@@ -24,9 +24,17 @@
from os.path import exists
from PySide6.QtWidgets import QFileDialog
+from click import Tuple
def show_location_dialog(parent=None, oper: str = "") -> str:
+ """
+ Select filepath for the intended file for delivering or collecting
+
+ :param parent: Parent window within which the location dialog exists
+ :param oper: Operation intent for choosing either file or directory
+ :return:
+ """
dialog = QFileDialog()
if oper == "dlvr":
client_path = dialog.getOpenFileName(parent, "Select location", "", "All Files (*)")[0]
@@ -36,6 +44,13 @@ def show_location_dialog(parent=None, oper: str = "") -> str:
def truncate_text(text: str = "", size: int = 32) -> str:
+ """
+ Limit text elements to a certain character count for an elegant fit
+
+ :param text: Text elements that need to be checked for fitting
+ :param size: Character count within which text needs to be fit
+ :return: Truncated text
+ """
if len(text) >= size:
return text[0:size-3] + "..."
else:
@@ -43,6 +58,11 @@ def truncate_text(text: str = "", size: int = 32) -> str:
def return_detail_text() -> str:
+ """
+ Retrieve application information text for showing on the dialog box
+
+ :return: Application information
+ """
text = """
Expedite Bridge v{vers}
A simple encrypted file transfer service for humans
@@ -54,7 +74,12 @@ def return_detail_text() -> str:
class ValidateFields:
- def __init__(self):
+ def __init__(self) -> None:
+ """
+ Initialize fields validation class for confirmation
+
+ :return:
+ """
self.okay = {
"size": False,
"time": False,
@@ -70,7 +95,13 @@ def __init__(self):
"pswd": "Password cannot be an empty string"
}
- def verify_size(self, size):
+ def verify_size(self, size) -> None:
+ """
+ Ensure that processing size must be an integer value between 1024 and 524288
+
+ :param size: Processing size
+ :return:
+ """
self.okay["size"] = True
try:
oper = int(size.strip())
@@ -79,7 +110,13 @@ def verify_size(self, size):
except ValueError:
self.okay["size"] = False
- def verify_time(self, time):
+ def verify_time(self, time) -> None:
+ """
+ Ensure that expiry window must be an integer value between 5 and 300
+
+ :param time: Expiry window
+ :return:
+ """
self.okay["time"] = True
try:
oper = int(time.strip())
@@ -88,38 +125,67 @@ def verify_time(self, time):
except ValueError:
self.okay["time"] = False
- def verify_file(self, file):
+ def verify_file(self, file) -> None:
+ """
+ Ensure that filepath for delivering or collecting contents must exist
+
+ :param path: Load filepath
+ :return:
+ """
self.okay["file"] = True
if not exists(file):
self.okay["file"] = False
- def verify_path(self, path):
+ def verify_path(self, path) -> None:
+ """
+ Ensure that filepath for delivering or collecting contents must exist
+
+ :param path: Save filepath
+ :return:
+ """
self.okay["path"] = True
if path.strip() != "" and not exists(path):
self.okay["path"] = False
- def verify_pswd(self, pswd):
+ def verify_pswd(self, pswd) -> None:
+ """
+ Ensure that password cannot be an empty string
+
+ :param pswd: Password string
+ :return:
+ """
self.okay["pswd"] = True
if pswd.strip() == "":
self.okay["pswd"] = False
- def report_dlvr(self, size, time, file, pswd):
+ def report_dlvr(self, size, time, file, pswd) -> tuple[tuple[bool, bool, bool, bool], str]:
+ """
+ Retrieve field validation results for delivering intent
+
+ :param size: Validity confirmation of processing size
+ :param time: Validity confirmation of waiting window
+ :param file: Validity confirmation of delivering filepath
+ :param pswd: Validity confirmation of password string
+ :return: Validity confirmation for required elements
+ """
self.verify_size(size)
self.verify_time(time)
self.verify_file(file)
self.verify_pswd(pswd)
lict = [self.text[indx] for indx in ["size", "time", "file", "pswd"] if not self.okay[indx]]
- return (
- (self.okay["size"], self.okay["time"], self.okay["file"], self.okay["pswd"]),
- "\n".join(lict).strip()
- )
+ return (self.okay["size"], self.okay["time"], self.okay["file"], self.okay["pswd"]), "\n".join(lict).strip()
+
+ def report_clct(self, time, path, pswd) -> tuple[tuple[bool, bool, bool], str]:
+ """
+ Retrieve field validation results for collecting intent
- def report_clct(self, time, path, pswd):
+ :param time: Validity confirmation of waiting window
+ :param path: Validity confirmation of collecting filepath
+ :param pswd: Validity confirmation of password string
+ :return: Validity confirmation for required elements
+ """
self.verify_time(time)
self.verify_path(path)
self.verify_pswd(pswd)
lict = [self.text[indx] for indx in ["time", "path", "pswd"] if not self.okay[indx]]
- return (
- (self.okay["time"], self.okay["path"], self.okay["pswd"]),
- "\n".join(lict).strip(),
- )
+ return (self.okay["time"], self.okay["path"], self.okay["pswd"]), "\n".join(lict).strip()
diff --git a/expedite/client/conn.py b/expedite/client/conn.py
index 8a484b2..6584db3 100644
--- a/expedite/client/conn.py
+++ b/expedite/client/conn.py
@@ -37,6 +37,12 @@
async def deliver_connection_to_server(sock: WebSocketClientProtocol) -> bool:
+ """
+ Inform target client of connecting to the exchange server
+
+ :param sock: Websocket object belonging to the server session
+ :return: Confirmation of the action completion
+ """
general("Attempting to connect to the network.")
try:
await sock.send(dumps({"call": "join", "plan": standard.client_plan, "scan": standard.client_endo, "wait": standard.client_time}))
@@ -46,7 +52,13 @@ async def deliver_connection_to_server(sock: WebSocketClientProtocol) -> bool:
return False
-async def collect_permission_to_join(iden: str = standard.client_iden) -> bool:
+async def collect_connection_to_server(iden: str = standard.client_iden) -> bool:
+ """
+ Show textual message of connecting to the exchange server
+
+ :param iden: Network identity of person client
+ :return: Confirmation of the action completion
+ """
standard.client_iden = iden
general("Successfully connected to the network.")
warning(f"You are now identified as {iden} in the network.")
@@ -54,6 +66,12 @@ async def collect_permission_to_join(iden: str = standard.client_iden) -> bool:
async def deliver_suspension_from_expiry(sock: WebSocketClientProtocol) -> bool:
+ """
+ Inform exchange server about the disconnection from timed expiry
+
+ :param sock: Websocket object belonging to the server session
+ :return: Confirmation of the action completion
+ """
if not standard.client_pair:
general("Attempting to abandon from the network after expiry.")
await sock.send(dumps({"call": "rest"}))
@@ -63,6 +81,12 @@ async def deliver_suspension_from_expiry(sock: WebSocketClientProtocol) -> bool:
async def collect_connection_from_pairness(iden: str = standard.client_endo) -> bool:
+ """
+ Show textual message of collecting confirmation of pairing
+
+ :param iden: Network identity of target client
+ :return: Confirmation of the action completion
+ """
standard.client_endo = iden
standard.client_pair = True
general(f"Attempting pairing with {standard.client_endo}.")
@@ -70,7 +94,25 @@ async def collect_connection_from_pairness(iden: str = standard.client_endo) ->
return True
+async def deliver_metadata(sock: WebSocketClientProtocol) -> bool:
+ """
+ Inform target client of encrypting and delivering the metadata
+
+ :param sock: Websocket object belonging to the server session
+ :return: Confirmation of the action completion
+ """
+ general("Generating cryptography sign.")
+ await sock.send(encr_metadata())
+ return True
+
+
async def collect_metadata(pack: bytes = b"") -> bool:
+ """
+ Show textual message of collecting and decrypting the metadata
+
+ :param pack: Encrypted metadata collected from target client
+ :return: Confirmation of the action completion
+ """
try:
general("Generating cryptography sign.")
standard.client_filename, standard.client_filesize, standard.client_chks = decr_metadata(pack)
@@ -79,24 +121,35 @@ async def collect_metadata(pack: bytes = b"") -> bool:
return False
-async def deliver_metadata(sock: WebSocketClientProtocol) -> bool:
- general("Generating cryptography sign.")
- await sock.send(encr_metadata())
- return True
-
-
async def deliver_dropping_summon(sock: WebSocketClientProtocol) -> bool:
+ """
+ Inform target client of starting the exchange process
+
+ :param sock: Websocket object belonging to the server session
+ :return: Confirmation of the action completion
+ """
await sock.send(dumps({"call": "drop"}))
general(f"Delivering collection summon to {standard.client_endo}.")
return True
async def collect_dropping_summon() -> bool:
+ """
+ Show textual message of starting the exchange process
+
+ :return:
+ """
general(f"Collecting delivering summon from {standard.client_endo}.")
return True
async def deliver_contents(sock: WebSocketClientProtocol) -> Generator[Tuple[bytes, int], None, None]:
+ """
+ Load contents from intended file and deliver them to target client
+
+ :param sock: Websocket object belonging to the server session
+ :return: Tuple of file contents and contents length
+ """
for indx in range(0, len(standard.client_bind) - 1):
bite = read_file(standard.client_bind[indx], standard.client_bind[indx + 1])
await sock.send(bite)
@@ -105,6 +158,12 @@ async def deliver_contents(sock: WebSocketClientProtocol) -> Generator[Tuple[byt
async def collect_contents(sock: WebSocketClientProtocol) -> Generator[Tuple[bytes, int], None, None]:
+ """
+ Collect contents from target client and save them to intended file
+
+ :param sock: Websocket object belonging to the server session
+ :return: Tuple of file contents and contents length
+ """
for _ in range(standard.client_chks - 1):
mesgcont = await sock.recv()
if isinstance(mesgcont, bytes):
@@ -114,17 +173,35 @@ async def collect_contents(sock: WebSocketClientProtocol) -> Generator[Tuple[byt
async def deliver_digest_checks(sock: WebSocketClientProtocol) -> bool:
+ """
+ Inform target client with message digest of file contents
+
+ :param sock: Websocket object belonging to the server session
+ :return: Confirmation of the action completion
+ """
general("Delivering contents digest for confirmation.")
await sock.send(dumps({"call": "hash", "data": standard.client_hash.hexdigest()}))
return True
async def collect_digest_checks() -> bool:
+ """
+ Show textual message with message digest of file contents
+
+ :return: Confirmation of the action completion
+ """
general("Collecting contents digest for confirmation.")
return True
async def deliver_confirmation(sock: WebSocketClientProtocol, data: str = standard.client_hash.hexdigest()) -> bool:
+ """
+ Inform target client of the file contents integrity confirmation
+
+ :param sock: Websocket object belonging to the server session
+ :param data: Message digest from the file contents exchanged
+ :return:
+ """
standard.client_movestop = time.time()
if data == standard.client_hash.hexdigest():
general(f"Contents integrity verified (Mean {ease_size(standard.client_filesize / (standard.client_movestop - standard.client_movestrt))}/s).")
@@ -137,6 +214,12 @@ async def deliver_confirmation(sock: WebSocketClientProtocol, data: str = standa
async def collect_confirmation(data: int = 0) -> bool:
+ """
+ Show textual message of the file contents integrity confirmation
+
+ :param data: Confirmation of the message digest comparison
+ :return: Confirmation of the action completion
+ """
standard.client_movestop = time.time()
if bool(data):
general(f"Contents integrity verified (Mean {ease_size(standard.client_filesize / (standard.client_movestop - standard.client_movestrt))}/s).")
@@ -147,11 +230,22 @@ async def collect_confirmation(data: int = 0) -> bool:
async def deliver_separation_from_mistaken_password(sock: WebSocketClientProtocol) -> bool:
+ """
+ Inform target client of disconnection due to mistaken password
+
+ :param sock: Websocket object belonging to the server session
+ :return: Confirmation of the action completion
+ """
general("Delivering status update on mistaken password.")
await sock.send(dumps({"call": "flub"}))
return True
async def collect_separation_from_mistaken_password() -> bool:
+ """
+ Show textual message of disconnection due to mistaken password
+
+ :return: Confirmation of the action completion
+ """
general("Collecting status update on mistaken password.")
return True
diff --git a/expedite/client/excp.py b/expedite/client/excp.py
index 8d45fb4..d8bbecb 100644
--- a/expedite/client/excp.py
+++ b/expedite/client/excp.py
@@ -22,5 +22,10 @@
class PasswordMistaken(Exception):
- def __init__(self):
+ def __init__(self) -> None:
+ """
+ Initialize an exception due to incorrect password being added on the collecting client
+
+ :return:
+ """
self.name = "Password Mistaken"
diff --git a/expedite/client/meet.py b/expedite/client/meet.py
index c636ae9..2e09a88 100644
--- a/expedite/client/meet.py
+++ b/expedite/client/meet.py
@@ -27,6 +27,11 @@
def talk() -> None:
+ """
+ Show information on the client side during startup
+
+ :return:
+ """
success(f"Expedite Client v{__versdata__}")
general(f"Addr. {standard.client_host}")
general(f"Pass. {standard.client_pswd}")
diff --git a/expedite/client/prompt/main.py b/expedite/client/prompt/main.py
index ee904c4..a01d338 100644
--- a/expedite/client/prompt/main.py
+++ b/expedite/client/prompt/main.py
@@ -36,6 +36,11 @@
def work() -> None:
+ """
+ Start the worker module to start the transfer service
+
+ :return:
+ """
talk()
try:
run(oper())
@@ -89,6 +94,14 @@ def main(
time: int = standard.client_time,
endo: str = standard.client_endo,
):
+ """
+ Configure the service particulars before starting it
+
+ :param host: Location where the exchange service is hosted
+ :param time: Time for which the client has to wait before disconnecting
+ :param endo: Network identity of target client for pairing and transfer
+ :return:
+ """
standard.client_host = host
standard.client_time = time
standard.client_endo = endo
@@ -128,6 +141,14 @@ def send(
file: str = standard.client_file,
size: int = standard.chunking_size,
) -> None:
+ """
+ Configure the service particulars before delivering
+
+ :param pswd: Password for encrypting purposes
+ :param file: Filepath for delivering file
+ :param size: Unit size for file chunking
+ :return:
+ """
standard.client_pswd = pswd
standard.client_file = file
standard.chunking_size = size
@@ -154,6 +175,12 @@ def send(
def recv(
pswd: str = standard.client_pswd
) -> None:
+ """
+ Configure the service particulars before collecting
+
+ :param pswd: Password for decrypting purposes
+ :return:
+ """
standard.client_pswd = pswd
standard.client_plan = "RECV"
work()
diff --git a/expedite/client/prompt/room.py b/expedite/client/prompt/room.py
index 13f7603..2dea377 100644
--- a/expedite/client/prompt/room.py
+++ b/expedite/client/prompt/room.py
@@ -41,7 +41,7 @@
collect_digest_checks,
collect_dropping_summon,
collect_metadata,
- collect_permission_to_join,
+ collect_connection_to_server,
collect_separation_from_mistaken_password,
deliver_confirmation,
deliver_connection_to_server,
@@ -56,7 +56,12 @@
from expedite.view import general
-async def oper():
+async def oper() -> None:
+ """
+ Exchange data to the target client after connecting to the exchange server
+
+ :return:
+ """
try:
async with connect(standard.client_host) as sock:
get_event_loop().call_later(standard.client_time, lambda: ensure_future(deliver_suspension_from_expiry_prompt(sock)))
@@ -68,7 +73,7 @@ async def oper():
if standard.client_plan in ["SEND", "RECV"]:
# If the purpose of the client is either DELIVERING or COLLECTING
if mesgdict["call"] == "okay":
- await collect_permission_to_join(mesgdict["iden"])
+ await collect_connection_to_server(mesgdict["iden"])
elif mesgdict["call"] in ["awry", "lone"]:
await facade_exit(sock, False, mesgdict["call"])
if standard.client_plan == "SEND":
@@ -113,6 +118,12 @@ async def oper():
async def show_deliver_contents(sock: WebSocketClientProtocol) -> bool:
+ """
+ Facilitate encrypting and delivering file contents
+
+ :param sock: Websocket object belonging to the server session
+ :return: Confirmation of the action completion
+ """
general(f"Delivering contents for '{standard.client_filename}' ({ease_size(standard.client_filesize)}) to {standard.client_endo}.")
standard.client_movestrt = time.time()
with logging_redirect_tqdm():
@@ -124,6 +135,13 @@ async def show_deliver_contents(sock: WebSocketClientProtocol) -> bool:
async def show_collect_contents(sock: WebSocketClientProtocol, pack: bytes = b"") -> bool:
+ """
+ Facilitate collecting and decrypting file contents
+
+ :param sock: Websocket object belonging to the server session
+ :param pack: Byte chunk that is to be collected and decrypted
+ :return: Confirmation of the action completion
+ """
general(f"Collecting contents for '{standard.client_filename}' ({ease_size(standard.client_filesize)}) from {standard.client_endo}.")
standard.client_movestrt = time.time()
fuse_file(pack)
diff --git a/expedite/client/prompt/util.py b/expedite/client/prompt/util.py
index 60aef3d..3cb662d 100644
--- a/expedite/client/prompt/util.py
+++ b/expedite/client/prompt/util.py
@@ -31,6 +31,14 @@
async def facade_exit(sock: WebSocketClientProtocol = None, cond: bool = True, note: str = "") -> None:
+ """
+ Terminate the websocket object elegantly before leaving the application
+
+ :param sock: Websocket object belonging to the server session
+ :param cond: Condition within which the disconnection has to take place
+ :param note: Situation within which the disconnection was caused
+ :return:
+ """
if note != "done":
warning(standard.client_note[note])
if sock:
@@ -45,6 +53,12 @@ async def facade_exit(sock: WebSocketClientProtocol = None, cond: bool = True, n
general("Exiting.")
-async def deliver_suspension_from_expiry_prompt(sock: WebSocketClientProtocol):
+async def deliver_suspension_from_expiry_prompt(sock: WebSocketClientProtocol) -> None:
+ """
+ Terminate the websocket session elegantly after the designated timeout
+
+ :param sock: Websocket object belonging to the server session
+ :return:
+ """
if await deliver_suspension_from_expiry(sock):
await facade_exit(sock, False, "rest")