├── .github ├── FUNDING.yml └── workflows │ ├── wipe.yml │ ├── gnul.yml │ └── mswn.yml ├── assets ├── font │ ├── sans-bdit.ttf │ ├── sans-bold.ttf │ ├── sans-rlar.ttf │ └── sans-rlit.ttf ├── icon │ └── expedite.ico └── main.qrc ├── data ├── bridge-info-stat.png ├── bridge-recv-prog.gif ├── bridge-recv-stat.png ├── bridge-send-prog.gif ├── bridge-send-stat.png ├── prompt-help-stat.png ├── prompt-recv-prog.gif ├── prompt-send-prog.gif ├── cert-atla-12112024.png ├── cert-mumb-12112024.png ├── test-mumb-12112024.txt └── test-atla-12112024.txt ├── Dockerfile ├── tox.ini ├── expedite ├── config │ ├── __init__.py │ └── standard.py ├── server │ ├── __init__.py │ ├── meet.py │ ├── base.py │ ├── room.py │ ├── main.py │ └── conn.py ├── client │ ├── bridge │ │ ├── __init__.py │ │ ├── main.py │ │ ├── util.py │ │ ├── room.py │ │ └── wind.py │ ├── prompt │ │ ├── __init__.py │ │ ├── util.py │ │ ├── main.py │ │ └── room.py │ ├── __init__.py │ ├── excp.py │ ├── meet.py │ ├── base.py │ ├── auth.py │ └── conn.py ├── __init__.py └── view.py ├── renovate.json ├── pyproject.toml ├── .gitignore └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: gridhead 4 | -------------------------------------------------------------------------------- /assets/font/sans-bdit.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/assets/font/sans-bdit.ttf -------------------------------------------------------------------------------- /assets/font/sans-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/assets/font/sans-bold.ttf -------------------------------------------------------------------------------- /assets/font/sans-rlar.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/assets/font/sans-rlar.ttf -------------------------------------------------------------------------------- /assets/font/sans-rlit.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/assets/font/sans-rlit.ttf -------------------------------------------------------------------------------- /assets/icon/expedite.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/assets/icon/expedite.ico -------------------------------------------------------------------------------- /data/bridge-info-stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/data/bridge-info-stat.png -------------------------------------------------------------------------------- /data/bridge-recv-prog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/data/bridge-recv-prog.gif -------------------------------------------------------------------------------- /data/bridge-recv-stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/data/bridge-recv-stat.png -------------------------------------------------------------------------------- /data/bridge-send-prog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/data/bridge-send-prog.gif -------------------------------------------------------------------------------- /data/bridge-send-stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/data/bridge-send-stat.png -------------------------------------------------------------------------------- /data/prompt-help-stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/data/prompt-help-stat.png -------------------------------------------------------------------------------- /data/prompt-recv-prog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/data/prompt-recv-prog.gif -------------------------------------------------------------------------------- /data/prompt-send-prog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/data/prompt-send-prog.gif -------------------------------------------------------------------------------- /data/cert-atla-12112024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/data/cert-atla-12112024.png -------------------------------------------------------------------------------- /data/cert-mumb-12112024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridhead/expedite/HEAD/data/cert-mumb-12112024.png -------------------------------------------------------------------------------- /assets/main.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | font/sans-bdit.ttf 4 | font/sans-bold.ttf 5 | font/sans-rlar.ttf 6 | font/sans-rlit.ttf 7 | 8 | 9 | icon/expedite.ico 10 | 11 | 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fedora:42 2 | 3 | LABEL maintainer "Akashdeep Dhar " 4 | 5 | EXPOSE 6969 6 | 7 | ENV PYTHONBUFFERED=1 8 | 9 | RUN dnf install python3-pip --assumeyes && dnf clean all --assumeyes 10 | RUN pip3 install --upgrade expedite==0.1.0 11 | 12 | ENTRYPOINT ["ed-server"] 13 | CMD ["--addr", "0.0.0.0", "--port", "6969"] 14 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.12.0 3 | envlist = cleaning 4 | isolated_build = true 5 | skip_missing_interpreters = true 6 | 7 | [testenv] 8 | skip_install = true 9 | sitepackages = false 10 | deps = 11 | poetry>=1.2.0 12 | commands_pre = 13 | poetry install --all-extras 14 | 15 | [testenv:cleaning] 16 | commands = 17 | poetry run ruff check expedite/ 18 | -------------------------------------------------------------------------------- /expedite/config/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | -------------------------------------------------------------------------------- /expedite/server/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | -------------------------------------------------------------------------------- /expedite/client/bridge/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | -------------------------------------------------------------------------------- /expedite/client/prompt/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | -------------------------------------------------------------------------------- /.github/workflows/wipe.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Manage artifacts to retain four recent artifacts 3 | on: [push] 4 | jobs: 5 | ci-wipe: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Manage artifacts to retain four recent artifacts 9 | run: | 10 | KEEP_QANT=4 11 | ARTIFACT_UNIT=$(gh api repos/gridhead/expedite/actions/artifacts --paginate --jq '.artifacts | sort_by(.created_at) | .[].id') 12 | ARTIFACT_LIST=($ARTIFACT_UNIT) 13 | WIPE_QANT=$((${#ARTIFACT_LIST[@]} - $KEEP_QANT)) 14 | if [ $WIPE_QANT -gt 0 ]; then 15 | for item in $(seq 0 $(($WIPE_QANT - 1))); do 16 | ARTIFACT_ID=${ARTIFACT_LIST[$item]} 17 | echo "Deleting artifact ID $ARTIFACT_ID..." 18 | gh api repos/gridhead/expedite/actions/artifacts/$ARTIFACT_ID -X DELETE 19 | done 20 | else 21 | echo "No artifacts available." 22 | fi 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GHBTOKEN }} 25 | -------------------------------------------------------------------------------- /expedite/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | from importlib.metadata import metadata 25 | 26 | __metadict__ = metadata("expedite").json 27 | __versdata__ = __metadict__.get("version") 28 | -------------------------------------------------------------------------------- /expedite/client/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | from importlib.metadata import metadata 25 | 26 | __metadict__ = metadata("expedite").json 27 | __versdata__ = __metadict__.get("version") 28 | -------------------------------------------------------------------------------- /expedite/client/excp.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | class PasswordMistaken(Exception): 25 | def __init__(self) -> None: 26 | """ 27 | Initialize an exception due to incorrect password being added on the collecting client 28 | 29 | :return: 30 | """ 31 | self.name = "Password Mistaken" 32 | -------------------------------------------------------------------------------- /expedite/server/meet.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | from expedite import __versdata__ 25 | from expedite.config import standard 26 | from expedite.view import general, success 27 | 28 | 29 | def talk() -> None: 30 | """ 31 | Show information on the server side during startup 32 | 33 | :return: 34 | """ 35 | success(f"Expedite Server v{__versdata__}") 36 | general(f"Addr. {standard.server_addr}") 37 | general(f"Port. {standard.server_port}") 38 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | "group:allNonMajor", 6 | "schedule:weekdays", 7 | ":maintainLockFilesWeekly", 8 | ":separateMultipleMajorReleases", 9 | ":automergeMinor", 10 | ":gitSignOff", 11 | ":enableVulnerabilityAlertsWithLabel(security)" 12 | ], 13 | "lockFileMaintenance": { 14 | "enabled": true, 15 | "automerge": true, 16 | "extends": [ 17 | "group:allNonMajor" 18 | ], 19 | "commitMessageAction": "Automated dependency updates for Expedite" 20 | }, 21 | "automergeStrategy": "rebase", 22 | "rangeStrategy": "widen", 23 | "stabilityDays": 4, 24 | "labels": ["dependencies"], 25 | "packageRules": [ 26 | { 27 | "matchLanguages": ["python"], 28 | "addLabels": ["python"] 29 | }, 30 | { 31 | "matchLanguages": ["python"], 32 | "matchPackageNames": [ 33 | "certifi", 34 | "pytz" 35 | ], 36 | "automerge": true 37 | }, 38 | { 39 | "matchDepTypes": ["devDependencies"], 40 | "groupName": "dev dependencies", 41 | "automerge": true 42 | }, 43 | { 44 | "extends": [ 45 | "packages:linters" 46 | ], 47 | "matchPackageNames": [ 48 | "flake8", 49 | "pylint", 50 | "pep8", 51 | "ruff", 52 | "black", 53 | "bandit", 54 | "safety", 55 | "reuse" 56 | ], 57 | "automerge": true 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /expedite/client/meet.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | from expedite import __versdata__ 25 | from expedite.config import standard 26 | from expedite.view import general, success, warning 27 | 28 | 29 | def talk() -> None: 30 | """ 31 | Show information on the client side during startup 32 | 33 | :return: 34 | """ 35 | success(f"Expedite Client v{__versdata__}") 36 | general(f"Addr. {standard.client_host}") 37 | general(f"Pass. {standard.client_pswd}") 38 | if standard.client_plan == "SEND": 39 | general("Plan. DELIVERING") 40 | elif standard.client_plan == "RECV": 41 | general("Plan. COLLECTING") 42 | general(f"Wait. {standard.client_time} seconds") 43 | if standard.client_endo == "": 44 | warning("Please share your acquired identity to begin interaction.") 45 | else: 46 | warning(f"Please wait for {standard.client_endo} to begin interaction.") 47 | -------------------------------------------------------------------------------- /expedite/view.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | from click import style 25 | 26 | from expedite.config import standard 27 | 28 | 29 | def success(text) -> None: 30 | """ 31 | Show textual message in success format 32 | 33 | :param text: Textual message 34 | :return: 35 | """ 36 | standard.logger.info(style(text, fg="green", bold=True)) 37 | 38 | 39 | def failure(text) -> None: 40 | """ 41 | Show textual message in failure format 42 | 43 | :param text: Textual message 44 | :return: 45 | """ 46 | standard.logger.error(style(text, fg="red", bold=True)) 47 | 48 | 49 | def warning(text) -> None: 50 | """ 51 | Show textual message in warning format 52 | 53 | :param text: Textual message 54 | :return: 55 | """ 56 | standard.logger.warning(style(text, fg="yellow", bold=True)) 57 | 58 | 59 | def general(text) -> None: 60 | """ 61 | Show textual message in general format 62 | 63 | :param text: Textual message 64 | :return: 65 | """ 66 | standard.logger.info(text) 67 | -------------------------------------------------------------------------------- /expedite/client/bridge/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | import os 25 | import sys 26 | 27 | from PySide6.QtGui import QFontDatabase, QIcon 28 | from PySide6.QtWidgets import QApplication 29 | 30 | from expedite.client.bridge import data # noqa 31 | from expedite.client.bridge.room import MainWindow 32 | 33 | 34 | def load_custom_font() -> None: 35 | """ 36 | Populate the application database with custom fonts 37 | 38 | :return: 39 | """ 40 | fontlist = [ 41 | ":font/font/sans-bold.ttf", 42 | ":font/font/sans-rlar.ttf", 43 | ":font/font/sans-bdit.ttf", 44 | ":font/font/sans-rlit.ttf", 45 | ] 46 | for indx in fontlist: 47 | QFontDatabase.addApplicationFont(indx) 48 | 49 | 50 | def main() -> None: 51 | """ 52 | Start the worker module to start the transfer service 53 | 54 | :return: 55 | """ 56 | os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" 57 | QApplication.setStyle("Fusion") 58 | app = QApplication(sys.argv) 59 | app.setWindowIcon(QIcon(":icon/icon/expedite.ico")) 60 | load_custom_font() 61 | main_window = MainWindow() 62 | main_window.show() 63 | sys.exit(app.exec()) 64 | 65 | 66 | if __name__ == "__main__": 67 | main() 68 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "expedite" 3 | version = "0.1.0" 4 | description = "Simple encrypted file transfer service for humans" 5 | authors = ["Akashdeep Dhar "] 6 | license = "GPL-3.0-or-later" 7 | maintainers = ["Akashdeep Dhar "] 8 | readme = "README.md" 9 | homepage = "https://github.com/gridhead/expedite" 10 | repository = "https://github.com/gridhead/expedite" 11 | documentation = "https://github.com/gridhead/expedite/blob/main/README.md" 12 | keywords = ["websockets", "file", "transfer", "delivering", "collecting"] 13 | classifiers= [ 14 | "Development Status :: 4 - Beta", 15 | "Environment :: X11 Applications :: Qt", 16 | "Intended Audience :: Developers", 17 | "Intended Audience :: End Users/Desktop", 18 | "Intended Audience :: System Administrators", 19 | "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", 20 | "Operating System :: Microsoft :: Windows", 21 | "Operating System :: POSIX :: Linux", 22 | "Natural Language :: English", 23 | "Programming Language :: Python :: 3", 24 | "Topic :: Communications", 25 | "Topic :: Communications :: File Sharing", 26 | "Topic :: Internet", 27 | "Topic :: Security", 28 | "Topic :: Security :: Cryptography", 29 | "Topic :: System :: Networking", 30 | "Topic :: Utilities" 31 | ] 32 | 33 | [tool.poetry.dependencies] 34 | python = ">=3.10,<3.15" 35 | websockets = "^12.0 || ^15.0.0" 36 | click = "^8.1.7" 37 | tqdm = "^4.66.4" 38 | cryptography = "^42.0.8 || ^43.0.0 || ^44.0.0 || ^45.0.0" 39 | pyside6-essentials = "^6.7.2" 40 | 41 | [tool.poetry.group.dev.dependencies] 42 | pytest = "^7.1.3 || ^8.0.0" 43 | pytest-cov = "^4.1.0 || ^5.0.0 || ^6.0.0" 44 | ruff = "^0.2.0 || ^0.3.0 || ^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0" 45 | tox = "^4.0.0" 46 | 47 | [tool.ruff] 48 | line-length = 100 49 | fix = true 50 | 51 | [tool.ruff.lint] 52 | select = ["E", "F", "W", "I", "S", "B", "UP"] 53 | 54 | [tool.ruff.lint.per-file-ignores] 55 | "expedite/*" = ["E501"] 56 | 57 | [build-system] 58 | requires = ["poetry-core"] 59 | build-backend = "poetry.core.masonry.api" 60 | 61 | [tool.poetry.scripts] 62 | ed-server = "expedite.server.main:main" 63 | ed-prompt = "expedite.client.prompt.main:main" 64 | ed-bridge = "expedite.client.bridge.main:main" 65 | -------------------------------------------------------------------------------- /.github/workflows/gnul.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Building the project binary for GNU/Linux OSes 3 | on: [push] 4 | jobs: 5 | ci-make-gnul: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | fail-fast: false 9 | 10 | steps: 11 | - name: Checkout the codebase in local working directory 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup a functioning local Python 3 installation 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: 3.12 || 3.13 || 3.14 18 | 19 | - name: Install the base dependencies of the project 20 | run: python3 -m pip install --upgrade poetry pyinstaller 21 | 22 | - name: Disable using virtual environments with Poetry 23 | run: poetry config virtualenvs.create false 24 | 25 | - name: Install the runtime dependencies of the project 26 | run: python3 -m poetry install 27 | 28 | - name: Build the project binary for Expedite Bridge 29 | run: pyinstaller expedite/client/bridge/main.py --clean --onefile --name ed-bridge-${GITHUB_HASH:0:8} 30 | env: 31 | GITHUB_HASH: ${{ github.sha }} 32 | 33 | - name: Upload the project binaries for Expedite Bridge 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: ed-bridge.gnul 37 | path: dist/ed-bridge-* 38 | retention-days: 90 39 | compression-level: 9 40 | overwrite: true 41 | 42 | - name: Build the project binary for Expedite Prompt 43 | run: pyinstaller expedite/client/prompt/main.py --clean --onefile --name ed-prompt-${GITHUB_HASH:0:8} 44 | env: 45 | GITHUB_HASH: ${{ github.sha }} 46 | 47 | - name: Upload the project binaries for Expedite Prompt 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: ed-prompt.gnul 51 | path: dist/ed-prompt-* 52 | retention-days: 90 53 | compression-level: 9 54 | overwrite: true 55 | 56 | - name: Build the project binary for Expedite Server 57 | run: pyinstaller expedite/server/main.py --clean --onefile --name ed-server-${GITHUB_HASH:0:8} 58 | env: 59 | GITHUB_HASH: ${{ github.sha }} 60 | 61 | - name: Upload the project binaries for Expedite Server 62 | uses: actions/upload-artifact@v4 63 | with: 64 | name: ed-server.gnul 65 | path: dist/ed-server-* 66 | retention-days: 90 67 | compression-level: 9 68 | overwrite: true 69 | -------------------------------------------------------------------------------- /expedite/client/prompt/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | import time 25 | 26 | from websockets.legacy.client import WebSocketClientProtocol 27 | 28 | from expedite.client.conn import deliver_suspension_from_expiry 29 | from expedite.config import standard 30 | from expedite.view import failure, general, success, warning 31 | 32 | 33 | async def facade_exit(sock: WebSocketClientProtocol = None, cond: bool = True, note: str = "") -> None: 34 | """ 35 | Terminate the websocket object elegantly before leaving the application 36 | 37 | :param sock: Websocket object belonging to the server session 38 | :param cond: Condition within which the disconnection has to take place 39 | :param note: Situation within which the disconnection was caused 40 | :return: 41 | """ 42 | if note != "done": 43 | warning(standard.client_note[note]) 44 | if sock: 45 | await sock.close() 46 | plan = "Delivering" if standard.client_plan == "SEND" else "Collecting" 47 | if cond: 48 | success(f"{plan} done after {(time.time() - standard.client_strt):.2f} seconds.") 49 | standard.client_exit = 0 50 | else: 51 | failure(f"{plan} fail after {(time.time() - standard.client_strt):.2f} seconds.") 52 | standard.client_exit = 1 53 | general("Exiting.") 54 | 55 | 56 | async def deliver_suspension_from_expiry_prompt(sock: WebSocketClientProtocol) -> None: 57 | """ 58 | Terminate the websocket session elegantly after the designated timeout 59 | 60 | :param sock: Websocket object belonging to the server session 61 | :return: 62 | """ 63 | if await deliver_suspension_from_expiry(sock): 64 | await facade_exit(sock, False, "rest") 65 | -------------------------------------------------------------------------------- /.github/workflows/mswn.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Building the project binary for Microsoft Windows 3 | on: [push] 4 | jobs: 5 | ci-make-mswn: 6 | runs-on: windows-latest 7 | strategy: 8 | fail-fast: false 9 | 10 | steps: 11 | - name: Checkout the codebase in local working directory 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup a functioning local Python 3 installation 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: 3.12 || 3.13 || 3.14 18 | 19 | - name: Install the base dependencies of the project 20 | run: python3 -m pip install --upgrade poetry pyinstaller 21 | 22 | - name: Disable using virtual environments with Poetry 23 | run: poetry config virtualenvs.create false 24 | 25 | - name: Install the runtime dependencies of the project 26 | run: python3 -m poetry install 27 | 28 | - name: Build the project binary for Expedite Bridge 29 | run: pyinstaller expedite/client/bridge/main.py --clean --onefile --name ed-bridge-$("$env:GITHUB_HASH".SubString(0, 8)) --windowed --icon assets/icon/expedite.ico 30 | env: 31 | GITHUB_HASH: ${{ github.sha }} 32 | 33 | - name: Upload the project binaries for Expedite Bridge 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: ed-bridge.mswn 37 | path: dist/ed-bridge-*.exe 38 | retention-days: 90 39 | compression-level: 9 40 | overwrite: true 41 | 42 | - name: Build the project binary for Expedite Prompt 43 | run: pyinstaller expedite/client/prompt/main.py --clean --onefile --name ed-prompt-$("$env:GITHUB_HASH".SubString(0, 8)) --icon assets/icon/expedite.ico 44 | env: 45 | GITHUB_HASH: ${{ github.sha }} 46 | 47 | - name: Upload the project binaries for Expedite Prompt 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: ed-prompt.mswn 51 | path: dist/ed-prompt-*.exe 52 | retention-days: 90 53 | compression-level: 9 54 | overwrite: true 55 | 56 | - name: Build the project binary for Expedite Server 57 | run: pyinstaller expedite/server/main.py --clean --onefile --name ed-server-$("$env:GITHUB_HASH".SubString(0, 8)) --icon assets/icon/expedite.ico 58 | env: 59 | GITHUB_HASH: ${{ github.sha }} 60 | 61 | - name: Upload the project binaries for Expedite Server 62 | uses: actions/upload-artifact@v4 63 | with: 64 | name: ed-server.mswn 65 | path: dist/ed-server-*.exe 66 | retention-days: 90 67 | compression-level: 9 68 | overwrite: true 69 | -------------------------------------------------------------------------------- /expedite/server/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | from websockets.legacy.server import WebSocketServerProtocol 25 | 26 | from expedite.config import standard 27 | 28 | 29 | class ExpediteConnection: 30 | def __init__(self, iden: str = standard.client_iden, plan: str = standard.client_plan, scan: str = standard.client_endo, time: int = standard.client_time) -> None: 31 | """ 32 | Initialize the Expedite connection with the client identity, operation intent, target identity and waiting time 33 | 34 | :param iden: Identity provided by the exchange server to the connecting client to be recognized within the network 35 | :param plan: Operation intent of the connecting client - This can be either SEND or RECV depending on the purpose 36 | :param scan: Target client sought by the connecting client - This can be either empty string or hexadecimal string 37 | :param time: Time for which a connecting client will stay connected to the network and wait for a pairing process 38 | :return: 39 | """ 40 | self.iden = iden 41 | self.plan = plan 42 | self.scan = scan if scan != "" else None 43 | self.time = time 44 | self.ptid = "" 45 | self.ptsc = None 46 | 47 | def pair_connection(self, ptid: str = standard.client_endo, ptsc: WebSocketServerProtocol = None) -> None: 48 | """ 49 | Configure the partner identity and partner sought when the pairing process with both the clients has completed 50 | 51 | These attributes belong to the pairmate and are populated for the client after the pairing process is complete 52 | 53 | :param ptid: Identity provided by the exchange server to the connecting client to be recognized within the network 54 | :param ptsc: Websocket object belonging to the connecting client 55 | :return: 56 | """ 57 | self.ptid = ptid 58 | self.ptsc = ptsc 59 | -------------------------------------------------------------------------------- /expedite/server/room.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | from json import loads 25 | 26 | from websockets.exceptions import ConnectionClosed 27 | from websockets.legacy.server import WebSocketServerProtocol 28 | 29 | from expedite.config import standard 30 | from expedite.server.conn import ( 31 | exchange_byte, 32 | exchange_inform, 33 | exchange_insert, 34 | exchange_json, 35 | exchange_remove, 36 | ) 37 | from expedite.view import failure, general, warning 38 | 39 | 40 | async def exchange(sock: WebSocketServerProtocol) -> None: 41 | """ 42 | Exchange data among connected clients depending on client identity, operation intent and target identity 43 | 44 | :param sock: Websocket object belonging to the client 45 | :return: 46 | """ 47 | try: 48 | async for mesgcont in sock: 49 | if isinstance(mesgcont, str): 50 | mesgdict = loads(mesgcont) 51 | if mesgdict["call"] == "join": 52 | identity = await exchange_insert(sock, mesgdict["plan"], mesgdict["scan"], mesgdict["wait"]) 53 | if bool(identity): 54 | if await exchange_inform(sock, mesgdict["plan"], mesgdict["scan"], identity) in [1, 2]: 55 | await exchange_remove(sock) 56 | else: 57 | await sock.close() 58 | elif mesgdict["call"] in ["meta", "drop", "hash", "conf", "flub"]: 59 | await exchange_json(sock, mesgdict["call"], mesgcont) 60 | elif mesgdict["call"] == "rest": 61 | failure(f"{standard.connection_dict[sock].iden} has achieved expiry.") 62 | await exchange_remove(sock) 63 | else: 64 | await exchange_byte(sock, mesgcont) 65 | except ConnectionClosed as expt: 66 | warning("Delivering client disconnected due to the disconnection of collecting client.") 67 | general(expt) 68 | finally: 69 | await exchange_remove(sock) 70 | -------------------------------------------------------------------------------- /expedite/server/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | import sys 25 | from asyncio import get_event_loop 26 | 27 | from click import IntRange, command, option, version_option 28 | from websockets import serve 29 | 30 | from expedite import __versdata__ 31 | from expedite.config import standard 32 | from expedite.server.meet import talk 33 | from expedite.server.room import exchange 34 | from expedite.view import failure, general 35 | 36 | 37 | def work() -> None: 38 | """ 39 | Start the worker module to serve the exchange service 40 | 41 | :return: 42 | """ 43 | func = serve(exchange, standard.server_addr, standard.server_port) 44 | get_event_loop().run_until_complete(func) 45 | get_event_loop().run_forever() 46 | 47 | 48 | @command( 49 | name="expedite", 50 | help="Configure the service particulars before starting it", 51 | context_settings={"show_default": True}, 52 | ) 53 | @option( 54 | "-a", 55 | "--addr", 56 | "addr", 57 | type=str, 58 | default=standard.server_addr, 59 | required=False, 60 | help="Set the interface for the service endpoint" 61 | ) 62 | @option( 63 | "-p", 64 | "--port", 65 | "port", 66 | type=IntRange(min=64, max=65535), 67 | default=standard.server_port, 68 | required=False, 69 | help="Set the port value for the service endpoint" 70 | ) 71 | @version_option( 72 | version=__versdata__, prog_name="Expedite Server by Akashdeep Dhar" 73 | ) 74 | def main(addr: str = standard.server_addr, port: int = standard.server_port) -> None: 75 | """ 76 | Configure the service particulars before starting it 77 | 78 | :param addr: Interface for the service endpoint 79 | :param port: Port value for the service endpoint 80 | :return: 81 | """ 82 | try: 83 | standard.server_addr = addr 84 | standard.server_port = port 85 | talk() 86 | work() 87 | except KeyboardInterrupt: 88 | failure("Interrupt received.") 89 | general("Exiting.") 90 | sys.exit(1) 91 | except OSError: 92 | failure("Port occupied.") 93 | general("Exiting.") 94 | sys.exit(1) 95 | 96 | 97 | if __name__ == "__main__": 98 | main() 99 | -------------------------------------------------------------------------------- /expedite/client/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | from os.path import basename, exists, getsize 25 | 26 | from expedite.client.auth import decr_bite, encr_bite 27 | from expedite.config import standard 28 | 29 | 30 | def find_size() -> int: 31 | """ 32 | Retrieve the file size using the file location 33 | 34 | :return: Size of the intended file 35 | """ 36 | return getsize(standard.client_file) 37 | 38 | 39 | def find_name() -> str: 40 | """ 41 | Retrieve the file name using the file location 42 | 43 | :return: Name of the intended file 44 | """ 45 | return basename(standard.client_file) 46 | 47 | 48 | def ease_size(size: int | float) -> str: 49 | """ 50 | Retrieve the file size in human-readable format 51 | 52 | :param size: Size in byte count format 53 | :return: Size in human-readable format 54 | """ 55 | unitlist = ["B", "KB", "MB", "GB", "TB", "PB"] 56 | indx, opsz = 0, size 57 | if size == 0: 58 | return "0.00B" 59 | else: 60 | while opsz >= 1024 and indx < len(unitlist) - 1: 61 | opsz, indx = opsz / 1024.0, indx + 1 62 | return f"{opsz:.2f}{unitlist[indx]}" 63 | 64 | 65 | def bite_file() -> list: 66 | """ 67 | Retrieve the list of read ranges from chunk size 68 | 69 | :return: List of read ranges 70 | """ 71 | init, size, bite = 0, standard.client_filesize, [] 72 | while init < size: 73 | bite.append(init) 74 | if size - init >= standard.chunking_size: 75 | init = init + standard.chunking_size 76 | else: 77 | bite.append(size) 78 | init = size 79 | return bite 80 | 81 | 82 | def read_file(init: int = 0, fina: int = 0) -> bytes: 83 | """ 84 | Retrieve the chunk from the provided byte range 85 | 86 | :param init: Starting byte index for reading 87 | :param fina: Stopping byte index for reading 88 | :return: Chunks to read 89 | """ 90 | if exists(standard.client_file): 91 | with open(standard.client_file, "rb") as file: 92 | file.seek(init) 93 | data = file.read(fina - init) 94 | endt = encr_bite(data, standard.client_code, standard.client_invc) 95 | standard.client_hash.update(data) 96 | return endt 97 | else: 98 | return b"" 99 | 100 | 101 | def fuse_file(pack: bytes = b"") -> bool: 102 | """ 103 | Create and join the chunks on the storage device 104 | 105 | :param pack: Chunks to save 106 | :return: 107 | """ 108 | if not standard.client_fileinit: 109 | mode, standard.client_fileinit = "wb", True 110 | else: 111 | mode = "ab" 112 | with open(standard.client_filename, mode) as file: 113 | dedt = decr_bite(pack, standard.client_code, standard.client_invc) 114 | file.write(dedt) 115 | standard.client_hash.update(dedt) 116 | return True 117 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /expedite/client/auth.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | import os 25 | from json import dumps, loads 26 | 27 | from cryptography.hazmat.backends import default_backend 28 | from cryptography.hazmat.primitives import hashes, padding 29 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 30 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 31 | 32 | from expedite.client.excp import PasswordMistaken 33 | from expedite.config import standard 34 | 35 | 36 | def derive_code(password: str = standard.client_pswd, salt: bytes = standard.client_salt) -> bytes: 37 | """ 38 | Derive byte convertion from password and salt composition 39 | 40 | :param password: Password provided by the clients 41 | :param salt: Byte array for additional protection 42 | :return: Cryptography HMAC code 43 | """ 44 | kdfo = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=default_backend()) 45 | return kdfo.derive(password.encode()) 46 | 47 | 48 | def encr_bite(data: bytes = b"", code: bytes = standard.client_code, invc: bytes = standard.client_invc) -> bytes: 49 | """ 50 | Encrypt file chunks using converted cryptography HMAC code 51 | 52 | :param data: Chunks to read, encrypt and deliver 53 | :param code: Generated cryptography HMAC code 54 | :param invc: Initialization vector for added protection 55 | :return: Encrypted bytes 56 | """ 57 | cipher = Cipher(algorithms.AES(code), modes.CBC(invc), backend=default_backend()) 58 | encrob = cipher.encryptor() 59 | padder = padding.PKCS7(algorithms.AES.block_size).padder() 60 | paddat = padder.update(data) + padder.finalize() 61 | return encrob.update(paddat) + encrob.finalize() 62 | 63 | 64 | def decr_bite(data: bytes = b"", code: bytes = standard.client_code, invc: bytes = standard.client_invc) -> bytes: 65 | """ 66 | Decrypt file chunks using converted cryptography HMAC code 67 | 68 | :param data: Chunks to collect, decrypt and save 69 | :param code: Generated cryptography HMAC code 70 | :param invc: Initialization vector for added protection 71 | :return: Decrypted bytes 72 | """ 73 | try: 74 | cipher = Cipher(algorithms.AES(code), modes.CBC(invc), backend=default_backend()) 75 | decrob = cipher.decryptor() 76 | ucrper = padding.PKCS7(algorithms.AES.block_size).unpadder() 77 | ucrdat = decrob.update(data) + decrob.finalize() 78 | return ucrper.update(ucrdat) + ucrper.finalize() 79 | except ValueError: 80 | raise PasswordMistaken from None 81 | 82 | 83 | def encr_metadata() -> bytes: 84 | """ 85 | Encrypt metadata to deliver before file contents 86 | 87 | :return: Encrypted bytes 88 | """ 89 | standard.client_invc, standard.client_salt = os.urandom(16), os.urandom(16) 90 | data = dumps({"call": "meta", "name": standard.client_filename, "size": standard.client_filesize, "chks": len(standard.client_bind)-1}) 91 | standard.client_code = derive_code(standard.client_pswd, standard.client_salt) 92 | endt = encr_bite(data.encode(encoding="utf-8"), standard.client_code, standard.client_invc) 93 | standard.client_metadone = True 94 | return standard.client_invc + standard.client_salt + endt 95 | 96 | 97 | def decr_metadata(pack: bytes = b"") -> tuple[str, int, bytes]: 98 | """ 99 | Decrypt metadata to collect before file contents 100 | 101 | :param pack: Chunks to decrypt 102 | :return: Decrypted chunks 103 | """ 104 | standard.client_invc, standard.client_salt = pack[0:16], pack[16:32] 105 | data = pack[32:] 106 | standard.client_code = derive_code(standard.client_pswd, standard.client_salt) 107 | dedt = loads(decr_bite(data, standard.client_code, standard.client_invc).decode(encoding="utf-8")) 108 | standard.client_metadone = True 109 | return dedt["name"], dedt["size"], dedt["chks"] 110 | -------------------------------------------------------------------------------- /expedite/client/prompt/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | import sys 25 | from asyncio import run 26 | 27 | from click import IntRange, Path, group, option, version_option 28 | from websockets.exceptions import InvalidURI 29 | 30 | from expedite import __versdata__ 31 | from expedite.client.base import bite_file, find_name, find_size 32 | from expedite.client.meet import talk 33 | from expedite.client.prompt.room import oper 34 | from expedite.client.prompt.util import facade_exit 35 | from expedite.config import standard 36 | 37 | 38 | def work() -> None: 39 | """ 40 | Start the worker module to start the transfer service 41 | 42 | :return: 43 | """ 44 | talk() 45 | try: 46 | run(oper()) 47 | except OSError: 48 | run(facade_exit(None, False, "oser")) 49 | sys.exit(standard.client_exit) 50 | except InvalidURI: 51 | run(facade_exit(None, False, "iuri")) 52 | sys.exit(standard.client_exit) 53 | except KeyboardInterrupt: 54 | run(facade_exit(None, False, "intr")) 55 | sys.exit(standard.client_exit) 56 | 57 | 58 | @group( 59 | name="expedite", 60 | help="Configure the service particulars before starting it", 61 | context_settings={"show_default": True}, 62 | ) 63 | @option( 64 | "-h", 65 | "--host", 66 | "host", 67 | type=str, 68 | default=standard.client_host, 69 | required=True, 70 | help="Set the address for the service endpoint" 71 | ) 72 | @option( 73 | "-t", 74 | "--time", 75 | "time", 76 | type=IntRange(5, 300), 77 | default=standard.client_time, 78 | required=False, 79 | help="Set the expiry period for participants" 80 | ) 81 | @option( 82 | "-e", 83 | "--endo", 84 | "endo", 85 | type=str, 86 | default=standard.client_endo, 87 | required=False, 88 | help="Set the identity of the opposing client" 89 | ) 90 | @version_option( 91 | version=__versdata__, prog_name="Expedite Prompt by Akashdeep Dhar" 92 | ) 93 | def main( 94 | host: str = standard.client_host, 95 | time: int = standard.client_time, 96 | endo: str = standard.client_endo, 97 | ) -> None: 98 | """ 99 | Configure the service particulars before starting it 100 | 101 | :param host: Location where the exchange service is hosted 102 | :param time: Time for which the client has to wait before disconnecting 103 | :param endo: Network identity of target client for pairing and transfer 104 | :return: 105 | """ 106 | standard.client_host = host 107 | standard.client_time = time 108 | standard.client_endo = endo 109 | 110 | 111 | @main.command( 112 | name="send", 113 | help="Deliver file through an encrypted transfer", 114 | context_settings={"show_default": True}, 115 | ) 116 | @option( 117 | "-p", 118 | "--pswd", 119 | "pswd", 120 | type=str, 121 | default=standard.client_pswd, 122 | help="Set the password for delivering encryption", 123 | ) 124 | @option( 125 | "-f", 126 | "--file", 127 | "file", 128 | type=Path(exists=True), 129 | required=True, 130 | help="Set the filepath for delivering to network", 131 | ) 132 | @option( 133 | "-s", 134 | "--size", 135 | "size", 136 | type=IntRange(1024, 524288, clamp=True), 137 | default=standard.chunking_size, 138 | help="Set the unit size for file chunking (in B)", 139 | ) 140 | def send( 141 | pswd: str = standard.client_pswd, 142 | file: str = standard.client_file, 143 | size: int = standard.chunking_size, 144 | ) -> None: 145 | """ 146 | Configure the service particulars before delivering 147 | 148 | :param pswd: Password for encrypting purposes 149 | :param file: Filepath for delivering file 150 | :param size: Unit size for file chunking 151 | :return: 152 | """ 153 | standard.client_pswd = pswd 154 | standard.client_file = file 155 | standard.chunking_size = size 156 | standard.client_filesize = find_size() 157 | standard.client_filename = find_name() 158 | standard.client_bind = bite_file() 159 | standard.client_plan = "SEND" 160 | work() 161 | 162 | 163 | @main.command( 164 | name="recv", 165 | help="Collect file through an encrypted transfer", 166 | context_settings={"show_default": True}, 167 | ) 168 | @option( 169 | "-p", 170 | "--pswd", 171 | "pswd", 172 | type=str, 173 | required=True, 174 | help="Set the password for collecting encryption" 175 | ) 176 | def recv( 177 | pswd: str = standard.client_pswd 178 | ) -> None: 179 | """ 180 | Configure the service particulars before collecting 181 | 182 | :param pswd: Password for decrypting purposes 183 | :return: 184 | """ 185 | standard.client_pswd = pswd 186 | standard.client_plan = "RECV" 187 | work() 188 | 189 | 190 | if __name__ == "__main__": 191 | main() 192 | -------------------------------------------------------------------------------- /expedite/config/standard.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | import time 25 | from hashlib import sha256 26 | from logging import getLogger 27 | from logging.config import dictConfig 28 | from uuid import uuid4 29 | 30 | server_addr = "127.0.0.1" 31 | server_port = 8080 32 | 33 | server_note = { 34 | "meta": "{sj} is attempting to share file metadata to {oj}.", 35 | "drop": "{sj} is attempting to fetch file contents from {oj}.", 36 | "hash": "{sj} is delivering digest to {oj}.", 37 | "conf": "{sj} is delivering confirmation to {oj}.", 38 | "flub": "{sj} has received mistaken password from {oj}.", 39 | } 40 | 41 | client_host = "" 42 | client_time = 150 43 | client_pswd = uuid4().hex[0:8].upper() 44 | client_plan = "" 45 | client_endo = "" 46 | client_file = "" 47 | client_iden = "" 48 | client_pair = False 49 | client_bind = [] 50 | client_comp = 0 51 | client_hash = sha256() 52 | client_exit = 1 53 | client_strt = time.time() 54 | client_chks = 0 55 | 56 | client_filename = "" 57 | client_filesize = 0 58 | client_fileinit = False 59 | client_saltsize = 16 60 | client_movestrt = 0 61 | client_movestop = 0 62 | client_salt = b"" 63 | client_invc = b"" 64 | client_code = b"" 65 | client_metadone = False 66 | client_progress = False 67 | 68 | chunking_size = 1024 * 64 69 | 70 | connection_dict = dict() 71 | connection_list = set() 72 | 73 | testdict = dict() 74 | 75 | client_note = { 76 | "awry": "Mismatch interactions.", 77 | "lone": "Hitherto paired.", 78 | "dprt": "Node disconnected.", 79 | "intr": "Interrupt received.", 80 | "oser": "Connection failed.", 81 | "iuri": "Mistaken URI.", 82 | "rest": "Expiry achieved.", 83 | "flub": "Mistaken password.", 84 | "succ": "Operation complete.", 85 | "fail": "Operation failed." 86 | } 87 | 88 | client_text = { 89 | "awry": "Interaction cannot proceed further as the both clients have requested for the identical actions from each other. Please try again while ensuring that both clients have requested dissimilar actions this time.", 90 | "lone": "Interaction cannot proceed further as the client that was requested to be connected to is already connected with a different client. Please try again while ensuring that the client is not already connected.", 91 | "dprt": "Interaction cannot proceed further as the client has disconnected from the broker server. Please ensure that the interaction remains stable in your next attempt by requesting for a decreased processing size.", 92 | "intr": "Interaction cannot proceed further as the client has been requested to cancel the ongoing task. Please initiate the interaction again using the interface if the interaction had been aborted unintentionally.", 93 | "oser": "Interaction cannot proceed further as the client cannot connect reliably to the broker server using the provided broker server URI. Please try again after ensuring that the broker server is working properly.", 94 | "iuri": "Interaction cannot proceed further as the client cannot connect reliably to the broker server using the provided broker server URI. Please try again to start interaction after revising the broker server URI.", 95 | "rest": "Interaction cannot proceed further as the client has timed out waiting for pairing. Please consider increasing the expiry time for participants and requesting the other client to connect as soon as possible.", 96 | "flub": "Interaction cannot proceed further as the client has been provided with a mistaken password. Please try again while confirming that the correct password is provided at both delivering and collecting clients.", 97 | "succ": "Contents integrity verified.

The client {iden} has succeeded to {verb} file contents {drct} client {endo}.

File name. {name}
File size. {size}
SHA256 sum. {hash}
Duration. {time}
Mean speed. {spid}", 98 | "fail": "Contents integrity mismatch.

The client {iden} has failed to {verb} file contents {drct} client {endo}.

File name. {name}
File size. {size}
SHA256 sum. {hash}
Duration. {time}
Mean speed. {spid}", 99 | } 100 | 101 | logrconf = { 102 | "version": 1, 103 | "disable_existing_loggers": False, 104 | "formatters": { 105 | "standard": { 106 | "format": "%(asctime)s %(message)s", 107 | "datefmt": "[%Y-%m-%d %H:%M:%S]", 108 | }, 109 | }, 110 | "handlers": { 111 | "console": { 112 | "level": "INFO", 113 | "formatter": "standard", 114 | "class": "logging.StreamHandler", 115 | "stream": "ext://sys.stdout", 116 | }, 117 | }, 118 | "root": { 119 | "level": "INFO", 120 | "handlers": ["console"], 121 | }, 122 | } 123 | 124 | dictConfig(logrconf) 125 | 126 | logger = getLogger(__name__) 127 | -------------------------------------------------------------------------------- /expedite/client/prompt/room.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | import sys 25 | import time 26 | from asyncio import ensure_future, get_event_loop 27 | from datetime import datetime 28 | from json import loads 29 | 30 | from tqdm.asyncio import tqdm 31 | from tqdm.contrib.logging import logging_redirect_tqdm 32 | from websockets import connect 33 | from websockets.exceptions import ConnectionClosed 34 | from websockets.legacy.client import WebSocketClientProtocol 35 | 36 | from expedite.client.base import ease_size, fuse_file 37 | from expedite.client.conn import ( 38 | collect_confirmation, 39 | collect_connection_from_pairness, 40 | collect_connection_to_server, 41 | collect_contents, 42 | collect_digest_checks, 43 | collect_dropping_summon, 44 | collect_metadata, 45 | collect_separation_from_mistaken_password, 46 | deliver_confirmation, 47 | deliver_connection_to_server, 48 | deliver_contents, 49 | deliver_digest_checks, 50 | deliver_dropping_summon, 51 | deliver_metadata, 52 | deliver_separation_from_mistaken_password, 53 | ) 54 | from expedite.client.prompt.util import deliver_suspension_from_expiry_prompt, facade_exit 55 | from expedite.config import standard 56 | from expedite.view import general 57 | 58 | 59 | async def oper() -> None: 60 | """ 61 | Exchange data to the target client after connecting to the exchange server 62 | 63 | :return: 64 | """ 65 | try: 66 | async with connect(standard.client_host) as sock: 67 | get_event_loop().call_later(standard.client_time, lambda: ensure_future(deliver_suspension_from_expiry_prompt(sock))) 68 | await deliver_connection_to_server(sock) 69 | async for mesgcont in sock: 70 | if isinstance(mesgcont, str): 71 | mesgdict = loads(mesgcont) 72 | # If the data received is of STRING type 73 | if standard.client_plan in ["SEND", "RECV"]: 74 | # If the purpose of the client is either DELIVERING or COLLECTING 75 | if mesgdict["call"] == "okay": 76 | await collect_connection_to_server(mesgdict["iden"]) 77 | elif mesgdict["call"] in ["awry", "lone"]: 78 | await facade_exit(sock, False, mesgdict["call"]) 79 | if standard.client_plan == "SEND": 80 | # If the purpose of the client is DELIVERING 81 | if mesgdict["call"] == "note": 82 | await collect_connection_from_pairness(mesgdict["part"]) 83 | await deliver_metadata(sock) 84 | elif mesgdict["call"] == "conf": 85 | complete = await collect_confirmation(mesgdict["data"]) 86 | await facade_exit(sock, complete, "done" if complete else "dprt") 87 | elif mesgdict["call"] == "flub": 88 | await collect_separation_from_mistaken_password() 89 | await facade_exit(sock, False, "flub") 90 | elif mesgdict["call"] == "drop": 91 | await collect_dropping_summon() 92 | await show_deliver_contents(sock) 93 | await deliver_digest_checks(sock) 94 | else: 95 | # If the purpose of the client is COLLECTING 96 | if mesgdict["call"] == "note": 97 | await collect_connection_from_pairness(mesgdict["part"]) 98 | elif mesgdict["call"] == "hash": 99 | await collect_digest_checks() 100 | complete = await deliver_confirmation(sock, mesgdict["data"]) 101 | await facade_exit(sock, complete, "done" if complete else "dprt") 102 | else: 103 | # If the data received is of BYTES type 104 | if standard.client_plan == "RECV": 105 | # If the purpose of the client is COLLECTING 106 | if not standard.client_metadone: 107 | if await collect_metadata(mesgcont): 108 | await deliver_dropping_summon(sock) 109 | else: 110 | await deliver_separation_from_mistaken_password(sock) 111 | await facade_exit(sock, False, "flub") 112 | else: 113 | await show_collect_contents(sock, mesgcont) 114 | sys.exit(standard.client_exit) 115 | except ConnectionClosed: 116 | await facade_exit(sock, False, "dprt") 117 | sys.exit(standard.client_exit) 118 | 119 | 120 | async def show_deliver_contents(sock: WebSocketClientProtocol) -> bool: 121 | """ 122 | Facilitate encrypting and delivering file contents 123 | 124 | :param sock: Websocket object belonging to the server session 125 | :return: Confirmation of the action completion 126 | """ 127 | general(f"Delivering contents for '{standard.client_filename}' ({ease_size(standard.client_filesize)}) to {standard.client_endo}.") 128 | standard.client_movestrt = time.time() 129 | with logging_redirect_tqdm(): 130 | with tqdm(total=standard.client_filesize, unit="B", unit_scale=True, unit_divisor=1024, leave=False, initial=0) as prog: 131 | async for dgst, size in deliver_contents(sock): 132 | prog.set_description(f"{datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")} SHA256 {dgst}") 133 | prog.update(size) 134 | return True 135 | 136 | 137 | async def show_collect_contents(sock: WebSocketClientProtocol, pack: bytes = b"") -> bool: 138 | """ 139 | Facilitate collecting and decrypting file contents 140 | 141 | :param sock: Websocket object belonging to the server session 142 | :param pack: Byte chunk that is to be collected and decrypted 143 | :return: Confirmation of the action completion 144 | """ 145 | general(f"Collecting contents for '{standard.client_filename}' ({ease_size(standard.client_filesize)}) from {standard.client_endo}.") 146 | standard.client_movestrt = time.time() 147 | fuse_file(pack) 148 | with logging_redirect_tqdm(): 149 | with tqdm(total=standard.client_filesize, unit="B", unit_scale=True, unit_divisor=1024, leave=False, initial=len(pack)) as prog: 150 | async for dgst, size in collect_contents(sock): 151 | prog.set_description(f"{datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")} SHA256 {dgst}") 152 | prog.update(size) 153 | return True 154 | -------------------------------------------------------------------------------- /expedite/client/bridge/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | from os.path import exists 25 | 26 | from PySide6.QtWidgets import QFileDialog 27 | 28 | 29 | def show_location_dialog(parent=None, oper: str = "") -> str: 30 | """ 31 | Select filepath for the intended file for delivering or collecting 32 | 33 | :param parent: Parent window within which the location dialog exists 34 | :param oper: Operation intent for choosing either file or directory 35 | :return: 36 | """ 37 | dialog = QFileDialog() 38 | if oper == "dlvr": 39 | client_path = dialog.getOpenFileName(parent, "Select location", "", "All Files (*)")[0] 40 | else: 41 | client_path = dialog.getExistingDirectory(parent, "Select location", "", QFileDialog.ShowDirsOnly) 42 | return client_path 43 | 44 | 45 | def truncate_text(text: str = "", size: int = 32) -> str: 46 | """ 47 | Limit text elements to a certain character count for an elegant fit 48 | 49 | :param text: Text elements that need to be checked for fitting 50 | :param size: Character count within which text needs to be fit 51 | :return: Truncated text 52 | """ 53 | if len(text) >= size: 54 | return text[0:size-3] + "..." 55 | else: 56 | return text 57 | 58 | 59 | def return_detail_text() -> str: 60 | """ 61 | Retrieve application information text for showing on the dialog box 62 | 63 | :return: Application information 64 | """ 65 | text = """ 66 | Expedite Bridge v{vers}
67 | A simple encrypted file transfer service for humans

68 | Expedite is a simple encrypted file transfer service that allows for people to share synchronously assets among each other without having to rely on third party file sharing services (and constantly worrying about how their data might be used) or feeling the need of having publicly visible IP addresses (and constantly worrying about script kiddies attacking your computer).

69 | Expedite Server can be deployed on a virtual private server having an IP address that is discoverable by the Expedite Client users to broker file contents. The transfers facilitated using WebSockets are end-to-end encrypted with the use of 128-bit Advanced Encryption Standard and the server is restricted to logging only unidentifiable activities to the volatile memory.

70 | Expedite is currently in BETA phase and if you like to direction the project is heading towards, kindly consider helping me out by starring the project repository, filing issue tickets for software errors or feature requests, contributing to the codebase of the project or sponsoring me to help maintain the servers and to help me keep working on more FOSS projects like these.

71 | """ 72 | return text 73 | 74 | 75 | class ValidateFields: 76 | def __init__(self) -> None: 77 | """ 78 | Initialize fields validation class for confirmation 79 | 80 | :return: 81 | """ 82 | self.okay = { 83 | "size": False, 84 | "time": False, 85 | "file": False, 86 | "path": False, 87 | "pswd": False, 88 | } 89 | self.text = { 90 | "size": "Processing size must be an integer value between 1024 and 524288", 91 | "time": "Expiry window must be an integer value between 5 and 300", 92 | "file": "Filepath for delivering or collecting contents must exist", 93 | "path": "Filepath for delivering or collecting contents must exist", 94 | "pswd": "Password cannot be an empty string" 95 | } 96 | 97 | def verify_size(self, size) -> None: 98 | """ 99 | Ensure that processing size must be an integer value between 1024 and 524288 100 | 101 | :param size: Processing size 102 | :return: 103 | """ 104 | self.okay["size"] = True 105 | try: 106 | oper = int(size.strip()) 107 | if oper not in range(1024, 524288 + 1): 108 | self.okay["size"] = False 109 | except ValueError: 110 | self.okay["size"] = False 111 | 112 | def verify_time(self, time) -> None: 113 | """ 114 | Ensure that expiry window must be an integer value between 5 and 300 115 | 116 | :param time: Expiry window 117 | :return: 118 | """ 119 | self.okay["time"] = True 120 | try: 121 | oper = int(time.strip()) 122 | if oper not in range(5, 300 + 1): 123 | self.okay["time"] = False 124 | except ValueError: 125 | self.okay["time"] = False 126 | 127 | def verify_file(self, file) -> None: 128 | """ 129 | Ensure that filepath for delivering or collecting contents must exist 130 | 131 | :param file: Load filepath 132 | :return: 133 | """ 134 | self.okay["file"] = True 135 | if not exists(file): 136 | self.okay["file"] = False 137 | 138 | def verify_path(self, path) -> None: 139 | """ 140 | Ensure that filepath for delivering or collecting contents must exist 141 | 142 | :param path: Save filepath 143 | :return: 144 | """ 145 | self.okay["path"] = True 146 | if path.strip() != "" and not exists(path): 147 | self.okay["path"] = False 148 | 149 | def verify_pswd(self, pswd) -> None: 150 | """ 151 | Ensure that password cannot be an empty string 152 | 153 | :param pswd: Password string 154 | :return: 155 | """ 156 | self.okay["pswd"] = True 157 | if pswd.strip() == "": 158 | self.okay["pswd"] = False 159 | 160 | def report_dlvr(self, size, time, file, pswd) -> tuple[tuple[bool, bool, bool, bool], str]: 161 | """ 162 | Retrieve field validation results for delivering intent 163 | 164 | :param size: Validity confirmation of processing size 165 | :param time: Validity confirmation of waiting window 166 | :param file: Validity confirmation of delivering filepath 167 | :param pswd: Validity confirmation of password string 168 | :return: Validity confirmation for required elements 169 | """ 170 | self.verify_size(size) 171 | self.verify_time(time) 172 | self.verify_file(file) 173 | self.verify_pswd(pswd) 174 | lict = [self.text[indx] for indx in ["size", "time", "file", "pswd"] if not self.okay[indx]] 175 | return (self.okay["size"], self.okay["time"], self.okay["file"], self.okay["pswd"]), "\n".join(lict).strip() 176 | 177 | def report_clct(self, time, path, pswd) -> tuple[tuple[bool, bool, bool], str]: 178 | """ 179 | Retrieve field validation results for collecting intent 180 | 181 | :param time: Validity confirmation of waiting window 182 | :param path: Validity confirmation of collecting filepath 183 | :param pswd: Validity confirmation of password string 184 | :return: Validity confirmation for required elements 185 | """ 186 | self.verify_time(time) 187 | self.verify_path(path) 188 | self.verify_pswd(pswd) 189 | lict = [self.text[indx] for indx in ["time", "path", "pswd"] if not self.okay[indx]] 190 | return (self.okay["time"], self.okay["path"], self.okay["pswd"]), "\n".join(lict).strip() 191 | -------------------------------------------------------------------------------- /expedite/client/conn.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | import asyncio 25 | import time 26 | from hashlib import sha256 27 | from json import dumps 28 | from typing import Generator, Tuple 29 | 30 | from websockets.legacy.client import WebSocketClientProtocol 31 | 32 | from expedite.client.auth import decr_metadata, encr_metadata 33 | from expedite.client.base import ease_size, fuse_file, read_file 34 | from expedite.client.excp import PasswordMistaken 35 | from expedite.config import standard 36 | from expedite.view import general, warning 37 | 38 | 39 | async def deliver_connection_to_server(sock: WebSocketClientProtocol) -> bool: 40 | """ 41 | Inform target client of connecting to the exchange server 42 | 43 | :param sock: Websocket object belonging to the server session 44 | :return: Confirmation of the action completion 45 | """ 46 | general("Attempting to connect to the network.") 47 | try: 48 | await sock.send(dumps({"call": "join", "plan": standard.client_plan, "scan": standard.client_endo, "wait": standard.client_time})) 49 | return True 50 | except Exception as expt: 51 | warning(f"Failed to connect to the network - {expt}.") 52 | return False 53 | 54 | 55 | async def collect_connection_to_server(iden: str = standard.client_iden) -> bool: 56 | """ 57 | Show textual message of connecting to the exchange server 58 | 59 | :param iden: Network identity of person client 60 | :return: Confirmation of the action completion 61 | """ 62 | standard.client_iden = iden 63 | general("Successfully connected to the network.") 64 | warning(f"You are now identified as {iden} in the network.") 65 | return True 66 | 67 | 68 | async def deliver_suspension_from_expiry(sock: WebSocketClientProtocol) -> bool: 69 | """ 70 | Inform exchange server about the disconnection from timed expiry 71 | 72 | :param sock: Websocket object belonging to the server session 73 | :return: Confirmation of the action completion 74 | """ 75 | if not standard.client_pair: 76 | general("Attempting to abandon from the network after expiry.") 77 | await sock.send(dumps({"call": "rest"})) 78 | return True 79 | else: 80 | return False 81 | 82 | 83 | async def collect_connection_from_pairness(iden: str = standard.client_endo) -> bool: 84 | """ 85 | Show textual message of collecting confirmation of pairing 86 | 87 | :param iden: Network identity of target client 88 | :return: Confirmation of the action completion 89 | """ 90 | standard.client_endo = iden 91 | standard.client_pair = True 92 | general(f"Attempting pairing with {standard.client_endo}.") 93 | warning("Starting transmission.") 94 | return True 95 | 96 | 97 | async def deliver_metadata(sock: WebSocketClientProtocol) -> bool: 98 | """ 99 | Inform target client of encrypting and delivering the metadata 100 | 101 | :param sock: Websocket object belonging to the server session 102 | :return: Confirmation of the action completion 103 | """ 104 | general("Generating cryptography sign.") 105 | await sock.send(encr_metadata()) 106 | return True 107 | 108 | 109 | async def collect_metadata(pack: bytes = b"") -> bool: 110 | """ 111 | Show textual message of collecting and decrypting the metadata 112 | 113 | :param pack: Encrypted metadata collected from target client 114 | :return: Confirmation of the action completion 115 | """ 116 | try: 117 | general("Generating cryptography sign.") 118 | standard.client_filename, standard.client_filesize, standard.client_chks = decr_metadata(pack) 119 | return True 120 | except PasswordMistaken: 121 | return False 122 | 123 | 124 | async def deliver_dropping_summon(sock: WebSocketClientProtocol) -> bool: 125 | """ 126 | Inform target client of starting the exchange process 127 | 128 | :param sock: Websocket object belonging to the server session 129 | :return: Confirmation of the action completion 130 | """ 131 | await sock.send(dumps({"call": "drop"})) 132 | general(f"Delivering collection summon to {standard.client_endo}.") 133 | return True 134 | 135 | 136 | async def collect_dropping_summon() -> bool: 137 | """ 138 | Show textual message of starting the exchange process 139 | 140 | :return: Confirmation of the action completion 141 | """ 142 | general(f"Collecting delivering summon from {standard.client_endo}.") 143 | return True 144 | 145 | 146 | async def deliver_contents(sock: WebSocketClientProtocol) -> Generator[Tuple[bytes, int], None, None]: 147 | """ 148 | Load contents from intended file and deliver them to target client 149 | 150 | :param sock: Websocket object belonging to the server session 151 | :return: Tuple of file contents and contents length 152 | """ 153 | for indx in range(0, len(standard.client_bind) - 1): 154 | bite = read_file(standard.client_bind[indx], standard.client_bind[indx + 1]) 155 | await sock.send(bite) 156 | await asyncio.sleep(0) 157 | yield sha256(bite).hexdigest(), len(bite) 158 | 159 | 160 | async def collect_contents(sock: WebSocketClientProtocol) -> Generator[Tuple[bytes, int], None, None]: 161 | """ 162 | Collect contents from target client and save them to intended file 163 | 164 | :param sock: Websocket object belonging to the server session 165 | :return: Tuple of file contents and contents length 166 | """ 167 | for _ in range(standard.client_chks - 1): 168 | mesgcont = await sock.recv() 169 | if isinstance(mesgcont, bytes): 170 | fuse_file(mesgcont) 171 | await asyncio.sleep(0) 172 | yield sha256(mesgcont).hexdigest(), len(mesgcont) - 16 173 | 174 | 175 | async def deliver_digest_checks(sock: WebSocketClientProtocol) -> bool: 176 | """ 177 | Inform target client with message digest of file contents 178 | 179 | :param sock: Websocket object belonging to the server session 180 | :return: Confirmation of the action completion 181 | """ 182 | general("Delivering contents digest for confirmation.") 183 | await sock.send(dumps({"call": "hash", "data": standard.client_hash.hexdigest()})) 184 | return True 185 | 186 | 187 | async def collect_digest_checks() -> bool: 188 | """ 189 | Show textual message with message digest of file contents 190 | 191 | :return: Confirmation of the action completion 192 | """ 193 | general("Collecting contents digest for confirmation.") 194 | return True 195 | 196 | 197 | async def deliver_confirmation(sock: WebSocketClientProtocol, data: str = standard.client_hash.hexdigest()) -> bool: 198 | """ 199 | Inform target client of the file contents integrity confirmation 200 | 201 | :param sock: Websocket object belonging to the server session 202 | :param data: Message digest from the file contents exchanged 203 | :return: 204 | """ 205 | standard.client_movestop = time.time() 206 | if data == standard.client_hash.hexdigest(): 207 | general(f"Contents integrity verified (Mean {ease_size(standard.client_filesize / (standard.client_movestop - standard.client_movestrt))}/s).") 208 | await sock.send(dumps({"call": "conf", "data": 1})) 209 | return True 210 | else: 211 | general(f"Contents integrity mismatch (Mean {ease_size(standard.client_filesize / (standard.client_movestop - standard.client_movestrt))}/s).") 212 | await sock.send(dumps({"call": "conf", "data": 0})) 213 | return False 214 | 215 | 216 | async def collect_confirmation(data: int = 0) -> bool: 217 | """ 218 | Show textual message of the file contents integrity confirmation 219 | 220 | :param data: Confirmation of the message digest comparison 221 | :return: Confirmation of the action completion 222 | """ 223 | standard.client_movestop = time.time() 224 | if bool(data): 225 | general(f"Contents integrity verified (Mean {ease_size(standard.client_filesize / (standard.client_movestop - standard.client_movestrt))}/s).") 226 | return True 227 | else: 228 | general(f"Contents integrity mismatch (Mean {ease_size(standard.client_filesize / (standard.client_movestop - standard.client_movestrt))}/s).") 229 | return False 230 | 231 | 232 | async def deliver_separation_from_mistaken_password(sock: WebSocketClientProtocol) -> bool: 233 | """ 234 | Inform target client of disconnection due to mistaken password 235 | 236 | :param sock: Websocket object belonging to the server session 237 | :return: Confirmation of the action completion 238 | """ 239 | general("Delivering status update on mistaken password.") 240 | await sock.send(dumps({"call": "flub"})) 241 | return True 242 | 243 | 244 | async def collect_separation_from_mistaken_password() -> bool: 245 | """ 246 | Show textual message of disconnection due to mistaken password 247 | 248 | :return: Confirmation of the action completion 249 | """ 250 | general("Collecting status update on mistaken password.") 251 | return True 252 | -------------------------------------------------------------------------------- /expedite/server/conn.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | from json import dumps 25 | from uuid import uuid4 26 | 27 | from websockets.legacy.server import WebSocketServerProtocol 28 | 29 | from expedite.config import standard 30 | from expedite.server.base import ExpediteConnection 31 | from expedite.view import failure, general, success, warning 32 | 33 | 34 | async def exchange_insert(sock: WebSocketServerProtocol, plan: str = standard.client_plan, scan: str = standard.client_endo, time: int = standard.client_time) -> str | bool: 35 | """ 36 | Comply with the client request of joining the network 37 | 38 | If client ABC joins before client XYZ 39 | - assuming that client ABC wants to connect to client XYZ 40 | - client ABC does not need to provide any target identity 41 | - client XYZ has to provide client ABC as target identity 42 | 43 | :param sock: Websocket object belonging to the connecting client 44 | :param plan: Operation intent of the connecting client - This can be either SEND or RECV depending on the purpose 45 | :param scan: Target client sought by the connecting client - This can be either empty string or hexadecimal string 46 | :param time: Identity provided by the exchange server to the connecting client to be recognized within the network 47 | :return: Confirmation of the action completion 48 | """ 49 | if sock not in standard.connection_dict: 50 | if plan in ["SEND", "RECV"]: 51 | iden = uuid4().hex[0:8].upper() 52 | standard.connection_dict[sock] = ExpediteConnection(iden, plan, scan, time) 53 | if plan == "SEND": 54 | warning(f"{iden} joined with the intention of delivering.") 55 | elif plan == "RECV": 56 | warning(f"{iden} joined with the intention of collecting.") 57 | if scan == "": 58 | general(f"{iden} is waiting for client for {time} seconds.") 59 | else: 60 | general(f"{iden} is looking for {scan} for {time} seconds.") 61 | await sock.send(dumps({"call": "okay", "iden": iden})) 62 | return iden 63 | else: 64 | return False 65 | else: 66 | return False 67 | 68 | 69 | async def exchange_remove(sock: WebSocketServerProtocol) -> bool: 70 | """ 71 | Inform the client about them being booted off the network 72 | 73 | Here is how the logic works - 74 | 75 | Once participant ABC is flagged for removal either from client side or server side 76 | - If participant ABC exists in the connection dictionary 77 | - If participant ABC is paired with participant XYZ 78 | - Participant XYZ is disconnected from the network and removed from the connection dictionary 79 | - Participant ABC is disconnected from the network and removed from the connection dictionary 80 | - If participant ABC is not paired with anyone else 81 | - Participant ABC is disconnected from the network and removed from the connection dictionary 82 | - If participant ABC does not exist in the connection dictionary 83 | - Do nothing 84 | 85 | :param sock: Websocket object belonging to the disconnecting client 86 | :return: Confirmation of the action completion 87 | """ 88 | if sock in standard.connection_dict: 89 | if standard.connection_dict[sock].ptsc in standard.connection_dict and standard.connection_dict[sock].ptsc.state == 1: 90 | warning(f"{standard.connection_dict[sock].ptid} left.") 91 | await standard.connection_dict[sock].ptsc.close(code=1000) 92 | standard.connection_dict.pop(standard.connection_dict[sock].ptsc) 93 | warning(f"{standard.connection_dict[sock].iden} left.") 94 | await sock.close(code=1000) 95 | standard.connection_dict.pop(sock) 96 | return True 97 | else: 98 | return False 99 | 100 | 101 | async def exchange_inform(sock: WebSocketServerProtocol, plan: str = standard.client_plan, scan: str = standard.client_endo, iden: str = standard.client_iden) -> int: 102 | """ 103 | Inform the client about them being able to join the network 104 | 105 | Here is how the logic works - 106 | 107 | After client ABC is able to join the network with operation intent P 108 | - If client ABC has provided that they are looking for client XYZ for DEF seconds 109 | - If client XYZ has not yet joined the network 110 | - Client ABC will wait until the client XYZ will join the network [Code 3] 111 | - Client ABC will disconnect after DEF seconds if the client XYZ does not turn up 112 | - If client XYZ has been connected to the network for a while 113 | - If client XYZ is not yet paired with anyone else 114 | - If client XYZ has the operation intent Q (opposite of operation intent P of client ABC) 115 | - Client ABC will be paired with client XYZ due to the positive operation intents [Code 0] 116 | - Client XYZ will be paired with client ABC due to the positive operation intents [Code 0] 117 | - If client XYZ has the operation intent P (selfsame of operation intent P of client ABC) 118 | - Client ABC will be booted off the network due to the negative operation intents [Code 1] 119 | - Client XYZ will be booted off the network due to the negative operation intents [Code 1] 120 | - If client XYZ has already paired with anyone else 121 | - Client ABC will be booted off the network as client XYZ is already paired [Code 2] 122 | - If client ABC has provided that they are waiting for connection for DEF seconds 123 | - Client ABC will wait until they are connected with some client [Code 3] 124 | - Client ABC will disconnect after DEF seconds if they are not paired until then 125 | 126 | :param sock: Websocket object belonging to the connecting client 127 | :param plan: Operation intent of the connecting client - This can be either SEND or RECV depending on the purpose 128 | :param scan: Target client sought by the connecting client - This can be either empty string or hexadecimal string 129 | :param iden: Identity provided by the exchange server to the connecting client to be recognized within the network 130 | :return: Confirmation of the action completion 131 | """ 132 | for indx in standard.connection_dict: 133 | if standard.connection_dict[indx].iden == scan: 134 | if not standard.connection_dict[indx].ptsc: 135 | if plan != standard.connection_dict[indx].plan: 136 | success(f"{iden} and {scan} are positively paired.") 137 | await indx.send(dumps({"call": "note", "part": iden})) 138 | await sock.send(dumps({"call": "note", "part": scan})) 139 | standard.connection_dict[indx].pair_connection(iden, sock) 140 | standard.connection_dict[sock].pair_connection(scan, indx) 141 | return 0 142 | else: 143 | failure(f"{iden} and {scan} are negatively paired.") 144 | await indx.send(dumps({"call": "awry", "part": iden})) 145 | await sock.send(dumps({"call": "awry", "part": scan})) 146 | return 1 147 | else: 148 | failure(f"{iden} and {scan} cannot pair as {scan} is already paired.") 149 | await sock.send(dumps({"call": "lone", "part": scan})) 150 | return 2 151 | return 3 152 | 153 | 154 | async def exchange_json(sock: WebSocketServerProtocol, note: str = "", data: str = "") -> bool: 155 | """ 156 | Convey the JSON elements from delivering client to collecting client 157 | 158 | :param sock: Websocket object belonging to the delivering client 159 | :param note: Action requested to be performed by the collecting client 160 | :param data: Data elements that are to be conveyed across 161 | :return: Confirmation of the action completion 162 | """ 163 | general(standard.server_note[note].format(sj=standard.connection_dict[sock].iden, oj=standard.connection_dict[sock].ptid)) 164 | if sock in standard.connection_dict: 165 | if standard.connection_dict[sock].ptsc in standard.connection_dict and standard.connection_dict[sock].ptsc.state == 1: 166 | await standard.connection_dict[sock].ptsc.send(data) 167 | return True 168 | else: 169 | return False 170 | else: 171 | return False 172 | 173 | 174 | async def exchange_byte(sock: WebSocketServerProtocol, pack: bytes = b"") -> bool: 175 | """ 176 | Convey the file contents from delivering client to collecting client 177 | 178 | :param sock: Websocket object belonging to the delivering client 179 | :param pack: File contents that are to be conveyed across 180 | :return: Confirmation of the action completion 181 | """ 182 | if sock in standard.connection_dict: 183 | if standard.connection_dict[sock].ptsc in standard.connection_dict and standard.connection_dict[sock].ptsc.state == 1: 184 | await standard.connection_dict[sock].ptsc.send(pack) 185 | return True 186 | else: 187 | general(f"{standard.connection_dict[sock].iden} could not deliver contents to {standard.connection_dict[sock].ptid} as {standard.connection_dict[sock].ptid} is no longer connected.") 188 | return False 189 | else: 190 | general("Attempting for aborting connection.") 191 | return False 192 | -------------------------------------------------------------------------------- /data/test-mumb-12112024.txt: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | testssl 3.2rc3 from https://testssl.sh/dev/ 3 | 4 | This program is free software. Distribution and 5 | modification under GPLv2 permitted. 6 | USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK! 7 | 8 | Please file bugs @ https://testssl.sh/bugs/ 9 | 10 | ########################################################### 11 | 12 | Using "OpenSSL 3.2.2 4 Jun 2024 (Library: OpenSSL 3.2.2 4 Jun 2024)" [~94 ciphers] 13 | on fedohide-origin:/usr/bin/openssl 14 | (built: "Sep 12 00:00:00 2024", platform: "linux-x86_64") 15 | 16 | 17 | Start 2024-11-12 05:22:18 -->> ***.***.***.***:443 (expedite-mumb.gridhead.net) <<-- 18 | 19 | rDNS (***.***.***.***): -- 20 | Service detected: HTTP 21 | 22 | 23 | Testing protocols via sockets except NPN+ALPN 24 | 25 | SSLv2 not offered (OK) 26 | SSLv3 not offered (OK) 27 | TLS 1 not offered 28 | TLS 1.1 not offered 29 | TLS 1.2 offered (OK) 30 | TLS 1.3 offered (OK): final 31 | NPN/SPDY not offered 32 | ALPN/HTTP2 not offered 33 | 34 | Testing cipher categories 35 | 36 | NULL ciphers (no encryption) not offered (OK) 37 | Anonymous NULL Ciphers (no authentication) not offered (OK) 38 | Export ciphers (w/o ADH+NULL) not offered (OK) 39 | LOW: 64 Bit + DES, RC[2,4], MD5 (w/o export) not offered (OK) 40 | Triple DES Ciphers / IDEA not offered 41 | Obsoleted CBC ciphers (AES, ARIA etc.) offered 42 | Strong encryption (AEAD ciphers) with no FS not offered 43 | Forward Secrecy strong encryption (AEAD ciphers) offered (OK) 44 | 45 | 46 | Testing server's cipher preferences 47 | 48 | Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits Cipher Suite Name (IANA/RFC) 49 | ----------------------------------------------------------------------------------------------------------------------------- 50 | SSLv2 51 | - 52 | SSLv3 53 | - 54 | TLSv1 55 | - 56 | TLSv1.1 57 | - 58 | TLSv1.2 (server order -- server prioritizes ChaCha ciphers when preferred by clients) 59 | xc02c ECDHE-ECDSA-AES256-GCM-SHA384 ECDH 253 AESGCM 256 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 60 | xcca9 ECDHE-ECDSA-CHACHA20-POLY1305 ECDH 253 ChaCha20 256 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 61 | xc0ad ECDHE-ECDSA-AES256-CCM ECDH 253 AESCCM 256 TLS_ECDHE_ECDSA_WITH_AES_256_CCM 62 | xc02b ECDHE-ECDSA-AES128-GCM-SHA256 ECDH 253 AESGCM 128 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 63 | xc0ac ECDHE-ECDSA-AES128-CCM ECDH 253 AESCCM 128 TLS_ECDHE_ECDSA_WITH_AES_128_CCM 64 | xc023 ECDHE-ECDSA-AES128-SHA256 ECDH 253 AES 128 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 65 | xc00a ECDHE-ECDSA-AES256-SHA ECDH 253 AES 256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 66 | xc009 ECDHE-ECDSA-AES128-SHA ECDH 253 AES 128 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 67 | TLSv1.3 (server order -- server prioritizes ChaCha ciphers when preferred by clients) 68 | x1302 TLS_AES_256_GCM_SHA384 ECDH 253 AESGCM 256 TLS_AES_256_GCM_SHA384 69 | x1303 TLS_CHACHA20_POLY1305_SHA256 ECDH 253 ChaCha20 256 TLS_CHACHA20_POLY1305_SHA256 70 | x1301 TLS_AES_128_GCM_SHA256 ECDH 253 AESGCM 128 TLS_AES_128_GCM_SHA256 71 | x1304 TLS_AES_128_CCM_SHA256 ECDH 253 AESCCM 128 TLS_AES_128_CCM_SHA256 72 | 73 | Has server cipher order? yes (OK) -- TLS 1.3 and below 74 | 75 | 76 | Testing robust forward secrecy (FS) -- omitting Null Authentication/Encryption, 3DES, RC4 77 | 78 | FS is offered (OK) TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-CCM TLS_AES_128_GCM_SHA256 TLS_AES_128_CCM_SHA256 ECDHE-ECDSA-AES128-GCM-SHA256 79 | ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES128-SHA ECDHE-ECDSA-AES128-CCM 80 | Elliptic curves offered: prime256v1 secp384r1 secp521r1 X25519 X448 81 | Finite field group: ffdhe2048 ffdhe3072 ffdhe4096 ffdhe6144 ffdhe8192 82 | TLS 1.2 sig_algs offered: ECDSA+SHA256 ECDSA+SHA384 ECDSA+SHA512 ECDSA+SHA224 83 | TLS 1.3 sig_algs offered: ECDSA+SHA256 84 | 85 | Testing server defaults (Server Hello) 86 | 87 | TLS extensions (standard) "renegotiation info/#65281" "server name/#0" "EC point formats/#11" "session ticket/#35" "supported versions/#43" "key share/#51" "max fragment length/#1" "encrypt-then-mac/#22" "extended master secret/#23" 88 | Session Ticket RFC 5077 hint 7200 seconds, session tickets keys seems to be rotated < daily 89 | SSL Session ID support yes 90 | Session Resumption Tickets: yes, ID: yes 91 | TLS clock skew Random values, no fingerprinting possible 92 | Certificate Compression none 93 | Client Authentication none 94 | Signature Algorithm ECDSA with SHA384 95 | Server key size EC 256 bits (curve P-256) 96 | Server key usage Digital Signature 97 | Server extended key usage TLS Web Server Authentication, TLS Web Client Authentication 98 | Serial 032949A41F4938FAF4C1DBAA984F965F6380 (OK: length 18) 99 | Fingerprints SHA1 7FA23560DDD26C28EF497C286F59F411C367F61F 100 | SHA256 10772449545FC60A04A177BB84611F12BCB2FBA179B5675AEEC6DB23E2A2ECD9 101 | Common Name (CN) *.gridhead.net (CN in response to request w/o SNI: *.apexaltruism.net ) 102 | subjectAltName (SAN) *.gridhead.net gridhead.net 103 | Trust (hostname) Ok via SAN wildcard and CN wildcard (SNI mandatory) 104 | Chain of trust basename: extra operand ‘/etc/pki/tls/fips_local.cnf’ 105 | Try 'basename --help' for more information. 106 | "/etc/pki/tls/*.pem" cannot be found / not readable 107 | EV cert (experimental) no 108 | Certificate Validity (UTC) 62 >= 30 days (2024-10-16 05:05 --> 2025-01-14 05:05) 109 | ETS/"eTLS", visibility info not present 110 | Certificate Revocation List -- 111 | OCSP URI http://e5.o.lencr.org 112 | OCSP stapling not offered 113 | OCSP must staple extension -- 114 | DNS CAA RR (experimental) not offered 115 | Certificate Transparency yes (certificate extension) 116 | Certificates provided 2 117 | Issuer E5 (Let's Encrypt from US) 118 | Intermediate cert validity #1: ok > 40 days (2027-03-12 23:59). E5 <-- ISRG Root X1 119 | Intermediate Bad OCSP (exp.) Ok 120 | 121 | 122 | Testing HTTP header response @ "/" 123 | 124 | HTTP Status Code 426 Upgrade Required. Oh, didn't expect "426 Upgrade Required" 125 | HTTP clock skew -1 sec from localtime 126 | Strict Transport Security not offered 127 | Public Key Pinning -- 128 | Server banner Python/3.12 websockets/12.0 129 | Application banner -- 130 | Cookie(s) (none issued at "/") -- maybe better try target URL of 30x 131 | Security headers Upgrade: websocket 132 | Reverse Proxy banner -- 133 | 134 | 135 | Testing vulnerabilities 136 | 137 | Heartbleed (CVE-2014-0160) not vulnerable (OK), no heartbeat extension 138 | CCS (CVE-2014-0224) not vulnerable (OK) 139 | Ticketbleed (CVE-2016-9244), experiment. not vulnerable (OK) 140 | ROBOT Server does not support any cipher suites that use RSA key transport 141 | Secure Renegotiation (RFC 5746) supported (OK) 142 | Secure Client-Initiated Renegotiation not vulnerable (OK) 143 | CRIME, TLS (CVE-2012-4929) not vulnerable (OK) 144 | BREACH (CVE-2013-3587) no gzip/deflate/compress/br HTTP compression (OK) - only supplied "/" tested 145 | POODLE, SSL (CVE-2014-3566) not vulnerable (OK), no SSLv3 support 146 | TLS_FALLBACK_SCSV (RFC 7507) No fallback possible (OK), no protocol below TLS 1.2 offered 147 | SWEET32 (CVE-2016-2183, CVE-2016-6329) not vulnerable (OK) 148 | FREAK (CVE-2015-0204) not vulnerable (OK) 149 | DROWN (CVE-2016-0800, CVE-2016-0703) not vulnerable on this host and port (OK) 150 | no RSA certificate, thus certificate can't be used with SSLv2 elsewhere 151 | LOGJAM (CVE-2015-4000), experimental not vulnerable (OK): no DH EXPORT ciphers, no DH key detected with <= TLS 1.2 152 | BEAST (CVE-2011-3389) not vulnerable (OK), no SSL3 or TLS1 153 | LUCKY13 (CVE-2013-0169), experimental potentially VULNERABLE, uses cipher block chaining (CBC) ciphers with TLS. Check patches 154 | Winshock (CVE-2014-6321), experimental not vulnerable (OK) 155 | RC4 (CVE-2013-2566, CVE-2015-2808) no RC4 ciphers detected (OK) 156 | 157 | 158 | Running client simulations (HTTP) via sockets 159 | 160 | Browser Protocol Cipher Suite Name (OpenSSL) Forward Secrecy 161 | ------------------------------------------------------------------------------------------------ 162 | Android 6.0 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256 256 bit ECDH (P-256) 163 | Android 7.0 (native) TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 164 | Android 8.1 (native) TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 253 bit ECDH (X25519) 165 | Android 9.0 (native) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 166 | Android 10.0 (native) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 167 | Android 11 (native) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 168 | Android 12 (native) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 169 | Chrome 79 (Win 10) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 170 | Chrome 101 (Win 10) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 171 | Firefox 66 (Win 8.1/10) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 172 | Firefox 100 (Win 10) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 173 | IE 6 XP No connection 174 | IE 8 Win 7 No connection 175 | IE 8 XP No connection 176 | IE 11 Win 7 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 177 | IE 11 Win 8.1 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 178 | IE 11 Win Phone 8.1 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 179 | IE 11 Win 10 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 180 | Edge 15 Win 10 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 253 bit ECDH (X25519) 181 | Edge 101 Win 10 21H2 TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 182 | Safari 12.1 (iOS 12.2) TLSv1.3 TLS_CHACHA20_POLY1305_SHA256 253 bit ECDH (X25519) 183 | Safari 13.0 (macOS 10.14.6) TLSv1.3 TLS_CHACHA20_POLY1305_SHA256 253 bit ECDH (X25519) 184 | Safari 15.4 (macOS 12.3.1) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 185 | Java 7u25 No connection 186 | Java 8u161 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 187 | Java 11.0.2 (OpenJDK) TLSv1.3 TLS_AES_256_GCM_SHA384 256 bit ECDH (P-256) 188 | Java 17.0.3 (OpenJDK) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 189 | go 1.17.8 TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 190 | LibreSSL 2.8.3 (Apple) TLSv1.2 ECDHE-ECDSA-CHACHA20-POLY1305 253 bit ECDH (X25519) 191 | OpenSSL 1.0.2e TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 192 | OpenSSL 1.1.0l (Debian) TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 253 bit ECDH (X25519) 193 | OpenSSL 1.1.1d (Debian) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 194 | OpenSSL 3.0.3 (git) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 195 | Apple Mail (16.0) TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 196 | Thunderbird (91.9) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 197 | 198 | 199 | Rating (experimental) 200 | 201 | Rating specs (not complete) SSL Labs's 'SSL Server Rating Guide' (version 2009q from 2020-01-30) 202 | Specification documentation https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide 203 | Protocol Support (weighted) 100 (30) 204 | Key Exchange (weighted) 100 (30) 205 | Cipher Strength (weighted) 90 (36) 206 | Final Score 96 207 | Overall Grade A 208 | Grade cap reasons Grade capped to A. HSTS is not offered 209 | 210 | Done 2024-11-12 05:23:24 [ 70s] -->> ***.***.***.***:443 (expedite-mumb.gridhead.net) <<-- 211 | -------------------------------------------------------------------------------- /data/test-atla-12112024.txt: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | testssl 3.2rc3 from https://testssl.sh/dev/ 3 | 4 | This program is free software. Distribution and 5 | modification under GPLv2 permitted. 6 | USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK! 7 | 8 | Please file bugs @ https://testssl.sh/bugs/ 9 | 10 | ########################################################### 11 | 12 | Using "OpenSSL 3.2.2 4 Jun 2024 (Library: OpenSSL 3.2.2 4 Jun 2024)" [~94 ciphers] 13 | on fedohide-origin:/usr/bin/openssl 14 | (built: "Sep 12 00:00:00 2024", platform: "linux-x86_64") 15 | 16 | 17 | Start 2024-11-12 05:25:34 -->> ***.***.***.***:443 (expedite-atla.gridhead.net) <<-- 18 | 19 | rDNS (***.***.***.***): ***-***-***-***-host.colocrossing.com. 20 | Service detected: HTTP 21 | 22 | 23 | Testing protocols via sockets except NPN+ALPN 24 | 25 | SSLv2 not offered (OK) 26 | SSLv3 not offered (OK) 27 | TLS 1 not offered 28 | TLS 1.1 not offered 29 | TLS 1.2 offered (OK) 30 | TLS 1.3 offered (OK): final 31 | NPN/SPDY not offered 32 | ALPN/HTTP2 h2, http/1.1 (offered) 33 | 34 | Testing cipher categories 35 | 36 | NULL ciphers (no encryption) not offered (OK) 37 | Anonymous NULL Ciphers (no authentication) not offered (OK) 38 | Export ciphers (w/o ADH+NULL) not offered (OK) 39 | LOW: 64 Bit + DES, RC[2,4], MD5 (w/o export) not offered (OK) 40 | Triple DES Ciphers / IDEA not offered 41 | Obsoleted CBC ciphers (AES, ARIA etc.) offered 42 | Strong encryption (AEAD ciphers) with no FS not offered 43 | Forward Secrecy strong encryption (AEAD ciphers) offered (OK) 44 | 45 | 46 | Testing server's cipher preferences 47 | 48 | Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits Cipher Suite Name (IANA/RFC) 49 | ----------------------------------------------------------------------------------------------------------------------------- 50 | SSLv2 51 | - 52 | SSLv3 53 | - 54 | TLSv1 55 | - 56 | TLSv1.1 57 | - 58 | TLSv1.2 (server order -- server prioritizes ChaCha ciphers when preferred by clients) 59 | xc02c ECDHE-ECDSA-AES256-GCM-SHA384 ECDH 253 AESGCM 256 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 60 | xcca9 ECDHE-ECDSA-CHACHA20-POLY1305 ECDH 253 ChaCha20 256 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 61 | xc0ad ECDHE-ECDSA-AES256-CCM ECDH 253 AESCCM 256 TLS_ECDHE_ECDSA_WITH_AES_256_CCM 62 | xc02b ECDHE-ECDSA-AES128-GCM-SHA256 ECDH 253 AESGCM 128 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 63 | xc0ac ECDHE-ECDSA-AES128-CCM ECDH 253 AESCCM 128 TLS_ECDHE_ECDSA_WITH_AES_128_CCM 64 | xc023 ECDHE-ECDSA-AES128-SHA256 ECDH 253 AES 128 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 65 | xc00a ECDHE-ECDSA-AES256-SHA ECDH 253 AES 256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 66 | xc009 ECDHE-ECDSA-AES128-SHA ECDH 253 AES 128 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 67 | TLSv1.3 (server order -- server prioritizes ChaCha ciphers when preferred by clients) 68 | x1302 TLS_AES_256_GCM_SHA384 ECDH 253 AESGCM 256 TLS_AES_256_GCM_SHA384 69 | x1303 TLS_CHACHA20_POLY1305_SHA256 ECDH 253 ChaCha20 256 TLS_CHACHA20_POLY1305_SHA256 70 | x1301 TLS_AES_128_GCM_SHA256 ECDH 253 AESGCM 128 TLS_AES_128_GCM_SHA256 71 | x1304 TLS_AES_128_CCM_SHA256 ECDH 253 AESCCM 128 TLS_AES_128_CCM_SHA256 72 | 73 | Has server cipher order? yes (OK) -- TLS 1.3 and below 74 | 75 | 76 | Testing robust forward secrecy (FS) -- omitting Null Authentication/Encryption, 3DES, RC4 77 | 78 | FS is offered (OK) TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-CHACHA20-POLY1305 TLS_AES_128_GCM_SHA256 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-SHA256 79 | ECDHE-ECDSA-AES128-SHA 80 | Elliptic curves offered: prime256v1 secp384r1 secp521r1 X25519 X448 81 | Finite field group: ffdhe2048 ffdhe3072 ffdhe4096 ffdhe6144 ffdhe8192 82 | TLS 1.2 sig_algs offered: ECDSA+SHA256 ECDSA+SHA384 ECDSA+SHA512 ECDSA+SHA224 83 | TLS 1.3 sig_algs offered: ECDSA+SHA256 84 | 85 | Testing server defaults (Server Hello) 86 | 87 | TLS extensions (standard) "renegotiation info/#65281" "server name/#0" "EC point formats/#11" "session ticket/#35" "supported versions/#43" "key share/#51" "max fragment length/#1" "application layer protocol negotiation/#16" "encrypt-then-mac/#22" "extended master secret/#23" 88 | Session Ticket RFC 5077 hint 7200 seconds, session tickets keys seems to be rotated < daily 89 | SSL Session ID support yes 90 | Session Resumption Tickets: yes, ID: yes 91 | TLS clock skew Random values, no fingerprinting possible 92 | Certificate Compression none 93 | Client Authentication none 94 | Signature Algorithm ECDSA with SHA384 95 | Server key size EC 256 bits (curve P-256) 96 | Server key usage Digital Signature 97 | Server extended key usage TLS Web Server Authentication, TLS Web Client Authentication 98 | Serial 032949A41F4938FAF4C1DBAA984F965F6380 (OK: length 18) 99 | Fingerprints SHA1 7FA23560DDD26C28EF497C286F59F411C367F61F 100 | SHA256 10772449545FC60A04A177BB84611F12BCB2FBA179B5675AEEC6DB23E2A2ECD9 101 | Common Name (CN) *.gridhead.net (CN in response to request w/o SNI: *.apexaltruism.net ) 102 | subjectAltName (SAN) *.gridhead.net gridhead.net 103 | Trust (hostname) Ok via SAN wildcard and CN wildcard (SNI mandatory) 104 | Chain of trust basename: extra operand ‘/etc/pki/tls/fips_local.cnf’ 105 | Try 'basename --help' for more information. 106 | "/etc/pki/tls/*.pem" cannot be found / not readable 107 | EV cert (experimental) no 108 | Certificate Validity (UTC) 62 >= 30 days (2024-10-16 05:05 --> 2025-01-14 05:05) 109 | ETS/"eTLS", visibility info not present 110 | Certificate Revocation List -- 111 | OCSP URI http://e5.o.lencr.org 112 | OCSP stapling not offered 113 | OCSP must staple extension -- 114 | DNS CAA RR (experimental) not offered 115 | Certificate Transparency yes (certificate extension) 116 | Certificates provided 2 117 | Issuer E5 (Let's Encrypt from US) 118 | Intermediate cert validity #1: ok > 40 days (2027-03-12 23:59). E5 <-- ISRG Root X1 119 | Intermediate Bad OCSP (exp.) Ok 120 | 121 | 122 | Testing HTTP header response @ "/" 123 | 124 | HTTP Status Code 426 Upgrade Required. Oh, didn't expect "426 Upgrade Required" 125 | HTTP clock skew -35 sec from localtime 126 | Strict Transport Security not offered 127 | Public Key Pinning -- 128 | Server banner Python/3.12 websockets/12.0 129 | Application banner -- 130 | Cookie(s) (none issued at "/") -- maybe better try target URL of 30x 131 | Security headers Upgrade: websocket 132 | Reverse Proxy banner -- 133 | 134 | 135 | Testing vulnerabilities 136 | 137 | Heartbleed (CVE-2014-0160) not vulnerable (OK), no heartbeat extension 138 | CCS (CVE-2014-0224) not vulnerable (OK) 139 | Ticketbleed (CVE-2016-9244), experiment. not vulnerable (OK) 140 | ROBOT Server does not support any cipher suites that use RSA key transport 141 | Secure Renegotiation (RFC 5746) supported (OK) 142 | Secure Client-Initiated Renegotiation not vulnerable (OK) 143 | CRIME, TLS (CVE-2012-4929) not vulnerable (OK) 144 | BREACH (CVE-2013-3587) no gzip/deflate/compress/br HTTP compression (OK) - only supplied "/" tested 145 | POODLE, SSL (CVE-2014-3566) not vulnerable (OK), no SSLv3 support 146 | TLS_FALLBACK_SCSV (RFC 7507) No fallback possible (OK), no protocol below TLS 1.2 offered 147 | SWEET32 (CVE-2016-2183, CVE-2016-6329) not vulnerable (OK) 148 | FREAK (CVE-2015-0204) not vulnerable (OK) 149 | DROWN (CVE-2016-0800, CVE-2016-0703) not vulnerable on this host and port (OK) 150 | no RSA certificate, thus certificate can't be used with SSLv2 elsewhere 151 | LOGJAM (CVE-2015-4000), experimental not vulnerable (OK): no DH EXPORT ciphers, no DH key detected with <= TLS 1.2 152 | BEAST (CVE-2011-3389) not vulnerable (OK), no SSL3 or TLS1 153 | LUCKY13 (CVE-2013-0169), experimental potentially VULNERABLE, uses cipher block chaining (CBC) ciphers with TLS. Check patches 154 | Winshock (CVE-2014-6321), experimental not vulnerable (OK) 155 | RC4 (CVE-2013-2566, CVE-2015-2808) no RC4 ciphers detected (OK) 156 | 157 | 158 | Running client simulations (HTTP) via sockets 159 | 160 | Browser Protocol Cipher Suite Name (OpenSSL) Forward Secrecy 161 | ------------------------------------------------------------------------------------------------ 162 | Android 6.0 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256 256 bit ECDH (P-256) 163 | Android 7.0 (native) TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 164 | Android 8.1 (native) TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 253 bit ECDH (X25519) 165 | Android 9.0 (native) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 166 | Android 10.0 (native) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 167 | Android 11 (native) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 168 | Android 12 (native) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 169 | Chrome 79 (Win 10) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 170 | Chrome 101 (Win 10) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 171 | Firefox 66 (Win 8.1/10) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 172 | Firefox 100 (Win 10) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 173 | IE 6 XP No connection 174 | IE 8 Win 7 No connection 175 | IE 8 XP No connection 176 | IE 11 Win 7 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 177 | IE 11 Win 8.1 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 178 | IE 11 Win Phone 8.1 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 179 | IE 11 Win 10 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 180 | Edge 15 Win 10 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 253 bit ECDH (X25519) 181 | Edge 101 Win 10 21H2 TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 182 | Safari 12.1 (iOS 12.2) TLSv1.3 TLS_CHACHA20_POLY1305_SHA256 253 bit ECDH (X25519) 183 | Safari 13.0 (macOS 10.14.6) TLSv1.3 TLS_CHACHA20_POLY1305_SHA256 253 bit ECDH (X25519) 184 | Safari 15.4 (macOS 12.3.1) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 185 | Java 7u25 No connection 186 | Java 8u161 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 187 | Java 11.0.2 (OpenJDK) TLSv1.3 TLS_AES_256_GCM_SHA384 256 bit ECDH (P-256) 188 | Java 17.0.3 (OpenJDK) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 189 | go 1.17.8 TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 190 | LibreSSL 2.8.3 (Apple) TLSv1.2 ECDHE-ECDSA-CHACHA20-POLY1305 253 bit ECDH (X25519) 191 | OpenSSL 1.0.2e TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 192 | OpenSSL 1.1.0l (Debian) TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 253 bit ECDH (X25519) 193 | OpenSSL 1.1.1d (Debian) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 194 | OpenSSL 3.0.3 (git) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 195 | Apple Mail (16.0) TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384 256 bit ECDH (P-256) 196 | Thunderbird (91.9) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519) 197 | 198 | 199 | Rating (experimental) 200 | 201 | Rating specs (not complete) SSL Labs's 'SSL Server Rating Guide' (version 2009q from 2020-01-30) 202 | Specification documentation https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide 203 | Protocol Support (weighted) 100 (30) 204 | Key Exchange (weighted) 100 (30) 205 | Cipher Strength (weighted) 90 (36) 206 | Final Score 96 207 | Overall Grade A 208 | Grade cap reasons Grade capped to A. HSTS is not offered 209 | 210 | Done 2024-11-12 05:28:32 [ 182s] -->> ***.***.***.***:443 (expedite-atla.gridhead.net) <<-- 211 | -------------------------------------------------------------------------------- /expedite/client/bridge/room.py: -------------------------------------------------------------------------------- 1 | """ 2 | expedite 3 | Copyright (C) 2024 Akashdeep Dhar 4 | 5 | This program is free software: you can redistribute it and/or modify it under 6 | the terms of the GNU General Public License as published by the Free Software 7 | Foundation, either version 3 of the License, or (at your option) any later 8 | version. 9 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | details. 14 | 15 | You should have received a copy of the GNU General Public License along with 16 | this program. If not, see . 17 | 18 | Any Red Hat trademarks that are incorporated in the codebase or documentation 19 | are not subject to the GNU General Public License and may only be utilized or 20 | replicated with the express permission of Red Hat, Inc. 21 | """ 22 | 23 | 24 | import time 25 | from asyncio import ensure_future, get_event_loop, new_event_loop, set_event_loop 26 | from json import loads 27 | from os.path import basename, getsize 28 | from pathlib import Path 29 | from uuid import uuid4 30 | 31 | from PySide6.QtCore import QTimer 32 | from PySide6.QtWidgets import QMainWindow, QMessageBox 33 | from websockets import connect 34 | from websockets.exceptions import ConnectionClosed, InvalidURI 35 | 36 | from expedite import __versdata__ 37 | from expedite.client.base import bite_file, ease_size, find_size, fuse_file 38 | from expedite.client.bridge.util import ( 39 | ValidateFields, 40 | return_detail_text, 41 | show_location_dialog, 42 | truncate_text, 43 | ) 44 | from expedite.client.bridge.wind import Ui_mainwind 45 | from expedite.client.conn import ( 46 | collect_confirmation, 47 | collect_connection_from_pairness, 48 | collect_connection_to_server, 49 | collect_contents, 50 | collect_digest_checks, 51 | collect_dropping_summon, 52 | collect_metadata, 53 | collect_separation_from_mistaken_password, 54 | deliver_confirmation, 55 | deliver_connection_to_server, 56 | deliver_contents, 57 | deliver_digest_checks, 58 | deliver_dropping_summon, 59 | deliver_metadata, 60 | deliver_separation_from_mistaken_password, 61 | deliver_suspension_from_expiry, 62 | ) 63 | from expedite.client.meet import talk 64 | from expedite.config import standard 65 | from expedite.view import warning 66 | 67 | 68 | class MainWindow(QMainWindow, Ui_mainwind): 69 | def __init__(self) -> None: 70 | """ 71 | Initialize the application to enable user interaction 72 | 73 | :return: 74 | """ 75 | super().__init__() 76 | self.headtext = f"Expedite Bridge v{__versdata__}" 77 | self.loop = new_event_loop() 78 | set_event_loop(self.loop) 79 | self.sock = None 80 | self.setupUi(self) 81 | self.setWindowTitle(self.headtext) 82 | self.normal_both_side() 83 | self.dlvr_butn_browse.clicked.connect(self.handle_delivering_location) 84 | self.clct_butn_browse.clicked.connect(self.handle_collecting_location) 85 | self.dlvr_butn_random.clicked.connect(self.random_delivering_password) 86 | self.clct_butn_random.clicked.connect(self.random_collecting_password) 87 | self.dlvr_butn_normal.clicked.connect(self.normal_delivering_side) 88 | self.clct_butn_normal.clicked.connect(self.normal_collecting_side) 89 | self.dlvr_butn_incept.clicked.connect(self.incept_delivering_client) 90 | self.clct_butn_incept.clicked.connect(self.incept_collecting_client) 91 | self.dlvr_butn_detail.clicked.connect(self.show_detail) 92 | self.clct_butn_detail.clicked.connect(self.show_detail) 93 | self.progbarg.setMinimum(0) 94 | self.progbarg.setMaximum(100) 95 | self.timekeeper = QTimer() 96 | self.timekeeper.timeout.connect(self.manage_events) 97 | self.timekeeper.start(1) 98 | 99 | def handle_delivering_location(self) -> None: 100 | """ 101 | Select filepath for the intended file for delivering 102 | 103 | :return: 104 | """ 105 | path = show_location_dialog(self, "dlvr") 106 | if path: 107 | self.dlvr_line_file.setText(path) 108 | self.dlvr_head_file.setText(f"Delivering {truncate_text(basename(path), 28)} ({ease_size(getsize(path))})") 109 | 110 | def handle_collecting_location(self) -> None: 111 | """ 112 | Select filepath for the intended file for collecting 113 | 114 | :return: 115 | """ 116 | path = show_location_dialog(self, "clct") 117 | if path: 118 | self.clct_line_file.setText(path) 119 | self.clct_head_file.setText(f"Saving to {truncate_text(basename(path), 28)}") 120 | 121 | def show_detail(self) -> None: 122 | """ 123 | Retrieve application information text for showing on the dialog box 124 | 125 | :return: 126 | """ 127 | self.show_dialog( 128 | QMessageBox.Information, 129 | "Detail", 130 | return_detail_text().format( 131 | vers=__versdata__, 132 | star="https://github.com/gridhead/expedite/stargazers", 133 | tick="https://github.com/gridhead/expedite/issues", 134 | pull="https://github.com/gridhead/expedite/pulls", 135 | help="https://github.com/sponsors/gridhead", 136 | ) 137 | ) 138 | 139 | def normal_delivering_side(self) -> None: 140 | """ 141 | Define defaults for delivering view 142 | 143 | :return: 144 | """ 145 | self.dlvr_head_file.setText("No location selected") 146 | self.dlvr_line_size.setText(str(standard.chunking_size)) 147 | self.dlvr_line_time.setText(str(standard.client_time)) 148 | self.dlvr_line_file.clear() 149 | self.dlvr_line_pswd.clear() 150 | self.dlvr_line_endo.clear() 151 | 152 | def normal_collecting_side(self) -> None: 153 | """ 154 | Define defaults for collecting view 155 | 156 | :return: 157 | """ 158 | self.clct_head_file.setText("No location selected") 159 | self.clct_line_size.clear() 160 | self.clct_line_time.setText(str(standard.client_time)) 161 | self.clct_line_file.clear() 162 | self.clct_line_pswd.clear() 163 | self.clct_line_endo.clear() 164 | 165 | def random_delivering_password(self) -> None: 166 | """ 167 | Insert randomly generated password in delivering view 168 | 169 | :return: 170 | """ 171 | self.dlvr_line_pswd.setText(uuid4().hex[0:16].upper()) 172 | 173 | def random_collecting_password(self) -> None: 174 | """ 175 | Insert randomly generated password in collecting view 176 | 177 | :return: 178 | """ 179 | self.clct_line_pswd.setText(uuid4().hex[0:16].upper()) 180 | 181 | def incept_delivering_client(self) -> None: 182 | """ 183 | Initialize delivering of file contents on connection 184 | 185 | :return: 186 | """ 187 | if not standard.client_progress: 188 | report = ValidateFields().report_dlvr( 189 | self.dlvr_line_size.text(), 190 | self.dlvr_line_time.text(), 191 | self.dlvr_line_file.text(), 192 | self.dlvr_line_pswd.text() 193 | ) 194 | if report[0] == (True, True, True, True): 195 | standard.client_plan = "SEND" 196 | standard.chunking_size = int(self.dlvr_line_size.text()) 197 | standard.client_time = int(self.dlvr_line_time.text()) 198 | standard.client_file = self.dlvr_line_file.text() 199 | standard.client_pswd = self.dlvr_line_pswd.text() 200 | standard.client_endo = self.dlvr_line_endo.text() 201 | standard.client_filename = basename(standard.client_file) 202 | standard.client_filesize = find_size() 203 | standard.client_bind = bite_file() 204 | self.initialize_connection() 205 | else: 206 | self.show_dialog(QMessageBox.Warning, "Invalid information", f"Please correct the filled data\n\n{report[1]}") 207 | else: 208 | self.show_dialog(QMessageBox.Warning, "Ongoing interaction", "Please wait for the ongoing interaction to complete first before starting another or considering cancelling the ongoing interaction.") 209 | 210 | def incept_collecting_client(self) -> None: 211 | """ 212 | Initialize collecting of file contents on connection 213 | 214 | :return: 215 | """ 216 | if not standard.client_progress: 217 | report = ValidateFields().report_clct( 218 | self.clct_line_time.text(), 219 | self.clct_line_file.text(), 220 | self.clct_line_pswd.text() 221 | ) 222 | if report[0] == (True, True, True): 223 | standard.client_plan = "RECV" 224 | standard.client_time = int(self.clct_line_time.text()) 225 | standard.client_file = self.clct_line_file.text() 226 | standard.client_pswd = self.clct_line_pswd.text() 227 | standard.client_endo = self.clct_line_endo.text() 228 | standard.client_fileinit = False 229 | standard.client_metadone = False 230 | self.initialize_connection() 231 | else: 232 | self.show_dialog(QMessageBox.Warning, "Invalid information", f"Please correct the filled data\n\n{report[1]}") 233 | else: 234 | self.show_dialog(QMessageBox.Warning, "Ongoing interaction", "Please wait for the ongoing interaction to complete first before starting another or considering cancelling the ongoing interaction.") 235 | 236 | def initialize_connection(self) -> None: 237 | """ 238 | Form connection with exchange server to perform activity 239 | 240 | :return: 241 | """ 242 | standard.client_host = self.sockaddr.text() 243 | standard.client_progress = True 244 | self.statarea.showMessage("Please wait while the client connects to the broker") 245 | talk() 246 | ensure_future(self.maintain_connection()) 247 | 248 | def normal_both_side(self) -> None: 249 | """ 250 | Define defaults for both delivering and collecting views 251 | 252 | :return: 253 | """ 254 | self.normal_delivering_side() 255 | self.normal_collecting_side() 256 | self.identity.clear() 257 | self.progbarg.setValue(0) 258 | self.statarea.showMessage("READY") 259 | 260 | def manage_events(self) -> None: 261 | """ 262 | Manage execution of event loop after regular time period 263 | 264 | :return: 265 | """ 266 | self.loop.call_soon(self.loop.stop) 267 | self.loop.run_forever() 268 | 269 | def show_dialog(self, icon: QMessageBox.Icon, head: str, text: str) -> None: 270 | """ 271 | Modify the dialog with the passed details before showing 272 | 273 | :param icon: Icon to be used for visual representation 274 | :param head: Head text for the subject of the dialog box 275 | :param text: Body text for the subject of the dialog box 276 | :return: 277 | """ 278 | dialog = QMessageBox(parent=self) 279 | dialog.setIcon(icon) 280 | dialog.setWindowTitle(f"{self.headtext} - {head}") 281 | dialog.setText(text) 282 | dialog.setFont("IBM Plex Sans") 283 | dialog.exec() 284 | 285 | async def maintain_connection(self) -> None: 286 | """ 287 | Exchange data to the target client after connecting to the exchange server 288 | 289 | :return: 290 | """ 291 | try: 292 | async with connect(standard.client_host) as self.sock: 293 | get_event_loop().call_later(standard.client_time, lambda: ensure_future(self.deliver_suspension_from_expiry_bridge())) 294 | await deliver_connection_to_server(self.sock) 295 | async for mesgcont in self.sock: 296 | if isinstance(mesgcont, str): 297 | mesgdict = loads(mesgcont) 298 | # If the data received is of STRING type 299 | if standard.client_plan in ["SEND", "RECV"]: 300 | # If the purpose of the client is either DELIVERING or COLLECTING 301 | if mesgdict["call"] == "okay": 302 | await collect_connection_to_server(mesgdict["iden"]) 303 | self.statarea.showMessage("Please share your acquired identity to begin interaction") 304 | self.identity.setText(f"{mesgdict["iden"]}") 305 | elif mesgdict["call"] in ["awry", "lone"]: 306 | await self.sock.close() 307 | warning(standard.client_note[mesgdict["call"]]) 308 | self.show_dialog(QMessageBox.Critical, standard.client_note[mesgdict["call"]], standard.client_text[mesgdict["call"]]) 309 | if standard.client_plan == "SEND": 310 | # If the purpose of the client is DELIVERING 311 | if mesgdict["call"] == "note": 312 | await collect_connection_from_pairness(mesgdict["part"]) 313 | self.statarea.showMessage(f"You are now paired with {mesgdict["part"]}") 314 | standard.client_endo = mesgdict["part"] 315 | await deliver_metadata(self.sock) 316 | elif mesgdict["call"] == "conf": 317 | complete = await collect_confirmation(mesgdict["data"]) 318 | await self.sock.close() 319 | standard.client_movestop = time.time() 320 | head = standard.client_note["succ"] if complete else standard.client_note["fail"] 321 | text = standard.client_text["succ"] if complete else standard.client_text["fail"] 322 | self.show_dialog( 323 | QMessageBox.Information, 324 | head, 325 | text.format( 326 | iden=standard.client_iden, 327 | verb="deliver", 328 | drct="to", 329 | endo=standard.client_endo, 330 | name=standard.client_filename, 331 | size=ease_size(standard.client_filesize), 332 | hash=standard.client_hash.hexdigest(), 333 | time=f"{(standard.client_movestop - standard.client_movestrt):.2f} seconds", 334 | spid=f"{ease_size(standard.client_filesize / (standard.client_movestop - standard.client_movestrt))}/s", 335 | ) 336 | ) 337 | elif mesgdict["call"] == "flub": 338 | await collect_separation_from_mistaken_password() 339 | await self.sock.close() 340 | warning(standard.client_note[mesgdict["call"]]) 341 | self.show_dialog( 342 | QMessageBox.Critical, 343 | standard.client_note[mesgdict["call"]], 344 | standard.client_text[mesgdict["call"]] 345 | ) 346 | elif mesgdict["call"] == "drop": 347 | await collect_dropping_summon() 348 | self.statarea.showMessage(f"File contents are requested by {standard.client_endo}") 349 | await self.show_deliver_contents() 350 | await deliver_digest_checks(self.sock) 351 | else: 352 | # If the purpose of the client is COLLECTING 353 | if mesgdict["call"] == "note": 354 | await collect_connection_from_pairness(mesgdict["part"]) 355 | self.statarea.showMessage(f"You are now paired with {mesgdict["part"]}") 356 | standard.client_endo = mesgdict["part"] 357 | elif mesgdict["call"] == "hash": 358 | await collect_digest_checks() 359 | complete = await deliver_confirmation(self.sock, mesgdict["data"]) 360 | await self.sock.close() 361 | standard.client_movestop = time.time() 362 | head = standard.client_note["succ"] if complete else standard.client_note["fail"] 363 | text = standard.client_text["succ"] if complete else standard.client_text["fail"] 364 | self.show_dialog( 365 | QMessageBox.Information, 366 | head, 367 | text.format( 368 | iden=standard.client_iden, 369 | verb="collect", 370 | drct="from", 371 | endo=standard.client_endo, 372 | name=standard.client_filename, 373 | size=ease_size(standard.client_filesize), 374 | hash=standard.client_hash.hexdigest(), 375 | time=f"{(standard.client_movestop - standard.client_movestrt):.2f} seconds", 376 | spid=f"{ease_size(standard.client_filesize / (standard.client_movestop - standard.client_movestrt))}/s", 377 | ) 378 | ) 379 | else: 380 | # If the data received is of BYTES type 381 | if standard.client_plan == "RECV": 382 | # If the purpose of the client is COLLECTING 383 | if not standard.client_metadone: 384 | if await collect_metadata(mesgcont): 385 | self.clct_head_file.setText(f"Collecting {truncate_text(standard.client_filename, 28)} ({ease_size(standard.client_filesize)})") 386 | standard.client_filename = Path(standard.client_file) / Path(standard.client_filename) 387 | await deliver_dropping_summon(self.sock) 388 | else: 389 | await deliver_separation_from_mistaken_password(self.sock) 390 | await self.sock.close() 391 | warning(standard.client_note["flub"]) 392 | self.show_dialog(QMessageBox.Critical, standard.client_note["flub"], standard.client_text["flub"]) 393 | else: 394 | await self.show_collect_contents(mesgcont) 395 | except InvalidURI: 396 | self.show_dialog(QMessageBox.Critical, standard.client_note["iuri"], standard.client_text["iuri"]) 397 | except OSError: 398 | self.show_dialog(QMessageBox.Critical, standard.client_note["oser"], standard.client_text["oser"]) 399 | except ConnectionClosed: 400 | self.show_dialog(QMessageBox.Critical, standard.client_note["dprt"], standard.client_text["dprt"]) 401 | self.normal_both_side() 402 | standard.client_progress = False 403 | 404 | async def show_deliver_contents(self) -> None: 405 | """ 406 | Facilitate encrypting and delivering file contents 407 | 408 | :return: 409 | """ 410 | standard.client_movestrt, progress = time.time(), 0 411 | async for dgst, size in deliver_contents(self.sock): 412 | self.statarea.showMessage(f"[{standard.client_endo}] Since {(time.time() - standard.client_movestrt):.2f} seconds | SHA256 {dgst[0:6]} ({ease_size(size)})") 413 | progress += size * 100 / standard.client_filesize 414 | self.progbarg.setValue(progress) 415 | self.progbarg.setValue(100) 416 | 417 | async def show_collect_contents(self, pack: bytes) -> None: 418 | """ 419 | Facilitate collecting and decrypting file contents 420 | 421 | :param pack: Byte chunk that is to be collected and decrypted 422 | :return: 423 | """ 424 | standard.client_movestrt, progress = time.time(), 0 425 | fuse_file(pack) 426 | async for dgst, size in collect_contents(self.sock): 427 | self.statarea.showMessage(f"[{standard.client_endo}] Since {(time.time() - standard.client_movestrt):.2f} seconds | SHA256 {dgst[0:6]} ({ease_size(size)})") 428 | progress += size * 100 / standard.client_filesize 429 | self.progbarg.setValue(progress) 430 | self.progbarg.setValue(100) 431 | 432 | async def deliver_suspension_from_expiry_bridge(self) -> None: 433 | """ 434 | Terminate the bridge session elegantly after the designated timeout 435 | 436 | :return: 437 | """ 438 | if not standard.client_pair: 439 | await deliver_suspension_from_expiry(self.sock) 440 | await self.sock.close() 441 | warning(standard.client_note["rest"]) 442 | self.show_dialog(QMessageBox.Warning, standard.client_note["rest"], standard.client_text["rest"]) 443 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Expedite 2 | 3 | Simple encrypted file transfer service for humans 4 | 5 | ## Introduction 6 | 7 | Expedite is a simple encrypted file transfer service that allows for people to 8 | share synchronously assets among each other without having to rely on third 9 | party file sharing services (and constantly worrying about how their data might 10 | be used) or feeling the need of having publicly visible IP addresses (and 11 | constantly worrying about script kiddies attacking your computer). 12 | 13 | Expedite Server can be deployed on a virtual private server having an IP 14 | address that is discoverable by the Expedite Client users to broker file 15 | contents. The transfers facilitated using WebSockets are end-to-end encrypted 16 | with the use of 128-bit Advanced Encryption Standard and the server is 17 | restricted to logging only unidentifiable activities to the volatile memory. 18 | 19 | Expedite is currently in BETA phase and if you like to direction the project is 20 | heading towards, kindly consider helping me out by starring the project 21 | repository, filing issue tickets for software errors or feature requests, 22 | contributing to the codebase of the project or sponsoring me to help maintain 23 | the servers and to help me keep working on more FOSS projects like these. 24 | 25 | ## Offerings 26 | 27 | You can either deploy your own Expedite Server to broker file contents from 28 | your group of Expedite Client users or you can use the following publicly 29 | available servers setup by me instead. Picking the server that is closer to 30 | your group of users can help with improving performance and reliability of the 31 | transfer. Please open up a pull request if you wish to list your server here. 32 | 33 | - **Mumbai, MH** 34 | `wss://expedite-mumb.gridhead.net` or `wss://expedite-mumb.gridhead.net:443` 35 | [**Grade A - Qualys**](https://www.ssllabs.com/ssltest/analyze.html?d=expedite-mumb.gridhead.net) 36 | [**Grade A - TestSSL**](https://github.com/gridhead/expedite/blob/main/data/test-mumb-12112024.txt) 37 | ![](https://raw.githubusercontent.com/gridhead/expedite/main/data/cert-mumb-12112024.png) 38 | 39 | - **Atlanta, GA** 40 | `wss://expedite-atla.gridhead.net` or `wss://expedite-atla.gridhead.net:443` 41 | [**Grade A - Qualys**](https://www.ssllabs.com/ssltest/analyze.html?d=expedite-atla.gridhead.net) 42 | [**Grade A - TestSSL**](https://github.com/gridhead/expedite/blob/main/data/test-atla-12112024.txt) 43 | ![](https://raw.githubusercontent.com/gridhead/expedite/main/data/cert-atla-12112024.png) 44 | 45 | ## Illustration 46 | 47 | ### Client 48 | 49 | #### Bridge - Info 50 | 51 | ![](https://raw.githubusercontent.com/gridhead/expedite/main/data/bridge-info-stat.png) 52 | 53 | #### Bridge - Delivering - Static 54 | 55 | ![](https://raw.githubusercontent.com/gridhead/expedite/main/data/bridge-send-stat.png) 56 | 57 | #### Bridge - Collecting - Static 58 | 59 | ![](https://raw.githubusercontent.com/gridhead/expedite/main/data/bridge-recv-stat.png) 60 | 61 | #### Bridge - Delivering - Progress 62 | 63 | ![](https://raw.githubusercontent.com/gridhead/expedite/main/data/bridge-send-prog.gif) 64 | 65 | #### Bridge - Collecting - Progress 66 | 67 | ![](https://raw.githubusercontent.com/gridhead/expedite/main/data/bridge-recv-prog.gif) 68 | 69 | #### Prompt - Help 70 | 71 | ```shell 72 | (venv) $ ed-prompt --help 73 | ``` 74 | 75 | ``` 76 | Usage: ed-prompt [OPTIONS] COMMAND [ARGS]... 77 | 78 | Configure the service particulars before starting it 79 | 80 | Options: 81 | -h, --host TEXT Set the address for the service endpoint 82 | [required] 83 | -t, --time INTEGER RANGE Set the expiry period for participants [default: 84 | 150; 5<=x<=300] 85 | -e, --endo TEXT Set the identity of the opposing client 86 | --version Show the version and exit. 87 | --help Show this message and exit. 88 | 89 | Commands: 90 | recv Collect file through an encrypted transfer 91 | send Deliver file through an encrypted transfer 92 | ``` 93 | 94 | #### Prompt - Delivering - Help 95 | 96 | ```shell 97 | (venv) $ ed-prompt send --help 98 | ``` 99 | 100 | ``` 101 | Usage: ed-prompt send [OPTIONS] 102 | 103 | Deliver file through an encrypted transfer 104 | 105 | Options: 106 | -p, --pswd TEXT Set the password for delivering encryption 107 | [default: CD87C56C] 108 | -f, --file PATH Set the filepath for delivering to network 109 | [required] 110 | -s, --size INTEGER RANGE Set the unit size for file chunking (in B) 111 | [default: 65536; 1024<=x<=524288] 112 | --help Show this message and exit. 113 | ``` 114 | 115 | #### Prompt - Collecting - Help 116 | 117 | ```shell 118 | (venv) $ ed-prompt recv --help 119 | ``` 120 | 121 | ``` 122 | Usage: ed-prompt recv [OPTIONS] 123 | 124 | Collect file through an encrypted transfer 125 | 126 | Options: 127 | -p, --pswd TEXT Set the password for collecting encryption [required] 128 | --help Show this message and exit. 129 | ``` 130 | 131 | #### Prompt - Delivering - Static 132 | 133 | ```shell 134 | (venv) $ ed-prompt --host wss://expedite-mumb.gridhead.net --time 150 --endo 2E8EC1AC send --pswd PASSWORDINCOMING --size 65536 --file dist/ed-bridge 135 | ``` 136 | 137 | ``` 138 | [2024-11-12 17:09:02] Expedite Client v0.1.0 139 | [2024-11-12 17:09:02] Addr. wss://expedite-mumb.gridhead.net 140 | [2024-11-12 17:09:02] Pass. PASSWORDINCOMING 141 | [2024-11-12 17:09:02] Plan. DELIVERING 142 | [2024-11-12 17:09:02] Wait. 150 seconds 143 | [2024-11-12 17:09:02] Please wait for 2E8EC1AC to begin interaction. 144 | [2024-11-12 17:09:02] Attempting to connect to the network. 145 | [2024-11-12 17:09:02] Successfully connected to the network. 146 | [2024-11-12 17:09:02] You are now identified as 14CF663D in the network. 147 | [2024-11-12 17:09:02] Attempting pairing with 2E8EC1AC. 148 | [2024-11-12 17:09:02] Starting transmission. 149 | [2024-11-12 17:09:02] Generating cryptography sign. 150 | [2024-11-12 17:09:02] Collecting delivering summon from 2E8EC1AC. 151 | [2024-11-12 17:09:02] Delivering contents for 'ed-bridge' (78.32MB) to 2E8EC1AC. 152 | [2024-11-12 17:09:19] Delivering contents digest for confirmation. 153 | [2024-11-12 17:09:20] Contents integrity verified (Mean 4.36MB/s). 154 | [2024-11-12 17:09:20] Delivering done after 18.12 seconds. 155 | [2024-11-12 17:09:20] Exiting. 156 | ``` 157 | 158 | #### Prompt - Collecting - Static 159 | 160 | ```shell 161 | (venv) $ ed-prompt --host wss://expedite-mumb.gridhead.net --time 150 --endo 1AAE5935 recv --pswd PASSWORDINCOMING 162 | ``` 163 | 164 | ``` 165 | [2024-11-12 17:15:10] Expedite Client v0.1.0 166 | [2024-11-12 17:15:10] Addr. wss://expedite-mumb.gridhead.net 167 | [2024-11-12 17:15:10] Pass. PASSWORDINCOMING 168 | [2024-11-12 17:15:10] Plan. COLLECTING 169 | [2024-11-12 17:15:10] Wait. 150 seconds 170 | [2024-11-12 17:15:10] Please wait for 1AAE5935 to begin interaction. 171 | [2024-11-12 17:15:10] Attempting to connect to the network. 172 | [2024-11-12 17:15:10] Successfully connected to the network. 173 | [2024-11-12 17:15:10] You are now identified as 96B33383 in the network. 174 | [2024-11-12 17:15:10] Attempting pairing with 1AAE5935. 175 | [2024-11-12 17:15:10] Starting transmission. 176 | [2024-11-12 17:15:10] Generating cryptography sign. 177 | [2024-11-12 17:15:10] Delivering collection summon to 1AAE5935. 178 | [2024-11-12 17:15:10] Collecting contents for 'ed-bridge' (78.32MB) from 1AAE5935. 179 | [2024-11-12 17:15:32] Collecting contents digest for confirmation. 180 | [2024-11-12 17:15:32] Contents integrity verified (Mean 3.52MB/s). 181 | [2024-11-12 17:15:32] Collecting done after 22.57 seconds. 182 | [2024-11-12 17:15:32] Exiting. 183 | ``` 184 | 185 | #### Prompt - Delivering - Progress 186 | 187 | ![](https://raw.githubusercontent.com/gridhead/expedite/main/data/prompt-send-prog.gif) 188 | 189 | #### Prompt - Collecting - Progress 190 | 191 | ![](https://raw.githubusercontent.com/gridhead/expedite/main/data/prompt-recv-prog.gif) 192 | 193 | ### Server 194 | 195 | #### Help 196 | 197 | ```shell 198 | (venv) $ ed-server --help 199 | ``` 200 | 201 | ``` 202 | Usage: ed-server [OPTIONS] 203 | 204 | Configure the service particulars before starting it 205 | 206 | Options: 207 | -a, --addr TEXT Set the interface for the service endpoint 208 | [default: 127.0.0.1] 209 | -p, --port INTEGER RANGE Set the port value for the service endpoint 210 | [default: 8080; 64<=x<=65535] 211 | --version Show the version and exit. 212 | --help Show this message and exit. 213 | ``` 214 | 215 | #### Broker 216 | 217 | ```shell 218 | (venv) $ ed-server --addr 0.0.0.0 --port 8181 219 | ``` 220 | 221 | ``` 222 | [2024-11-12 17:46:46] Expedite Server v0.1.0 223 | [2024-11-12 17:46:46] Addr. 0.0.0.0 224 | [2024-11-12 17:46:46] Port. 8181 225 | [2024-11-12 17:46:46] server listening on 0.0.0.0:8181 226 | [2024-11-12 17:48:30] connection open 227 | [2024-11-12 17:48:30] 97939184 joined with the intention of collecting. 228 | [2024-11-12 17:48:30] 97939184 is looking for ACE751B4 for 150 seconds. 229 | [2024-11-12 17:48:51] connection open 230 | [2024-11-12 17:48:51] DEA38DDF joined with the intention of delivering. 231 | [2024-11-12 17:48:51] DEA38DDF is waiting for client for 150 seconds. 232 | [2024-11-12 17:48:52] 97939184 left. 233 | [2024-11-12 17:48:52] connection closed 234 | [2024-11-12 17:49:00] connection open 235 | [2024-11-12 17:49:00] 69988F01 joined with the intention of collecting. 236 | [2024-11-12 17:49:00] 69988F01 is looking for DEA38DDF for 150 seconds. 237 | [2024-11-12 17:49:00] 69988F01 and DEA38DDF are positively paired. 238 | [2024-11-12 17:49:00] 69988F01 is attempting to fetch file contents from DEA38DDF. 239 | [2024-11-12 17:49:03] DEA38DDF is delivering digest to 69988F01. 240 | [2024-11-12 17:49:03] 69988F01 is delivering confirmation to DEA38DDF. 241 | [2024-11-12 17:49:03] DEA38DDF left. 242 | [2024-11-12 17:49:03] 69988F01 left. 243 | [2024-11-12 17:49:03] connection closed 244 | [2024-11-12 17:49:03] connection closed 245 | [2024-11-12 17:49:11] connection open 246 | [2024-11-12 17:49:11] 64595E02 joined with the intention of delivering. 247 | [2024-11-12 17:49:11] 64595E02 is waiting for client for 150 seconds. 248 | [2024-11-12 17:49:27] connection open 249 | [2024-11-12 17:49:27] ABBBF4B1 joined with the intention of delivering. 250 | [2024-11-12 17:49:27] ABBBF4B1 is looking for 64595E02 for 150 seconds. 251 | [2024-11-12 17:49:27] ABBBF4B1 and 64595E02 are negatively paired. 252 | [2024-11-12 17:49:27] ABBBF4B1 left. 253 | [2024-11-12 17:49:27] connection closed 254 | [2024-11-12 17:49:27] 64595E02 left. 255 | [2024-11-12 17:49:27] connection closed 256 | [2024-11-12 17:49:41] connection open 257 | [2024-11-12 17:49:41] 58FEEF9C joined with the intention of delivering. 258 | [2024-11-12 17:49:41] 58FEEF9C is looking for 64595E02 for 5 seconds. 259 | [2024-11-12 17:49:46] 58FEEF9C has achieved expiry. 260 | [2024-11-12 17:49:46] 58FEEF9C left. 261 | [2024-11-12 17:49:46] connection closed 262 | [2024-11-12 17:50:03] connection open 263 | [2024-11-12 17:50:03] 58B8F046 joined with the intention of collecting. 264 | [2024-11-12 17:50:03] 58B8F046 is looking for DEA38DDF for 5 seconds. 265 | [2024-11-12 17:50:08] 58B8F046 has achieved expiry. 266 | [2024-11-12 17:50:08] 58B8F046 left. 267 | [2024-11-12 17:50:08] connection closed 268 | ... 269 | ``` 270 | 271 | ## Installation 272 | 273 | ### For development 274 | 275 | 1. Ensure that the required tools and dependencies are installed. 276 | ``` 277 | $ sudo dnf install python3 python3-virtualenv python3-pip git poetry 278 | ``` 279 | 2. Fork the repository and clone the project to your local storage. 280 | ``` 281 | $ git clone git@github.com:$(whoami)/expedite.git 282 | ``` 283 | 3. Make the project cloning location the present working directory. 284 | ``` 285 | $ cd expedite 286 | ``` 287 | 4. Create a virtual environment for installing project dependencies. 288 | ``` 289 | $ virtualenv venv 290 | ``` 291 | 5. Activate the newly created virtual environment before proceeding. 292 | ``` 293 | $ source venv/bin/activate 294 | ``` 295 | 6. Install the project codebase alongside the dependencies. 296 | ``` 297 | (venv) $ poetry install 298 | ``` 299 | 300 | ### For consumption 301 | 302 | #### From PyPI 303 | 304 | 1. Ensure that the required tools and dependencies are installed. 305 | ``` 306 | $ sudo dnf install python3 python3-virtualenv python3-pip 307 | ``` 308 | 2. Create a virtual environment for installing project dependencies. 309 | ``` 310 | $ virtualenv venv 311 | ``` 312 | 3. Activate the newly created virtual environment before proceeding. 313 | ``` 314 | $ source venv/bin/activate 315 | ``` 316 | 4. Install the project codebase from Python Package Index. 317 | ``` 318 | (venv) $ pip3 install expedite 319 | ``` 320 | 321 | #### From GitHub 322 | 323 | ##### Nightly 324 | 325 | 1. Visit the **GitHub Actions** page of the project repository. 326 | ``` 327 | https://github.com/gridhead/expedite/actions 328 | ``` 329 | 2. To get automated builds for **GNU/Linux distributions**, visit the following page. 330 | ``` 331 | https://github.com/gridhead/expedite/actions/workflows/gnul.yml 332 | ``` 333 | 3. To get automated builds for **Microsoft Windows**, visit the following page. 334 | ``` 335 | https://github.com/gridhead/expedite/actions/workflows/mswn.yml 336 | ``` 337 | 4. Please request for the builds if they are unavailable in the recent workflow runs. 338 | ``` 339 | https://github.com/gridhead/expedite/issues 340 | ``` 341 | 342 | ##### Stable 343 | 344 | 1. Visit the **GitHub Releases** page of the project repository. 345 | ``` 346 | https://github.com/gridhead/expedite/releases 347 | ``` 348 | 2. Please file for bug reports and feature requests based on the stable releases. 349 | ``` 350 | https://github.com/gridhead/expedite/issues 351 | ``` 352 | 353 | ## Execution 354 | 355 | ### Server 356 | 357 | 1. Ensure that the previously created virtual environment is activated. 358 | ``` 359 | $ source venv/bin/activate 360 | ``` 361 | 2. Execute the following command to view the help topics of the project. 362 | ``` 363 | (venv) $ ed-server --help 364 | ``` 365 | ``` 366 | Usage: ed-server [OPTIONS] 367 | 368 | Options: 369 | -a, --addr TEXT Set the interface for the service endpoint 370 | [default: 127.0.0.1] 371 | -p, --port INTEGER RANGE Set the port value for the service endpoint 372 | [default: 8080; 64<=x<=65535] 373 | --version Show the version and exit. 374 | --help Show this message and exit. 375 | ``` 376 | 3. Start the broker service using the following command. 377 | ``` 378 | (venv) $ ed-server --addr 0.0.0.0 --p 9090 379 | ``` 380 | 1. The broker service will run on IPv4 addressing (i.e. `0.0.0.0`) and on a specific port (i.e. `9090`). 381 | 3. The broker service can be stopped by sending a keyboard interrupt (i.e. `Ctrl` + `C`) when done. 382 | 4. Note the IP address or the hostname for use by client connections. 383 | ``` 384 | ip a 385 | ``` 386 | 387 | ### Client 388 | 389 | 1. Ensure that the previously created virtual environment is activated. 390 | ``` 391 | $ source venv/bin/activate 392 | ``` 393 | 2. Execute the following command to view the help topics of the project. 394 | ``` 395 | (venv) $ ed-server --help 396 | ``` 397 | ``` 398 | Usage: ed-client [OPTIONS] COMMAND [ARGS]... 399 | 400 | Options: 401 | -h, --host TEXT Set the address for the service endpoint 402 | [required] 403 | -t, --time INTEGER RANGE Set the expiry period for participants [default: 404 | 15; 5<=x<=30] 405 | -e, --endo TEXT Set the identity of the opposing client 406 | --version Show the version and exit. 407 | --help Show this message and exit. 408 | 409 | Commands: 410 | recv Collect file through an encrypted transfer 411 | send Deliver file through an encrypted transfer 412 | ``` 413 | 414 | #### Delivering 415 | 416 | 1. Execute the following command to view the help topics of the `SEND` subcommand. 417 | ``` 418 | (venv) $ ed-client send --help 419 | ``` 420 | ``` 421 | Usage: ed-client send [OPTIONS] 422 | 423 | Deliver file through an encrypted transfer 424 | 425 | Options: 426 | -p, --pswd TEXT Set the password for delivering encryption 427 | [default: 123972B4] 428 | -f, --file PATH Set the filepath for delivering to network 429 | [required] 430 | -s, --size INTEGER RANGE Set the unit size for file chunking (in B) 431 | [default: 262144; 1024<=x<=524288] 432 | --help Show this message and exit. 433 | ``` 434 | 2. If the delivering client is joining the network **before** the collecting client, execute the following command. 435 | ``` 436 | (venv) $ ed-client --host ws://localhost:9090 --time 30 send --file /path/to/file.extn --pswd expedite --size 131072 437 | ``` 438 | ``` 439 | [2024-07-06 11:52:10] Expedite Client v0.1.0 440 | [2024-07-06 11:52:10] Addr. ws://localhost:9090 441 | [2024-07-06 11:52:10] Pass. expedite 442 | [2024-07-06 11:52:10] Plan. DELIVERING 443 | [2024-07-06 11:52:10] Wait. 30 seconds 444 | [2024-07-06 11:52:10] Please share your acquired identity to begin interaction. 445 | [2024-07-06 11:52:10] Attempting to connect to the network. 446 | [2024-07-06 11:52:10] Successfully connected to the network. 447 | [2024-07-06 11:52:10] You are now identified as 01276D06 in the network. 448 | ``` 449 | 1. The delivering client is attempting to connect to the broker service deployed at `ws://localhost:9090`. 450 | 2. The delivering client has an inactivity timeout for `30 seconds` beyond which it will automatically disconnect. 451 | 3. The delivering client has acquired the identity `01276D06` which can be used by the collecting client for discovery. 452 | 4. The delivering client is attempting to share the file named `file.extn` from the location `/path/to/file.extn`. 453 | 5. The delivering client is using the password `expedite` to encrypt the file contents with 128-bit AES encryption. 454 | 6. The delivering client is going to process chunks of size `131072 byte` or `128KiB` at a time for delivering. 455 | 7. The user of the delivering client must share their identity `01276D06` and password to start delivering process. 456 | 8. The delivering client will disconnect from the network if the collecting client opens the program in the wrong mode. 457 | 3. If the delivering client is joining the network **after** the collecting client, execute the following command. 458 | ``` 459 | (venv) $ ed-client --host ws://localhost:9090 --time 30 --endo DEADCAFE send --file /path/to/file.extn --pswd expedite --size 131072 460 | ``` 461 | ``` 462 | [2024-07-06 12:02:09] Expedite Client v0.1.0 463 | [2024-07-06 12:02:09] Addr. ws://localhost:9090 464 | [2024-07-06 12:02:09] Pass. expedite 465 | [2024-07-06 12:02:09] Plan. DELIVERING 466 | [2024-07-06 12:02:09] Wait. 30 seconds 467 | [2024-07-06 12:02:09] Please wait for DEADCAFE to begin interaction. 468 | [2024-07-06 12:02:09] Attempting to connect to the network. 469 | [2024-07-06 12:02:09] Successfully connected to the network. 470 | [2024-07-06 12:02:09] You are now identified as BA40BB0F in the network. 471 | ``` 472 | 1. The delivering client is attempting to connect to the broker service deployed at `ws://localhost:9090`. 473 | 2. The delivering client has an inactivity timeout for `30 seconds` beyond which it will automatically disconnect. 474 | 3. The delivering client has acquired the identity `BA40BB0F` which can be used by the collecting client for discovery. 475 | 4. The delivering client is attempting to share the file named `file.extn` from the location `/path/to/file.extn`. 476 | 5. The delivering client is using the password `expedite` to encrypt the file contents with 128-bit AES encryption. 477 | 6. The delivering client is going to process chunks of size `131072 byte` or `128KiB` at a time for delivering. 478 | 7. The user of the delivering client expects the collecting client with the identity `DEADCAFE` to start interaction. 479 | 8. The delivering client will disconnect from the network if the collecting client opens the program in the wrong mode. 480 | 4. If the average latency from the delivering client to the broker service is **below 100ms**, consider **increasing the chunking size** to **improve the stability** of the delivering process. 481 | 5. If the average latency from the delivering client to the broker service is **above 100ms**, consider **decreasing the chunking size** to **improve the performance** of the delivering process. 482 | 6. Let the delivering process complete or if needed, abort an ongoing delivering process by sending a keyboard interrupt (i.e. `Ctrl` + `C`). 483 | 484 | #### Collecting 485 | 486 | 1. Execute the following command to view the help topics of the `RECV` subcommand. 487 | ``` 488 | Usage: ed-client recv [OPTIONS] 489 | 490 | Collect file through an encrypted transfer 491 | 492 | Options: 493 | -p, --pswd TEXT Set the password for collecting encryption [required] 494 | --help Show this message and exit. 495 | ``` 496 | 2. If the collecting client is joining the network **before** the delivering client, execute the following command. 497 | ``` 498 | (venv) $ ed-client --host ws://localhost:8080 --time 30 recv --pswd expedite 499 | ``` 500 | ``` 501 | [2024-07-06 12:57:43] Expedite Client v0.1.0 502 | [2024-07-06 12:57:43] Addr. ws://localhost:8080 503 | [2024-07-06 12:57:43] Pass. expedite 504 | [2024-07-06 12:57:43] Plan. COLLECTING 505 | [2024-07-06 12:57:43] Wait. 30 seconds 506 | [2024-07-06 12:57:43] Please share your acquired identity to begin interaction. 507 | [2024-07-06 12:57:43] Attempting to connect to the network. 508 | [2024-07-06 12:57:43] Successfully connected to the network. 509 | [2024-07-06 12:57:43] You are now identified as 13755346 in the network. 510 | ``` 511 | 1. The collecting client is attempting to connect to the broker service deployed at `ws://localhost:9090`. 512 | 2. The collecting client has an inactivity timeout for `30 seconds` beyond which it will automatically disconnect. 513 | 3. The collecting client has acquired the identity `13755346` which can be used by the delivering client for discovery. 514 | 4. The collecting client is using the password `expedite` to decrypt the file contents with 128-bit AES encryption. 515 | 5. The user of the collecting client must share their identity `13755346` and password to start collecting process. 516 | 6. The collecting client will disconnect from the network if the delivering client opens the program in the wrong mode. 517 | 3. If the collecting client is joining the network **after** the delivering client, execute the following command. 518 | ``` 519 | (venv) $ ed-client --host ws://localhost:8080 --time 30 --endo DEADCAFE recv --pswd expedite 520 | ``` 521 | ``` 522 | [2024-07-06 12:55:30] Expedite Client v0.1.0 523 | [2024-07-06 12:55:30] Addr. ws://localhost:8080 524 | [2024-07-06 12:55:30] Pass. expedite 525 | [2024-07-06 12:55:30] Plan. COLLECTING 526 | [2024-07-06 12:55:30] Wait. 30 seconds 527 | [2024-07-06 12:55:30] Please wait for DEADCAFE to begin interaction. 528 | [2024-07-06 12:55:30] Attempting to connect to the network. 529 | [2024-07-06 12:55:30] Successfully connected to the network. 530 | [2024-07-06 12:55:30] You are now identified as 13AA7DB2 in the network. 531 | ``` 532 | 1. The collecting client is attempting to connect to the broker service deployed at `ws://localhost:9090`. 533 | 2. The collecting client has an inactivity timeout for `30 seconds` beyond which it will automatically disconnect. 534 | 3. The collecting client has acquired the identity `13AA7DB2` which can be used by the delivering client for discovery. 535 | 4. The collecting client is using the password `expedite` to decrypt the file contents with 128-bit AES encryption. 536 | 5. The user of the collecting client must share their identity `13AA7DB2` and password to start collecting process. 537 | 6. The collecting client will disconnect from the network if the delivering client opens the program in the wrong mode. 538 | 4. Let the collecting process complete or if needed, abort an ongoing collecting process by sending a keyboard interrupt (i.e. `Ctrl` + `C`). 539 | -------------------------------------------------------------------------------- /expedite/client/bridge/wind.py: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | ## Form generated from reading UI file 'main.ui' 4 | ## 5 | ## Created by: Qt User Interface Compiler version 6.7.2 6 | ## 7 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 8 | ################################################################################ 9 | 10 | from PySide6.QtCore import QCoreApplication, QMetaObject, QRect, QSize 11 | from PySide6.QtGui import QFont 12 | from PySide6.QtWidgets import ( 13 | QFrame, 14 | QLabel, 15 | QLineEdit, 16 | QProgressBar, 17 | QPushButton, 18 | QSizePolicy, 19 | QStatusBar, 20 | QTabWidget, 21 | QWidget, 22 | ) 23 | 24 | 25 | class Ui_mainwind: 26 | def setupUi(self, mainwind): 27 | if not mainwind.objectName(): 28 | mainwind.setObjectName("mainwind") 29 | mainwind.resize(395, 310) 30 | sizePolicy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) 31 | sizePolicy.setHorizontalStretch(0) 32 | sizePolicy.setVerticalStretch(0) 33 | sizePolicy.setHeightForWidth(mainwind.sizePolicy().hasHeightForWidth()) 34 | mainwind.setSizePolicy(sizePolicy) 35 | mainwind.setMinimumSize(QSize(395, 310)) 36 | mainwind.setMaximumSize(QSize(395, 310)) 37 | font = QFont() 38 | font.setFamilies(["IBM Plex Sans"]) 39 | font.setPointSize(8) 40 | mainwind.setFont(font) 41 | self.centwdgt = QWidget(mainwind) 42 | self.centwdgt.setObjectName("centwdgt") 43 | sizePolicy.setHeightForWidth(self.centwdgt.sizePolicy().hasHeightForWidth()) 44 | self.centwdgt.setSizePolicy(sizePolicy) 45 | self.centwdgt.setMinimumSize(QSize(395, 290)) 46 | self.centwdgt.setMaximumSize(QSize(395, 290)) 47 | self.mainwdgt = QTabWidget(self.centwdgt) 48 | self.mainwdgt.setObjectName("mainwdgt") 49 | self.mainwdgt.setGeometry(QRect(5, 65, 385, 190)) 50 | sizePolicy.setHeightForWidth(self.mainwdgt.sizePolicy().hasHeightForWidth()) 51 | self.mainwdgt.setSizePolicy(sizePolicy) 52 | self.mainwdgt.setMinimumSize(QSize(385, 190)) 53 | self.mainwdgt.setMaximumSize(QSize(385, 190)) 54 | self.mainwdgt.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 55 | self.mainwdgt.setTabShape(QTabWidget.Rounded) 56 | self.dlvrwtab = QWidget() 57 | self.dlvrwtab.setObjectName("dlvrwtab") 58 | sizePolicy.setHeightForWidth(self.dlvrwtab.sizePolicy().hasHeightForWidth()) 59 | self.dlvrwtab.setSizePolicy(sizePolicy) 60 | self.dlvrwtab.setMinimumSize(QSize(380, 160)) 61 | self.dlvrwtab.setMaximumSize(QSize(380, 160)) 62 | self.dlvr_line_file = QLineEdit(self.dlvrwtab) 63 | self.dlvr_line_file.setObjectName("dlvr_line_file") 64 | self.dlvr_line_file.setGeometry(QRect(5, 35, 370, 25)) 65 | sizePolicy.setHeightForWidth(self.dlvr_line_file.sizePolicy().hasHeightForWidth()) 66 | self.dlvr_line_file.setSizePolicy(sizePolicy) 67 | self.dlvr_line_file.setMinimumSize(QSize(370, 25)) 68 | self.dlvr_line_file.setMaximumSize(QSize(370, 25)) 69 | self.dlvr_line_file.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 70 | self.dlvr_line_file.setReadOnly(True) 71 | self.dlvr_line_file.setClearButtonEnabled(True) 72 | self.dlvr_line_pswd = QLineEdit(self.dlvrwtab) 73 | self.dlvr_line_pswd.setObjectName("dlvr_line_pswd") 74 | self.dlvr_line_pswd.setGeometry(QRect(5, 65, 182, 25)) 75 | sizePolicy.setHeightForWidth(self.dlvr_line_pswd.sizePolicy().hasHeightForWidth()) 76 | self.dlvr_line_pswd.setSizePolicy(sizePolicy) 77 | self.dlvr_line_pswd.setMinimumSize(QSize(182, 25)) 78 | self.dlvr_line_pswd.setMaximumSize(QSize(182, 25)) 79 | self.dlvr_line_pswd.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 80 | self.dlvr_line_pswd.setClearButtonEnabled(True) 81 | self.dlvr_butn_browse = QPushButton(self.dlvrwtab) 82 | self.dlvr_butn_browse.setObjectName("dlvr_butn_browse") 83 | self.dlvr_butn_browse.setGeometry(QRect(80, 130, 70, 25)) 84 | sizePolicy.setHeightForWidth(self.dlvr_butn_browse.sizePolicy().hasHeightForWidth()) 85 | self.dlvr_butn_browse.setSizePolicy(sizePolicy) 86 | self.dlvr_butn_browse.setMinimumSize(QSize(70, 25)) 87 | self.dlvr_butn_browse.setMaximumSize(QSize(70, 25)) 88 | self.dlvr_butn_browse.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 89 | self.dlvr_butn_random = QPushButton(self.dlvrwtab) 90 | self.dlvr_butn_random.setObjectName("dlvr_butn_random") 91 | self.dlvr_butn_random.setGeometry(QRect(155, 130, 70, 25)) 92 | sizePolicy.setHeightForWidth(self.dlvr_butn_random.sizePolicy().hasHeightForWidth()) 93 | self.dlvr_butn_random.setSizePolicy(sizePolicy) 94 | self.dlvr_butn_random.setMinimumSize(QSize(70, 25)) 95 | self.dlvr_butn_random.setMaximumSize(QSize(70, 25)) 96 | self.dlvr_butn_random.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 97 | self.dlvr_line_size = QLineEdit(self.dlvrwtab) 98 | self.dlvr_line_size.setObjectName("dlvr_line_size") 99 | self.dlvr_line_size.setGeometry(QRect(5, 95, 182, 25)) 100 | sizePolicy.setHeightForWidth(self.dlvr_line_size.sizePolicy().hasHeightForWidth()) 101 | self.dlvr_line_size.setSizePolicy(sizePolicy) 102 | self.dlvr_line_size.setMinimumSize(QSize(182, 25)) 103 | self.dlvr_line_size.setMaximumSize(QSize(182, 25)) 104 | self.dlvr_line_size.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 105 | self.dlvr_line_size.setClearButtonEnabled(True) 106 | self.dlvr_butn_normal = QPushButton(self.dlvrwtab) 107 | self.dlvr_butn_normal.setObjectName("dlvr_butn_normal") 108 | self.dlvr_butn_normal.setGeometry(QRect(230, 130, 70, 25)) 109 | sizePolicy.setHeightForWidth(self.dlvr_butn_normal.sizePolicy().hasHeightForWidth()) 110 | self.dlvr_butn_normal.setSizePolicy(sizePolicy) 111 | self.dlvr_butn_normal.setMinimumSize(QSize(70, 25)) 112 | self.dlvr_butn_normal.setMaximumSize(QSize(70, 25)) 113 | self.dlvr_butn_normal.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 114 | self.dlvr_line_endo = QLineEdit(self.dlvrwtab) 115 | self.dlvr_line_endo.setObjectName("dlvr_line_endo") 116 | self.dlvr_line_endo.setGeometry(QRect(193, 65, 182, 25)) 117 | sizePolicy.setHeightForWidth(self.dlvr_line_endo.sizePolicy().hasHeightForWidth()) 118 | self.dlvr_line_endo.setSizePolicy(sizePolicy) 119 | self.dlvr_line_endo.setMinimumSize(QSize(182, 25)) 120 | self.dlvr_line_endo.setMaximumSize(QSize(182, 25)) 121 | self.dlvr_line_endo.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 122 | self.dlvr_line_endo.setClearButtonEnabled(True) 123 | self.dlvr_line_time = QLineEdit(self.dlvrwtab) 124 | self.dlvr_line_time.setObjectName("dlvr_line_time") 125 | self.dlvr_line_time.setGeometry(QRect(193, 95, 182, 25)) 126 | sizePolicy.setHeightForWidth(self.dlvr_line_time.sizePolicy().hasHeightForWidth()) 127 | self.dlvr_line_time.setSizePolicy(sizePolicy) 128 | self.dlvr_line_time.setMinimumSize(QSize(182, 25)) 129 | self.dlvr_line_time.setMaximumSize(QSize(182, 25)) 130 | self.dlvr_line_time.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 131 | self.dlvr_line_time.setClearButtonEnabled(True) 132 | self.dlvr_horiline_b = QFrame(self.dlvrwtab) 133 | self.dlvr_horiline_b.setObjectName("dlvr_horiline_b") 134 | self.dlvr_horiline_b.setGeometry(QRect(5, 125, 370, 2)) 135 | sizePolicy.setHeightForWidth(self.dlvr_horiline_b.sizePolicy().hasHeightForWidth()) 136 | self.dlvr_horiline_b.setSizePolicy(sizePolicy) 137 | self.dlvr_horiline_b.setMinimumSize(QSize(370, 2)) 138 | self.dlvr_horiline_b.setMaximumSize(QSize(370, 2)) 139 | self.dlvr_horiline_b.setStyleSheet("") 140 | self.dlvr_horiline_b.setFrameShape(QFrame.Shape.HLine) 141 | self.dlvr_horiline_b.setFrameShadow(QFrame.Shadow.Sunken) 142 | self.dlvr_horiline_a = QFrame(self.dlvrwtab) 143 | self.dlvr_horiline_a.setObjectName("dlvr_horiline_a") 144 | self.dlvr_horiline_a.setGeometry(QRect(5, 30, 370, 2)) 145 | sizePolicy.setHeightForWidth(self.dlvr_horiline_a.sizePolicy().hasHeightForWidth()) 146 | self.dlvr_horiline_a.setSizePolicy(sizePolicy) 147 | self.dlvr_horiline_a.setMinimumSize(QSize(370, 2)) 148 | self.dlvr_horiline_a.setMaximumSize(QSize(370, 2)) 149 | self.dlvr_horiline_a.setStyleSheet("") 150 | self.dlvr_horiline_a.setFrameShape(QFrame.Shape.HLine) 151 | self.dlvr_horiline_a.setFrameShadow(QFrame.Shadow.Sunken) 152 | self.dlvr_butn_incept = QPushButton(self.dlvrwtab) 153 | self.dlvr_butn_incept.setObjectName("dlvr_butn_incept") 154 | self.dlvr_butn_incept.setGeometry(QRect(305, 130, 70, 25)) 155 | sizePolicy.setHeightForWidth(self.dlvr_butn_incept.sizePolicy().hasHeightForWidth()) 156 | self.dlvr_butn_incept.setSizePolicy(sizePolicy) 157 | self.dlvr_butn_incept.setMinimumSize(QSize(70, 25)) 158 | self.dlvr_butn_incept.setMaximumSize(QSize(70, 25)) 159 | self.dlvr_butn_incept.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 160 | self.dlvr_head_file = QLabel(self.dlvrwtab) 161 | self.dlvr_head_file.setObjectName("dlvr_head_file") 162 | self.dlvr_head_file.setGeometry(QRect(5, 5, 370, 20)) 163 | sizePolicy.setHeightForWidth(self.dlvr_head_file.sizePolicy().hasHeightForWidth()) 164 | self.dlvr_head_file.setSizePolicy(sizePolicy) 165 | self.dlvr_head_file.setMinimumSize(QSize(370, 20)) 166 | self.dlvr_head_file.setMaximumSize(QSize(370, 20)) 167 | self.dlvr_head_file.setStyleSheet("font: italic 10pt \"IBM Plex Sans\";") 168 | self.dlvr_butn_detail = QPushButton(self.dlvrwtab) 169 | self.dlvr_butn_detail.setObjectName("dlvr_butn_detail") 170 | self.dlvr_butn_detail.setGeometry(QRect(5, 130, 70, 25)) 171 | sizePolicy.setHeightForWidth(self.dlvr_butn_detail.sizePolicy().hasHeightForWidth()) 172 | self.dlvr_butn_detail.setSizePolicy(sizePolicy) 173 | self.dlvr_butn_detail.setMinimumSize(QSize(70, 25)) 174 | self.dlvr_butn_detail.setMaximumSize(QSize(70, 25)) 175 | self.dlvr_butn_detail.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 176 | self.mainwdgt.addTab(self.dlvrwtab, "") 177 | self.clctwtab = QWidget() 178 | self.clctwtab.setObjectName("clctwtab") 179 | sizePolicy.setHeightForWidth(self.clctwtab.sizePolicy().hasHeightForWidth()) 180 | self.clctwtab.setSizePolicy(sizePolicy) 181 | self.clctwtab.setMinimumSize(QSize(380, 160)) 182 | self.clctwtab.setMaximumSize(QSize(380, 160)) 183 | self.clct_butn_normal = QPushButton(self.clctwtab) 184 | self.clct_butn_normal.setObjectName("clct_butn_normal") 185 | self.clct_butn_normal.setGeometry(QRect(230, 130, 70, 25)) 186 | sizePolicy.setHeightForWidth(self.clct_butn_normal.sizePolicy().hasHeightForWidth()) 187 | self.clct_butn_normal.setSizePolicy(sizePolicy) 188 | self.clct_butn_normal.setMinimumSize(QSize(70, 25)) 189 | self.clct_butn_normal.setMaximumSize(QSize(70, 25)) 190 | self.clct_butn_normal.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 191 | self.clct_line_file = QLineEdit(self.clctwtab) 192 | self.clct_line_file.setObjectName("clct_line_file") 193 | self.clct_line_file.setGeometry(QRect(5, 35, 370, 25)) 194 | sizePolicy.setHeightForWidth(self.clct_line_file.sizePolicy().hasHeightForWidth()) 195 | self.clct_line_file.setSizePolicy(sizePolicy) 196 | self.clct_line_file.setMinimumSize(QSize(370, 25)) 197 | self.clct_line_file.setMaximumSize(QSize(370, 25)) 198 | self.clct_line_file.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 199 | self.clct_line_file.setReadOnly(True) 200 | self.clct_line_file.setClearButtonEnabled(True) 201 | self.clct_horiline_a = QFrame(self.clctwtab) 202 | self.clct_horiline_a.setObjectName("clct_horiline_a") 203 | self.clct_horiline_a.setGeometry(QRect(5, 30, 370, 2)) 204 | sizePolicy.setHeightForWidth(self.clct_horiline_a.sizePolicy().hasHeightForWidth()) 205 | self.clct_horiline_a.setSizePolicy(sizePolicy) 206 | self.clct_horiline_a.setMinimumSize(QSize(370, 2)) 207 | self.clct_horiline_a.setMaximumSize(QSize(370, 2)) 208 | self.clct_horiline_a.setStyleSheet("") 209 | self.clct_horiline_a.setFrameShape(QFrame.Shape.HLine) 210 | self.clct_horiline_a.setFrameShadow(QFrame.Shadow.Sunken) 211 | self.clct_line_pswd = QLineEdit(self.clctwtab) 212 | self.clct_line_pswd.setObjectName("clct_line_pswd") 213 | self.clct_line_pswd.setGeometry(QRect(5, 65, 182, 25)) 214 | sizePolicy.setHeightForWidth(self.clct_line_pswd.sizePolicy().hasHeightForWidth()) 215 | self.clct_line_pswd.setSizePolicy(sizePolicy) 216 | self.clct_line_pswd.setMinimumSize(QSize(182, 25)) 217 | self.clct_line_pswd.setMaximumSize(QSize(182, 25)) 218 | self.clct_line_pswd.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 219 | self.clct_line_pswd.setClearButtonEnabled(True) 220 | self.clct_butn_random = QPushButton(self.clctwtab) 221 | self.clct_butn_random.setObjectName("clct_butn_random") 222 | self.clct_butn_random.setGeometry(QRect(155, 130, 70, 25)) 223 | sizePolicy.setHeightForWidth(self.clct_butn_random.sizePolicy().hasHeightForWidth()) 224 | self.clct_butn_random.setSizePolicy(sizePolicy) 225 | self.clct_butn_random.setMinimumSize(QSize(70, 25)) 226 | self.clct_butn_random.setMaximumSize(QSize(70, 25)) 227 | self.clct_butn_random.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 228 | self.clct_line_endo = QLineEdit(self.clctwtab) 229 | self.clct_line_endo.setObjectName("clct_line_endo") 230 | self.clct_line_endo.setGeometry(QRect(193, 65, 182, 25)) 231 | sizePolicy.setHeightForWidth(self.clct_line_endo.sizePolicy().hasHeightForWidth()) 232 | self.clct_line_endo.setSizePolicy(sizePolicy) 233 | self.clct_line_endo.setMinimumSize(QSize(182, 25)) 234 | self.clct_line_endo.setMaximumSize(QSize(182, 25)) 235 | self.clct_line_endo.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 236 | self.clct_line_endo.setClearButtonEnabled(True) 237 | self.clct_butn_incept = QPushButton(self.clctwtab) 238 | self.clct_butn_incept.setObjectName("clct_butn_incept") 239 | self.clct_butn_incept.setGeometry(QRect(305, 130, 70, 25)) 240 | sizePolicy.setHeightForWidth(self.clct_butn_incept.sizePolicy().hasHeightForWidth()) 241 | self.clct_butn_incept.setSizePolicy(sizePolicy) 242 | self.clct_butn_incept.setMinimumSize(QSize(70, 25)) 243 | self.clct_butn_incept.setMaximumSize(QSize(70, 25)) 244 | self.clct_butn_incept.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 245 | self.clct_horiline_b = QFrame(self.clctwtab) 246 | self.clct_horiline_b.setObjectName("clct_horiline_b") 247 | self.clct_horiline_b.setGeometry(QRect(5, 125, 370, 2)) 248 | sizePolicy.setHeightForWidth(self.clct_horiline_b.sizePolicy().hasHeightForWidth()) 249 | self.clct_horiline_b.setSizePolicy(sizePolicy) 250 | self.clct_horiline_b.setMinimumSize(QSize(370, 2)) 251 | self.clct_horiline_b.setMaximumSize(QSize(370, 2)) 252 | self.clct_horiline_b.setStyleSheet("") 253 | self.clct_horiline_b.setFrameShape(QFrame.Shape.HLine) 254 | self.clct_horiline_b.setFrameShadow(QFrame.Shadow.Sunken) 255 | self.clct_line_time = QLineEdit(self.clctwtab) 256 | self.clct_line_time.setObjectName("clct_line_time") 257 | self.clct_line_time.setGeometry(QRect(193, 95, 182, 25)) 258 | sizePolicy.setHeightForWidth(self.clct_line_time.sizePolicy().hasHeightForWidth()) 259 | self.clct_line_time.setSizePolicy(sizePolicy) 260 | self.clct_line_time.setMinimumSize(QSize(182, 25)) 261 | self.clct_line_time.setMaximumSize(QSize(182, 25)) 262 | self.clct_line_time.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 263 | self.clct_line_time.setClearButtonEnabled(True) 264 | self.clct_butn_browse = QPushButton(self.clctwtab) 265 | self.clct_butn_browse.setObjectName("clct_butn_browse") 266 | self.clct_butn_browse.setGeometry(QRect(80, 130, 70, 25)) 267 | sizePolicy.setHeightForWidth(self.clct_butn_browse.sizePolicy().hasHeightForWidth()) 268 | self.clct_butn_browse.setSizePolicy(sizePolicy) 269 | self.clct_butn_browse.setMinimumSize(QSize(70, 25)) 270 | self.clct_butn_browse.setMaximumSize(QSize(70, 25)) 271 | self.clct_butn_browse.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 272 | self.clct_head_file = QLabel(self.clctwtab) 273 | self.clct_head_file.setObjectName("clct_head_file") 274 | self.clct_head_file.setGeometry(QRect(5, 5, 370, 20)) 275 | sizePolicy.setHeightForWidth(self.clct_head_file.sizePolicy().hasHeightForWidth()) 276 | self.clct_head_file.setSizePolicy(sizePolicy) 277 | self.clct_head_file.setMinimumSize(QSize(370, 20)) 278 | self.clct_head_file.setMaximumSize(QSize(370, 20)) 279 | self.clct_head_file.setStyleSheet("font: italic 10pt \"IBM Plex Sans\";") 280 | self.clct_line_size = QLineEdit(self.clctwtab) 281 | self.clct_line_size.setObjectName("clct_line_size") 282 | self.clct_line_size.setGeometry(QRect(5, 95, 182, 25)) 283 | sizePolicy.setHeightForWidth(self.clct_line_size.sizePolicy().hasHeightForWidth()) 284 | self.clct_line_size.setSizePolicy(sizePolicy) 285 | self.clct_line_size.setMinimumSize(QSize(182, 25)) 286 | self.clct_line_size.setMaximumSize(QSize(182, 25)) 287 | self.clct_line_size.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 288 | self.clct_line_size.setReadOnly(True) 289 | self.clct_line_size.setClearButtonEnabled(True) 290 | self.clct_butn_detail = QPushButton(self.clctwtab) 291 | self.clct_butn_detail.setObjectName("clct_butn_detail") 292 | self.clct_butn_detail.setGeometry(QRect(5, 130, 70, 25)) 293 | sizePolicy.setHeightForWidth(self.clct_butn_detail.sizePolicy().hasHeightForWidth()) 294 | self.clct_butn_detail.setSizePolicy(sizePolicy) 295 | self.clct_butn_detail.setMinimumSize(QSize(70, 25)) 296 | self.clct_butn_detail.setMaximumSize(QSize(70, 25)) 297 | self.clct_butn_detail.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 298 | self.mainwdgt.addTab(self.clctwtab, "") 299 | self.sockaddr = QLineEdit(self.centwdgt) 300 | self.sockaddr.setObjectName("sockaddr") 301 | self.sockaddr.setGeometry(QRect(5, 5, 385, 25)) 302 | sizePolicy.setHeightForWidth(self.sockaddr.sizePolicy().hasHeightForWidth()) 303 | self.sockaddr.setSizePolicy(sizePolicy) 304 | self.sockaddr.setMinimumSize(QSize(385, 25)) 305 | self.sockaddr.setMaximumSize(QSize(385, 25)) 306 | self.sockaddr.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 307 | self.sockaddr.setClearButtonEnabled(True) 308 | self.progbarg = QProgressBar(self.centwdgt) 309 | self.progbarg.setObjectName("progbarg") 310 | self.progbarg.setGeometry(QRect(5, 260, 385, 20)) 311 | sizePolicy.setHeightForWidth(self.progbarg.sizePolicy().hasHeightForWidth()) 312 | self.progbarg.setSizePolicy(sizePolicy) 313 | self.progbarg.setMinimumSize(QSize(385, 20)) 314 | self.progbarg.setMaximumSize(QSize(385, 20)) 315 | self.progbarg.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 316 | self.progbarg.setValue(0) 317 | self.progbarg.setTextVisible(True) 318 | self.identity = QLineEdit(self.centwdgt) 319 | self.identity.setObjectName("identity") 320 | self.identity.setGeometry(QRect(5, 35, 385, 25)) 321 | sizePolicy.setHeightForWidth(self.identity.sizePolicy().hasHeightForWidth()) 322 | self.identity.setSizePolicy(sizePolicy) 323 | self.identity.setMinimumSize(QSize(385, 25)) 324 | self.identity.setMaximumSize(QSize(385, 25)) 325 | self.identity.setStyleSheet("font: 10pt \"IBM Plex Sans\";") 326 | self.identity.setReadOnly(True) 327 | self.identity.setClearButtonEnabled(False) 328 | mainwind.setCentralWidget(self.centwdgt) 329 | self.statarea = QStatusBar(mainwind) 330 | self.statarea.setObjectName("statarea") 331 | sizePolicy.setHeightForWidth(self.statarea.sizePolicy().hasHeightForWidth()) 332 | self.statarea.setSizePolicy(sizePolicy) 333 | self.statarea.setMinimumSize(QSize(345, 20)) 334 | self.statarea.setMaximumSize(QSize(345, 20)) 335 | self.statarea.setFont(font) 336 | self.statarea.setSizeGripEnabled(False) 337 | mainwind.setStatusBar(self.statarea) 338 | QWidget.setTabOrder(self.sockaddr, self.identity) 339 | QWidget.setTabOrder(self.identity, self.mainwdgt) 340 | QWidget.setTabOrder(self.mainwdgt, self.dlvr_line_file) 341 | QWidget.setTabOrder(self.dlvr_line_file, self.dlvr_line_pswd) 342 | QWidget.setTabOrder(self.dlvr_line_pswd, self.dlvr_line_endo) 343 | QWidget.setTabOrder(self.dlvr_line_endo, self.dlvr_line_size) 344 | QWidget.setTabOrder(self.dlvr_line_size, self.dlvr_line_time) 345 | QWidget.setTabOrder(self.dlvr_line_time, self.dlvr_butn_detail) 346 | QWidget.setTabOrder(self.dlvr_butn_detail, self.dlvr_butn_browse) 347 | QWidget.setTabOrder(self.dlvr_butn_browse, self.dlvr_butn_random) 348 | QWidget.setTabOrder(self.dlvr_butn_random, self.dlvr_butn_normal) 349 | QWidget.setTabOrder(self.dlvr_butn_normal, self.dlvr_butn_incept) 350 | QWidget.setTabOrder(self.dlvr_butn_incept, self.clct_line_file) 351 | QWidget.setTabOrder(self.clct_line_file, self.clct_line_pswd) 352 | QWidget.setTabOrder(self.clct_line_pswd, self.clct_line_endo) 353 | QWidget.setTabOrder(self.clct_line_endo, self.clct_line_size) 354 | QWidget.setTabOrder(self.clct_line_size, self.clct_line_time) 355 | QWidget.setTabOrder(self.clct_line_time, self.clct_butn_detail) 356 | QWidget.setTabOrder(self.clct_butn_detail, self.clct_butn_browse) 357 | QWidget.setTabOrder(self.clct_butn_browse, self.clct_butn_random) 358 | QWidget.setTabOrder(self.clct_butn_random, self.clct_butn_normal) 359 | QWidget.setTabOrder(self.clct_butn_normal, self.clct_butn_incept) 360 | 361 | self.retranslateUi(mainwind) 362 | 363 | self.mainwdgt.setCurrentIndex(0) 364 | 365 | 366 | QMetaObject.connectSlotsByName(mainwind) 367 | # setupUi 368 | 369 | def retranslateUi(self, mainwind): 370 | mainwind.setWindowTitle(QCoreApplication.translate("mainwind", "MainWindow", None)) 371 | self.dlvr_line_file.setPlaceholderText(QCoreApplication.translate("mainwind", "Delivering filepath", None)) 372 | self.dlvr_line_pswd.setPlaceholderText(QCoreApplication.translate("mainwind", "Encryption password", None)) 373 | self.dlvr_butn_browse.setText(QCoreApplication.translate("mainwind", "BROWSE", None)) 374 | self.dlvr_butn_random.setText(QCoreApplication.translate("mainwind", "RANDOM", None)) 375 | self.dlvr_line_size.setText("") 376 | self.dlvr_line_size.setPlaceholderText(QCoreApplication.translate("mainwind", "Processing size", None)) 377 | self.dlvr_butn_normal.setText(QCoreApplication.translate("mainwind", "NORMAL", None)) 378 | self.dlvr_line_endo.setText("") 379 | self.dlvr_line_endo.setPlaceholderText(QCoreApplication.translate("mainwind", "Collecting identity", None)) 380 | self.dlvr_line_time.setText("") 381 | self.dlvr_line_time.setPlaceholderText(QCoreApplication.translate("mainwind", "Expiry period", None)) 382 | self.dlvr_butn_incept.setText(QCoreApplication.translate("mainwind", "INCEPT", None)) 383 | self.dlvr_head_file.setText(QCoreApplication.translate("mainwind", "No location selected", None)) 384 | self.dlvr_butn_detail.setText(QCoreApplication.translate("mainwind", "DETAIL", None)) 385 | self.mainwdgt.setTabText(self.mainwdgt.indexOf(self.dlvrwtab), QCoreApplication.translate("mainwind", "DELIVERING", None)) 386 | self.clct_butn_normal.setText(QCoreApplication.translate("mainwind", "NORMAL", None)) 387 | self.clct_line_file.setPlaceholderText(QCoreApplication.translate("mainwind", "Collecting filepath", None)) 388 | self.clct_line_pswd.setPlaceholderText(QCoreApplication.translate("mainwind", "Decryption password", None)) 389 | self.clct_butn_random.setText(QCoreApplication.translate("mainwind", "RANDOM", None)) 390 | self.clct_line_endo.setText("") 391 | self.clct_line_endo.setPlaceholderText(QCoreApplication.translate("mainwind", "Delivering identity", None)) 392 | self.clct_butn_incept.setText(QCoreApplication.translate("mainwind", "INCEPT", None)) 393 | self.clct_line_time.setText("") 394 | self.clct_line_time.setPlaceholderText(QCoreApplication.translate("mainwind", "Expiry period", None)) 395 | self.clct_butn_browse.setText(QCoreApplication.translate("mainwind", "BROWSE", None)) 396 | self.clct_head_file.setText(QCoreApplication.translate("mainwind", "No location selected", None)) 397 | self.clct_line_size.setText("") 398 | self.clct_line_size.setPlaceholderText(QCoreApplication.translate("mainwind", "Processing size", None)) 399 | self.clct_butn_detail.setText(QCoreApplication.translate("mainwind", "DETAIL", None)) 400 | self.mainwdgt.setTabText(self.mainwdgt.indexOf(self.clctwtab), QCoreApplication.translate("mainwind", "COLLECTING", None)) 401 | self.sockaddr.setPlaceholderText(QCoreApplication.translate("mainwind", "Set the address for the service endpoint", None)) 402 | self.identity.setPlaceholderText(QCoreApplication.translate("mainwind", "Please initialize the connection to acquire an identity", None)) 403 | # retranslateUi 404 | 405 | --------------------------------------------------------------------------------