From 698a775c379c05b9b8fea3b3adb5ae5ab315aac1 Mon Sep 17 00:00:00 2001 From: Julian Kiedaisch Date: Wed, 30 Sep 2026 09:11:39 +0000 Subject: [PATCH] feat(fls): export the editor model's files exportProject.ts, step X3: the files of a PC or notebook from the model's virtual file system under Java's root. A file's type follows from its kind: text by a known extension (else "text"), an image or binary data by its media type. Images and binary data are Base64 as FILIUS's Base64.encodeBytes (iHarder 2.x) writes it: a line break after every 76 characters, also after the last full line. Without that, all 31 images of the corpus came back with other content. A new oracle tool, Base64Cases, has Java encode 19 lengths around the line length; javaBase64 matches all. A router or home router keeps the files its constructor makes. Checked by Java: every corpus file, imported, exported and written, loads with the files of the original, byte for byte, up to named departures of the model: files in its order (directories first, then by name), with the type of their kind rather than the one the file named (Java itself names them unevenly), and no router files beyond the constructor's (www.conf, 16 times). The synthetic model adds nested directories, text with and without a known extension, a PNG, a JPEG, binary data with and without a media type and 57 bytes, one Base64 line exactly. 10 mutation probes, each loaded by Java, were all caught. Co-Authored-By: Claude Opus 5.5 --- CHANGES.md | 4 +- packages/fls/src/exportProject.ts | 69 +- packages/fls/test/base64.java.json | 78 ++ packages/fls/test/exportProject.test.ts | 153 ++- packages/fls/test/exported.java.json | 1530 +++++++++++++++++++++-- tools/java-oracle/README.md | 9 + tools/java-oracle/run.sh | 8 +- tools/java-oracle/src/Base64Cases.java | 32 + 8 files changed, 1777 insertions(+), 106 deletions(-) create mode 100644 packages/fls/test/base64.java.json create mode 100644 tools/java-oracle/src/Base64Cases.java diff --git a/CHANGES.md b/CHANGES.md index 38d13af..acaea68 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -51,8 +51,8 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand | `test/javaBeansWriter.test.ts`, `test/beans/` | — | neu: 11 Dateien des Schreibers, von Javas `XMLDecoder` gelesen und beschrieben; Erwartungen von Hand | | `src/topologyWriter.ts` | Java `SzenarioVerwaltung.netzwerkSpeichern`, Konstruktoren von `NetzwerkInterface`, `Port`, `GUIKabelItem` und der Systemsoftware | neu nach Java: `konfiguration.xml` aus Javas Sicht — Geräte, Schnittstellen (vom Konstruktor angelegte über Getter, weitere per `add`), Ports über Getter-Verweise, Kabel samt Zeichen-Enden (`kabelpanel`), Doku-Elemente; Systemsoftware über ihre Setter: SSID, Lernzeit, RIP, manuelle Routen, DHCP-Client und -Server, statische NAT, Modem, Router-Firewall (über das `konfig`-Plug-in des Webservers, wie XMLEncoder sie erreicht); Dateisysteme (an die Wurzel des Konstruktors angehängt wie bei XMLEncoder, beim Router hinter dessen Vorlagen); installierte Programme nach den Dateien (Webserver, Tauschbörse und E-Mail-Server legen ihre Ordner nur an, wo sie fehlen; der DNS-Server liest `/dns/hosts` beim Installieren), je unter ihrem Schlüssel mit der Systemsoftware wie `installApp`, samt Einstellungen und persönlicher Firewall; E-Mail: Domain und Bean-Konten des Servers samt Postfächern, Ordner und Adressbuch des Clients (sein Konto steht in `/konten.txt`, einer Datei); Javas Fehler nicht übernommen: ein Empfänger, den eine ältere Datei als Zeichenkette hält, wird als der `AddressEntry` geschrieben, den Javas `AddressEntry(String)` daraus macht (Java legt die Zeichenkette ungeprüft in die typisierte Liste, sein E-Mail-Fenster scheitert daran), ein `null`-Empfänger fällt weg; Kopf „Filius version: 2.14.0 (mdg-filius)“ | | `test/topologyWriter.test.ts`, `test/written.java.json` | — | neu: alle 23 Korpus-Dateien gelesen und neu geschrieben, von Java geladen — Topologie, Systemsoftware, Dateisysteme und Programme samt E-Mail wie beim Original (bis auf die benannte Abweichung bei Empfängern als Zeichenkette, nur in `man-in-the-middle.fls`) (Java gegen Java); dazu eine künstliche Topologie mit dem, was der Korpus nicht hat (u. a. eingeschaltete Router-Firewall mit Regeln jedes Protokolls, alle Firewall-Schalter abseits der Vorgabe, Modem mit eigenem Port und Ziel, verschachtelter Dateibaum mit umbenannter Wurzel, namenlosem Ordner, Datei ohne Typ und Inhalt mit CR/CRLF und Zeichen jenseits der BMP, Heimrouter mit eigener Wurzel, jedes Filius-Programm mit Einstellungen abseits der Vorgabe samt persönlicher Firewall, von Java angelegte Ordner `/peer2peer` und `/mailserver`, E-Mail-Server und -Client mit Konto, Postfach, drei Ordnern, Adressbuch und Empfängern aller Formen) | -| `src/exportProject.ts` | Gegenstück zu `importProject.ts`; Java `JHostKonfiguration.updateWifiConnection`, `InternetKnotenBetriebssystem.getStandardGateway`/`setStandardGateway` | neu: Editor-Modell → Javas Sicht für den Schreiber, Schritt X1 Topologie — Geräte, Schnittstellen (MAC wie in der Simulation: aus der Datei oder aus der Port-ID abgeleitet), Kabel samt Funk (Karte drahtlos und Switch, wie Java verbindet), Gateway und DNS an allen Schnittstellen wie Javas eigene Setter, Doku-Elemente; X2 Systemsoftware — SSIDs, Lernzeit, Routen, RIP, DHCP-Client und -Server, Portweiterleitung, Firewall (Namen des Kerns als Javas Zahlen), Modems aus den Paaren des Modells (Port aus dem Paarnamen, der erste eines Paars dient, Ziel `localhost`) | -| `test/exportProject.test.ts`, `test/exported.java.json` | — | neu: alle 23 Korpus-Dateien importiert, exportiert, geschrieben und von Java geladen — Topologie wie beim Original bis auf benannte Abweichungen (abgeleiteter MAC und abgeleitete Switch-SSID statt gewürfelter, Modem-Rollen nach Reihenfolge im Paar, Doku in Zeichenreihenfolge mit Text, Schrift und Mindestgröße des Modells); dazu ein künstliches Modell, das Java ebenfalls lädt | +| `src/exportProject.ts` | Gegenstück zu `importProject.ts`; Java `JHostKonfiguration.updateWifiConnection`, `InternetKnotenBetriebssystem.getStandardGateway`/`setStandardGateway` | neu: Editor-Modell → Javas Sicht für den Schreiber, Schritt X1 Topologie — Geräte, Schnittstellen (MAC wie in der Simulation: aus der Datei oder aus der Port-ID abgeleitet), Kabel samt Funk (Karte drahtlos und Switch, wie Java verbindet), Gateway und DNS an allen Schnittstellen wie Javas eigene Setter, Doku-Elemente; X2 Systemsoftware — SSIDs, Lernzeit, Routen, RIP, DHCP-Client und -Server, Portweiterleitung, Firewall (Namen des Kerns als Javas Zahlen), Modems aus den Paaren des Modells (Port aus dem Paarnamen, der erste eines Paars dient, Ziel `localhost`); X3 Dateien von Rechner und Notebook — Typ aus der Inhaltsart (Text nach Endung, sonst `text`; Bild und Binärdaten nach Medientyp), Bilder und Binärdaten in Base64 wie Filius' `Base64.encodeBytes` (Umbruch nach 76 Zeichen, an 19 Längen mit Java gemessen) | +| `test/exportProject.test.ts`, `test/exported.java.json` | — | neu: alle 23 Korpus-Dateien importiert, exportiert, geschrieben und von Java geladen — Topologie wie beim Original bis auf benannte Abweichungen (abgeleiteter MAC und abgeleitete Switch-SSID statt gewürfelter, Modem-Rollen nach Reihenfolge im Paar, Doku in Zeichenreihenfolge mit Text, Schrift und Mindestgröße des Modells, Dateien in der Ordnung und mit dem Typ des Modells, keine eigenen Dateien von Routern); dazu ein künstliches Modell, das Java ebenfalls lädt | | `src/importEmail.ts` | `Persistence/TopologyProjectStore.swift` (`registerJavaEmailConfigurations`, `assignedJavaEmailMessages`) | übertragen, Eingabe ist Javas Sicht: Server-Konten aus `/mailserver/konten.txt`, ohne Datei aus der Bean-Liste (Java verlöre sie); Client-Konto aus `/konten.txt`, ohne Datei aus der älteren `kontoListe` (Filius 2.14 liest sie nicht mehr); Konten und Nachrichten bleiben, wo Swift verwirft, was seine Prüfung nicht besteht; Vor- und Nachname getrennt; Empfangszeit nur aus der Datei (Swift nummeriert); gegen Swifts Modell geprüft | | `test/importEmail.test.ts`, `test/email-*.swift.json` | `State/TopologyEmail.swift` (`decodeJavaServer`, `decodeJavaClient`) als Vergleich | neu: dieselben 43 Kontodateien mit Swifts Leser; wie Swift bis auf benannte Fälle, in denen Swift prüft und die ganze Datei verwirft, wo Java liest | | `src/javaFileSystem.ts`, `src/javaString.ts` | Java `Dateisystem.holeDatei`, `String.trim`/`split`/`equalsIgnoreCase` | neu nach Java; die Groß-/Kleinschreibung gegenüber ASCII am JDK über die ganze BMP gemessen | diff --git a/packages/fls/src/exportProject.ts b/packages/fls/src/exportProject.ts index 54dd4ef..cc39e84 100644 --- a/packages/fls/src/exportProject.ts +++ b/packages/fls/src/exportProject.ts @@ -15,7 +15,13 @@ // model has no setting for is what Java's constructors make. A remote link // pair becomes modems on the port its identifier names (12345 when it names // none): the first of a pair in node order serves, the others connect to -// localhost — the model's pairs live in one project. System software, files and programs +// localhost — the model's pairs live in one project. +// +// The files of a PC or notebook: the model's directories and files under +// Java's root, each file a Datei of the type Java's importer reads it by — +// text by its extension (else "text"), an image or binary data by its media +// type, in Base64 as FILIUS's Base64.encodeBytes writes it (javaBase64). A router or home +// router keeps the files its constructor makes; the model holds none for it. System software, files and programs // follow in later steps. // // What the model keeps is written as it keeps it, and so differs from the @@ -31,12 +37,16 @@ // - a documentation item always has a text and a font, bold or plain: where // Java has none, the empty text and Dialog 12 the model gives it; // - a switch's SSID the file lacked (Java rolls one) is the one the model -// derives from the node's id. +// derives from the node's id; +// - files in the model's order (directories first, then by name) and of the +// type their kind says, not the one the file named; a router's own files +// (a web server's www.conf) are not in the model. // // Checked by Java: every corpus file, imported, exported and written, loads in // Java FILIUS to the topology of the original up to those departures. import { + base64Encode, defaultSwitchConfiguration, dhcpServerConfiguration, effectiveMacAddress, @@ -60,8 +70,9 @@ import { type NodeKind, type TopologyGraph, type TopologyNode, + type VirtualFileSystem, } from '@mdg-filius/core'; -import type { FlsDhcpServer, FlsFirewall } from './systemSoftware.js'; +import type { FlsDhcpServer, FlsFileTree, FlsFirewall } from './systemSoftware.js'; import type { FlsDocumentationItem, FlsInterface, FlsLink, FlsNodeClass } from './topology.js'; import type { NodeToWrite, SystemToWrite, TopologyToWrite } from './topologyWriter.js'; @@ -79,6 +90,7 @@ export interface ProjectToExport { readonly dhcpServerConfigurations: ReadonlyMap; readonly portForwardingRowsByNodeId: ReadonlyMap; readonly remoteLinkConfigurations: ReadonlyMap; + readonly virtualFileSystems: ReadonlyMap; readonly documentationItems: readonly DocumentationItem[]; } @@ -206,7 +218,12 @@ function exportSystem(node: TopologyNode, project: ProjectToExport, servingModem case 'pc': case 'notebook': { const ssid = project.hostWirelessConfigurations.get(id)?.ssid ?? ''; - return { ...system, ssid: ssid === '' ? null : ssid }; + const files = project.virtualFileSystems.get(id); + return { + ...system, + ssid: ssid === '' ? null : ssid, + fileSystem: { kind: 'directory', name: 'root', children: files === undefined ? [] : fileTrees(files, '/') }, + }; } case 'router': return { @@ -228,6 +245,50 @@ function exportSystem(node: TopologyNode, project: ProjectToExport, servingModem } } +/** The text types Java's importer reads as text, named by a file's extension. */ +const TEXT_EXTENSIONS = new Set(['txt', 'html', 'htm', 'css', 'xml', 'csv', 'conf', 'log']); + +function fileTrees(files: VirtualFileSystem, directory: string): FlsFileTree[] { + return files.entries(directory).map((entry): FlsFileTree => { + const name = entry.path.slice(entry.path.lastIndexOf('/') + 1); + const file = (type: string | null, content: string): FlsFileTree => ({ kind: 'file', name, type, content, children: [] }); + switch (entry.content.kind) { + case 'directory': + return { kind: 'directory', name, children: fileTrees(files, entry.path) }; + case 'text': { + const dot = name.lastIndexOf('.'); + const extension = dot < 0 ? '' : name.slice(dot + 1).toLowerCase(); + return file(TEXT_EXTENSIONS.has(extension) ? extension : 'text', entry.content.text); + } + case 'image': + return file(subtype(entry.content.mediaType), javaBase64(entry.content.data)); + case 'binary': + return file(entry.content.mediaType === undefined ? '' : subtype(entry.content.mediaType), javaBase64(entry.content.data)); + } + }); +} + +/** + * FILIUS's Base64.encodeBytes (iHarder Base64 2.x): a line break after every + * 76 characters — also after the last full line — then the padded rest. + */ +export function javaBase64(bytes: Uint8Array): string { + const full = bytes.length - (bytes.length % 3); + let text = ''; + for (let offset = 0; offset < full; offset += 57) { + const end = Math.min(offset + 57, full); + text += base64Encode(bytes.subarray(offset, end)); + if (end - offset === 57) text += '\n'; + } + return text + base64Encode(bytes.subarray(full)); +} + +/** "image/png" → "png", "image/jpeg" → "jpg", as Java names them. */ +function subtype(mediaType: string): string { + const name = mediaType.slice(mediaType.indexOf('/') + 1); + return name === 'jpeg' ? 'jpg' : name; +} + function exportDhcpServer(server: DhcpServerConfiguration): FlsDhcpServer { return { isActive: server.isActive, diff --git a/packages/fls/test/base64.java.json b/packages/fls/test/base64.java.json new file mode 100644 index 0000000..9e1b198 --- /dev/null +++ b/packages/fls/test/base64.java.json @@ -0,0 +1,78 @@ +[ + [ + 0, + "" + ], + [ + 1, + "Cw==" + ], + [ + 2, + "CzA=" + ], + [ + 3, + "CzBV" + ], + [ + 55, + "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02Q==" + ], + [ + 56, + "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4=" + ], + [ + 57, + "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4j\n" + ], + [ + 58, + "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4j\nSA==" + ], + [ + 59, + "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4j\nSG0=" + ], + [ + 113, + "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4j\nSG2St9wBJktwlbrfBClOc5i94gcsUXabwOUKL1R5nsPoDTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjs=" + ], + [ + 114, + "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4j\nSG2St9wBJktwlbrfBClOc5i94gcsUXabwOUKL1R5nsPoDTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtg\n" + ], + [ + 115, + "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4j\nSG2St9wBJktwlbrfBClOc5i94gcsUXabwOUKL1R5nsPoDTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtg\nhQ==" + ], + [ + 116, + "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4j\nSG2St9wBJktwlbrfBClOc5i94gcsUXabwOUKL1R5nsPoDTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtg\nhao=" + ], + [ + 171, + "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4j\nSG2St9wBJktwlbrfBClOc5i94gcsUXabwOUKL1R5nsPoDTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtg\nharP9Bk+Y4it0vccQWaLsNX6H0RpjrPY/SJHbJG22wAlSm+Uud4DKE1yl7zhBitQdZq/5AkuU3id\n" + ], + [ + 172, + "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4j\nSG2St9wBJktwlbrfBClOc5i94gcsUXabwOUKL1R5nsPoDTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtg\nharP9Bk+Y4it0vccQWaLsNX6H0RpjrPY/SJHbJG22wAlSm+Uud4DKE1yl7zhBitQdZq/5AkuU3id\nwg==" + ], + [ + 2000, + "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\nYoes0fY=" + ], + [ + 2001, + "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\nYoes0fYb" + ], + [ + 2002, + "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\nYoes0fYbQA==" + ], + [ + 2052, + "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\n" + ] +] diff --git a/packages/fls/test/exportProject.test.ts b/packages/fls/test/exportProject.test.ts index 50da906..10995f1 100644 --- a/packages/fls/test/exportProject.test.ts +++ b/packages/fls/test/exportProject.test.ts @@ -20,12 +20,14 @@ import { stableGeneratedMacAddress, type Id, type TopologyNode, + VirtualFileSystem, } from '@mdg-filius/core'; import { describe, expect, it } from 'vitest'; import { decodeJavaBeans, exportProject, importProject, + javaBase64, readFlsArchive, readTopology, writeFlsArchive, @@ -66,10 +68,12 @@ const original = JSON.parse(readFileSync(join(corpusRoot, 'corpus.java.json'), ' const exportedPath = join(here, 'exported.java.json'); const exported = JSON.parse(readFileSync(exportedPath, 'utf8')) as Record; -/** The system software without its files and applications, later steps. */ +/** The system software without its applications, a later step; files in any order. */ function systemOf(system: Record) { - const { fileSystem: _files, applications: _applications, ...settings } = system; - return settings; + const { applications: _applications, ...settings } = system; + return settings.fileSystem === undefined || settings.fileSystem === null + ? settings + : { ...settings, fileSystem: sortedTree(settings.fileSystem as JavaTree) }; } /** The topology of Java's view: the label's position, not its size. */ @@ -102,7 +106,15 @@ function topologyOf(view: JavaFile) { * the model derives from the node's id; * - modemRoles: the model keeps a pair of modems, not which one serves: the * first of a pair in node order serves, the others connect. Both ways - * work within one project, where the client connects to localhost. + * work within one project, where the client connects to localhost; + * - routerFiles: the model holds no files for a router or home router; what + * the file adds to the ones its constructor makes (a web server's + * www.conf) is not written; + * - fileTypes: a file's type follows from its kind in the model (fileType + * below), not the one the file named — Java names them unevenly itself + * (txt, text/txt, html, none); + * - fileOrder: directories first, then by name (compared regardless of + * order here). */ const DEPARTURES = { generatedMac(view: JavaFile, macs: (string | undefined)[][]): JavaFile { @@ -130,6 +142,25 @@ const DEPARTURES = { return { ...node.system, modem: { ...(node.system.modem as object), mode: serves ? 1 : 2 } }; }); }, + routerFiles(view: JavaFile, added: number[]): JavaFile { + return mapSystems(view, (node, index) => { + if ((node.class !== 'Vermittlungsrechner' && node.class !== 'Gateway') || added[index] === 0) return node.system; + const tree = node.system.fileSystem as JavaTree; + return { ...node.system, fileSystem: { ...tree, children: tree.children.slice(0, tree.children.length - added[index]!) } }; + }); + }, + fileTypes(view: JavaFile): JavaFile { + const retype = (tree: JavaTree): JavaTree => ({ + ...tree, + ...('file' in tree ? { type: fileType(tree.file as string, tree.type as string | null) } : {}), + children: tree.children.map(retype), + }); + return mapSystems(view, (node) => + node.system.fileSystem === undefined || node.system.fileSystem === null + ? node.system + : { ...node.system, fileSystem: retype(node.system.fileSystem as JavaTree) }, + ); + }, documentation(view: JavaFile): JavaFile { const items = (view.documentation as Record[]).map((item) => { const [x, y, width, height] = item.bounds as number[]; @@ -144,6 +175,45 @@ const DEPARTURES = { }, }; +type JavaTree = { file?: string; type?: string | null; children: JavaTree[] } & Record; + +const IMAGE_TYPES = new Set(['png', 'jpg', 'jpeg', 'gif', 'bmp', 'image']); +const TEXT_TYPES = new Set(['', 'text', 'txt', 'text/txt', 'html', 'htm', 'css', 'xml', 'csv', 'conf', 'log']); +const TEXT_EXTENSIONS = new Set(['txt', 'html', 'htm', 'css', 'xml', 'csv', 'conf', 'log']); + +/** + * The type of a file in the model's kind: an image (by its type or + * extension) by its media type, "jpg" for JPEG; text by a known extension, + * else "text"; other data by its type. + */ +function fileType(name: string, type: string | null): string { + const declared = (type ?? '').trim().toLowerCase(); + const dot = name.lastIndexOf('.'); + const extension = dot < 0 ? '' : name.slice(dot + 1).toLowerCase(); + if (IMAGE_TYPES.has(declared) || IMAGE_TYPES.has(extension)) { + const subtype = declared === 'image' || declared === '' ? (extension === '' ? 'png' : extension) : declared; + return subtype === 'jpeg' ? 'jpg' : subtype; + } + if (TEXT_TYPES.has(declared)) return TEXT_EXTENSIONS.has(extension) ? extension : 'text'; + return declared; +} + +/** A file tree with every directory's children in an order that does not depend on the list's. */ +function sortedTree(tree: JavaTree): JavaTree { + const children = tree.children.map(sortedTree); + return { ...tree, children: children.map((child) => JSON.stringify(child)).sort().map((text) => JSON.parse(text) as JavaTree) }; +} + +/** Every node's file tree as text, sorted or as it is. */ +function fileTrees(view: JavaFile, sorted: boolean): string { + return JSON.stringify( + view.nodes.map((node) => { + const tree = node.system.fileSystem as JavaTree | null | undefined; + return tree === undefined || tree === null ? null : sorted ? sortedTree(tree) : tree; + }), + ); +} + function mapSystems(view: JavaFile, system: (node: JavaNode, index: number) => Record): JavaFile { return { ...view, nodes: view.nodes.map((node, index) => ({ ...node, system: system(node, index) })) }; } @@ -163,6 +233,9 @@ const departed = { documentation: new Set(), switchSsid: new Set(), modemRoles: new Set(), + routerFiles: new Set(), + fileTypes: new Set(), + fileOrder: new Set(), }; describe('the editor model exported, as Java loads it', () => { @@ -192,10 +265,14 @@ describe('the editor model exported, as Java loads it', () => { const source = original[name]!; const nodeIds = project.graph.nodes.map((node) => node.id); const pairs = nodeIds.map((id) => project.remoteLinkConfigurations.get(id)?.pairIdentifier); + // What each router's file adds to its constructor's files. + const added = topology.nodes.map((node) => (node.system.templateFiles === true ? (node.system.fileSystem?.children.length ?? 0) : 0)); const steps: [keyof typeof departed, (view: JavaFile) => JavaFile][] = [ ['generatedMac', (view) => DEPARTURES.generatedMac(view, macs)], ['switchSsid', (view) => DEPARTURES.switchSsid(view, nodeIds)], ['modemRoles', (view) => DEPARTURES.modemRoles(view, pairs)], + ['routerFiles', (view) => DEPARTURES.routerFiles(view, added)], + ['fileTypes', (view) => DEPARTURES.fileTypes(view)], ['documentation', (view) => DEPARTURES.documentation(view)], ]; let expected = source; @@ -208,6 +285,9 @@ describe('the editor model exported, as Java loads it', () => { } expected = next; } + if (fileTrees(expected, true) === fileTrees(java!, true) && fileTrees(expected, false) !== fileTrees(java!, false)) { + departed.fileOrder.add(name); + } expect(topologyOf({ ...java!, documentation: sortedDocumentation(java!.documentation) })).toEqual(topologyOf(expected)); }); } @@ -234,12 +314,49 @@ function emptyProject(): ProjectToExport { dhcpServerConfigurations: new Map(), portForwardingRowsByNodeId: new Map(), remoteLinkConfigurations: new Map(), + virtualFileSystems: new Map(), documentationItems: [], }; } const port = (id: string) => ({ id, label: id, isOccupied: false }); +/** Bytes of a length: (index * 37 + 11) mod 256. */ +function bytesOf(length: number): Uint8Array { + return Uint8Array.from({ length }, (_, index) => (index * 37 + 11) % 256); +} + +/** + * Nested directories, text with a known extension and without one, a PNG and + * a JPEG, binary data with and without a media type, 57 bytes — one line of + * Base64 exactly. + */ +function syntheticFiles(): VirtualFileSystem { + const files = new VirtualFileSystem(); + files.createDirectory('/docs/deep', true); + files.writeTextFile('/docs/readme.txt', 'Zeile 1\r\nZeile 2'); + files.writeTextFile('/docs/deep/notes', 'no extension'); + files.writeTextFile('/index.HTML', '

upper case extension

'); + files.writeImageFile('/bild.png', bytesOf(57), 'image/png'); + files.writeImageFile('/foto.jpg', bytesOf(100), 'image/jpeg'); + files.writeBinaryFile('/daten.bin', bytesOf(58), 'application/zip'); + files.writeBinaryFile('/roh', bytesOf(3)); + return files; +} + +/** The files a router's or home router's constructor makes, as Java loaded them from the corpus. */ +function templateOf(javaClass: string): unknown[] { + const node = Object.values(original) + .flatMap((file) => file.nodes) + .find((each) => each.class === javaClass); + return (node!.system.fileSystem as JavaTree).children.slice(0, 1); +} + +/** A file as the oracle describes it: its content's UTF-16 length and the SHA-256 of its UTF-8 bytes. */ +function javaFile(name: string, type: string | null, content: string) { + return { file: name, type, length: content.length, sha256: createHash('sha256').update(content, 'utf8').digest('hex'), children: [] }; +} + /** * What the corpus lacks: a label with IP and MAC, a position between * pixels, a switch with five ports, a wireless notebook, a router with three @@ -319,6 +436,7 @@ const SYNTHETIC: ProjectToExport = { ], ]), portForwardingRowsByNodeId: new Map([['gw', [{ protocolValue: 'TCP', publicPortValue: '8080', lanIPAddress: '192.168.1.10', lanPortValue: '80' }]]]), + virtualFileSystems: new Map([['pc', syntheticFiles()]]), remoteLinkConfigurations: new Map([ ['m1', { pairIdentifier: 'java-modem-port-4242', latencyMilliseconds: 20, isEnabled: true }], ['m2', { pairIdentifier: 'java-modem-port-4242', latencyMilliseconds: 20, isEnabled: true }], @@ -390,6 +508,20 @@ describe('a model beyond the corpus, as Java loads it', () => { manualRoutes: [['10.5.0.0', '255.255.0.0', '10.0.0.254', '10.0.0.1']], dhcpClient: true, dhcpServer: JAVA_DHCP_DEFAULTS, + fileSystem: sortedTree({ + directory: 'root', + children: [ + { + directory: 'docs', + children: [{ directory: 'deep', children: [javaFile('notes', 'text', 'no extension')] }, javaFile('readme.txt', 'txt', 'Zeile 1\r\nZeile 2')], + }, + javaFile('bild.png', 'png', javaBase64(bytesOf(57))), + javaFile('daten.bin', 'zip', javaBase64(bytesOf(58))), + javaFile('foto.jpg', 'jpg', javaBase64(bytesOf(100))), + javaFile('index.HTML', 'html', '

upper case extension

'), + javaFile('roh', '', javaBase64(bytesOf(3))), + ], + }), }, }, { @@ -408,7 +540,7 @@ describe('a model beyond the corpus, as Java loads it', () => { useIpAsName: false, useMacAsName: true, ports: [card('10.0.0.2', '255.255.255.0', stableGeneratedMacAddress('n0'), '', '', true)], - system: { ssid: 'net', manualRoutes: [], dhcpClient: false, dhcpServer: JAVA_DHCP_DEFAULTS }, + system: { ssid: 'net', manualRoutes: [], dhcpClient: false, dhcpServer: JAVA_DHCP_DEFAULTS, fileSystem: { directory: 'root', children: [] } }, }, { class: 'Vermittlungsrechner', @@ -423,6 +555,7 @@ describe('a model beyond the corpus, as Java loads it', () => { ], system: { ripEnabled: true, + fileSystem: sortedTree({ directory: 'root', children: templateOf('Vermittlungsrechner') as JavaTree[] }), manualRoutes: [['10.6.0.0', '255.255.0.0', '10.1.0.2', '10.1.0.1']], dhcpClient: false, dhcpServer: JAVA_DHCP_DEFAULTS, @@ -452,6 +585,7 @@ describe('a model beyond the corpus, as Java loads it', () => { card('192.168.1.1', '255.255.255.0', stableGeneratedMacAddress('g1'), '', ''), ], system: { + fileSystem: sortedTree({ directory: 'root', children: templateOf('Gateway') as JavaTree[] }), manualRoutes: [], dhcpClient: true, dhcpServer: { @@ -498,6 +632,15 @@ const JAVA_DHCP_DEFAULTS = { // ── Rules the corpus does not exercise ───────────────────────────────────── +describe('javaBase64', () => { + // FILIUS's Base64.encodeBytes around its line length (tools/java-oracle Base64Cases). + const cases = JSON.parse(readFileSync(join(here, 'base64.java.json'), 'utf8')) as [number, string][]; + it.each(cases)('encodes %i bytes as Java does', (length, java) => { + const bytes = Uint8Array.from({ length }, (_, index) => (index * 37 + 11) % 256); + expect(javaBase64(bytes)).toBe(java); + }); +}); + describe('exportProject rules', () => { const node = (id: string, kind: TopologyNode['kind'], ports: string[], extra: Partial = {}): TopologyNode => ({ id, diff --git a/packages/fls/test/exported.java.json b/packages/fls/test/exported.java.json index 37ae5ee..ca1bf7b 100644 --- a/packages/fls/test/exported.java.json +++ b/packages/fls/test/exported.java.json @@ -229,7 +229,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 538, + "sha256": "1966c377d3651754cfc513da8f425ec0e943e6cc5e8f7112aa06102e21eb0775", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -271,7 +291,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 36, + "sha256": "c4f62d9c112a0def5f67e6b803e8a55886a46712b52bfb73941e12f2f8a63c59", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -728,7 +761,7 @@ ] } ], - "sha256": "f0defe45fbcc22b7a6ab73d3961907737ac5b528c362855bb236befa83e5507b" + "sha256": "de90771cb42c28259bb07b52b5b87fc26e8a084c0a8ee2a34ed515fc372f0b9d" }, "ipad-fixtures/dns_server.fls": { "success": true, @@ -801,7 +834,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 84, + "sha256": "84d02c9a311e5cc7d1c45f3da3f30331903e8f8fc6c45e0d8a2500d393a6adb9", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -843,7 +889,31 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "7d56103add4688141350c5901c1c9bb2c9495a054531b71a32cc9f96aed4ef45", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + }, + { + "directory": "www.conf", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -927,7 +997,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 47, + "sha256": "d6fcb4528251a65a261ddefad36602c40aa55574cdb88d711a8955d999f2473a", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -1010,7 +1093,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 68, + "sha256": "2f916dbcb3ff8d29840ee37fe341fd8d323960526e0c891128963217d4039e53", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -1052,7 +1148,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 34, + "sha256": "fdba09cf276c8c66cf4676b2c5b741075268911b5ebac2c384c187a6f7137282", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -1190,7 +1299,7 @@ } ], "documentation": [], - "sha256": "6b40b826ebbd20964e1e4f4c19c3be105d5357704c6966aa875241cabaeec759" + "sha256": "c653639f118af6851ce1bbfe90da6d131a69cd0ddbcce942376528c6f8972231" }, "ipad-fixtures/zwei_rechnernetze_komplett.fls": { "success": true, @@ -1304,7 +1413,51 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 33, + "sha256": "3d801783d135a0e42a61ecbba025a318f92c79945e0ca7ae6ea54698b6f5e917", + "children": [] + } + ] + }, + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "156dfa798344ce7f75e3f40ab416c5e2c994c68a110cec5c8d686c31a403f10b", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + }, + { + "directory": "www.conf", + "children": [ + { + "file": "vhosts", + "type": "text", + "length": 52, + "sha256": "528219400a878871604b1ed891f8926fcf142fbaa329722d1b6fcec159c54241", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -1388,7 +1541,39 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "7d56103add4688141350c5901c1c9bb2c9495a054531b71a32cc9f96aed4ef45", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + }, + { + "directory": "www.conf", + "children": [ + { + "file": "vhosts", + "type": "text", + "length": 50, + "sha256": "49583d26538d3b0a66f4332e97ffeaeee1b91c4199dc44deaaa5f1e40ffc6089", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -1567,7 +1752,7 @@ } ], "documentation": [], - "sha256": "2ed2c7a1240329c741000aa6eec7f8ed721e5448efc957cdc8ab04dd94898f69" + "sha256": "fd7a250c308e2ade4b7aed196c145b4d06742394834bbea966d922002ecafb3e" }, "java-examples/Internet_Complete_with_e-Mail_Webserver_Intranet_Portforwarding_Firewall_DHCP_EN.fls": { "success": true, @@ -1599,7 +1784,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 84, + "sha256": "011c91d5e632db92ddd42ddf351d2741a99be3410f76b6528fbeca029ce90ab5", + "children": [] + } + ] }, "applications": [], "dhcpClient": true, @@ -1682,7 +1875,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 88, + "sha256": "f4692cfecba61e6a4f0c6db1009cf76756d0a0239b1d2ca4ab5bd95693c3d63f", + "children": [] + } + ] }, "applications": [], "dhcpClient": true, @@ -2238,7 +2439,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 214, + "sha256": "8c8286bc4ce20a063fd6bcea621c7e576ec1b55b172c3f33d226be0d34cb32a4", + "children": [] + }, + { + "file": "restored-1770036475063", + "type": "text", + "length": 215, + "sha256": "024f81af0105fc629070ef4c3de21426c0e37cac4a415c1cb3ad01edf726e8ab", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -2280,7 +2501,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 500, + "sha256": "94fc9cc5ea871634a045e3e2c01838796d4623ec4488ea23cff50e949c2929b4", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -2322,7 +2563,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "mailserver", + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 90, + "sha256": "378f8265131d36031047d7918f0e5931c746c52bea39758143f7a42ed16cc379", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -2364,7 +2618,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "mailserver", + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 46, + "sha256": "d860c7be31f9fa05c689eba6f5b527371e3c81de5e54aa93719a09decd44fb93", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -2406,7 +2673,39 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 33, + "sha256": "5f8235d016bcc8024b554ec91df22ec0bebab7f799fc6009bbd34073224c87d4", + "children": [] + } + ] + }, + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 502, + "sha256": "742fcdde965a05cb80624374067cab0560a9196d224d97d5e7ab9bae6c507127", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -2526,7 +2825,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 78, + "sha256": "37acca898f0c5587824be7fe2ea7ca4771976a2a16b90dc262531126d4970437", + "children": [] + } + ] }, "applications": [], "dhcpClient": true, @@ -2568,7 +2875,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 81, + "sha256": "cd35dc5dacfe0681c4ca63b0930d64586328f35313bfd207fe90c9647361de63", + "children": [] + } + ] }, "applications": [], "dhcpClient": true, @@ -3786,7 +4101,7 @@ ] } ], - "sha256": "b6c00e3e81dc32d03c349622e8559993575a00bf5d699f39e19f955963edcbc6" + "sha256": "55c56cb7e5ce455128c180515c6b12a93c2f57256b841521856bb127aa4d5ba0" }, "java-examples/Internet_Komplett_mit_eMail_Webserver_Intranet_Portforwarding_Firewall_DHCP_DE.fls": { "success": true, @@ -3818,7 +4133,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 81, + "sha256": "cd35dc5dacfe0681c4ca63b0930d64586328f35313bfd207fe90c9647361de63", + "children": [] + } + ] }, "applications": [], "dhcpClient": true, @@ -3901,7 +4224,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 85, + "sha256": "584f97b2108104e758f2896f914a8c02f91bedc28dc4d762714596c116c853c5", + "children": [] + } + ] }, "applications": [], "dhcpClient": true, @@ -4457,7 +4788,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 210, + "sha256": "609f4ac4d06ac54a04b080458b83ed7ee2f523807ffa2bf9a64849117c921368", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -4499,7 +4843,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 500, + "sha256": "94fc9cc5ea871634a045e3e2c01838796d4623ec4488ea23cff50e949c2929b4", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -4541,7 +4905,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "mailserver", + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 88, + "sha256": "d5eef2ffc8970f093433912ff9ef74e0b0d166310d9ed56f5f4f8ff3ed7c97e6", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -4583,7 +4960,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "mailserver", + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 45, + "sha256": "63cba5c91ef0bc92aec8c3c5b800397a59f4959dde5a818dde9abd8ab37946b6", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -4625,7 +5015,39 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 33, + "sha256": "5f8235d016bcc8024b554ec91df22ec0bebab7f799fc6009bbd34073224c87d4", + "children": [] + } + ] + }, + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 518, + "sha256": "534e5a8f60e38149d47c863b4b7c143ade68a31744a44e02ed1e770f9e7a7b3f", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -4745,7 +5167,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 75, + "sha256": "6d8585e1f74b612e758505a156694dfb412b842bdc28e8ed3841199a3970682c", + "children": [] + } + ] }, "applications": [], "dhcpClient": true, @@ -4787,7 +5217,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 81, + "sha256": "cd35dc5dacfe0681c4ca63b0930d64586328f35313bfd207fe90c9647361de63", + "children": [] + } + ] }, "applications": [], "dhcpClient": true, @@ -5963,7 +6401,7 @@ ] } ], - "sha256": "4aa181ace877932ab88fbd0a7715f59c21ac00a84aa12ec18d1556b6d91b0e27" + "sha256": "9bf1d655d2c53bb7887271c97b65518c03461d2237575ccc855d0660770eadcd" }, "java-examples/Public_and_Private_Networks_2_EN.fls": { "success": true, @@ -6195,7 +6633,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 538, + "sha256": "1966c377d3651754cfc513da8f425ec0e943e6cc5e8f7112aa06102e21eb0775", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -6237,7 +6695,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 36, + "sha256": "c4f62d9c112a0def5f67e6b803e8a55886a46712b52bfb73941e12f2f8a63c59", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -6694,7 +7165,7 @@ ] } ], - "sha256": "f0defe45fbcc22b7a6ab73d3961907737ac5b528c362855bb236befa83e5507b" + "sha256": "de90771cb42c28259bb07b52b5b87fc26e8a084c0a8ee2a34ed515fc372f0b9d" }, "java-examples/anwendung_komplett.fls": { "success": true, @@ -6921,7 +7392,7 @@ } ], "documentation": [], - "sha256": "c4d7f545e981678988135cf988f87863d385ecc281a607cf5aab97b62f0f6b08" + "sha256": "1d22ad715862ffc950c9323b7372eaeb241455753995be089e8541dd23b72f6b" }, "java-examples/dhcp_test_scenario.fls": { "success": true, @@ -7341,7 +7812,7 @@ } ], "documentation": [], - "sha256": "863f5d67e356ba363db33d31ec818fa3de17a022812644551bf0c9838d90247d" + "sha256": "76d8317e6d352e8686663957f0bbc74f1945b0c419a80ac94992e40590e75e8c" }, "java-examples/dns_server.fls": { "success": true, @@ -7414,7 +7885,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 84, + "sha256": "84d02c9a311e5cc7d1c45f3da3f30331903e8f8fc6c45e0d8a2500d393a6adb9", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -7456,7 +7940,31 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "7d56103add4688141350c5901c1c9bb2c9495a054531b71a32cc9f96aed4ef45", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + }, + { + "directory": "www.conf", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -7540,7 +8048,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 47, + "sha256": "d6fcb4528251a65a261ddefad36602c40aa55574cdb88d711a8955d999f2473a", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -7623,7 +8144,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 68, + "sha256": "2f916dbcb3ff8d29840ee37fe341fd8d323960526e0c891128963217d4039e53", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -7665,7 +8199,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 34, + "sha256": "fdba09cf276c8c66cf4676b2c5b741075268911b5ebac2c384c187a6f7137282", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -7803,7 +8350,7 @@ } ], "documentation": [], - "sha256": "6b40b826ebbd20964e1e4f4c19c3be105d5357704c6966aa875241cabaeec759" + "sha256": "c653639f118af6851ce1bbfe90da6d131a69cd0ddbcce942376528c6f8972231" }, "java-examples/einfaches_rechnernetz_komplett.fls": { "success": true, @@ -8003,7 +8550,65 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 64, + "sha256": "94c7a19c36a5e11af62ffba031840ce9c238231f18f1e999f1a908b3956540aa", + "children": [] + } + ] + }, + { + "directory": "webserver", + "children": [ + { + "file": "800px-Loewe_3.JPG", + "type": "jpg", + "length": 117116, + "sha256": "04d6353a6e750e8af80058fc299ebaaf85a14d740eaa7c19b5db8b42e9000f9d", + "children": [] + }, + { + "file": "index.html", + "type": "html", + "length": 615, + "sha256": "c7682b3a9758588021d73ec7a01769c1a3056b666a252822ecaea18514b6e784", + "children": [] + }, + { + "file": "meine-seite.html", + "type": "html", + "length": 121, + "sha256": "3f9f9a107b72e97f97671f00232db9ab60e96859d6be66553faf329b64136427", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + }, + { + "directory": "www.conf", + "children": [ + { + "file": "vhosts", + "type": "text", + "length": 52, + "sha256": "528219400a878871604b1ed891f8926fcf142fbaa329722d1b6fcec159c54241", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -8500,7 +9105,7 @@ } ], "documentation": [], - "sha256": "b6c7806c4394e91a338416bb8d9547d9f7f5cba0d77edbcf99d0963b00a9cabe" + "sha256": "cdba1260edf112085e1975c8ad76342fbcb59e72bbc3b6bc7efa7bc97235d706" }, "java-examples/email_komplett.fls": { "success": true, @@ -8532,7 +9137,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "7d56103add4688141350c5901c1c9bb2c9495a054531b71a32cc9f96aed4ef45", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -8574,7 +9199,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 82, + "sha256": "db07ea20f140dc28e0c08d4379c3745c527012935d14b006c481c4e66db101b9", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -8616,7 +9249,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 82, + "sha256": "46fda62f5183c745e1ea1fc627511761a3532c6240a70230b8d2b7c022fad134", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -8658,7 +9299,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 82, + "sha256": "a1d89620fc770742f30003283d9db30a2993b0f22eb53eed5bfb9fd38bb5514b", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -8700,7 +9349,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 82, + "sha256": "0884e1ff30882ed87c758e949b05df36a3c38f8af9a2461d449517f143eeb92b", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -8783,7 +9440,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 173, + "sha256": "597ad9f41447233f8bb178a0ade919345ac79dbb599d748abea2564b496193eb", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -8825,7 +9495,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "mailserver", + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 92, + "sha256": "0899cce8e063b7287c0b73b011adf1e02bfab11e834765b3e6d43ebf3a37d478", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -8867,7 +9550,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "mailserver", + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 92, + "sha256": "bb3604ef16c2182b5097e628624b62e95c4872b45ffecba550e983cef5b066ab", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -9022,7 +9718,7 @@ } ], "documentation": [], - "sha256": "cb3e3989a69306a93a28f1fcae310fe3717c59103695a91a76bd5175d05c5461" + "sha256": "b6a77cdbca831776ac8093fd7873f3cbe60e8c1a72384688420b44375a68aeb0" }, "java-examples/internetwork_komplett.fls": { "success": true, @@ -9423,7 +10119,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 33, + "sha256": "6d4d6e43368e520d27734832db0ba83ba078865398d0ee6c57b3345a733b66ba", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -9507,7 +10216,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "7d56103add4688141350c5901c1c9bb2c9495a054531b71a32cc9f96aed4ef45", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -9591,7 +10320,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "7d56103add4688141350c5901c1c9bb2c9495a054531b71a32cc9f96aed4ef45", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -9956,7 +10705,7 @@ } ], "documentation": [], - "sha256": "4fad67697c52751855d232ce12ac5b7d33f3b36a300a9563d66d0deb1499a66e" + "sha256": "8731f9355d9bf2679cf8e393b680e8092be2ef44d85d6787bcb910b864697463" }, "java-examples/man-in-the-middle.fls": { "success": true, @@ -9988,7 +10737,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 71, + "sha256": "2bc178b534af284e8e997b2306c83630e4eca07bf5ffcd110e6de9725c103a4f", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -10030,7 +10787,39 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 144, + "sha256": "15be56da82ced21860c3c859f7d615a2e0e2393ddba8823fb603958ee875cd4e", + "children": [] + } + ] + }, + { + "directory": "mailserver", + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "children": [] + } + ] + }, + { + "file": "konten.txt", + "type": "txt", + "length": 82, + "sha256": "46fda62f5183c745e1ea1fc627511761a3532c6240a70230b8d2b7c022fad134", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -10072,7 +10861,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 63, + "sha256": "e8c73d7f647fad92ef790fe273b4685b376c3ebfab403e66e2982b077eabe1bc", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -10155,7 +10952,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 180, + "sha256": "0978bc12de5a52832ee7b7df9d5aa2bbdcd2e6b506f8a5ea58173dc148b0daf0", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -10197,7 +11007,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "mailserver", + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 216, + "sha256": "96e965caacdef402e4041d55f337e1113f7d59b500658ec2b74ae20a9feca59c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -10239,7 +11062,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "mailserver", + "children": [ + { + "file": "konten.txt", + "type": "txt", + "length": 40, + "sha256": "336d0c1e9a13c917c64d91f072e56891c7685562d7cefcb9d8c548e13bb11b41", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -10424,7 +11260,7 @@ ] } ], - "sha256": "d56bb01b5b7d7125e3c5897b11aede16497e1401e780c4952a8f74f1e2aaae4e" + "sha256": "665f9ae5ec9573f1922248adcfebf663f26f16e25759317dcfb2d629bd157bdc" }, "java-examples/modem_beispiel.fls": { "success": true, @@ -10456,7 +11292,31 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 504, + "sha256": "28c812abf90ecf38cb27490ba231b3ae2b9c9a5311744038a7c248d4ebdbd145", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + }, + { + "directory": "www.conf", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -10593,7 +11453,7 @@ } ], "documentation": [], - "sha256": "eae83c9db80a8289f425c2026bfab2dd3e417fb571a56bdf144a6a1c61c32f4f" + "sha256": "12e37a892c32b55eab37ab683dd18ac0fa6d5361c2a009cc26a4b83b1dca58a4" }, "java-examples/multicast_server.fls": { "success": true, @@ -10667,7 +11527,19 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "peer2peer", + "children": [] + }, + { + "file": "test.txt", + "type": "txt", + "length": 5, + "sha256": "6382c29c5fdcdc94705afc46f1d9b37c21e1663042ae0a3778ade3f5556a9254", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -10820,7 +11692,7 @@ } ], "documentation": [], - "sha256": "2eee88e78a1a50308a6f1a63485e6927c7bd81bf0d96c481fddb1e91ac1c67d3" + "sha256": "dcc87330dead893eaf9801e2faf517d569e763977ea210dfe634811cbf67102b" }, "java-examples/nat.fls": { "success": true, @@ -10927,7 +11799,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 538, + "sha256": "1966c377d3651754cfc513da8f425ec0e943e6cc5e8f7112aa06102e21eb0775", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -11080,7 +11972,7 @@ } ], "documentation": [], - "sha256": "4ead19012d1bb245f5649fca807561ba8974544e720e6cb486c4b1fd9c6c82ea" + "sha256": "f3cbdf99f552c567275cd08e86c73049eeadce7e21aca0f9709e31cac09be21f" }, "java-examples/nat_with_mapped_port.fls": { "success": true, @@ -11440,7 +12332,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 69, + "sha256": "39238be537759ecfb24fc46723a27fe5e7136996792df18c6daa647929017529", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -11524,7 +12429,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "7d56103add4688141350c5901c1c9bb2c9495a054531b71a32cc9f96aed4ef45", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -11566,7 +12491,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 213, + "sha256": "36a1dbc59a58840d2308fbecefa3646902a143938c36892628bd12d2e5645928", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -11608,7 +12553,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 393, + "sha256": "a914b401e99f75d1d9becb3e290e92f0a77b6d7bff44b5f2f4039bafc18d974b", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -12336,7 +13301,7 @@ ] } ], - "sha256": "4d36314f4f69837268d5b0efc27f0730345fbbffc85bb977721583992c3991ba" + "sha256": "bd7e9369123196c4f9db9dc1e44d6ffb02ca85b0c0d5afab4b3b599d1ee95cea" }, "java-examples/private_and_public_network.fls": { "success": true, @@ -12451,7 +13416,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "7d56103add4688141350c5901c1c9bb2c9495a054531b71a32cc9f96aed4ef45", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": true, @@ -12609,7 +13594,39 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 34, + "sha256": "83b965bcc5eef638772b9cb7e0db056797e9a1201da798dd100151a0413a0043", + "children": [] + } + ] + }, + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 429, + "sha256": "225419200943fbe708a6680b66570b3fe609cbbd4e05f1c2faa0d96d9e98b980", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -12861,7 +13878,7 @@ ] } ], - "sha256": "bd7f5a5f96982f8db3d7730f459407b0638ab9d148803e46b10a3fee706e30d7" + "sha256": "e1389dd78c357dda1a60f59d226517574f5a12057da1b45e0d6424cbb3a812ea" }, "java-examples/private_und_public_networks_mit_provider_DE.fls": { "success": true, @@ -12976,7 +13993,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "7d56103add4688141350c5901c1c9bb2c9495a054531b71a32cc9f96aed4ef45", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": true, @@ -13134,7 +14171,39 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 31, + "sha256": "91576d35c0857efcc36c4af0ed40a90cd231f3a905ee4c411ed929f0ed2e0bae", + "children": [] + } + ] + }, + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 445, + "sha256": "7436e0ed47fdaaefc06b8ee991cae129e09231007a302f8af83eb9ee14182b3f", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -13259,7 +14328,27 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 540, + "sha256": "c31760d4ecb049671d80666845f315f35f44bcb94ecc8905cdad5976aa95047c", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -13301,7 +14390,20 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 36, + "sha256": "2ada78d7cdaf174e4657b0ab6fe37b912287577c07d65ec43db3ffc4768d1f4b", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -13767,7 +14869,7 @@ ] } ], - "sha256": "bdb684d8f4653604e6a40d7783b7235743a340605c55ef0a886967e40fc52cde" + "sha256": "815faa519722a0c93350aac97466a3dedd2727778039d0bb2425051cc22ca0a7" }, "java-examples/webserver.fls": { "success": true, @@ -13799,7 +14901,45 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "Ahornblatt.jpg", + "type": "jpg", + "length": 33069, + "sha256": "bf38aff1cafd1bf38df6cf47c25448b3c25ee0a9ffa35f28e98f327902c111ac", + "children": [] + }, + { + "file": "beispiel.html", + "type": "html", + "length": 149, + "sha256": "8ba3a312c296ee02d5dfcb69a931d95582ac5b5d67884d25230fc27f1e16208a", + "children": [] + }, + { + "file": "index.html", + "type": "html", + "length": 443, + "sha256": "61e19fe97d2681ae0ec7c8a53ccda98577a0716b92af0c7f5e8d9bbe2bedffcd", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + }, + { + "directory": "www.conf", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -13841,7 +14981,15 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "file": "test", + "type": "text", + "length": 5, + "sha256": "6382c29c5fdcdc94705afc46f1d9b37c21e1663042ae0a3778ade3f5556a9254", + "children": [] + } + ] }, "applications": [], "dhcpClient": false, @@ -13935,7 +15083,7 @@ } ], "documentation": [], - "sha256": "ba5908aa925435ca6c7bb68939a33c4a0ce702ace4a96a11adb579406f1ae4b7" + "sha256": "8f2103782c99f8718953dc56c73af72fde9024654395d9c378cb322431a3c1f9" }, "java-examples/zwei_rechnernetze_komplett.fls": { "success": true, @@ -14049,7 +15197,51 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 33, + "sha256": "3d801783d135a0e42a61ecbba025a318f92c79945e0ca7ae6ea54698b6f5e917", + "children": [] + } + ] + }, + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "156dfa798344ce7f75e3f40ab416c5e2c994c68a110cec5c8d686c31a403f10b", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + }, + { + "directory": "www.conf", + "children": [ + { + "file": "vhosts", + "type": "text", + "length": 52, + "sha256": "528219400a878871604b1ed891f8926fcf142fbaa329722d1b6fcec159c54241", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -14133,7 +15325,39 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "webserver", + "children": [ + { + "file": "index.html", + "type": "html", + "length": 557, + "sha256": "7d56103add4688141350c5901c1c9bb2c9495a054531b71a32cc9f96aed4ef45", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + }, + { + "directory": "www.conf", + "children": [ + { + "file": "vhosts", + "type": "text", + "length": 50, + "sha256": "49583d26538d3b0a66f4332e97ffeaeee1b91c4199dc44deaaa5f1e40ffc6089", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -14312,7 +15536,7 @@ } ], "documentation": [], - "sha256": "2ed2c7a1240329c741000aa6eec7f8ed721e5448efc957cdc8ab04dd94898f69" + "sha256": "fd7a250c308e2ade4b7aed196c145b4d06742394834bbea966d922002ecafb3e" }, "java-tests/einfaches_rechnernetz_komplett.fls": { "success": true, @@ -14512,7 +15736,65 @@ "manualRoutes": [], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "dns", + "children": [ + { + "file": "hosts", + "type": "text", + "length": 64, + "sha256": "94c7a19c36a5e11af62ffba031840ce9c238231f18f1e999f1a908b3956540aa", + "children": [] + } + ] + }, + { + "directory": "webserver", + "children": [ + { + "file": "800px-Loewe_3.JPG", + "type": "jpg", + "length": 117116, + "sha256": "04d6353a6e750e8af80058fc299ebaaf85a14d740eaa7c19b5db8b42e9000f9d", + "children": [] + }, + { + "file": "index.html", + "type": "html", + "length": 615, + "sha256": "c7682b3a9758588021d73ec7a01769c1a3056b666a252822ecaea18514b6e784", + "children": [] + }, + { + "file": "meine-seite.html", + "type": "html", + "length": 121, + "sha256": "3f9f9a107b72e97f97671f00232db9ab60e96859d6be66553faf329b64136427", + "children": [] + }, + { + "file": "splashscreen-mini.png", + "type": "png", + "length": 8397, + "sha256": "c876b828d7258ddfce4aa01ecaa69d4685d238bccf6a94842972165bd1a0107c", + "children": [] + } + ] + }, + { + "directory": "www.conf", + "children": [ + { + "file": "vhosts", + "type": "text", + "length": 52, + "sha256": "528219400a878871604b1ed891f8926fcf142fbaa329722d1b6fcec159c54241", + "children": [] + } + ] + } + ] }, "applications": [], "dhcpClient": false, @@ -15009,7 +16291,7 @@ } ], "documentation": [], - "sha256": "b6c7806c4394e91a338416bb8d9547d9f7f5cba0d77edbcf99d0963b00a9cabe" + "sha256": "cdba1260edf112085e1975c8ad76342fbcb59e72bbc3b6bc7efa7bc97235d706" }, "java-tests/empty.fls": { "success": true, @@ -15055,7 +16337,67 @@ ], "fileSystem": { "directory": "root", - "children": [] + "children": [ + { + "directory": "docs", + "children": [ + { + "directory": "deep", + "children": [ + { + "file": "notes", + "type": "text", + "length": 12, + "sha256": "5344287ad0cd517b62386e7698af494ea129fbf7fa96a5905ca0011d4b972a40", + "children": [] + } + ] + }, + { + "file": "readme.txt", + "type": "txt", + "length": 16, + "sha256": "926b191ec3a9aaaf0bf36487aa8b728b2ba8fc93fa27ae3f6ba999f19d6a9ef7", + "children": [] + } + ] + }, + { + "file": "bild.png", + "type": "png", + "length": 77, + "sha256": "57af5eced0df44fdb2e7ab8313ebc1488ca3a9dd995a83f19ea04e05b6e95d8e", + "children": [] + }, + { + "file": "daten.bin", + "type": "zip", + "length": 81, + "sha256": "e28d2a92bdde4fd7a0cc04c4451025dc53250493a4796a1b0bd7b5995f2dd22e", + "children": [] + }, + { + "file": "foto.jpg", + "type": "jpg", + "length": 137, + "sha256": "067cf19657ad73978e20da6cdbdddff3f1815736026938b652cbe553c2ee4ab0", + "children": [] + }, + { + "file": "index.HTML", + "type": "html", + "length": 27, + "sha256": "ac4b4251cfd56976b9eeb547912ff7e709d56bdb1d1caa0ade73dff843dff318", + "children": [] + }, + { + "file": "roh", + "type": "", + "length": 4, + "sha256": "09006ade084f4668397a1e9e1d7763c683af7957e3a62e04e039425aaaeff7aa", + "children": [] + } + ] }, "applications": [], "dhcpClient": true, @@ -15506,6 +16848,6 @@ ] } ], - "sha256": "874f2f33f2f5beb5c301fc58f61deacc666fe65fb7289cd52abae84bd7dfb8d4" + "sha256": "16986c17c7720dffb33a07d64a316de8ce9d9fca08bec1f3ebcfe68de8cb0370" } } diff --git a/tools/java-oracle/README.md b/tools/java-oracle/README.md index e700bd2..56cda43 100644 --- a/tools/java-oracle/README.md +++ b/tools/java-oracle/README.md @@ -98,6 +98,15 @@ passt. Je Kabel steht auch `panel`: die Geräte, zwischen denen die Oberfläche es zeichnet (`JCablePanel.ziel1/ziel2`) — im Korpus bei allen 183 Kabeln die Geräte, die es verbindet. +## Wie Java Base64 schreibt + +`Base64Cases` ruft Filius' eigenes `Base64.encodeBytes` (iHarder 2.x) auf +Byte-Folgen von 19 Längen rund um die Zeilenlänge auf +(`packages/fls/test/base64.java.json`). Befund: nach je 76 Zeichen ein +`\n`, auch nach der letzten vollen Zeile, dann der aufgefüllte Rest ohne +Umbruch. So stehen Bilder in den Dateien, auch die, die Javas Konstruktoren +selbst anlegen. + ## Was Java aus unserem XML macht `BeansDump` liest jede `*.xml` unter `packages/fls/test/beans/` mit diff --git a/tools/java-oracle/run.sh b/tools/java-oracle/run.sh index 15fa3cf..874e663 100755 --- a/tools/java-oracle/run.sh +++ b/tools/java-oracle/run.sh @@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then fi classpath="$upstream/target/filius.jar:$upstream/target/lib/*" mkdir -p "$here/.build" -javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" +javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" # FILIUS writes its settings below $HOME; keep them out of the real one. mkdir -p "$here/.build/home" # Removed first: a crashed run must not leave the previous result standing. @@ -67,6 +67,12 @@ rm -f "$ipcheck" HOME="$here/.build/home" java -cp "$classpath:$here/.build" IpCheckCases "$cases" "$ipcheck" 2>&1 | grep -v 'logback\|^$' || true test -s "$ipcheck" +# Base64.encodeBytes around its line length (packages/fls/src/exportProject.ts, javaBase64). +base64="$root/packages/fls/test/base64.java.json" +rm -f "$base64" +HOME="$here/.build/home" java -cp "$classpath:$here/.build" Base64Cases "$base64" 0 1 2 3 55 56 57 58 59 113 114 115 116 171 172 2000 2001 2002 2052 2>&1 | grep -v 'logback\|^$' || true +test -s "$base64" + # DNSServer's own reading of /dns/hosts on hand-picked files (packages/fls/src/dnsHosts.ts). cases="$root/packages/fls/test/dns-hosts.cases.txt" dnshosts="$root/packages/fls/test/dns-hosts.java.json" diff --git a/tools/java-oracle/src/Base64Cases.java b/tools/java-oracle/src/Base64Cases.java new file mode 100644 index 0000000..ff13e5d --- /dev/null +++ b/tools/java-oracle/src/Base64Cases.java @@ -0,0 +1,32 @@ +// Runs FILIUS's own Base64.encodeBytes on byte arrays of the given lengths +// and writes what Java returns — the reference for javaBase64 in +// packages/fls/src/exportProject.ts. The bytes of length n are +// (index * 37 + 11) mod 256, so both sides make them alike. +// +// Usage: java -cp filius.jar:lib/*:classes Base64Cases ... + +import filius.rahmenprogramm.Base64; + +import java.io.FileOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Base64Cases { + + public static void main(String[] args) throws Exception { + List result = new ArrayList<>(); + for (int argument = 1; argument < args.length; argument++) { + int length = Integer.parseInt(args[argument]); + byte[] bytes = new byte[length]; + for (int index = 0; index < length; index++) bytes[index] = (byte) ((index * 37 + 11) % 256); + result.add(Arrays.asList(length, Base64.encodeBytes(bytes))); + } + try (FileOutputStream out = new FileOutputStream(args[0])) { + out.write((FlsOracle.Json.write(result, "") + "\n").getBytes(StandardCharsets.UTF_8)); + } + System.out.println("base64 cases: " + result.size()); + System.exit(0); + } +}