Merge branch 'main' into android_migration
This commit is contained in:
commit
e6cceac19f
|
|
@ -17,20 +17,20 @@ permissions:
|
|||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: "pip"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install ".[win10]"
|
||||
run: pip install .
|
||||
|
||||
- name: Install pyinstaller
|
||||
run: pip install "pyinstaller==6.13.0"
|
||||
|
|
@ -42,47 +42,49 @@ jobs:
|
|||
run: mv dist/TgWsProxy.exe dist/TgWsProxy_windows.exe
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: TgWsProxy
|
||||
path: |
|
||||
dist/TgWsProxy_windows.exe
|
||||
path: dist/TgWsProxy_windows.exe
|
||||
|
||||
build-win7:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: x64
|
||||
suffix: 64bit
|
||||
- arch: x86
|
||||
suffix: 32bit
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python 3.8 (last version supporting Win7)
|
||||
uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.8"
|
||||
architecture: ${{ matrix.arch }}
|
||||
cache: "pip"
|
||||
|
||||
- name: Install dependencies (Win7-compatible)
|
||||
run: pip install ".[win7]"
|
||||
- name: Install dependencies & pyinstaller
|
||||
run: pip install . "pyinstaller==5.13.2"
|
||||
|
||||
- name: Install pyinstaller
|
||||
run: pip install "pyinstaller==5.13.2"
|
||||
|
||||
- name: Build EXE with PyInstaller (Win7)
|
||||
- name: Build EXE with PyInstaller
|
||||
run: pyinstaller packaging/windows.spec --noconfirm
|
||||
|
||||
- name: Rename artifact
|
||||
run: mv dist/TgWsProxy.exe dist/TgWsProxy_windows_7.exe
|
||||
run: mv dist/TgWsProxy.exe dist/TgWsProxy_windows_7_${{ matrix.suffix }}.exe
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: TgWsProxy-win7
|
||||
path: dist/TgWsProxy_windows_7.exe
|
||||
name: TgWsProxy-win7-${{ matrix.suffix }}
|
||||
path: dist/TgWsProxy_windows_7_${{ matrix.suffix }}.exe
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install universal2 Python
|
||||
run: |
|
||||
|
|
@ -142,7 +144,7 @@ jobs:
|
|||
-w wheelhouse/universal2
|
||||
|
||||
python3.12 -m pip install --no-deps wheelhouse/universal2/*.whl
|
||||
python3.12 -m pip install ".[macos]"
|
||||
python3.12 -m pip install .
|
||||
python3.12 -m pip install pyinstaller==6.13.0
|
||||
|
||||
- name: Create macOS icon from ICO
|
||||
|
|
@ -217,7 +219,7 @@ jobs:
|
|||
rm -rf "$DMG_TEMP"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: TgWsProxy-macOS
|
||||
path: dist/TgWsProxy_macos_universal.dmg
|
||||
|
|
@ -226,7 +228,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
|
|
@ -244,7 +246,7 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: |
|
||||
.venv/bin/pip install --upgrade pip
|
||||
.venv/bin/pip install ".[linux]"
|
||||
.venv/bin/pip install .
|
||||
.venv/bin/pip install "pyinstaller==6.13.0"
|
||||
|
||||
- name: Build binary with PyInstaller
|
||||
|
|
@ -309,7 +311,7 @@ jobs:
|
|||
"dist/TgWsProxy_linux_amd64.deb"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: TgWsProxy-linux
|
||||
path: |
|
||||
|
|
@ -389,33 +391,15 @@ jobs:
|
|||
path: android/app/build/outputs/apk/release/${{ env.ANDROID_APK_NAME }}
|
||||
|
||||
release:
|
||||
needs: [build, build-win7, build-macos, build-linux, build-android]
|
||||
needs: [build-windows, build-win7, build-macos, build-linux, build-android]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.inputs.make_release == 'true' }}
|
||||
steps:
|
||||
- name: Download main build
|
||||
uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: TgWsProxy
|
||||
path: dist
|
||||
|
||||
- name: Download Win7 build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: TgWsProxy-win7
|
||||
path: dist
|
||||
|
||||
- name: Download macOS build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: TgWsProxy-macOS
|
||||
path: dist
|
||||
|
||||
- name: Download Linux build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: TgWsProxy-linux
|
||||
pattern: TgWsProxy*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download Android build
|
||||
uses: actions/download-artifact@v4
|
||||
|
|
@ -432,7 +416,8 @@ jobs:
|
|||
## TG WS Proxy ${{ github.event.inputs.version }}
|
||||
files: |
|
||||
dist/TgWsProxy_windows.exe
|
||||
dist/TgWsProxy_windows_7.exe
|
||||
dist/TgWsProxy_windows_7_64bit.exe
|
||||
dist/TgWsProxy_windows_7_32bit.exe
|
||||
dist/TgWsProxy_macos_universal.dmg
|
||||
dist/TgWsProxy_linux_amd64
|
||||
dist/TgWsProxy_linux_amd64.deb
|
||||
|
|
|
|||
33
README.md
33
README.md
|
|
@ -56,6 +56,21 @@ Telegram Desktop → SOCKS5 (127.0.0.1:1080) → TG WS Proxy → WSS → Telegra
|
|||
|
||||
Для Debian/Ubuntu скачайте со [страницы релизов](https://github.com/Flowseal/tg-ws-proxy/releases) пакет **`TgWsProxy_linux_amd64.deb`**.
|
||||
|
||||
Для Arch и Arch-Based дистрибутивов подготовлены пакеты в AUR: [tg-ws-proxy-bin](https://aur.archlinux.org/packages/tg-ws-proxy-bin), [tg-ws-proxy-git](https://aur.archlinux.org/packages/tg-ws-proxy-git), [tg-ws-proxy-cli](https://aur.archlinux.org/packages/tg-ws-proxy-cli)
|
||||
|
||||
```shell
|
||||
# Установка без AUR-helper
|
||||
git clone https://aur.archlinux.org/tg-ws-proxy-bin.git
|
||||
cd tg-ws-proxy-bin
|
||||
makepkg -si
|
||||
|
||||
# При помощи AUR-helper
|
||||
paru -S tg-ws-proxy-bin
|
||||
|
||||
# Если вы установили -cli пакет, то запуск осуществляется через systemctl, где 8888 это номер порта прокси:
|
||||
sudo systemctl start tg-ws-proxy-cli@8888
|
||||
```
|
||||
|
||||
Для остальных дистрибутивов можно использовать **`TgWsProxy_linux_amd64`** (бинарный файл для x86_64).
|
||||
|
||||
```bash
|
||||
|
|
@ -93,31 +108,24 @@ pip install -e .
|
|||
tg-ws-proxy
|
||||
```
|
||||
|
||||
### Windows 10+
|
||||
### Windows 7/10+
|
||||
|
||||
```bash
|
||||
pip install -e ".[win10]"
|
||||
tg-ws-proxy-tray-win
|
||||
```
|
||||
|
||||
### Windows 7
|
||||
|
||||
```bash
|
||||
pip install -e ".[win7]"
|
||||
pip install -e .
|
||||
tg-ws-proxy-tray-win
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
```bash
|
||||
pip install -e ".[macos]"
|
||||
pip install -e .
|
||||
tg-ws-proxy-tray-macos
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
```bash
|
||||
pip install -e ".[linux]"
|
||||
pip install -e .
|
||||
tg-ws-proxy-tray-linux
|
||||
```
|
||||
|
||||
|
|
@ -262,7 +270,8 @@ Tray-приложение хранит данные в:
|
|||
Минимально поддерживаемые версии ОС для текущих бинарных сборок:
|
||||
|
||||
- Windows 10+ для `TgWsProxy_windows.exe`
|
||||
- Windows 7 для `TgWsProxy_windows_7.exe`
|
||||
- Windows 7 (x64) для `TgWsProxy_windows_7_64bit.exe`
|
||||
- Windows 7 (x32) для `TgWsProxy_windows_7_32bit.exe`
|
||||
- Intel macOS 10.15+
|
||||
- Apple Silicon macOS 11.0+
|
||||
- Linux x86_64 (требуется AppIndicator для системного трея)
|
||||
|
|
|
|||
60
macos.py
60
macos.py
|
|
@ -193,6 +193,10 @@ def _ensure_menubar_icon():
|
|||
|
||||
# Native macOS dialogs
|
||||
|
||||
def _escape_osascript_text(text: str) -> str:
|
||||
return text.replace('\\', '\\\\').replace('"', '\\"')
|
||||
|
||||
|
||||
def _osascript(script: str) -> str:
|
||||
r = subprocess.run(
|
||||
['osascript', '-e', script],
|
||||
|
|
@ -201,28 +205,46 @@ def _osascript(script: str) -> str:
|
|||
|
||||
|
||||
def _show_error(text: str, title: str = "TG WS Proxy"):
|
||||
text_esc = text.replace('\\', '\\\\').replace('"', '\\"')
|
||||
title_esc = title.replace('\\', '\\\\').replace('"', '\\"')
|
||||
text_esc = _escape_osascript_text(text)
|
||||
title_esc = _escape_osascript_text(title)
|
||||
_osascript(
|
||||
f'display dialog "{text_esc}" with title "{title_esc}" '
|
||||
f'buttons {{"OK"}} default button "OK" with icon stop')
|
||||
|
||||
|
||||
def _show_info(text: str, title: str = "TG WS Proxy"):
|
||||
text_esc = text.replace('\\', '\\\\').replace('"', '\\"')
|
||||
title_esc = title.replace('\\', '\\\\').replace('"', '\\"')
|
||||
text_esc = _escape_osascript_text(text)
|
||||
title_esc = _escape_osascript_text(title)
|
||||
_osascript(
|
||||
f'display dialog "{text_esc}" with title "{title_esc}" '
|
||||
f'buttons {{"OK"}} default button "OK" with icon note')
|
||||
|
||||
|
||||
def _ask_yes_no(text: str, title: str = "TG WS Proxy") -> bool:
|
||||
text_esc = text.replace('\\', '\\\\').replace('"', '\\"')
|
||||
title_esc = title.replace('\\', '\\\\').replace('"', '\\"')
|
||||
result = _osascript(
|
||||
f'display dialog "{text_esc}" with title "{title_esc}" '
|
||||
f'buttons {{"Нет", "Да"}} default button "Да" with icon note')
|
||||
return "Да" in result
|
||||
result = _ask_yes_no_close(text, title)
|
||||
return result is True
|
||||
|
||||
|
||||
def _ask_yes_no_close(text: str,
|
||||
title: str = "TG WS Proxy") -> Optional[bool]:
|
||||
text_esc = _escape_osascript_text(text)
|
||||
title_esc = _escape_osascript_text(title)
|
||||
r = subprocess.run(
|
||||
['osascript', '-e',
|
||||
f'button returned of (display dialog "{text_esc}" '
|
||||
f'with title "{title_esc}" '
|
||||
f'buttons {{"Закрыть", "Нет", "Да"}} '
|
||||
f'default button "Да" cancel button "Закрыть" with icon note)'],
|
||||
capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
return None
|
||||
|
||||
result = r.stdout.strip()
|
||||
if result == "Да":
|
||||
return True
|
||||
if result == "Нет":
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
# Proxy lifecycle
|
||||
|
|
@ -291,15 +313,16 @@ def _on_open_logs(_=None):
|
|||
# Show a native text input dialog. Returns None if cancelled.
|
||||
def _osascript_input(prompt: str, default: str,
|
||||
title: str = "TG WS Proxy") -> Optional[str]:
|
||||
prompt_esc = prompt.replace('\\', '\\\\').replace('"', '\\"')
|
||||
default_esc = default.replace('\\', '\\\\').replace('"', '\\"')
|
||||
title_esc = title.replace('\\', '\\\\').replace('"', '\\"')
|
||||
prompt_esc = _escape_osascript_text(prompt)
|
||||
default_esc = _escape_osascript_text(default)
|
||||
title_esc = _escape_osascript_text(title)
|
||||
r = subprocess.run(
|
||||
['osascript', '-e',
|
||||
f'text returned of (display dialog "{prompt_esc}" '
|
||||
f'default answer "{default_esc}" '
|
||||
f'with title "{title_esc}" '
|
||||
f'buttons {{"Отмена", "OK"}} default button "OK")'],
|
||||
f'buttons {{"Закрыть", "OK"}} '
|
||||
f'default button "OK" cancel button "Закрыть")'],
|
||||
capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
return None
|
||||
|
|
@ -360,7 +383,9 @@ def _edit_config_dialog():
|
|||
return
|
||||
|
||||
# Verbose
|
||||
verbose = _ask_yes_no("Включить подробное логирование (verbose)?")
|
||||
verbose = _ask_yes_no_close("Включить подробное логирование (verbose)?")
|
||||
if verbose is None:
|
||||
return
|
||||
|
||||
# Advanced settings
|
||||
adv_str = _osascript_input(
|
||||
|
|
@ -369,6 +394,8 @@ def _edit_config_dialog():
|
|||
f"{cfg.get('buf_kb', DEFAULT_CONFIG['buf_kb'])},"
|
||||
f"{cfg.get('pool_size', DEFAULT_CONFIG['pool_size'])},"
|
||||
f"{cfg.get('log_max_mb', DEFAULT_CONFIG['log_max_mb'])}")
|
||||
if adv_str is None:
|
||||
return
|
||||
|
||||
adv = {}
|
||||
if adv_str:
|
||||
|
|
@ -399,7 +426,8 @@ def _edit_config_dialog():
|
|||
if _app:
|
||||
_app.update_menu_title()
|
||||
|
||||
if _ask_yes_no("Настройки сохранены.\n\nПерезапустить прокси сейчас?"):
|
||||
if _ask_yes_no_close(
|
||||
"Настройки сохранены.\n\nПерезапустить прокси сейчас?"):
|
||||
restart_proxy()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
__version__ = "1.1.3"
|
||||
__version__ = "1.3.0"
|
||||
|
|
@ -18,59 +18,37 @@ authors = [
|
|||
|
||||
keywords = [
|
||||
"telegram",
|
||||
"tdesktop",
|
||||
"proxy",
|
||||
"websocket"
|
||||
"bypass",
|
||||
"websocket",
|
||||
"socks5",
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Console",
|
||||
"Environment :: MacOS X :: Cocoa",
|
||||
"Environment :: Win32 (MS Windows)",
|
||||
"Environment :: X11 Applications :: GTK",
|
||||
"Intended Audience :: Customer Service",
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Operating System :: OS Independent",
|
||||
"Topic :: System :: Networking :: Firewalls",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"pyperclip==1.9.0",
|
||||
|
||||
"psutil==5.9.8; platform_system == 'Windows' and python_version < '3.9'",
|
||||
"cryptography==41.0.7; platform_system == 'Windows' and python_version < '3.9'",
|
||||
"Pillow==10.4.0; platform_system == 'Windows' and python_version < '3.9'",
|
||||
|
||||
"psutil==7.0.0; platform_system != 'Windows' or python_version >= '3.9'",
|
||||
"cryptography==46.0.5; platform_system != 'Windows' or python_version >= '3.9'",
|
||||
]
|
||||
"Pillow==12.1.1; (platform_system != 'Windows' or python_version >= '3.9') and platform_system != 'Darwin'",
|
||||
|
||||
[project.optional-dependencies]
|
||||
win7 = [
|
||||
"customtkinter==5.2.2",
|
||||
"Pillow==10.4.0",
|
||||
"psutil==5.9.8",
|
||||
"pystray==0.19.5",
|
||||
"pyperclip==1.9.0",
|
||||
]
|
||||
|
||||
win10 = [
|
||||
"customtkinter==5.2.2",
|
||||
"Pillow==12.1.1",
|
||||
"psutil==7.0.0",
|
||||
"pystray==0.19.5",
|
||||
"pyperclip==1.9.0",
|
||||
]
|
||||
|
||||
macos = [
|
||||
"Pillow==12.1.0",
|
||||
"psutil==7.0.0",
|
||||
"pyperclip==1.9.0",
|
||||
"rumps==0.4.0",
|
||||
]
|
||||
|
||||
linux = [
|
||||
"customtkinter==5.2.2",
|
||||
"Pillow==12.1.1",
|
||||
"psutil==7.0.0",
|
||||
"pystray==0.19.5",
|
||||
"pyperclip==1.9.0",
|
||||
"customtkinter==5.2.2; platform_system != 'Darwin'",
|
||||
"pystray==0.19.5; platform_system != 'Darwin'",
|
||||
"rumps==0.4.0; platform_system == 'Darwin'",
|
||||
"Pillow==12.1.0; platform_system == 'Darwin'",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
|
|
|||
246
stress_test.py
246
stress_test.py
|
|
@ -1,246 +0,0 @@
|
|||
"""
|
||||
Stress-test: сравнение OLD vs NEW реализаций горячих функций прокси.
|
||||
|
||||
Тестируются:
|
||||
1. _build_frame — сборка WS-фрейма (masked binary)
|
||||
2. _build_frame — сборка WS-фрейма (unmasked)
|
||||
3. _socks5_reply — генерация SOCKS5-ответа
|
||||
4. _dc_from_init XOR-часть (bytes(a^b for …) vs int.from_bytes)
|
||||
5. mask key generation (os.urandom vs PRNG)
|
||||
"""
|
||||
|
||||
import gc
|
||||
import os
|
||||
import random
|
||||
import struct
|
||||
import time
|
||||
|
||||
# ── Размеры данных, типичные для Telegram ──────────────────────────
|
||||
SMALL = 64 # init-пакет / ack
|
||||
MEDIUM = 1024 # текстовое сообщение
|
||||
LARGE = 65536 # фото / голосовое
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# XOR mask (не менялся — для полноты)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
def xor_mask(data: bytes, mask: bytes) -> bytes:
|
||||
if not data:
|
||||
return data
|
||||
n = len(data)
|
||||
mask_rep = (mask * (n // 4 + 1))[:n]
|
||||
return (int.from_bytes(data, 'big') ^ int.from_bytes(mask_rep, 'big')).to_bytes(n, 'big')
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# _build_frame
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
def build_frame_old(opcode: int, data: bytes, mask: bool = False) -> bytes:
|
||||
"""Старая: bytearray + append/extend + os.urandom."""
|
||||
header = bytearray()
|
||||
header.append(0x80 | opcode)
|
||||
length = len(data)
|
||||
mask_bit = 0x80 if mask else 0x00
|
||||
|
||||
if length < 126:
|
||||
header.append(mask_bit | length)
|
||||
elif length < 65536:
|
||||
header.append(mask_bit | 126)
|
||||
header.extend(struct.pack('>H', length))
|
||||
else:
|
||||
header.append(mask_bit | 127)
|
||||
header.extend(struct.pack('>Q', length))
|
||||
|
||||
if mask:
|
||||
mask_key = os.urandom(4)
|
||||
header.extend(mask_key)
|
||||
return bytes(header) + xor_mask(data, mask_key)
|
||||
return bytes(header) + data
|
||||
|
||||
|
||||
# ── Новая: pre-compiled struct + PRNG ──────────────────────────────
|
||||
_st_BB = struct.Struct('>BB')
|
||||
_st_BBH = struct.Struct('>BBH')
|
||||
_st_BBQ = struct.Struct('>BBQ')
|
||||
_st_BB4s = struct.Struct('>BB4s')
|
||||
_st_BBH4s = struct.Struct('>BBH4s')
|
||||
_st_BBQ4s = struct.Struct('>BBQ4s')
|
||||
|
||||
_mask_rng = random.Random(int.from_bytes(os.urandom(16), 'big'))
|
||||
_mask_pack = struct.Struct('>I').pack
|
||||
|
||||
def _random_mask_key() -> bytes:
|
||||
return _mask_pack(_mask_rng.getrandbits(32))
|
||||
|
||||
def build_frame_new(opcode: int, data: bytes, mask: bool = False) -> bytes:
|
||||
"""Новая: struct.pack + PRNG mask."""
|
||||
length = len(data)
|
||||
fb = 0x80 | opcode
|
||||
|
||||
if not mask:
|
||||
if length < 126:
|
||||
return _st_BB.pack(fb, length) + data
|
||||
if length < 65536:
|
||||
return _st_BBH.pack(fb, 126, length) + data
|
||||
return _st_BBQ.pack(fb, 127, length) + data
|
||||
|
||||
mask_key = _random_mask_key()
|
||||
masked = xor_mask(data, mask_key)
|
||||
if length < 126:
|
||||
return _st_BB4s.pack(fb, 0x80 | length, mask_key) + masked
|
||||
if length < 65536:
|
||||
return _st_BBH4s.pack(fb, 0x80 | 126, length, mask_key) + masked
|
||||
return _st_BBQ4s.pack(fb, 0x80 | 127, length, mask_key) + masked
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# _socks5_reply
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
def socks5_reply_old(status):
|
||||
return bytes([0x05, status, 0x00, 0x01]) + b'\x00' * 6
|
||||
|
||||
_SOCKS5_REPLIES = {s: bytes([0x05, s, 0x00, 0x01, 0, 0, 0, 0, 0, 0])
|
||||
for s in (0x00, 0x05, 0x07, 0x08)}
|
||||
|
||||
def socks5_reply_new(status):
|
||||
return _SOCKS5_REPLIES[status]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# dc_from_init XOR (8 байт keystream ^ data)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
def dc_xor_old(data8: bytes, ks8: bytes) -> bytes:
|
||||
"""Старая: генераторное выражение."""
|
||||
return bytes(a ^ b for a, b in zip(data8, ks8))
|
||||
|
||||
def dc_xor_new(data8: bytes, ks8: bytes) -> bytes:
|
||||
"""Новая: int.from_bytes."""
|
||||
return (int.from_bytes(data8, 'big') ^ int.from_bytes(ks8, 'big')).to_bytes(8, 'big')
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# mask key: os.urandom(4) vs PRNG
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
def mask_key_old() -> bytes:
|
||||
return os.urandom(4)
|
||||
|
||||
def mask_key_new() -> bytes:
|
||||
return _random_mask_key()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Бенчмарк
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
def bench(func, args_list: list, iters: int) -> float:
|
||||
gc.collect()
|
||||
for i in range(min(100, iters)):
|
||||
func(*args_list[i % len(args_list)])
|
||||
start = time.perf_counter()
|
||||
for i in range(iters):
|
||||
func(*args_list[i % len(args_list)])
|
||||
elapsed = time.perf_counter() - start
|
||||
return elapsed / iters * 1_000_000 # мкс
|
||||
|
||||
|
||||
def compare(name: str, old_fn, new_fn, args_list: list, iters: int):
|
||||
t_old = bench(old_fn, args_list, iters)
|
||||
t_new = bench(new_fn, args_list, iters)
|
||||
speedup = t_old / t_new if t_new > 0 else float('inf')
|
||||
marker = '✅' if speedup >= 1.0 else '⚠️'
|
||||
print(f" {name:.<42s} OLD {t_old:8.3f} мкс | NEW {t_new:8.3f} мкс | {speedup:5.2f}x {marker}")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
def main():
|
||||
print("=" * 74)
|
||||
print(" Stress Test: OLD vs NEW (горячие функции tg_ws_proxy)")
|
||||
print("=" * 74)
|
||||
|
||||
N = 500_000
|
||||
|
||||
# # ── 1. _build_frame masked ────────────────────────────────────
|
||||
# print(f"\n── _build_frame masked ({N:,} итераций) ──")
|
||||
# for size, label in [(SMALL, "64B"), (MEDIUM, "1KB"), (LARGE, "64KB")]:
|
||||
# data_list = [(0x2, os.urandom(size), True) for _ in range(1000)]
|
||||
# compare(f"build_frame masked {label}",
|
||||
# build_frame_old, build_frame_new, data_list, N)
|
||||
|
||||
# # ── 2. _build_frame unmasked ──────────────────────────────────
|
||||
# print(f"\n── _build_frame unmasked ({N:,} итераций) ──")
|
||||
# for size, label in [(SMALL, "64B"), (MEDIUM, "1KB"), (LARGE, "64KB")]:
|
||||
# data_list = [(0x2, os.urandom(size), False) for _ in range(1000)]
|
||||
# compare(f"build_frame unmasked {label}",
|
||||
# build_frame_old, build_frame_new, data_list, N)
|
||||
|
||||
# # ── 3. mask key generation ────────────────────────────────────
|
||||
# print(f"\n── mask key: os.urandom(4) vs PRNG ({N:,} итераций) ──")
|
||||
# compare("mask_key", mask_key_old, mask_key_new, [()] * 100, N)
|
||||
|
||||
# # ── 4. _socks5_reply ─────────────────────────────────────────
|
||||
N2 = 2_000_000
|
||||
# print(f"\n── _socks5_reply ({N2:,} итераций) ──")
|
||||
# compare("socks5_reply", socks5_reply_old, socks5_reply_new,
|
||||
# [(s,) for s in (0x00, 0x05, 0x07, 0x08)], N2)
|
||||
|
||||
# # ── 5. dc_from_init XOR (8 bytes) ────────────────────────────
|
||||
# print(f"\n── dc_xor 8B: generator vs int.from_bytes ({N2:,} итераций) ──")
|
||||
# compare("dc_xor_8B", dc_xor_old, dc_xor_new,
|
||||
# [(os.urandom(8), os.urandom(8)) for _ in range(1000)], N2)
|
||||
|
||||
# ── 6. _read_frame struct.unpack vs pre-compiled ─────────────
|
||||
print(f"\n── struct unpack read-path ({N2:,} итераций) ──")
|
||||
_st_H_pre = struct.Struct('>H')
|
||||
_st_Q_pre = struct.Struct('>Q')
|
||||
h_bufs = [(os.urandom(2),) for _ in range(1000)]
|
||||
q_bufs = [(os.urandom(8),) for _ in range(1000)]
|
||||
compare("unpack >H",
|
||||
lambda b: struct.unpack('>H', b),
|
||||
lambda b: _st_H_pre.unpack(b),
|
||||
h_bufs, N2)
|
||||
compare("unpack >Q",
|
||||
lambda b: struct.unpack('>Q', b),
|
||||
lambda b: _st_Q_pre.unpack(b),
|
||||
q_bufs, N2)
|
||||
|
||||
# ── 7. dc_from_init: 2x unpack vs 1x merged ─────────────────
|
||||
print(f"\n── dc_from_init unpack: 2 calls vs 1 merged ({N2:,} итераций) ──")
|
||||
_st_Ih = struct.Struct('<Ih')
|
||||
plains = [(os.urandom(8),) for _ in range(1000)]
|
||||
def dc_unpack_old(p):
|
||||
return struct.unpack('<I', p[0:4])[0], struct.unpack('<h', p[4:6])[0]
|
||||
def dc_unpack_new(p):
|
||||
return _st_Ih.unpack(p[:6])
|
||||
compare("dc_unpack", dc_unpack_old, dc_unpack_new, plains, N2)
|
||||
|
||||
# ── 8. bytes() copy vs direct slice ──────────────────────────
|
||||
print(f"\n── bytes(slice) vs direct slice ({N2:,} итераций) ──")
|
||||
raw_data = [(os.urandom(64),) for _ in range(1000)]
|
||||
def slice_copy(d):
|
||||
return bytes(d[8:40]), bytes(d[40:56])
|
||||
def slice_direct(d):
|
||||
return d[8:40], d[40:56]
|
||||
compare("bytes(slice) vs slice", slice_copy, slice_direct, raw_data, N2)
|
||||
|
||||
# ── 9. MsgSplitter unpack_from: struct vs pre-compiled ───────
|
||||
print(f"\n── unpack_from <I: struct vs pre-compiled ({N2:,} итераций) ──")
|
||||
_st_I_le = struct.Struct('<I')
|
||||
splitter_bufs = [(os.urandom(64), 1) for _ in range(1000)]
|
||||
compare("unpack_from <I",
|
||||
lambda b, p: struct.unpack_from('<I', b, p),
|
||||
lambda b, p: _st_I_le.unpack_from(b, p),
|
||||
splitter_bufs, N2)
|
||||
|
||||
print("\n" + "=" * 74)
|
||||
print(" Готово!")
|
||||
print("=" * 74)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue