Move MTProxy policy engine from tgnet/ to self-contained jni/mtproxy/ module. Consolidate reconnect hold computation into single MtProxyRetryAuthority owner (was split across Connection, ConnectionSocket, endpoint policy, and probe coordinator, causing hold misalignment bugs). Add MtProxyTerminalDiagnostic module with host tests for pre-I/O verdict preservation (fixes 02.07/30.06 clobber livelocks). Auto-generate phase classification (isPreIoTerminalVerdict, needsReconnectBackoff, isObservationFacadePhase, isLocalSchedulerTimeout) from single Python contract into both C++ and Java to eliminate parallel maintenance. Pass native hold (coordinator terminal hold + endpoint cooldown) via JNI to Java so reconnect timer and health store use THE clock, not re-derived ones. Optimize log flushing: batch debug lines, flush errors immediately. Add module boundary guard, host build system (MSVC), and unit tests for retry logic and terminal diagnostic derivation.
198 lines
9.4 KiB
Python
198 lines
9.4 KiB
Python
#!/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"
|
|
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"
|
|
MACHINE_H = ROOT / "TMessagesProj/jni/tgnet/ConnectionSocketStateMachine.h"
|
|
SHAPER_H = ROOT / "TMessagesProj/jni/mtproxy/MtProxyDataPathShaper.h"
|
|
SHAPER_CPP = ROOT / "TMessagesProj/jni/mtproxy/MtProxyDataPathShaper.cpp"
|
|
CMAKE = ROOT / "TMessagesProj/jni/CMakeLists.txt"
|
|
PROXY_CHECK = ROOT / "TMessagesProj/jni/tgnet/ProxyCheckInfo.h"
|
|
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:
|
|
if not path.exists():
|
|
return ""
|
|
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)
|
|
wrapper = text(WRAPPER_CPP)
|
|
manager_cpp = text(MANAGER_CPP)
|
|
manager_h = text(MANAGER_H)
|
|
socket_cpp = text(SOCKET_CPP)
|
|
socket_h = text(SOCKET_H)
|
|
shaper_h = text(SHAPER_H)
|
|
shaper_cpp = text(SHAPER_CPP)
|
|
cmake = text(CMAKE)
|
|
socket_state = socket_h + "\n" + text(MACHINE_H) + "\n" + socket_cpp
|
|
proxy_check = text(PROXY_CHECK)
|
|
|
|
require(
|
|
"mtProxyRecordSizingMode" in shared_config
|
|
and 'getInt("mtProxyRecordSizingMode", 0)' in shared_config
|
|
and 'putInt("mtProxyRecordSizingMode", mtProxyRecordSizingMode)' in shared_config,
|
|
"SharedConfig must persist TLS record sizing mode as a runtime setting",
|
|
)
|
|
require(
|
|
"mtProxyTimingMode" in shared_config
|
|
and 'getInt("mtProxyTimingMode", 0)' in shared_config
|
|
and 'putInt("mtProxyTimingMode", mtProxyTimingMode)' in shared_config,
|
|
"SharedConfig must persist inter-packet timing mode as a runtime setting",
|
|
)
|
|
require(
|
|
"MT_PROXY_RECORD_SIZING_OFF" in connections
|
|
and "MT_PROXY_RECORD_SIZING_CONSERVATIVE" in connections
|
|
and "MT_PROXY_RECORD_SIZING_VARIED" in connections
|
|
and "resolveMtProxyRecordSizingMode()" in connections,
|
|
"Java must expose record sizing mode constants and resolver",
|
|
)
|
|
require(
|
|
"MT_PROXY_TIMING_OFF" in connections
|
|
and "MT_PROXY_TIMING_GENTLE" in connections
|
|
and "MT_PROXY_TIMING_BALANCED" in connections
|
|
and "resolveMtProxyTimingMode()" in connections,
|
|
"Java must expose timing mode constants and resolver",
|
|
)
|
|
require(
|
|
"mtProxyRecordSizingRow" in proxy_list
|
|
and "getMtProxyRecordSizingLabels" in proxy_list
|
|
and "SharedConfig.mtProxyRecordSizingMode" in proxy_list,
|
|
"proxy settings UI must expose selectable TLS record sizing modes",
|
|
)
|
|
require(
|
|
"mtProxyTimingRow" in proxy_list
|
|
and "getMtProxyTimingLabels" in proxy_list
|
|
and "SharedConfig.mtProxyTimingMode" in proxy_list,
|
|
"proxy settings UI must expose selectable timing modes",
|
|
)
|
|
require(
|
|
'native_setProxySettings", "(ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/telegram/tgnet/MtProxyOptions;ILjava/lang/String;)V"' in wrapper
|
|
and 'native_checkProxy", "(ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/telegram/tgnet/MtProxyOptions;Lorg/telegram/tgnet/RequestTimeDelegate;)J"' in wrapper,
|
|
"JNI signatures must carry profile, fragmentation, admission, record sizing, timing, and startup-cover via MtProxyOptions",
|
|
)
|
|
require(
|
|
"MtProxyOptions proxyMtProxyOptions" in manager_h
|
|
and "optionsChanged" in manager_cpp
|
|
and "proxyMtProxyOptions = normalizedOptions" in manager_cpp,
|
|
"native ConnectionsManager must store data-path modes in MtProxyOptions and reconnect when they change",
|
|
)
|
|
require(
|
|
"MtProxyOptions mtProxyOptions" in proxy_check,
|
|
"proxy checks must carry MtProxyOptions for same-path testing",
|
|
)
|
|
require(
|
|
"currentRecordSizingMode" in socket_state
|
|
and "currentTimingMode" in socket_state
|
|
and "mtproxyTlsFrameCompletedCount" in socket_state
|
|
and "nextMtProxyTlsRecordPayloadSize" in shaper_cpp
|
|
and "scheduleMtProxyDataTimingIfNeeded" in socket_cpp,
|
|
"MtProxyDataPathShaper must own record sizing policy while ConnectionSocket applies timing waits in the FakeTLS data path",
|
|
)
|
|
require(
|
|
"remaining > sizingDecision.payloadSize" in socket_cpp
|
|
and "mtproxy_data record_sizing" in socket_cpp
|
|
and "mtproxy_data timing_delay" in socket_cpp,
|
|
"data-path layers must be logged and must not replace the pending TLS write queue",
|
|
)
|
|
require(
|
|
"MtProxyDataPathShaper.cpp" in cmake
|
|
and "MtProxyDataPathShaper.h" in socket_cpp,
|
|
"native build and ConnectionSocket must use the MtProxyDataPathShaper module",
|
|
)
|
|
require(
|
|
"struct MtProxyRecordSizingInput" in shaper_h
|
|
and "struct MtProxyStartupCoverPolicy" in shaper_h
|
|
and "struct MtProxyDataTimingDecision" in shaper_h
|
|
and "mtProxyEffectiveRecordSizingMode" in shaper_cpp
|
|
and "mtProxyEffectiveTimingMode" in shaper_cpp
|
|
and "mtProxyStartupCoverPolicy" in shaper_cpp
|
|
and "mtProxyDataTimingDecision" in shaper_cpp
|
|
and "mtProxyDataAwareIptDelayMs" in shaper_cpp,
|
|
"MtProxyDataPathShaper must own record sizing, timing, and startup-cover decisions",
|
|
)
|
|
require(
|
|
"uint32_t ConnectionSocket::nextMtProxyTlsRecordPayloadSize" not in socket_cpp
|
|
and "static uint32_t mtProxyDataAwareIptDelayMs" not in socket_cpp
|
|
and "MT_PROXY_STARTUP_COVER_SOFT_WINDOW_MS" not in socket_cpp
|
|
and "MT_PROXY_STARTUP_COVER_STRICT_WINDOW_MS" not in socket_cpp
|
|
and "currentStartupCoverMode == MT_PROXY_STARTUP_COVER_STRICT ? MT_PROXY_RECORD_SIZING_VARIED" not in socket_cpp
|
|
and "currentStartupCoverMode == MT_PROXY_STARTUP_COVER_STRICT ? MT_PROXY_TIMING_BALANCED" not in socket_cpp,
|
|
"ConnectionSocket must not regrow data-path shaping policy",
|
|
)
|
|
send_start = socket_cpp.find("bool ConnectionSocket::sendPendingTlsFrame()")
|
|
send_end = socket_cpp.find("void ConnectionSocket::openConnection", send_start)
|
|
schedule_start = socket_cpp.find("bool ConnectionSocket::scheduleMtProxyDataTimingIfNeeded()")
|
|
schedule_end = socket_cpp.find("void ConnectionSocket::startMtProxyStartupCover", schedule_start)
|
|
require(send_start != -1 and send_end != -1, "sendPendingTlsFrame body must be present")
|
|
require(schedule_start != -1 and schedule_end != -1, "scheduleMtProxyDataTimingIfNeeded body must be present")
|
|
send_body = socket_cpp[send_start:send_end]
|
|
schedule_body = socket_cpp[schedule_start:schedule_end]
|
|
discard_pos = send_body.find("outgoingByteStream->discard(pendingTlsPayloadSize);")
|
|
complete_count_pos = send_body.find("mtproxyTlsFrameCompletedCount++")
|
|
complete_log_pos = send_body.find("mtproxy_data tls_frame_complete")
|
|
clear_pos = send_body.find("clearPendingTlsFrame();")
|
|
timing_pos = send_body.find("nextTlsFrameWriteTime")
|
|
require(
|
|
discard_pos != -1 and clear_pos != -1 and discard_pos < clear_pos,
|
|
"FakeTLS data path must discard payload only after the full TLS frame was sent and before clearing the pending frame",
|
|
)
|
|
require(
|
|
discard_pos < complete_count_pos < clear_pos
|
|
and discard_pos < complete_log_pos < clear_pos,
|
|
"FakeTLS record-boundary diagnostics must be emitted only after payload discard and before clearing the completed TLS frame",
|
|
)
|
|
require(
|
|
"tls_frames_completed=%u" in socket_cpp,
|
|
"mtproxy_disconnect must include the completed FakeTLS frame count so long-session data-path drops remain diagnosable",
|
|
)
|
|
require(
|
|
clear_pos < timing_pos
|
|
and "MtProxyDataTimingDecision timingDecision = mtProxyDataTimingDecision" in send_body
|
|
and "timingDecision.shouldDelay" in send_body,
|
|
"data-aware IPT must be scheduled only after a complete TLS frame, never while a partial TLS frame is pending",
|
|
)
|
|
require(
|
|
"mtProxyDataTimingWaitDecision" in schedule_body
|
|
and "pendingTlsFrame != nullptr" in schedule_body,
|
|
"data-aware IPT wait must be skipped while a TLS frame is pending or when no delay was scheduled",
|
|
)
|
|
for path in (STRINGS, STRINGS_RU):
|
|
source = text(path)
|
|
for key in (
|
|
"MtProxyRecordSizing",
|
|
"MtProxyRecordSizingOff",
|
|
"MtProxyRecordSizingConservative",
|
|
"MtProxyRecordSizingVaried",
|
|
"MtProxyTiming",
|
|
"MtProxyTimingOff",
|
|
"MtProxyTimingGentle",
|
|
"MtProxyTimingBalanced",
|
|
):
|
|
require(f'name="{key}"' in source, f"{path.name} must define {key}")
|
|
|
|
print("MTProxy data-layer guard passed.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|