Mult Protocol

This commit is contained in:
2026-08-16 15:22:03 -03:00
parent 96ea761b72
commit 1fb431ccba
17 changed files with 1873 additions and 204 deletions
Binary file not shown.
+35 -3
View File
@@ -11,6 +11,8 @@ public final class AppLog {
private static final int MAX_LINES = 600;
private static final ArrayDeque<String> lines = new ArrayDeque<>();
private static final CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
private static String lastLine;
private static int repeatCount;
private AppLog() {}
@@ -18,10 +20,36 @@ public final class AppLog {
if (line == null) return;
line = line.trim();
if (line.isEmpty()) return;
// The core emits one adaptation line per proxied flow, so a busy page
// load produces dozens of identical entries. Collapse consecutive
// repeats into a single counted line instead of burying the log.
String emit;
synchronized (lines) {
while (lines.size() >= MAX_LINES) lines.removeFirst();
lines.addLast(line);
if (line.equals(lastLine)) {
repeatCount++;
return;
}
if (repeatCount > 0) {
String summary = " (previous line repeated " + repeatCount + " more times)";
repeatCount = 0;
push(summary);
notifyListeners(summary);
}
lastLine = line;
push(line);
emit = line;
}
notifyListeners(emit);
}
/** Caller must hold the {@code lines} lock. */
private static void push(String line) {
while (lines.size() >= MAX_LINES) lines.removeFirst();
lines.addLast(line);
}
private static void notifyListeners(String line) {
for (Listener listener : listeners) {
try { listener.onLine(line); } catch (Throwable ignored) {}
}
@@ -36,7 +64,11 @@ public final class AppLog {
}
public static void clear() {
synchronized (lines) { lines.clear(); }
synchronized (lines) {
lines.clear();
lastLine = null;
repeatCount = 0;
}
}
public static void addListener(Listener listener) { listeners.addIfAbsent(listener); }
@@ -38,6 +38,8 @@ public class DragonService extends VpnService {
public static final String EXTRA_SERVER = "server";
public static final String EXTRA_PORT = "port";
public static final String EXTRA_TOKEN = "token";
/** Wire format: "auto", "b" or "x". */
public static final String EXTRA_WIRE = "wire";
public static final String EXTRA_CHUNK_MAX = "chunkMax";
public static final String EXTRA_CHUNK_MIN = "chunkMin";
/** Maximum download records per request. A transport setting, not a thread count. */
@@ -110,11 +112,12 @@ public class DragonService extends VpnService {
String server = intent.getStringExtra(EXTRA_SERVER);
int port = intent.getIntExtra(EXTRA_PORT, 53);
String token = intent.getStringExtra(EXTRA_TOKEN);
String wire = intent.getStringExtra(EXTRA_WIRE);
int chunkMax = intent.getIntExtra(EXTRA_CHUNK_MAX, 1024 * 1024);
int chunkMin = intent.getIntExtra(EXTRA_CHUNK_MIN, 32);
int batchMax = intent.getIntExtra(EXTRA_BATCH_MAX, 1);
int batchMin = intent.getIntExtra(EXTRA_BATCH_MIN, 1);
int reconnect = intent.getIntExtra(EXTRA_RECONNECT, 0);
int reconnect = intent.getIntExtra(EXTRA_RECONNECT, 1);
int timeout = intent.getIntExtra(EXTRA_TIMEOUT, 2);
if (server == null || server.trim().isEmpty()) {
@@ -123,6 +126,7 @@ public class DragonService extends VpnService {
}
server = server.trim();
if (token == null) token = "";
if (wire == null || !(wire.equals("b") || wire.equals("x"))) wire = "auto";
chunkMax = Math.max(32, Math.min(1024 * 1024, chunkMax));
chunkMin = Math.max(32, Math.min(chunkMax, chunkMin));
batchMax = Math.max(1, Math.min(BATCH_LIMIT, batchMax));
@@ -133,7 +137,8 @@ public class DragonService extends VpnService {
try {
AppLog.append("Starting DragonTCP → " + server + ":" + port);
AppLog.append(describeBatch(batchMin, batchMax));
Process process = startDragonCore(server, port, token, chunkMax, chunkMin, batchMax, batchMin, reconnect, timeout);
AppLog.append(describeWire(wire));
Process process = startDragonCore(server, port, token, chunkMax, chunkMin, batchMax, batchMin, wire, reconnect, timeout);
synchronized (stateLock) { coreProcess = process; }
startCoreLogReader(process);
@@ -199,6 +204,7 @@ public class DragonService extends VpnService {
int chunkMin,
int batchMax,
int batchMin,
String wire,
int reconnect,
int timeout
) throws Exception {
@@ -217,6 +223,7 @@ public class DragonService extends VpnService {
cmd.add("--chunk-min"); cmd.add(Integer.toString(chunkMin));
cmd.add("--chunk-max"); cmd.add(Integer.toString(chunkMax));
cmd.add("--chunk-pollers"); cmd.add("1");
cmd.add("--wire"); cmd.add(wire);
cmd.add("--chunk-concurrency"); cmd.add(Integer.toString(batchMax));
cmd.add("--chunk-concurrency-min"); cmd.add(Integer.toString(batchMin));
cmd.add("--chunk-reconnect-every"); cmd.add(Integer.toString(reconnect));
@@ -239,6 +246,14 @@ public class DragonService extends VpnService {
return "Download batch: adaptive " + min + "-" + max + " records per request";
}
/** Human-readable summary of the wire selection, for the log screen. */
private static String describeWire(String wire) {
if ("b".equals(wire)) return "Wire: B (manual)";
if ("x".equals(wire)) return "Wire: X (manual)";
return "Wire: auto (probing)";
}
private void startCoreLogReader(Process process) {
Thread reader = new Thread(() -> {
try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
@@ -246,7 +261,7 @@ public class DragonService extends VpnService {
while ((line = br.readLine()) != null) {
// Keep the UI useful: adaptation changes and real errors only.
String lower = line.toLowerCase();
if (line.startsWith("adaptive ") || line.startsWith("path probe:") || lower.contains("error") || lower.contains("failed")) {
if (line.startsWith("adaptive ") || line.startsWith("path probe:") || line.startsWith("wire") || lower.contains("error") || lower.contains("failed")) {
AppLog.append(line);
}
}
@@ -53,9 +53,14 @@ public class MainActivity extends Activity {
private EditText batchMax;
private EditText batchMin;
private EditText reconnect;
private Button wireAuto;
private Button wireB;
private Button wireX;
private String wireMode = "auto";
private EditText timeout;
private TextView batchHint;
private TextView wireHint;
private Button connectButton;
private Button stopButton;
private Button logsButton;
@@ -159,6 +164,27 @@ public class MainActivity extends Activity {
connectionCard.addView(connectionRow);
settings.addView(connectionCard, cardParams());
// ---------------------------------------------------------------- wire
LinearLayout wireCard = card("WIRE");
wireCard.addView(hint("Networks differ in which mode they pass."));
LinearLayout wireRow = row();
wireAuto = segmentButton("A");
wireB = segmentButton("B");
wireX = segmentButton("X");
addSegment(wireRow, wireAuto);
addSegment(wireRow, wireB);
addSegment(wireRow, wireX);
wireCard.addView(wireRow);
wireHint = text("", 11, MUTED, true);
wireHint.setLineSpacing(0, 1.08f);
wireHint.setPadding(dp(2), dp(8), dp(2), dp(2));
wireCard.addView(wireHint);
settings.addView(wireCard, cardParams());
wireAuto.setOnClickListener(v -> setWireMode("auto"));
wireB.setOnClickListener(v -> setWireMode("b"));
wireX.setOnClickListener(v -> setWireMode("x"));
// --------------------------------------------------------- record size
LinearLayout sizeCard = card("RECORD SIZE (BYTES)");
LinearLayout chunks = row();
@@ -200,10 +226,12 @@ public class MainActivity extends Activity {
// ------------------------------------------------------------ advanced
LinearLayout advancedCard = card("ADVANCED");
LinearLayout timing = row();
reconnect = addFieldToRow(timing, "Reconnect every", "0 = persistent", "0", true, false, 0.58f);
reconnect = addFieldToRow(timing, "Reconnect every", "1 = auto", "1", true, false, 0.58f);
timeout = addFieldToRow(timing, "Timeout (s)", "2", "2", true, false, 0.42f);
advancedCard.addView(timing);
advancedCard.addView(hint("0 reconnect = persistent • 1 = auto • N = rotate every N requests"));
advancedCard.addView(hint(
"1 = auto (recommended: probes the path, falls back to one request per "
+ "connection) • 0 = persistent • N = rotate every N requests"));
settings.addView(advancedCard, cardParams());
// ------------------------------------------------------------- buttons
@@ -284,6 +312,7 @@ public class MainActivity extends Activity {
+ " on errors, recovers to " + max + ".");
}
private Integer readInt(EditText field) {
if (field == null) return null;
String raw = field.getText().toString().trim();
@@ -364,6 +393,47 @@ public class MainActivity extends Activity {
return edit;
}
private Button segmentButton(String label) {
Button b = new Button(this);
b.setText(label);
b.setTextSize(15);
b.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
b.setAllCaps(false);
return b;
}
private void addSegment(LinearLayout parent, Button b) {
LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(0, dp(46), 1f);
if (parent.getChildCount() > 0) p.setMarginStart(dp(8));
parent.addView(b, p);
}
/** Selects the wire format and repaints the segmented control. */
private void setWireMode(String mode) {
wireMode = mode;
paintSegment(wireAuto, "auto".equals(mode));
paintSegment(wireB, "b".equals(mode));
paintSegment(wireX, "x".equals(mode));
if (wireHint == null) return;
if ("auto".equals(mode)) {
wireHint.setTextColor(ACCENT);
wireHint.setText("Auto: tries B, then X, keeping the one that connects.");
} else if ("b".equals(mode)) {
wireHint.setTextColor(OK);
wireHint.setText("Manual: B.");
} else {
wireHint.setTextColor(OK);
wireHint.setText("Manual: X.");
}
}
private void paintSegment(Button b, boolean selected) {
if (b == null) return;
b.setTextColor(selected ? Color.WHITE : Color.rgb(132, 142, 155));
b.setBackground(roundRect(selected ? ACCENT : Color.rgb(32, 38, 47), 12,
selected ? ACCENT : BORDER, 1));
}
private LinearLayout row() {
LinearLayout row = new LinearLayout(this);
row.setOrientation(LinearLayout.HORIZONTAL);
@@ -446,11 +516,12 @@ public class MainActivity extends Activity {
i.putExtra(DragonService.EXTRA_SERVER, p.getString("server", ""));
i.putExtra(DragonService.EXTRA_PORT, p.getInt("port", 53));
i.putExtra(DragonService.EXTRA_TOKEN, p.getString("token", ""));
i.putExtra(DragonService.EXTRA_WIRE, p.getString("wire", "auto"));
i.putExtra(DragonService.EXTRA_CHUNK_MAX, p.getInt("max", 1048576));
i.putExtra(DragonService.EXTRA_CHUNK_MIN, p.getInt("min", 32));
i.putExtra(DragonService.EXTRA_BATCH_MAX, p.getInt("batchMax", 1));
i.putExtra(DragonService.EXTRA_BATCH_MIN, p.getInt("batchMin", 1));
i.putExtra(DragonService.EXTRA_RECONNECT, p.getInt("reconnect", 0));
i.putExtra(DragonService.EXTRA_RECONNECT, p.getInt("reconnect", 1));
i.putExtra(DragonService.EXTRA_TIMEOUT, p.getInt("timeout", 2));
if (Build.VERSION.SDK_INT >= 26) startForegroundService(i); else startService(i);
}
@@ -471,6 +542,7 @@ public class MainActivity extends Activity {
.putString("server", h)
.putInt("port", p)
.putString("token", token.getText().toString())
.putString("wire", wireMode)
.putInt("max", max)
.putInt("min", min)
.putInt("batchMax", bMax)
@@ -493,11 +565,12 @@ public class MainActivity extends Activity {
server.setText(p.getString("server", ""));
port.setText(Integer.toString(p.getInt("port", 53)));
token.setText(p.getString("token", ""));
setWireMode(p.getString("wire", "auto"));
chunkMax.setText(Integer.toString(p.getInt("max", 1048576)));
chunkMin.setText(Integer.toString(p.getInt("min", 32)));
batchMax.setText(Integer.toString(p.getInt("batchMax", 1)));
batchMin.setText(Integer.toString(p.getInt("batchMin", 1)));
reconnect.setText(Integer.toString(p.getInt("reconnect", 0)));
reconnect.setText(Integer.toString(p.getInt("reconnect", 1)));
timeout.setText(Integer.toString(p.getInt("timeout", 2)));
}