Files
rea/tests/fixtures/ghidraHandshakeProbe.py
T
morluto d4bd65c53c fix(providers)!: preserve readiness, coverage, and diagnostic ownership
Share tagged readiness observations, retain partial provider facts, authenticate bridge identity, bound diagnostics, and preserve cancellation and lost-session reasons. Fail closed for unparsed MIPS ABI attributes and macOS saved Hopper databases without a script-capable bridge route.

Reviewed fixes: #2010, #2108, #2513, #2695, #2731, #2771, #2828, #2867, #2868, #2870, #2880, #2919, #2945, #2961, #2981, #2998, #3000, #3002.

Validation: 285 focused source/boundary tests passed. Real Ghidra, IDA, Linux Hopper, and Windows provider runs were not repeated.

BREAKING CHANGE: MIPS targets with unresolved GNU ABI attributes and saved Hopper databases on macOS are refused rather than advertised as supported.
2026-10-14 08:28:14 +08:00

108 lines
5.0 KiB
Python

"""JDK-only TCP transport regression using the production admission methods.
Run: python3 tests/fixtures/ghidraHandshakeProbe.py /path/to/java
Ghidra request parsing/dispatch are fixture seams; no engine is required.
"""
import pathlib
import subprocess
import sys
import tempfile
root = pathlib.Path(__file__).resolve().parents[2]
bridge = (root / "bridge/ghidra/ReaGhidraBridge.java").read_text()
start = bridge.index(" private static String readLoopbackHandshake(")
end = bridge.index(" private void acceptClient(", start)
methods = bridge[start:end]
harness = r'''
import java.io.*;
import java.net.*;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;
public class ReaHandshakeProbe {
record SessionDescriptor(String token) {}
record Request(String method) {}
private Request parseRequest(String line, String token) {
if (!line.equals(token + ":ping")) throw new IllegalArgumentException();
return new Request("ping");
}
private void serveClient(SessionDescriptor descriptor, BufferedReader reader,
BufferedWriter writer, Request first) throws Exception {
if (!first.method().equals("ping")) throw new AssertionError("first request lost");
String next = reader.readLine();
if (next == null || next.length() != 200_000)
throw new AssertionError("pipelined authenticated request lost or capped");
writer.write("accepted\n");
writer.flush();
}
static Socket connect(int port) throws Exception {
Socket peer = new Socket("127.0.0.1", port);
peer.setSoTimeout(3_000);
return peer;
}
static void closed(Socket peer) throws Exception {
if (peer.getInputStream().read() != -1) throw new AssertionError("peer not rejected");
}
public static void main(String[] args) throws Exception {
try (ServerSocketChannel server = ServerSocketChannel.open();
ExecutorService worker = Executors.newSingleThreadExecutor()) {
server.bind(new InetSocketAddress("127.0.0.1", 0));
int port = ((InetSocketAddress) server.getLocalAddress()).getPort();
Future<?> serving = worker.submit(() -> {
try { new ReaHandshakeProbe().acceptAuthenticatedLoopbackClient(
server, new SessionDescriptor("secret")); }
catch (Exception error) { throw new RuntimeException(error); }
});
try (Socket invalid = connect(port)) {
invalid.getOutputStream().write("bad-token:ping\n".getBytes(StandardCharsets.UTF_8));
closed(invalid);
}
// EOF before a newline is not an authenticated request.
try (Socket eof = connect(port)) {
eof.getOutputStream().write("secret:ping".getBytes(StandardCharsets.UTF_8));
eof.shutdownOutput();
closed(eof);
}
try (Socket oversized = connect(port)) {
oversized.getOutputStream().write(new byte[64 * 1024 + 1]);
closed(oversized);
}
try (Socket idle = connect(port)) {
long start = System.nanoTime();
closed(idle);
long elapsed = (System.nanoTime() - start) / 1_000_000;
if (elapsed < 700 || elapsed > 2_500) throw new AssertionError("idle deadline " + elapsed);
}
try (Socket trickle = connect(port)) {
long start = System.nanoTime();
for (int i = 0; i < 4; i++) {
trickle.getOutputStream().write('x');
Thread.sleep(250);
}
closed(trickle);
if ((System.nanoTime() - start) / 1_000_000 > 2_000)
throw new AssertionError("slow input extended deadline");
}
// Same write contains the first and the following frame. The first
// reader must consume exactly one line without swallowing lookahead.
try (Socket valid = connect(port)) {
valid.getOutputStream().write(("secret:ping\n" + "x".repeat(200_000) + "\n")
.getBytes(StandardCharsets.UTF_8));
String reply = new BufferedReader(new InputStreamReader(valid.getInputStream(),
StandardCharsets.UTF_8)).readLine();
if (!"accepted".equals(reply)) throw new AssertionError("legitimate client failed");
}
serving.get(3, TimeUnit.SECONDS);
}
System.out.println("PASS: rejection, EOF, frame ceiling, idle/trickle deadlines, retry, pipeline, authenticated large request");
}
__METHODS__
}
'''.replace("__METHODS__", methods)
with tempfile.TemporaryDirectory(prefix="rea-ghidra-handshake-") as directory:
source = pathlib.Path(directory) / "ReaHandshakeProbe.java"
source.write_text(harness)
subprocess.run([sys.argv[1] if len(sys.argv) > 1 else "java", str(source)],
check=True, timeout=30)