feat(core): Gnutella joining after Java, measured against Java

GnutellaRun lets Java's PeerToPeerAnwendung join peers on terminal-net.
The core's gnutella.ts does the same: a ping in Java's text format to
port 6346, a pong back, the ping sent on to the other peers and their
pongs back the way it came, at most the program's number of peers.

Departure from Java: a ping that comes a second time gets no pong and
its connection stays (Java's worker throws on the missing pong).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 12:29:54 +00:00
co-authored by Claude Opus 5.5
parent 9d42b90157
commit b4a95e6cac
18 changed files with 37937 additions and 3 deletions
+140
View File
@@ -0,0 +1,140 @@
// Runs a story of FILIUS' Gnutella program (PeerToPeerAnwendung with its
// PeerToPeerServer on port 6346 and PeerToPeerClient) on terminal-net in Java
// FILIUS and records what really happens: every frame each card saw, Java's
// tables of them, each program's known peers whenever it tells its window,
// and at the end each program's known peers and search results.
//
// GnutellaRun <scenario.json> <story> <milliseconds> <out.json>
//
// terminal-net: the program on 192.168.0.20, .30, .31 and 10.0.0.2 (behind
// the router), each keeping at most three peers unless the story says
// otherwise; .10 has none. A call comes as the program's window makes it
// (beitretenNetzwerk, resetNetwork); the acts are in the output.
// pair .30 joins .31
// chain .30 joins .31, then .20 joins .30 (the ping goes on to .31)
// twoPaths .31 joins 10.0.0.2, .30 joins .31 and 10.0.0.2, then .20 joins
// .30: the ping reaches .31 and 10.0.0.2 each on two paths
// full .31 keeps two peers, .30 and .20 one; .30, .20 and 10.0.0.2 join .31
// reset .30 joins .31, resets, joins again
// errors .20 joins itself, .10 (no program), .99 (no one)
import filius.software.dateiaustausch.PeerToPeerAnwendung;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class GnutellaRun {
static final String CLIENT = "00000000-0000-0000-0000-000000000401";
static final String WEB = "00000000-0000-0000-0000-000000000403";
static final String ECHO = "00000000-0000-0000-0000-000000000404";
static final String FAR = "00000000-0000-0000-0000-000000000406";
static final String[] PEERS = { CLIENT, WEB, ECHO, FAR };
static final String CLASS = "filius.software.dateiaustausch.PeerToPeerAnwendung";
static ScenarioNetwork network;
static final List<Object[]> events = new ArrayList<>();
static synchronized void event(String host, String kind, String text) {
events.add(new Object[] { System.currentTimeMillis() - network.start, host, kind, text });
}
static void sleepUntil(long milliseconds) throws InterruptedException {
long rest = network.start + milliseconds - System.currentTimeMillis();
if (rest > 0) Thread.sleep(rest);
}
/** The story's calls: time, host, call ("join <address>" or "reset"). */
static List<Object[]> acts(String story) {
List<Object[]> acts = new ArrayList<>();
switch (story) {
case "pair":
acts.add(new Object[] { 500L, WEB, "join 192.168.0.31" });
break;
case "chain":
acts.add(new Object[] { 500L, WEB, "join 192.168.0.31" });
acts.add(new Object[] { 3000L, CLIENT, "join 192.168.0.30" });
break;
case "twoPaths":
acts.add(new Object[] { 500L, ECHO, "join 10.0.0.2" });
acts.add(new Object[] { 3000L, WEB, "join 192.168.0.31" });
acts.add(new Object[] { 5500L, WEB, "join 10.0.0.2" });
acts.add(new Object[] { 8000L, CLIENT, "join 192.168.0.30" });
break;
case "full":
acts.add(new Object[] { 500L, WEB, "join 192.168.0.31" });
acts.add(new Object[] { 3000L, CLIENT, "join 192.168.0.31" });
acts.add(new Object[] { 5500L, FAR, "join 192.168.0.31" });
break;
case "reset":
acts.add(new Object[] { 500L, WEB, "join 192.168.0.31" });
acts.add(new Object[] { 3000L, WEB, "reset" });
acts.add(new Object[] { 3500L, WEB, "join 192.168.0.31" });
break;
case "errors":
acts.add(new Object[] { 500L, CLIENT, "join 192.168.0.20" });
acts.add(new Object[] { 1000L, CLIENT, "join 192.168.0.10" });
acts.add(new Object[] { 20000L, CLIENT, "join 192.168.0.99" });
break;
default:
throw new IllegalArgumentException("unknown story " + story);
}
return acts;
}
public static void main(String[] args) throws Exception {
network = ScenarioNetwork.read(args[0]);
String story = args[1];
long end = Long.parseLong(args[2]);
List<Object[]> acts = acts(story);
Map<String, PeerToPeerAnwendung> programs = new LinkedHashMap<>();
for (String host : PEERS) {
network.system(host).installApp(CLASS, false);
PeerToPeerAnwendung program = (PeerToPeerAnwendung) network.system(host).holeSoftware(CLASS);
program.setMaxTeilnehmerZahl(!story.equals("full") ? 3 : host.equals(ECHO) ? 2 : host.equals(FAR) ? 3 : 1);
program.hinzuBeobachter((observable, data) -> event(host, "peers", String.join(",", program.holeBekanntePeerToPeerTeilnehmer())));
programs.put(host, program);
}
network.start();
for (Object[] act : acts) {
sleepUntil((Long) act[0]);
String host = (String) act[1];
String call = (String) act[2];
event(host, "call", call);
if (call.startsWith("join ")) programs.get(host).beitretenNetzwerk(call.substring("join ".length()));
else programs.get(host).resetNetwork();
}
sleepUntil(end);
ScenarioNetwork.Json out = new ScenarioNetwork.Json();
out.open('{');
network.writeFrames(out);
out.key("acts").open('[');
for (Object[] act : acts) out.open('[').number((Long) act[0]).string((String) act[1]).string((String) act[2]).close(']');
out.close(']');
synchronized (GnutellaRun.class) {
out.key("events").open('[');
for (Object[] each : events) out.open('[').number((Long) each[0]).string((String) each[1]).string((String) each[2]).string((String) each[3]).close(']');
out.close(']');
}
out.key("programs").open('{');
for (Map.Entry<String, PeerToPeerAnwendung> entry : programs.entrySet()) {
PeerToPeerAnwendung program = entry.getValue();
out.key(entry.getKey()).open('{');
out.key("peers").open('[');
for (String peer : program.holeBekanntePeerToPeerTeilnehmer()) out.string(peer);
out.close(']');
out.key("results").open('[');
for (String result : program.holeErgebnisse()) out.string(result);
out.close(']');
out.close('}');
}
out.close('}');
out.close('}');
network.stop();
ScenarioNetwork.write(args[3], out);
System.exit(0);
}
}