ZaStoGram/Tools/check_wss_transport_mode.py

209 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
from pathlib import Path
import sys
ROOT = Path(__file__).resolve().parents[1]
SHARED_CONFIG = ROOT / "TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java"
CONNECTIONS_JAVA = ROOT / "TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java"
PROXY_LIST = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/ProxyListActivity.java"
PROXY_SETTINGS = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/ProxySettingsActivity.java"
BOT_WEBVIEW = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/web/BotWebViewContainer.java"
MINI_BRIDGE = ROOT / "TMessagesProj/src/main/java/org/telegram/messenger/WssMiniAppProxyBridge.java"
WRAPPER_CPP = ROOT / "TMessagesProj/jni/TgNetWrapper.cpp"
MANAGER_CPP = ROOT / "TMessagesProj/jni/tgnet/ConnectionsManager.cpp"
MANAGER_H = ROOT / "TMessagesProj/jni/tgnet/ConnectionsManager.h"
SOCKET_CPP = ROOT / "TMessagesProj/jni/tgnet/ConnectionSocket.cpp"
SOCKET_H = ROOT / "TMessagesProj/jni/tgnet/ConnectionSocket.h"
SOCKET_STATE_H = ROOT / "TMessagesProj/jni/tgnet/ConnectionSocketStateMachine.h"
CONNECTION_CPP = ROOT / "TMessagesProj/jni/tgnet/Connection.cpp"
TRANSPORT_H = ROOT / "TMessagesProj/jni/tgnet/transport/TransportSocket.h"
WSS_H = ROOT / "TMessagesProj/jni/tgnet/wss/WssSocket.h"
WSS_CPP = ROOT / "TMessagesProj/jni/tgnet/wss/WssSocket.cpp"
OLD_WSS_H = ROOT / "TMessagesProj/jni/tgnet/WssTransport.h"
OLD_WSS_CPP = ROOT / "TMessagesProj/jni/tgnet/WssTransport.cpp"
CMAKE = ROOT / "TMessagesProj/jni/CMakeLists.txt"
STRINGS = ROOT / "TMessagesProj/src/main/res/values/strings.xml"
STRINGS_RU = ROOT / "TMessagesProj/src/main/res/values-ru/strings.xml"
def text(path: Path) -> str:
return path.read_text(encoding="utf-8", errors="replace")
def require(condition: bool, message: str) -> None:
if not condition:
print(f"FAIL: {message}", file=sys.stderr)
sys.exit(1)
def main() -> None:
shared_config = text(SHARED_CONFIG)
connections = text(CONNECTIONS_JAVA)
proxy_list = text(PROXY_LIST)
proxy_settings = text(PROXY_SETTINGS)
bot_webview = text(BOT_WEBVIEW)
wrapper = text(WRAPPER_CPP)
manager_cpp = text(MANAGER_CPP)
manager_h = text(MANAGER_H)
socket_cpp = text(SOCKET_CPP)
socket_h = text(SOCKET_H)
socket_state_h = text(SOCKET_STATE_H)
socket_state_cpp = text(ROOT / "TMessagesProj/jni/tgnet/ConnectionSocketStateMachine.cpp")
connection_cpp = text(CONNECTION_CPP)
transport_h = text(TRANSPORT_H)
wss_h = text(WSS_H)
wss_cpp = text(WSS_CPP)
cmake = text(CMAKE)
require("boolean wssTransportEnabled" in shared_config,
"SharedConfig must persist only the client WSS checkbox")
require("setWssTransportEnabled(boolean enabled)" in shared_config,
"SharedConfig must expose the WSS checkbox setter")
require("PROXY_SCHEMA_V4" in shared_config,
"proxy schema V4 must keep proxy rows independent from WSS")
require("Consume obsolete per-row WSS metadata" in shared_config,
"schema V3 WSS metadata must be migrated, not reused")
for forbidden in ("currentWssSocksProxy", "saveWssSocksProxy", "clearWssSocksProxy", "isWssTransport()"):
require(forbidden not in shared_config, f"SharedConfig must not retain {forbidden}")
require("public static void setWssTransportEnabled()" in connections,
"Java must expose a boolean-only WSS setter")
require("native_setWssTransportEnabled(int currentAccount, boolean enabled)" in connections,
"Java JNI declaration must carry only account and enabled")
require("setWssTransportSettings" not in connections,
"Java must not carry gateway or upstream-proxy settings")
require('native_setWssTransportEnabled", "(IZ)V"' in wrapper,
"JNI registration must use the boolean-only WSS signature")
require("void setWssTransportEnabled(bool enabled)" in manager_h,
"native manager must expose the boolean-only WSS setting")
require("bool wssEnabled" in manager_h,
"native manager must store only the WSS toggle")
require("setWssTransportSettings" not in manager_cpp,
"native manager must not store WSS gateway/proxy configuration")
require("R.string.UseWssTransport" in proxy_list and "TextCheckCell" in proxy_list,
"proxy UI must expose WSS as a checkbox")
require("disableLegacyProxyForWss" in proxy_list
and "ConnectionsManager.setProxySettings(false" in proxy_list,
"enabling WSS must disable the legacy proxy")
require("SharedConfig.setWssTransportEnabled(false)" not in proxy_list,
"selecting a proxy must only suspend WSS, not clear the user's WSS preference")
require("SharedConfig.wssTransportEnabled && !useProxySettings" in proxy_list,
"the WSS checkbox must show WSS as off while a proxy is in use")
for forbidden in ("TYPE_WSS", "createWssGateway", "WSS_TRANSPORT_OPTIONS", "wssCustomGatewayRow"):
require(forbidden not in proxy_settings + proxy_list,
f"proxy screens must not retain {forbidden}")
require("class Socket" in transport_h and "virtual bool open" in transport_h,
"tgnet must expose an abstract transport socket boundary")
require("class Socket final : public transport::Socket" in wss_h,
"WSS must be its own transport socket module")
require("int socketFd = -1" in wss_h and "::socket(" in wss_cpp and "::connect(" in wss_cpp,
"WSS module must own its native descriptor and TCP connect")
require("SSL_VERIFY_PEER" in wss_cpp and "SSL_set1_host" in wss_cpp,
"WSS TLS must verify the peer and hostname")
require("Sec-WebSocket-Protocol: binary" in wss_cpp and "Sec-WebSocket-Key" in wss_cpp,
"WSS module must perform a real binary WebSocket upgrade")
require("kws2.web.telegram.org" not in wss_cpp or '"kws2"' in wss_cpp,
"official DC2 route must be generated inside the WSS module")
require('prefix = "kws" + std::to_string(dcId)' in wss_cpp and '"/apiws"' in wss_cpp,
"WSS module must use the official DC1-DC5 relay catalog")
require("officialRelayIpForDc" in wss_cpp
and "result.relayHostFallback = result.domain" in wss_cpp
and "result.connectHost = result.viaFallback" in wss_cpp
and "bool viaFallback = false" in wss_h,
"WSS module must own direct Telegram ingress and hostname fallback routing")
for forbidden in ("buildSocks5Greeting", "upstreamSocksEnabled", "customRoute"):
require(forbidden not in wss_cpp + wss_h,
f"WSS socket must not contain proxy/gateway feature {forbidden}")
require('#include "wss/WssSocket.h"' in socket_h + socket_state_h,
"ConnectionSocket state must depend on the dedicated WSS socket")
require("std::unique_ptr<tgnet::transport::Socket>" in socket_h + socket_state_h,
"ConnectionSocket state must hold WSS through the transport abstraction")
require("tgnet::wss::OfficialRoute" in socket_cpp and "tgnet::wss::CreateSocket" in socket_cpp,
"ConnectionSocket must select and instantiate the WSS module")
require("manager.wssEnabled" in socket_cpp and "proxyAddress->empty()" in socket_cpp,
"WSS selection must be independent and must not stack over a proxy")
require("currentWssTransport->open" in socket_cpp
and "currentWssTransport->onEvent" in socket_cpp
and "currentWssTransport->write" in socket_cpp,
"ConnectionSocket must delegate WSS I/O to the socket module")
require("if (!isCurrentTransportWss())" in socket_cpp
and "eventMask.events |= EPOLLET" in socket_cpp,
"WSS must stay level-triggered while TCP transports keep edge-triggered epoll")
require("enum class IoWait" in wss_h
and "SSL_ERROR_WANT_READ" in wss_cpp
and "SSL_ERROR_WANT_WRITE" in wss_cpp
and "ioWait == IoWait::Write" in wss_cpp,
"WSS must preserve OpenSSL read/write wait direction")
require("wss_socket tcp_connected" in wss_cpp
and "wss_socket tls_ready" in wss_cpp
and "wss_socket timeout" in wss_cpp,
"WSS diagnostics must identify TCP, TLS, and timeout phases")
require("getDiagnosticSnapshot" in socket_h
and 'append("wss_url"' in socket_cpp
and 'append("wss_relay"' in socket_cpp
and "collectConnectionDiagnostics" in manager_h + manager_cpp
and "native_getDatacenterConnectionDiagnostics" in connections
and 'native_getDatacenterConnectionDiagnostics", "(II)Ljava/lang/String;"' in wrapper,
"message diagnostics must expose the live native WSS route through a bounded JNI snapshot")
require('append("connect"' in socket_cpp
and 'append("target"' in socket_cpp
and "currentSecret=" not in socket_cpp[socket_cpp.index("std::string ConnectionSocket::getDiagnosticSnapshot"):socket_cpp.index("bool ConnectionSocket::matchesMtProxyEndpointKey")]
and 'append("endpoint_key"' not in socket_cpp
and 'append("probe_key"' not in socket_cpp,
"connection snapshots must identify endpoints without exposing proxy secrets")
# Измерено 2026-08-09: релей разбирает только ПЕРВЫЙ MTProto-пакет кадра и
# молча выбрасывает остальные. msgs_ack, склеенный со следующим шагом
# рукопожатия, оставлял датацентр без ключа навсегда.
require("flushWssStream" in socket_cpp
and "outgoingByteStream->get" in socket_cpp
and "outgoingWssPacketSizes" in socket_cpp
and "noteWssPacketBoundary" in socket_cpp
and "noteWssPacketBoundary" in connection_cpp,
"WSS frames must be cut exactly at MTProto packet boundaries")
require("queuedOutputBytes" in wss_cpp
and "readFailed" in wss_cpp
and "EPOLLRDHUP" in wss_cpp
and "dispatchWssPayloads(payloads)" in socket_cpp
and socket_cpp.index("dispatchWssPayloads(payloads)") < socket_cpp.index("wss_transport_failed"),
"WSS must deliver frames drained before EOF so transport errors reach MTProto")
# Measured against the official relays: a first binary frame of 63 bytes is
# never answered, 64 always is, and the failure is completely silent.
require("kObfuscationHeaderSize = 64" in wss_cpp
and "openingFrameSent" in wss_cpp
and "openingFrame.size() < kObfuscationHeaderSize" in wss_cpp
and "openingFrame" in wss_h,
"WSS must never emit a first frame shorter than the 64-byte obfuscation header")
require("std::deque<std::vector<uint8_t>> pendingOutput" in wss_h
and "pendingOutput.front()" in wss_cpp
and "pendingOutput.push_back(std::move(frame))" in wss_cpp,
"WSS TLS retries must keep immutable frame buffers during upload bursts")
require("The relay owns the socket address" in socket_cpp
and "ipv6 = false;" in socket_cpp,
"WSS relay address family must not inherit the ignored DC target family")
require("lastPushPingTime != 0" in manager_cpp,
"push ping timeout must not spin after clearing its timestamp")
for forbidden in ("wssSocksHost", "wssFallbackProxy", "WssRouteConfig"):
require(forbidden not in socket_cpp + socket_h,
f"ConnectionSocket must not contain obsolete WSS proxy field {forbidden}")
require("tgnet/wss/WssSocket.cpp" in cmake and "tgnet/WssTransport.cpp" not in cmake,
"CMake must compile only the dedicated WSS socket module")
require(not OLD_WSS_H.exists() and not OLD_WSS_CPP.exists(),
"the old mixed WssTransport implementation must be removed")
require(not MINI_BRIDGE.exists() and "WssMiniAppProxyBridge" not in bot_webview,
"WSS must not create a MiniApp/local proxy bridge")
for path in (STRINGS, STRINGS_RU):
source = text(path)
require('name="UseWssTransport"' in source, f"{path.name} must define UseWssTransport")
require('name="WssTransportInfo"' in source, f"{path.name} must define WssTransportInfo")
print("WSS transport architecture guard passed.")
if __name__ == "__main__":
main()