118 lines
5.3 KiB
JavaScript
118 lines
5.3 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const { EventEmitter } = require('events');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const test = require('node:test');
|
|
const { createPs5KlogService, cleanLine, severityFor, PS5_KLOG_RUNTIME } = require('../../src/services/ps5-klog-service');
|
|
|
|
class FakeSocket extends EventEmitter {
|
|
constructor() { super(); this.writeCalls = 0; this.destroyed = false; }
|
|
write() { this.writeCalls += 1; }
|
|
destroy() { this.destroyed = true; }
|
|
unref() {}
|
|
}
|
|
|
|
function fixture() {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-klog-'));
|
|
const settings = { get(key, fallback) { return key === 'ftp.host' ? '192.0.2.5' : key === 'klog.port' ? 3232 : fallback; } };
|
|
const socket = new FakeSocket();
|
|
const audited = [];
|
|
const service = createPs5KlogService({
|
|
settings,
|
|
paths: { logs: root },
|
|
logger: { audit: (event, details) => audited.push({ event, details }) },
|
|
socketFactory: options => { socket.options = options; return socket; },
|
|
});
|
|
return { root, socket, service, audited };
|
|
}
|
|
|
|
test('passive klog capture never writes to the PS5 and redacts stored lines', () => {
|
|
const f = fixture();
|
|
try {
|
|
const started = f.service.start({ durationSeconds: 30, byteLimit: 128 * 1024 });
|
|
assert.equal(started.zeroWriteContract, true);
|
|
assert.deepEqual(f.socket.options, { host: '192.0.2.5', port: 9081 });
|
|
assert.equal(started.configurationDrift, true);
|
|
assert.equal(started.runtime, PS5_KLOG_RUNTIME);
|
|
f.socket.emit('connect');
|
|
f.socket.emit('data', Buffer.from('kernel ready\nERROR password=hunter2 token:abc123\n'));
|
|
const stopped = f.service.stop('manual');
|
|
assert.equal(stopped.running, false);
|
|
assert.equal(stopped.lines, 2);
|
|
assert.equal(f.socket.writeCalls, 0);
|
|
const result = f.service.query({ severity: 'error', search: 'password' });
|
|
assert.equal(result.entries.length, 1);
|
|
assert.match(result.entries[0].line, /password=\[REDACTED\]/i);
|
|
assert.doesNotMatch(fs.readFileSync(f.service.file, 'utf8'), /hunter2|abc123/);
|
|
assert.equal(JSON.parse(f.service.exportLogs().toString('utf8')).status.payloadLaunchAttempted, false);
|
|
} finally { f.service.stop(); fs.rmSync(f.root, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('klog validation bounds captures and classifies console output conservatively', () => {
|
|
assert.equal(severityFor('kernel panic happened'), 'error');
|
|
assert.equal(severityFor('warning: retry'), 'warn');
|
|
assert.equal(cleanLine('Authorization: very-secret'), 'Authorization=[REDACTED]');
|
|
const f = fixture();
|
|
try {
|
|
const status = f.service.start({ durationSeconds: -1, byteLimit: 1 });
|
|
assert.equal(status.durationSeconds, 5);
|
|
assert.equal(status.byteLimit, 64 * 1024);
|
|
assert.equal(status.lineLimit, 20_000);
|
|
assert.throws(() => f.service.start(), /al een/);
|
|
} finally { f.service.stop(); fs.rmSync(f.root, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('bounded identity capture proves current firmware only from the TCP 9081 session', async () => {
|
|
const f = fixture();
|
|
try {
|
|
const pending = f.service.captureIdentity({ durationSeconds: 5 });
|
|
f.socket.emit('connect');
|
|
f.socket.emit('data', Buffer.from('system software 9.600.004\nbuild J03308751\n'));
|
|
const evidence = await pending;
|
|
assert.equal(evidence.endpoint.port, 9081);
|
|
assert.equal(evidence.readOnly, true);
|
|
assert.equal(evidence.connected, true);
|
|
assert.equal(evidence.systemSoftwareVersion, '9.600.004');
|
|
assert.equal(evidence.systemSoftwareEvidence, 'direct-version-line');
|
|
assert.equal(evidence.normalizedFirmware, '9.60');
|
|
assert.equal(evidence.buildIdentity, 'J03308751');
|
|
assert.equal(evidence.exact, true);
|
|
assert.equal(f.socket.writeCalls, 0);
|
|
} finally { f.service.stop(); fs.rmSync(f.root, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('bounded identity capture derives the exact version from the pinned fresh build identity', async () => {
|
|
const f = fixture();
|
|
try {
|
|
const pending = f.service.captureIdentity({ durationSeconds: 5 });
|
|
f.socket.emit('connect');
|
|
f.socket.emit('data', Buffer.from('W:\\\\Build\\\\J03308751\\\\vsh\\\\shell\\\\shell_core\\\\worker.cpp\n'));
|
|
const evidence = await pending;
|
|
assert.equal(evidence.connected, true);
|
|
assert.equal(evidence.currentEvidence, true);
|
|
assert.equal(evidence.systemSoftwareVersion, '9.600.004');
|
|
assert.equal(evidence.systemSoftwareEvidence, 'pinned-build-map');
|
|
assert.equal(evidence.normalizedFirmware, '9.60');
|
|
assert.equal(evidence.buildIdentity, 'J03308751');
|
|
assert.equal(evidence.exact, true);
|
|
assert.equal(f.socket.writeCalls, 0);
|
|
} finally { f.service.stop(); fs.rmSync(f.root, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('bounded identity capture rejects a version line without the exact fresh build identity', async () => {
|
|
const f = fixture();
|
|
try {
|
|
const pending = f.service.captureIdentity({ durationSeconds: 5 });
|
|
f.socket.emit('connect');
|
|
f.socket.emit('data', Buffer.from('system software 9.600.004\n'));
|
|
const evidence = await pending;
|
|
assert.equal(evidence.connected, true);
|
|
assert.equal(evidence.systemSoftwareEvidence, 'direct-version-line');
|
|
assert.equal(evidence.buildIdentity, null);
|
|
assert.equal(evidence.exact, false);
|
|
} finally { f.service.stop(); fs.rmSync(f.root, { recursive: true, force: true }); }
|
|
});
|