Beim Wiederholungsbesuch die erste Frage des Kunden ("da ist doch nichts
dazugekommen, oder?") und die einzige, die ein einzelnes Protokoll nicht
beantworten kann. Das Werkzeug holt das juengste fruehere Protokoll desselben
Kunden vom Server und vergleicht die Geraetelisten.
KEINE Wiedererkennung ueber MAC: /proc/net/arp ist ab Android 10 gesperrt, im
ausgelieferten Protokoll ND2026-0015 hatten ALLE 68 Geraete keine MAC. Ein
MAC-Diff haette dort bei jedem Besuch "alles neu, alles verschwunden"
gemeldet - schlimmer als gar keine Aussage, weil es im Kundendokument nach
einem umgebauten Netz aussieht. Stattdessen eine Kaskade: MAC (falls
ausnahmsweise da), mDNS-Name, NetBIOS-Name, Hostname, IP+Portmuster. Jede
Zeile nennt, worueber sie zugeordnet wurde - "erkannt ueber IP und Portmuster"
ist eine schwaechere Aussage als "ueber Netzwerkname".
Im Emulator gegen echte Serverdaten geprueft. Dabei ein Fehler gefunden und
behoben: dieselbe Adresse stand gleichzeitig unter "neu" und "nicht mehr
erreichbar", weil sich nur das Portmuster geaendert hatte. Formal richtig, im
Kundendokument aber Unsinn - liest sich wie ein kaputtes Werkzeug. Solche
Faelle haben jetzt eine eigene Kategorie "Gleiche Adresse, andere offene
Ports", die sogar der interessantere Befund ist.
Bewertung bewusst ohne Rot: ein neues Geraet ist erst einmal eine Tatsache,
womoeglich hat der Techniker es selbst angeschlossen. Gelb nur, wenn etwas
fehlt oder die Zuordnung wackelt (ab 50 % schwache Zuordnung).
tools/pruefe-geraetediff.mjs: 20 Faelle, alle mit Geraeten OHNE MAC - so wie
sie beim Kunden ankommen. Der Browser-Mock gibt jedem Geraet eine MAC, dort
saehe auch ein naiver MAC-Diff perfekt aus.
Ausserdem: Server-Adresse wird jetzt geprueft. Eine http://-Adresse wird
abgewiesen, solange die App selbst ueber https laeuft - sie wuerde sonst stumm
scheitern ("Keine Verbindung zum Server", wahrer Grund nur im logcat). Ein
Debug-Build auf http://localhost darf HTTP dagegen, sonst waere der Testbau
unbenutzbar. tools/pruefe-serveradresse.mjs: 11 Faelle.
Neu in api.ts: getProtocol() laedt ein Protokoll samt Geraeten vom Server -
nur lesend, das fruehere Protokoll wird nie ueberschrieben.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
113 lines
5.7 KiB
JavaScript
113 lines
5.7 KiB
JavaScript
/**
|
|
* Prueft den Geraete-Vergleich zweier Protokolle.
|
|
*
|
|
* ALLE Faelle arbeiten mit Geraeten OHNE MAC - genau so kommen sie beim Kunden
|
|
* an (/proc/net/arp ist ab Android 10 gesperrt; im ausgelieferten Protokoll
|
|
* ND2026-0015 waren bei allen 68 Geraeten MAC, Hostname und Hersteller leer).
|
|
* Der Browser-Mock dagegen gibt jedem Geraet eine MAC - dort saehe auch ein
|
|
* naiver MAC-Diff perfekt aus und wuerde nichts beweisen.
|
|
*
|
|
* Start: node tools/pruefe-geraetediff.mjs
|
|
*/
|
|
const { vergleicheGeraete, bewerteDiff, geraeteSchluessel } =
|
|
await import('../src/lib/tools/netzwerk/geraete-diff.ts');
|
|
|
|
// Geraete wie sie real ankommen: keine MAC, oft kein Name.
|
|
const kamera = { ip: '192.168.1.51', mdnsName: 'IPC-Hof', openPorts: [80, 554] };
|
|
const drucker = { ip: '192.168.1.20', mdnsName: 'Brother HL-L2350DW', openPorts: [80, 9100, 515] };
|
|
const pc = { ip: '192.168.1.30', netbiosName: 'BUERO-PC', openPorts: [139, 445] };
|
|
const nas = { ip: '192.168.1.10', hostname: 'nas.fritz.box', openPorts: [80, 445, 5000] };
|
|
const namenlos= { ip: '192.168.1.77', openPorts: [502] }; // Wechselrichter, Modbus
|
|
const router = { ip: '192.168.1.1', hostname: 'fritz.box', openPorts: [80, 443, 53] };
|
|
|
|
let fehler = 0;
|
|
function pruefe(name, ist, soll) {
|
|
const ok = JSON.stringify(ist) === JSON.stringify(soll);
|
|
if (!ok) fehler++;
|
|
console.log(`${ok ? 'OK ' : 'FEHL'} ${name}`);
|
|
if (!ok) console.log(` ist=${JSON.stringify(ist)} soll=${JSON.stringify(soll)}`);
|
|
}
|
|
|
|
// 1. Identisch -> keine Veraenderung
|
|
let d = vergleicheGeraete([router, nas, kamera], [router, nas, kamera]);
|
|
pruefe('Unveraendertes Netz -> 0 neu, 0 weg, 3 geblieben',
|
|
[d.neu.length, d.verschwunden.length, d.geblieben.length], [0, 0, 3]);
|
|
|
|
// 2. DER KERNFALL: dieselben Geraete OHNE MAC, aber mit neuen DHCP-Adressen.
|
|
// Ein MAC-Diff haette hier "alles neu, alles verschwunden" gemeldet.
|
|
const kameraNeueIp = { ...kamera, ip: '192.168.1.99' };
|
|
const druckerNeueIp = { ...drucker, ip: '192.168.1.98' };
|
|
d = vergleicheGeraete([kamera, drucker], [kameraNeueIp, druckerNeueIp]);
|
|
pruefe('Beide Geraete mit neuer IP -> 0 neu, 0 weg, 2 mit IP-Wechsel',
|
|
[d.neu.length, d.verschwunden.length, d.ipGewechselt.length], [0, 0, 2]);
|
|
pruefe('IP-Wechsel nennt die fruehere Adresse',
|
|
d.ipGewechselt.map((e) => e.frühereIp).sort(), ['192.168.1.20', '192.168.1.51']);
|
|
|
|
// 3. Echtes neues Geraet
|
|
d = vergleicheGeraete([router, nas], [router, nas, kamera]);
|
|
pruefe('Ein Geraet dazugekommen', [d.neu.length, d.neu[0]?.name], [1, 'IPC-Hof']);
|
|
|
|
// 4. Geraet verschwunden
|
|
d = vergleicheGeraete([router, nas, kamera], [router, nas]);
|
|
pruefe('Ein Geraet verschwunden', [d.verschwunden.length, d.verschwunden[0]?.name], [1, 'IPC-Hof']);
|
|
|
|
// 5. Namenloses Geraet: gleiche IP + gleiche Ports -> wiedererkannt
|
|
d = vergleicheGeraete([namenlos], [{ ...namenlos }]);
|
|
pruefe('Namenlos, IP+Ports gleich -> wiedererkannt', [d.geblieben.length, d.neu.length], [1, 0]);
|
|
|
|
// 6. Namenloses Geraet mit neuer IP -> NICHT zuordenbar, ehrlich als neu+weg
|
|
d = vergleicheGeraete([namenlos], [{ ...namenlos, ip: '192.168.1.78' }]);
|
|
pruefe('Namenlos mit neuer IP -> gilt als neu UND verschwunden (Grenze des Verfahrens)',
|
|
[d.neu.length, d.verschwunden.length], [1, 1]);
|
|
|
|
// 6b. DER FALL AUS DEM ERSTEN PDF: gleiche IP, anderes Portmuster.
|
|
// Darf NICHT gleichzeitig als neu und verschwunden erscheinen.
|
|
d = vergleicheGeraete(
|
|
[{ ip: '10.0.2.2', openPorts: [80] }],
|
|
[{ ip: '10.0.2.2', openPorts: [80, 443] }],
|
|
);
|
|
pruefe('Gleiche IP, andere Ports -> eigene Kategorie statt "neu UND weg"',
|
|
[d.neu.length, d.verschwunden.length, d.portsGeaendert.length], [0, 0, 1]);
|
|
|
|
// 7. Erkennungsart wird korrekt benannt
|
|
pruefe('Kaskade: mDNS schlaegt Hostname',
|
|
geraeteSchluessel({ ip: '1.2.3.4', mdnsName: 'A', hostname: 'B' }).erkennung, 'mdns');
|
|
pruefe('Kaskade: NetBIOS vor Hostname',
|
|
geraeteSchluessel({ ip: '1.2.3.4', netbiosName: 'A', hostname: 'B' }).erkennung, 'netbios');
|
|
pruefe('Kaskade: ohne alles -> ip+ports',
|
|
geraeteSchluessel({ ip: '1.2.3.4', openPorts: [80] }).erkennung, 'ip+ports');
|
|
pruefe('Kaskade: MAC schlaegt alles (falls ausnahmsweise da)',
|
|
geraeteSchluessel({ ip: '1.2.3.4', mac: 'AA:BB:CC:11:22:33', mdnsName: 'A' }).erkennung, 'mac');
|
|
|
|
// 8. Schreibweise: Gross/Klein und .local duerfen kein Unterschied sein
|
|
d = vergleicheGeraete(
|
|
[{ ip: '10.0.0.5', mdnsName: 'Drucker.local' }],
|
|
[{ ip: '10.0.0.5', mdnsName: 'drucker' }],
|
|
);
|
|
pruefe('mDNS "Drucker.local" == "drucker"', [d.geblieben.length, d.neu.length], [1, 0]);
|
|
|
|
// 9. Portmuster-Reihenfolge darf egal sein
|
|
d = vergleicheGeraete(
|
|
[{ ip: '10.0.0.9', openPorts: [443, 80] }],
|
|
[{ ip: '10.0.0.9', openPorts: [80, 443] }],
|
|
);
|
|
pruefe('Portreihenfolge egal', [d.geblieben.length, d.neu.length], [1, 0]);
|
|
|
|
// 10. Bewertung
|
|
let b = bewerteDiff(vergleicheGeraete([router, nas], [router, nas]), 2);
|
|
pruefe('Keine Veraenderung -> GRUEN', b.status, 0);
|
|
b = bewerteDiff(vergleicheGeraete([router, nas, kamera], [router, nas]), 3);
|
|
pruefe('Geraet fehlt -> GELB mit Einschraenkung', [b.status, /Standby/.test(b.hinweis || '')], [1, true]);
|
|
b = bewerteDiff(vergleicheGeraete([], []), 0);
|
|
pruefe('Kein Vorgaengerprotokoll -> NICHT MESSBAR', b.status, 3);
|
|
b = bewerteDiff(vergleicheGeraete([namenlos], [namenlos]), 1);
|
|
pruefe('Nur schwache Zuordnung -> GELB mit Warnung', [b.status, /IP und Portmuster/.test(b.hinweis || '')], [1, true]);
|
|
b = bewerteDiff(vergleicheGeraete([router, nas], [router, nas, kamera]), 2);
|
|
pruefe('Nur neue Geraete -> GRUEN (neu ist kein Fehler)', b.status, 0);
|
|
b = bewerteDiff(vergleicheGeraete(
|
|
[{ ip: '10.0.2.2', openPorts: [80] }],
|
|
[{ ip: '10.0.2.2', openPorts: [80, 443] }]), 1);
|
|
pruefe('Portwechsel wird im Label genannt', /andere.*Ports/.test(b.label), true);
|
|
|
|
console.log(fehler === 0 ? 'ALLE FAELLE KORREKT' : `${fehler} FEHLER`);
|
|
process.exit(fehler === 0 ? 0 : 1);
|