├── .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 | 
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 | 
44 |
45 | ## Illustration
46 |
47 | ### Client
48 |
49 | #### Bridge - Info
50 |
51 | 
52 |
53 | #### Bridge - Delivering - Static
54 |
55 | 
56 |
57 | #### Bridge - Collecting - Static
58 |
59 | 
60 |
61 | #### Bridge - Delivering - Progress
62 |
63 | 
64 |
65 | #### Bridge - Collecting - Progress
66 |
67 | 
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 | 
188 |
189 | #### Prompt - Collecting - Progress
190 |
191 | 
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 |
--------------------------------------------------------------------------------