Files
mdg-filius/tools/java-oracle/src/GnutellaRun.java
T
julian.kiedaischandClaude Opus 5.5 ce5b10c1f3 feat(core): Gnutella search and download after Java, measured against Java
GnutellaRun adds a line of four peers: a search goes on as a query, each
peer answers with a hit for each matching file and the hits come back the
way the query came; a hit's file is fetched by HTTP on port 6346. The
file type names of the model move from the project export and import into
the core, so that the download uses them too.

Departures from Java: a peer that sent hits keeps its connection, so that
the hits of farther peers come back too (Java closes it and loses them),
and sizes are in characters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 14:59:26 +00:00

200 lines
10 KiB
Java

// 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 and search results whenever it
// tells its window, and at the end its peers, results and files in /peer2peer.
//
// 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, sucheDatei, herunterladenDatei,
// loescheSuchergebnisse); 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)
// search a line .20 - .30 - .31 - 10.0.0.2 (by their numbers of peers);
// .31 shares notizen.txt, 10.0.0.2 "Lied der Woche.txt"; .20
// searches "woche" (10.0.0.2's hit comes back through .31 and
// .30) and downloads it, searches "notiz" and downloads it,
// again after .31 removed it, searches "xyz", clears
// searchBoth the line, .31 also shares lied.txt; .20 searches "lied"
import filius.software.dateiaustausch.PeerToPeerAnwendung;
import filius.software.system.Datei;
import filius.software.system.Dateisystem;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.tree.DefaultMutableTreeNode;
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 "search":
acts.add(new Object[] { 500L, CLIENT, "join 192.168.0.30" });
acts.add(new Object[] { 3000L, WEB, "join 192.168.0.31" });
acts.add(new Object[] { 5500L, FAR, "join 192.168.0.31" });
acts.add(new Object[] { 8000L, CLIENT, "search woche" });
acts.add(new Object[] { 11000L, CLIENT, "download 0" });
acts.add(new Object[] { 14000L, CLIENT, "search notiz" });
acts.add(new Object[] { 17000L, CLIENT, "download 0" });
acts.add(new Object[] { 20000L, ECHO, "remove notizen.txt" });
acts.add(new Object[] { 20500L, CLIENT, "download 0" });
acts.add(new Object[] { 23000L, CLIENT, "search xyz" });
acts.add(new Object[] { 26000L, CLIENT, "clear" });
break;
case "searchBoth":
acts.add(new Object[] { 500L, CLIENT, "join 192.168.0.30" });
acts.add(new Object[] { 3000L, WEB, "join 192.168.0.31" });
acts.add(new Object[] { 5500L, FAR, "join 192.168.0.31" });
acts.add(new Object[] { 8000L, CLIENT, "search lied" });
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);
if (story.equals("full")) program.setMaxTeilnehmerZahl(host.equals(ECHO) ? 2 : host.equals(FAR) ? 3 : 1);
else if (story.startsWith("search")) program.setMaxTeilnehmerZahl(host.equals(CLIENT) || host.equals(FAR) ? 1 : 2);
else program.setMaxTeilnehmerZahl(3);
program.hinzuBeobachter((observable, data) -> {
event(host, "peers", String.join(",", program.holeBekanntePeerToPeerTeilnehmer()));
event(host, "results", String.join("|", program.holeErgebnisse()));
});
programs.put(host, program);
}
if (story.startsWith("search")) {
Dateisystem echoFiles = network.system(ECHO).getDateisystem();
if (story.equals("searchBoth")) echoFiles.speicherDatei("/peer2peer", new Datei("lied.txt", "txt", "Hallo Welt"));
echoFiles.speicherDatei("/peer2peer", new Datei("notizen.txt", "txt", "Notiz"));
network.system(FAR).getDateisystem().speicherDatei("/peer2peer", new Datei("Lied der Woche.txt", "txt", "La la la"));
}
network.start();
for (Object[] act : acts) {
sleepUntil((Long) act[0]);
String host = (String) act[1];
String call = (String) act[2];
event(host, "call", call);
PeerToPeerAnwendung program = programs.get(host);
if (call.startsWith("join ")) program.beitretenNetzwerk(call.substring("join ".length()));
else if (call.startsWith("search ")) program.sucheDatei(call.substring("search ".length()));
else if (call.startsWith("download ")) {
// The window offers only the rows it shows.
int row = Integer.parseInt(call.substring("download ".length()));
if (row < program.holeErgebnisse().size()) program.herunterladenDatei(row);
else event(host, "skipped", call);
}
else if (call.startsWith("remove ")) {
// The file explorer's "delete" on the peer that has the file.
Dateisystem files = network.system(host).getDateisystem();
DefaultMutableTreeNode folder = files.verzeichnisKnoten("/peer2peer");
folder.remove(Dateisystem.verzeichnisKnoten(folder, call.substring("remove ".length())));
} else if (call.equals("clear")) program.loescheSuchergebnisse();
else program.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.key("files").open('[');
for (Datei file : network.system(entry.getKey()).getDateisystem().holeDateien(program.holeVerzeichnis())) {
out.open('[').string(file.getName()).string(file.getDateiTyp()).string(file.getDateiInhalt()).close(']');
}
out.close(']');
out.close('}');
}
out.close('}');
out.close('}');
network.stop();
ScenarioNetwork.write(args[3], out);
System.exit(0);
}
}