feat(fls): write the file systems of a .fls, loaded by Java as the original
topologyWriter.ts adds each node's file tree to the root its constructor makes, as XMLEncoder does (arbeitsVerzeichnis, files as new Datei(name, typ, inhalt)); a root of its own is set anew. The reader now reads the files of routers and home routers too: their constructor makes files from templates (the web interface), and the file adds to them (a web server's www.conf). systemSoftware.ts keeps what the file adds, and templateFiles says whether the constructor's files stay in front (false when the file sets a root of its own). The topology test finds our part at the end of Java's tree and the template in front, the same for every router of a class across the corpus. Checked by Java against Java: every corpus file, read and written again, loads with the same file systems as the original. The synthetic topology adds a nested tree with a renamed root, a nameless directory, a file without type, content with CR, CRLF and beyond-BMP characters, router files after the template and a home router with a root of its own. 9 mutation probes, each loaded by Java, were all caught. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// Every corpus file, read and written again: Java FILIUS loads the written
|
||||
// file (tools/java-oracle, written.java.json) to the same topology as the
|
||||
// original (corpus.java.json) — devices, names, positions, interfaces,
|
||||
// system software (without files and applications), cables and
|
||||
// system software and files (without applications), cables and
|
||||
// documentation. Java against Java: no mapping of this package stands
|
||||
// between the two.
|
||||
//
|
||||
@@ -43,9 +43,9 @@ type JavaFile = { success: boolean; nodes: JavaNode[]; cables: unknown[]; docume
|
||||
const original = JSON.parse(readFileSync(join(corpusRoot, 'corpus.java.json'), 'utf8')) as Record<string, JavaFile>;
|
||||
const written = JSON.parse(readFileSync(join(here, 'written.java.json'), 'utf8')) as Record<string, JavaFile>;
|
||||
|
||||
/** The system software without its files and applications, which are later layers. */
|
||||
/** The system software without its applications, which are a later layer. */
|
||||
function systemOf(system: Record<string, unknown>) {
|
||||
const { fileSystem: _files, applications: _applications, ...settings } = system;
|
||||
const { applications: _applications, ...settings } = system;
|
||||
return settings;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,9 @@ describe('the topology written again, as Java loads it', () => {
|
||||
}
|
||||
});
|
||||
|
||||
/** Text a file's content may hold: markup, CR and CRLF, a tab, beyond the BMP. */
|
||||
const FILE_CONTENT = '<p>a & b</p>\r\nline\rend\ttab Grüße 😀\n';
|
||||
|
||||
/**
|
||||
* What the corpus does not have: a name set to null, useMACAsName, fewer
|
||||
* ports than the constructor makes, more interfaces; a PC's SSID, routes and
|
||||
@@ -120,6 +123,21 @@ const SYNTHETIC: TopologyToWrite = {
|
||||
useOwnSettings: true,
|
||||
staticAssignedAddresses: [{ mac: 'AA:BB:CC:DD:EE:77', ip: '10.0.0.77' }],
|
||||
},
|
||||
fileSystem: {
|
||||
kind: 'directory',
|
||||
name: '/',
|
||||
children: [
|
||||
{
|
||||
kind: 'directory',
|
||||
name: 'docs',
|
||||
children: [
|
||||
{ kind: 'file', name: 'a.txt', type: 'text', content: FILE_CONTENT, children: [] },
|
||||
{ kind: 'directory', name: null, children: [] },
|
||||
],
|
||||
},
|
||||
{ kind: 'file', name: 'untyped', type: null, content: null, children: [] },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -150,6 +168,8 @@ const SYNTHETIC: TopologyToWrite = {
|
||||
portCount: 3,
|
||||
system: {
|
||||
ripEnabled: true,
|
||||
fileSystem: { kind: 'directory', name: 'root', children: [{ kind: 'directory', name: 'www.conf', children: [] }] },
|
||||
templateFiles: true,
|
||||
firewall: {
|
||||
activated: true,
|
||||
defaultPolicy: 1,
|
||||
@@ -176,6 +196,26 @@ const SYNTHETIC: TopologyToWrite = {
|
||||
portCount: 1,
|
||||
system: { modem: { mode: 1, port: 4242, ipAddress: '192.168.1.5' } },
|
||||
},
|
||||
{
|
||||
javaClass: 'Gateway',
|
||||
name: 'G',
|
||||
x: 400,
|
||||
y: 20,
|
||||
useIpAsName: false,
|
||||
useMacAsName: false,
|
||||
interfaces: ['4', '5'].map((octet) => ({
|
||||
ip: `10.0.${octet}.1`,
|
||||
mask: '255.255.255.0',
|
||||
mac: `AA:BB:CC:DD:EE:1${octet}`,
|
||||
gateway: '',
|
||||
dns: '',
|
||||
})),
|
||||
portCount: 2,
|
||||
system: {
|
||||
fileSystem: { kind: 'directory', name: 'own', children: [{ kind: 'file', name: 'x', type: 'text', content: 'y', children: [] }] },
|
||||
templateFiles: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
links: [
|
||||
{ ends: [{ node: 0, port: 0 }, { node: 1, port: 2 }], wireless: false, panel: [0, 1] },
|
||||
@@ -195,6 +235,28 @@ const JAVA_DHCP_DEFAULTS = {
|
||||
staticAssignedAddresses: [],
|
||||
};
|
||||
|
||||
/** A file as the oracle describes it: its content as UTF-16 length and SHA-256 of its UTF-8 bytes. */
|
||||
function javaFile(name: string, type: string | null, content: string | null) {
|
||||
return {
|
||||
file: name,
|
||||
type,
|
||||
length: content === null ? null : content.length,
|
||||
sha256: content === null ? null : createHash('sha256').update(content, 'utf8').digest('hex'),
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
|
||||
/** The files a router's constructor makes, as Java loaded them from the corpus. */
|
||||
function routerTemplate(): unknown[] {
|
||||
const router = Object.values(original)
|
||||
.flatMap((file) => file.nodes ?? [])
|
||||
.find((node) => node.class === 'Vermittlungsrechner');
|
||||
return ((router!.system.fileSystem as { children: unknown[] }).children).slice(0, 1);
|
||||
}
|
||||
|
||||
/** A router's firewall as its constructor leaves it. */
|
||||
const JAVA_ROUTER_FIREWALL = { activated: false, defaultPolicy: 0, dropICMP: false, filterSYNSegmentsOnly: true, filterUdp: true, rules: [] };
|
||||
|
||||
describe('a topology beyond the corpus, as Java loads it', () => {
|
||||
it('keeps what the corpus never sets', () => {
|
||||
const name = 'synthetic/synthetic.fls';
|
||||
@@ -231,6 +293,13 @@ describe('a topology beyond the corpus, as Java loads it', () => {
|
||||
ownSettings: true,
|
||||
staticAssignedAddresses: ['AA:BB:CC:DD:EE:77 10.0.0.77'],
|
||||
},
|
||||
fileSystem: {
|
||||
directory: '/',
|
||||
children: [
|
||||
{ directory: 'docs', children: [javaFile('a.txt', 'text', FILE_CONTENT), { directory: null, children: [] }] },
|
||||
javaFile('untyped', null, null),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -251,6 +320,7 @@ describe('a topology beyond the corpus, as Java loads it', () => {
|
||||
ports: ['1', '2', '3'].map(plainInterface),
|
||||
system: {
|
||||
ripEnabled: true,
|
||||
fileSystem: { directory: 'root', children: [...routerTemplate(), { directory: 'www.conf', children: [] }] },
|
||||
manualRoutes: [],
|
||||
dhcpClient: false,
|
||||
dhcpServer: JAVA_DHCP_DEFAULTS,
|
||||
@@ -278,6 +348,22 @@ describe('a topology beyond the corpus, as Java loads it', () => {
|
||||
ports: [{}],
|
||||
system: { modem: { mode: 1, port: 4242, ipAdresse: '192.168.1.5' } },
|
||||
},
|
||||
{
|
||||
class: 'Gateway',
|
||||
name: 'G',
|
||||
position: [400, 20],
|
||||
useIpAsName: undefined,
|
||||
useMacAsName: undefined,
|
||||
ports: ['4', '5'].map(plainInterface),
|
||||
system: {
|
||||
fileSystem: { directory: 'own', children: [javaFile('x', 'text', 'y')] },
|
||||
manualRoutes: [],
|
||||
dhcpClient: false,
|
||||
dhcpServer: JAVA_DHCP_DEFAULTS,
|
||||
staticNAT: [],
|
||||
firewall: JAVA_ROUTER_FIREWALL,
|
||||
},
|
||||
},
|
||||
],
|
||||
cables: [
|
||||
{ ends: [[0, 0], [1, 2]], wireless: false, panel: [0, 1] },
|
||||
|
||||
Reference in New Issue
Block a user