feat: F4.6g2 the DNS server window, its records being /dns/hosts
Oracle DnsWindowRun uses Java's DNS server window as a person does and asks a computer's resolver after each step. The core's DNS server is a program its window changes: on and off (its socket on port 53), recursive resolution, and records that are /dns/hosts, read on every query as Java's server reads them (the reader moves from fls to the core). The snapshot carries every installed DNS server with whether it runs. The room takes the window's actions, sends its view and writes the server back after the run; the editor shows Java's window with its three tabs. Not taken from Java: a cell edit spilling into records of another type, an add losing what another program wrote into the file, an empty mail domain, a removal hitting the first record of the name. A table cell no longer takes the room's value over what is being typed (the web server's too). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
// Uses FILIUS' own DNS server window (GUIApplicationDNSServerWindow on a
|
||||
// GUIDesktopPanel) on mail-lan's DNS server as a person does: the start
|
||||
// button, the check box for recursive resolution, each tab's two fields and
|
||||
// "Hinzufügen" (also with what Java does not take), a table cell edited (as
|
||||
// Java's table writes it when its editing stops), a row chosen and "Auswahl
|
||||
// entfernen"; and /dns/hosts changed beside it as another program would.
|
||||
// Java's message dialogs are answered and written down. After each step it
|
||||
// writes what the window shows (the button, the box, the fields, the three
|
||||
// tables), the file, and what the first computer's resolver gets for a few
|
||||
// names. The reference for the room's DNS server window
|
||||
// (packages/sim/test/dnsWindow.test.ts).
|
||||
//
|
||||
// Usage: java -cp filius.jar:lib/*:classes DnsWindowRun <scenario.json> <output json>
|
||||
|
||||
import filius.gui.anwendungssicht.DNSConfigTable;
|
||||
import filius.gui.anwendungssicht.GUIApplicationDNSServerWindow;
|
||||
import filius.gui.anwendungssicht.GUIDesktopPanel;
|
||||
import filius.software.system.Betriebssystem;
|
||||
import filius.software.system.Datei;
|
||||
import filius.software.system.InternetKnotenBetriebssystem;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Window;
|
||||
import java.io.FileOutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
public class DnsWindowRun {
|
||||
static final String CLIENT = "00000000-0000-0000-0000-000000000201";
|
||||
static final String SERVER = "00000000-0000-0000-0000-000000000202";
|
||||
static final String[] NAMES = { "www.filius.de", "web.filius.de", "neu.schule.de", "extern.schule.de" };
|
||||
|
||||
static final List<Object> steps = new ArrayList<>();
|
||||
static final List<String> dialogs = new ArrayList<>();
|
||||
static GUIApplicationDNSServerWindow window;
|
||||
static Betriebssystem server;
|
||||
static InternetKnotenBetriebssystem client;
|
||||
|
||||
static Object field(Class<?> owner, Object object, String name) throws Exception {
|
||||
Field field = owner.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
return field.get(object);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> T find(Container container, Class<T> kind) {
|
||||
for (Component component : container.getComponents()) {
|
||||
if (kind.isInstance(component)) return (T) component;
|
||||
if (component instanceof Container) {
|
||||
T found = find((Container) component, kind);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static void watchDialogs() {
|
||||
Thread watcher = new Thread(() -> {
|
||||
while (true) {
|
||||
for (Window each : Window.getWindows()) {
|
||||
if (!(each instanceof JDialog) || !each.isVisible()) continue;
|
||||
JOptionPane pane = find((Container) each, JOptionPane.class);
|
||||
if (pane == null) continue;
|
||||
synchronized (dialogs) {
|
||||
dialogs.add(String.valueOf(pane.getMessage()));
|
||||
}
|
||||
pane.setValue(JOptionPane.OK_OPTION);
|
||||
each.setVisible(false);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
watcher.setDaemon(true);
|
||||
watcher.start();
|
||||
}
|
||||
|
||||
static void onEdt(Runnable action) throws Exception {
|
||||
SwingUtilities.invokeLater(action);
|
||||
Thread.sleep(500);
|
||||
}
|
||||
|
||||
static List<List<String>> rows(JTable table) {
|
||||
List<List<String>> rows = new ArrayList<>();
|
||||
TableModel model = table.getModel();
|
||||
for (int row = 0; row < model.getRowCount(); row++) {
|
||||
List<String> cells = new ArrayList<>();
|
||||
for (int column = 0; column < model.getColumnCount(); column++) cells.add(String.valueOf(model.getValueAt(row, column)));
|
||||
rows.add(cells);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
static String file() {
|
||||
Datei hosts = server.getDateisystem().holeDatei("/dns/hosts");
|
||||
return hosts == null ? null : hosts.getDateiInhalt();
|
||||
}
|
||||
|
||||
/** What the first computer's resolver gets for the name: the address, or the exception's class. */
|
||||
static String resolve(String name) throws Exception {
|
||||
AtomicReference<String> result = new AtomicReference<>("(no answer)");
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
result.set(String.valueOf(client.holeDNSClient().holeIPAdresse(name)));
|
||||
} catch (Exception error) {
|
||||
result.set(error.getClass().getSimpleName());
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
thread.join(30000);
|
||||
return result.get();
|
||||
}
|
||||
|
||||
static void step(String name, boolean ask) throws Exception {
|
||||
Class<?> w = GUIApplicationDNSServerWindow.class;
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("step", name);
|
||||
result.put("button", ((JButton) field(w, window, "buttonStart")).getText());
|
||||
result.put("recursive", ((JCheckBox) field(w, window, "recResolution")).isSelected());
|
||||
Map<String, String> fields = new LinkedHashMap<>();
|
||||
for (String each : new String[] { "aDomainField", "aIpField", "mxMaildomainField", "mxURLField", "nsDomainField", "nsDomainServerField" }) {
|
||||
fields.put(each, ((JTextField) field(w, window, each)).getText());
|
||||
}
|
||||
result.put("fields", fields);
|
||||
result.put("a", rows((JTable) field(w, window, "aRecordsTable")));
|
||||
result.put("mx", rows((JTable) field(w, window, "mxRecordsTable")));
|
||||
result.put("ns", rows((JTable) field(w, window, "nsRecordsTable")));
|
||||
result.put("file", file());
|
||||
synchronized (dialogs) {
|
||||
result.put("dialogs", new ArrayList<>(dialogs));
|
||||
dialogs.clear();
|
||||
}
|
||||
if (ask) {
|
||||
Map<String, String> answers = new LinkedHashMap<>();
|
||||
for (String each : NAMES) answers.put(each, resolve(each));
|
||||
result.put("resolved", answers);
|
||||
}
|
||||
steps.add(result);
|
||||
}
|
||||
|
||||
static void type(String name, String text) throws Exception {
|
||||
JTextField field = (JTextField) field(GUIApplicationDNSServerWindow.class, window, name);
|
||||
onEdt(() -> field.setText(text));
|
||||
}
|
||||
|
||||
/** The tab's "Hinzufügen" or "Auswahl entfernen" (the n-th of that text in the window). */
|
||||
static void press(String text, int index) throws Exception {
|
||||
List<JButton> found = new ArrayList<>();
|
||||
collect(window, text, found);
|
||||
JButton button = found.get(index);
|
||||
onEdt(button::doClick);
|
||||
}
|
||||
|
||||
static void collect(Container container, String text, List<JButton> found) {
|
||||
for (Component component : container.getComponents()) {
|
||||
if (component instanceof JButton && text.equals(((JButton) component).getText())) found.add((JButton) component);
|
||||
if (component instanceof Container) collect((Container) component, text, found);
|
||||
}
|
||||
}
|
||||
|
||||
/** A cell written as Java's table writes it: edited, then its editing stopped. */
|
||||
static void edit(String table, int row, int column, String value) throws Exception {
|
||||
DNSConfigTable each = (DNSConfigTable) field(GUIApplicationDNSServerWindow.class, window, table);
|
||||
onEdt(() -> {
|
||||
each.editCellAt(row, column);
|
||||
((JTextField) each.getEditorComponent()).setText(value);
|
||||
each.getCellEditor().stopCellEditing();
|
||||
});
|
||||
}
|
||||
|
||||
static void choose(String table, int row) throws Exception {
|
||||
JTable each = (JTable) field(GUIApplicationDNSServerWindow.class, window, table);
|
||||
onEdt(() -> each.setRowSelectionInterval(row, row));
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
try {
|
||||
run(args);
|
||||
} catch (Throwable error) {
|
||||
error.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
static void run(String[] args) throws Exception {
|
||||
ScenarioNetwork network = ScenarioNetwork.read(args[0]);
|
||||
server = (Betriebssystem) network.nodes.get(SERVER).getSystemSoftware();
|
||||
client = (InternetKnotenBetriebssystem) network.nodes.get(CLIENT).getSystemSoftware();
|
||||
network.start();
|
||||
Thread.sleep(500);
|
||||
watchDialogs();
|
||||
|
||||
JFrame frame = new JFrame();
|
||||
GUIDesktopPanel desktop = new GUIDesktopPanel(server);
|
||||
frame.getContentPane().add(desktop);
|
||||
window = (GUIApplicationDNSServerWindow) desktop.starteAnwendung("filius.software.dns.DNSServer");
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
});
|
||||
Thread.sleep(500);
|
||||
step("open", true);
|
||||
|
||||
// A records: what Java does not take, then a new one; the fields after it.
|
||||
type("aDomainField", "kein name!");
|
||||
type("aIpField", "192.168.0.30");
|
||||
press("Hinzufügen", 0);
|
||||
step("A: add an invalid name", false);
|
||||
type("aDomainField", "neu.schule.de");
|
||||
type("aIpField", "192.168.0.300");
|
||||
press("Hinzufügen", 0);
|
||||
step("A: add an invalid address", false);
|
||||
type("aDomainField", "neu.schule.de");
|
||||
type("aIpField", "10.0.0.1");
|
||||
press("Hinzufügen", 0);
|
||||
step("A: add neu.schule.de", true);
|
||||
type("aDomainField", "neu.schule.de");
|
||||
type("aIpField", "10.0.0.2");
|
||||
press("Hinzufügen", 0);
|
||||
step("A: add neu.schule.de again", false);
|
||||
|
||||
// MX and NS.
|
||||
type("mxMaildomainField", "schule.de");
|
||||
type("mxURLField", "mail.schule.de");
|
||||
press("Hinzufügen", 1);
|
||||
step("MX: add schule.de", false);
|
||||
type("mxMaildomainField", "");
|
||||
type("mxURLField", "x");
|
||||
press("Hinzufügen", 1);
|
||||
step("MX: add with no domain", false);
|
||||
type("nsDomainField", "schule.de");
|
||||
type("nsDomainServerField", "ns.schule.de");
|
||||
press("Hinzufügen", 2);
|
||||
step("NS: add schule.de", false);
|
||||
|
||||
// A table cell edited: the first A record's name; an invalid value; the address of the third.
|
||||
edit("aRecordsTable", 0, 0, "web.filius.de");
|
||||
step("A: edit row 0's name to web.filius.de", true);
|
||||
edit("aRecordsTable", 1, 1, "999.1.1.1");
|
||||
step("A: edit row 1's address to 999.1.1.1", false);
|
||||
edit("aRecordsTable", 2, 1, "192.168.0.99");
|
||||
step("A: edit row 2's address", false);
|
||||
edit("mxRecordsTable", 0, 1, "post.filius.de");
|
||||
step("MX: edit row 0's server", false);
|
||||
|
||||
// A row chosen and removed: the second neu.schule.de.
|
||||
choose("aRecordsTable", 5);
|
||||
press("Auswahl entfernen", 0);
|
||||
step("A: remove row 5", true);
|
||||
|
||||
// Recursive resolution, start and stop.
|
||||
JCheckBox recursive = (JCheckBox) field(GUIApplicationDNSServerWindow.class, window, "recResolution");
|
||||
onEdt(recursive::doClick);
|
||||
step("recursive on", false);
|
||||
JButton start = (JButton) field(GUIApplicationDNSServerWindow.class, window, "buttonStart");
|
||||
onEdt(start::doClick);
|
||||
Thread.sleep(1000);
|
||||
step("button once", true);
|
||||
onEdt(start::doClick);
|
||||
Thread.sleep(1000);
|
||||
step("button twice", true);
|
||||
|
||||
// Another program writes /dns/hosts; the window, the answers; then a record added in the window.
|
||||
Datei hosts = server.getDateisystem().holeDatei("/dns/hosts");
|
||||
onEdt(() -> hosts.setDateiInhalt(hosts.getDateiInhalt() + "extern.schule.de. A 3600 10.0.0.9\n"));
|
||||
step("file changed by another program", true);
|
||||
type("aDomainField", "danach.schule.de");
|
||||
type("aIpField", "10.0.0.3");
|
||||
press("Hinzufügen", 0);
|
||||
step("A: add after the file changed", true);
|
||||
choose("aRecordsTable", 0);
|
||||
press("Auswahl entfernen", 0);
|
||||
step("A: remove row 0 after the file changed", true);
|
||||
|
||||
Map<String, Object> output = new LinkedHashMap<>();
|
||||
output.put("steps", steps);
|
||||
try (FileOutputStream out = new FileOutputStream(args[1])) {
|
||||
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
network.stop();
|
||||
System.out.println("dns window steps: " + steps.size());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user