Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974
This commit is contained in:
commit
c737a37f71
531 files changed
+119270
No files matched your search
@@ -0,0 +1,748 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
const { EventEmitter } = require('node:events');
|
||||
const { PS5UPLOAD_RUNTIME, createPs5UploadRuntimeService } = require('../../src/services/ps5upload-runtime-service');
|
||||
const { LINUX_LISTENER_PROCESS_EVIDENCE, LINUX_PROCESS_EVIDENCE } = require('../../src/services/linux-listener-process');
|
||||
|
||||
const digest = value => crypto.createHash('sha256').update(value).digest('hex');
|
||||
const TEST_BOOT_ID = '11111111-2222-4333-8444-555555555555';
|
||||
const TEST_NAMESPACES = Object.freeze({ net: 'net:[4001]', pid: 'pid:[4002]', mnt: 'mnt:[4003]' });
|
||||
|
||||
function listenerIdentity(input, overrides = {}) {
|
||||
return {
|
||||
verified: true,
|
||||
bootId: TEST_BOOT_ID,
|
||||
pid: 4242,
|
||||
startTimeTicks: '123456',
|
||||
socketInode: '555',
|
||||
exePath: input.enginePath,
|
||||
exeDev: '11',
|
||||
exeIno: '22',
|
||||
sha256: input.expectedSha256,
|
||||
uid: 10001,
|
||||
namespaces: TEST_NAMESPACES,
|
||||
cgroup: '0::/docker/chimera',
|
||||
evidence: LINUX_LISTENER_PROCESS_EVIDENCE,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function processIdentity(input, overrides = {}) {
|
||||
const { socketInode: _socketInode, ...identity } = listenerIdentity(input, overrides);
|
||||
return { ...identity, evidence: LINUX_PROCESS_EVIDENCE };
|
||||
}
|
||||
|
||||
test('A5 production pin selects the clean static 5.4.19 engine proven on the deployed host', () => {
|
||||
assert.equal(PS5UPLOAD_RUNTIME.version, '5.4.19');
|
||||
assert.equal(PS5UPLOAD_RUNTIME.revision, 'ec0e5c4e95520f2dc26c226a909dc5a95e385f41');
|
||||
assert.equal(PS5UPLOAD_RUNTIME.enginePath, '/payloads/ps5upload/5.4.19/ps5upload-engine-static');
|
||||
assert.equal(PS5UPLOAD_RUNTIME.engineSha256, 'd4055f43a57b36d1d2dec3e70ec03f73c9c47bc311d2b2f87145509ed53c8ba5');
|
||||
});
|
||||
|
||||
function fixture(t, overrides = {}) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5upload-runtime-'));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const engine = Buffer.from('engine-5.4.19');
|
||||
const payload = Buffer.from('elf-5.4.19');
|
||||
const dpi = Buffer.from('dpi-5.4.19');
|
||||
const enginePath = path.join(root, 'ps5upload-engine');
|
||||
const payloadPath = path.join(root, 'ps5upload.elf');
|
||||
const dpiPath = path.join(root, 'ezremote-dpi.elf');
|
||||
fs.writeFileSync(enginePath, engine, { mode: 0o750 });
|
||||
fs.writeFileSync(payloadPath, payload, { mode: 0o640 });
|
||||
fs.writeFileSync(dpiPath, dpi, { mode: 0o640 });
|
||||
let payloadGeneration = 1;
|
||||
const customFetch = overrides.fetch;
|
||||
const customTcpSend = overrides.tcpSend;
|
||||
const remainingOverrides = { ...overrides };
|
||||
delete remainingOverrides.fetch;
|
||||
delete remainingOverrides.tcpSend;
|
||||
if (remainingOverrides.processPlatform === 'linux' && !remainingOverrides.inspectSpawnedProcess) {
|
||||
remainingOverrides.inspectSpawnedProcess = async input => processIdentity(input);
|
||||
}
|
||||
return createPs5UploadRuntimeService({
|
||||
enginePath,
|
||||
payloadPath,
|
||||
dpiPath,
|
||||
expectedEngineSha256: digest(engine),
|
||||
expectedPayloadSha256: digest(payload),
|
||||
expectedDpiSha256: digest(dpi),
|
||||
consoleHost: () => '192.0.2.55',
|
||||
fetch: customFetch || (async url => {
|
||||
const href = String(url);
|
||||
if (href.endsWith('/api/version')) return { ok: true, status: 200, json: async () => ({ version: '5.4.19' }) };
|
||||
if (href.includes('/api/ps5/status')) return { ok: true, status: 200, json: async () => ({ version: '5.4.19', instance_id: String(payloadGeneration), started_at_unix: payloadGeneration }) };
|
||||
return { ok: true, status: 200, json: async () => ({ ok: true, users: [] }) };
|
||||
}),
|
||||
tcpProbe: async () => true,
|
||||
enginePortProbe: async () => false,
|
||||
tcpSend: async input => {
|
||||
payloadGeneration += 1;
|
||||
return customTcpSend ? customTcpSend(input) : { ok: true, bytes: input.bytes.length };
|
||||
},
|
||||
delay: async () => {},
|
||||
processPlatform: 'win32',
|
||||
inspectExistingEngine: async input => listenerIdentity(input),
|
||||
terminateProvenProcess: async () => ({ terminated: true, identityGone: true, forced: false }),
|
||||
...remainingOverrides,
|
||||
});
|
||||
}
|
||||
|
||||
test('runtime preflight proves exact engine and payload artifacts before reporting ready', async t => {
|
||||
const runtime = fixture(t);
|
||||
const result = await runtime.preflight();
|
||||
assert.equal(result.ready, true);
|
||||
assert.equal(result.engine.version, '5.4.19');
|
||||
assert.equal(result.payload.verified, true);
|
||||
assert.equal(result.loader.reachable, true);
|
||||
assert.equal(result.loader.port, 9021);
|
||||
});
|
||||
|
||||
test('runtime blocks hash drift and never offers the loader action', async t => {
|
||||
const runtime = fixture(t, { expectedPayloadSha256: '0'.repeat(64) });
|
||||
const result = await runtime.preflight();
|
||||
assert.equal(result.ready, false);
|
||||
assert.equal(result.payload.reason, 'sha256-mismatch');
|
||||
await assert.rejects(() => runtime.startPayload(), error => error.code === 'PS5UPLOAD_PREFLIGHT_BLOCKED');
|
||||
});
|
||||
|
||||
test('engine start uses a fixed executable without shell and keeps stdin open', async t => {
|
||||
const calls = [];
|
||||
const child = new EventEmitter();
|
||||
child.stdin = { destroyed: false, end() { this.destroyed = true; child.emit('exit', 0, null); } };
|
||||
child.stderr = new EventEmitter();
|
||||
child.kill = () => true;
|
||||
const runtime = fixture(t, {
|
||||
fetch: async () => { throw new Error('not listening yet'); },
|
||||
spawn: (file, args, options) => { calls.push({ file, args, options }); return child; },
|
||||
waitForEngine: async () => ({ available: true, version: '5.4.19' }),
|
||||
});
|
||||
const result = await runtime.startEngine();
|
||||
assert.equal(result.available, true);
|
||||
assert.equal(calls.length, 1);
|
||||
assert.deepEqual(calls[0].args, []);
|
||||
assert.equal(calls[0].options.shell, false);
|
||||
assert.equal(calls[0].options.env.PS5_ADDR, '192.0.2.55:9113');
|
||||
assert.equal(calls[0].options.env.PS5UPLOAD_ENGINE_PORT, '19113');
|
||||
await runtime.stopEngine();
|
||||
assert.equal(child.stdin.destroyed, true);
|
||||
});
|
||||
|
||||
test('Linux engine start goes through the fixed parent-death process guard', async t => {
|
||||
const calls = [];
|
||||
const guardPath = path.join(os.tmpdir(), 'chimera-process-guard-test');
|
||||
let running = false;
|
||||
const child = new EventEmitter();
|
||||
child.pid = 4242;
|
||||
child.stdin = { end() { running = false; child.emit('exit', 0, null); } };
|
||||
child.stderr = new EventEmitter();
|
||||
child.kill = () => true;
|
||||
const runtime = fixture(t, {
|
||||
fetch: async url => {
|
||||
if (!running || !String(url).endsWith('/api/version')) throw new Error('not listening');
|
||||
return { ok: true, status: 200, json: async () => ({ version: '5.4.19' }) };
|
||||
},
|
||||
processPlatform: 'linux',
|
||||
processGuardPath: guardPath,
|
||||
verifyProcessGuard: () => ({ verified: true, path: guardPath }),
|
||||
spawn: (file, args, options) => { calls.push({ file, args, options }); running = true; return child; },
|
||||
waitForEngine: async () => ({ available: true, version: '5.4.19' }),
|
||||
enginePortProbe: async () => running,
|
||||
});
|
||||
|
||||
await runtime.startEngine();
|
||||
assert.equal(calls.length, 1);
|
||||
assert.equal(calls[0].file, guardPath);
|
||||
assert.equal(calls[0].args[0], '--pdeathsig-exec');
|
||||
assert.equal(path.basename(calls[0].args[1]), 'ps5upload-engine');
|
||||
assert.equal(calls[0].options.shell, false);
|
||||
await runtime.stopEngine();
|
||||
});
|
||||
|
||||
test('Linux start rejects a matching endpoint unless its listener PID is the spawned child', async t => {
|
||||
const guardPath = path.join(os.tmpdir(), 'chimera-process-guard-test');
|
||||
let running = false;
|
||||
const child = new EventEmitter();
|
||||
child.pid = 4242;
|
||||
child.stdin = { end() { running = false; child.emit('exit', 0, null); } };
|
||||
child.stderr = new EventEmitter();
|
||||
child.kill = () => true;
|
||||
const runtime = fixture(t, {
|
||||
fetch: async url => {
|
||||
if (!running || !String(url).endsWith('/api/version')) throw new Error('not listening');
|
||||
return { ok: true, status: 200, json: async () => ({ version: '5.4.19' }) };
|
||||
},
|
||||
processPlatform: 'linux',
|
||||
processGuardPath: guardPath,
|
||||
verifyProcessGuard: () => ({ verified: true, path: guardPath }),
|
||||
spawn: () => { running = true; return child; },
|
||||
waitForEngine: async () => ({ available: true, version: '5.4.19' }),
|
||||
inspectExistingEngine: async input => listenerIdentity(input, { pid: 5252 }),
|
||||
enginePortProbe: async () => running,
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => runtime.startEngine(),
|
||||
error => error.code === 'PS5UPLOAD_ENGINE_START_FAILED'
|
||||
&& error.details?.reason === 'spawned-listener-unproven'
|
||||
&& error.details?.cleanup?.stopped === true,
|
||||
);
|
||||
});
|
||||
|
||||
test('Linux spawned fallback revalidates and terminates through pidfd without numeric child kill', async t => {
|
||||
const guardPath = path.join(os.tmpdir(), 'chimera-process-guard-test');
|
||||
let running = false;
|
||||
let childKills = 0;
|
||||
const terminations = [];
|
||||
const child = new EventEmitter();
|
||||
child.pid = 4242;
|
||||
child.stdin = { end() {} };
|
||||
child.stderr = new EventEmitter();
|
||||
child.kill = () => { childKills += 1; return true; };
|
||||
const runtime = fixture(t, {
|
||||
fetch: async url => {
|
||||
if (!running || !String(url).endsWith('/api/version')) throw new Error('not listening');
|
||||
return { ok: true, status: 200, json: async () => ({ version: '5.4.19' }) };
|
||||
},
|
||||
processPlatform: 'linux',
|
||||
processGuardPath: guardPath,
|
||||
verifyProcessGuard: () => ({ verified: true, path: guardPath }),
|
||||
spawn: () => { running = true; return child; },
|
||||
waitForEngine: async () => ({ available: true, version: '5.4.19' }),
|
||||
inspectExistingEngine: async input => listenerIdentity(input),
|
||||
terminateProvenProcess: async identity => {
|
||||
terminations.push(identity);
|
||||
running = false;
|
||||
return { terminated: true, identityGone: true, forced: false };
|
||||
},
|
||||
enginePortProbe: async () => running,
|
||||
});
|
||||
|
||||
await runtime.startEngine();
|
||||
assert.deepEqual(await runtime.stopEngine(), {
|
||||
stopped: true,
|
||||
endpointReleased: true,
|
||||
identityGone: true,
|
||||
forced: false,
|
||||
});
|
||||
assert.equal(terminations.length, 1);
|
||||
assert.equal(terminations[0].pid, 4242);
|
||||
assert.equal(childKills, 0);
|
||||
});
|
||||
|
||||
test('Linux spawned ownership can be re-attested after a transient read-only sample drift', async t => {
|
||||
const guardPath = path.join(os.tmpdir(), 'chimera-process-guard-test');
|
||||
let running = false;
|
||||
let inspections = 0;
|
||||
const child = new EventEmitter();
|
||||
child.pid = 4242;
|
||||
child.stdin = { end() { running = false; child.emit('exit', 0, null); } };
|
||||
child.stderr = new EventEmitter();
|
||||
child.kill = () => true;
|
||||
const runtime = fixture(t, {
|
||||
fetch: async url => {
|
||||
if (!running || !String(url).endsWith('/api/version')) throw new Error('not listening');
|
||||
return { ok: true, status: 200, json: async () => ({ version: '5.4.19' }) };
|
||||
},
|
||||
processPlatform: 'linux',
|
||||
processGuardPath: guardPath,
|
||||
verifyProcessGuard: () => ({ verified: true, path: guardPath }),
|
||||
spawn: () => { running = true; return child; },
|
||||
waitForEngine: async () => ({ available: true, version: '5.4.19' }),
|
||||
inspectExistingEngine: async input => {
|
||||
inspections += 1;
|
||||
return listenerIdentity(input, { pid: inspections === 3 ? 5252 : 4242 });
|
||||
},
|
||||
enginePortProbe: async () => running,
|
||||
});
|
||||
|
||||
await runtime.startEngine();
|
||||
assert.equal((await runtime.status()).engine.processOwned, false);
|
||||
const recovered = await runtime.status();
|
||||
assert.equal(recovered.engine.processOwned, true);
|
||||
assert.equal(recovered.engine.processOwnership, 'spawned');
|
||||
await runtime.stopEngine();
|
||||
});
|
||||
|
||||
test('engine stop waits for the shared endpoint to be released before allowing a replacement runtime', async t => {
|
||||
let stopping = false;
|
||||
let started = false;
|
||||
let portProbes = 0;
|
||||
let waits = 0;
|
||||
const child = new EventEmitter();
|
||||
child.stdin = { end() { stopping = true; child.emit('exit', 0, null); } };
|
||||
child.stderr = new EventEmitter();
|
||||
child.kill = () => true;
|
||||
const runtime = fixture(t, {
|
||||
fetch: async url => {
|
||||
if (!String(url).endsWith('/api/version') || !stopping) throw new Error('not listening');
|
||||
throw new Error('HTTP endpoint released');
|
||||
},
|
||||
spawn: () => { started = true; return child; },
|
||||
waitForEngine: async () => ({ available: true, version: '5.4.19' }),
|
||||
enginePortProbe: async () => { if (!started) return false; portProbes += 1; return portProbes < 3; },
|
||||
delay: async () => { waits += 1; },
|
||||
});
|
||||
await runtime.startEngine();
|
||||
const stopped = await runtime.stopEngine();
|
||||
assert.deepEqual(stopped, { stopped: true, endpointReleased: true, identityGone: true, forced: false });
|
||||
assert.equal(portProbes, 3);
|
||||
assert.equal(waits, 2);
|
||||
});
|
||||
|
||||
test('not-owned stop reports success only after proving the shared endpoint is already released', async t => {
|
||||
const released = fixture(t, {
|
||||
fetch: async () => { throw new Error('endpoint unavailable'); },
|
||||
enginePortProbe: async () => false,
|
||||
});
|
||||
assert.deepEqual(await released.stopEngine(), {
|
||||
stopped: true,
|
||||
endpointReleased: true,
|
||||
identityGone: true,
|
||||
alreadyStopped: true,
|
||||
});
|
||||
|
||||
const occupied = fixture(t, { enginePortProbe: async () => true });
|
||||
assert.deepEqual(await occupied.stopEngine(), {
|
||||
stopped: false,
|
||||
endpointReleased: false,
|
||||
identityGone: false,
|
||||
reason: 'not-owned',
|
||||
});
|
||||
});
|
||||
|
||||
test('engine stop fails closed without signaling again when a replacement endpoint remains occupied', async t => {
|
||||
let kills = 0;
|
||||
let portProbes = 0;
|
||||
let started = false;
|
||||
const child = new EventEmitter();
|
||||
child.stdin = { end() { child.emit('exit', 0, null); } };
|
||||
child.stderr = new EventEmitter();
|
||||
child.kill = () => { kills += 1; return true; };
|
||||
const runtime = fixture(t, {
|
||||
fetch: async () => { throw new Error('HTTP endpoint unavailable'); },
|
||||
spawn: () => { started = true; return child; },
|
||||
waitForEngine: async () => ({ available: true, version: '5.4.19' }),
|
||||
enginePortProbe: async () => { if (!started) return false; portProbes += 1; return true; },
|
||||
delay: async () => {},
|
||||
});
|
||||
await runtime.startEngine();
|
||||
const stopped = await runtime.stopEngine();
|
||||
assert.deepEqual(stopped, { stopped: false, endpointReleased: false, identityGone: true, reason: 'engine-endpoint-still-active' });
|
||||
assert.equal(portProbes, 20);
|
||||
assert.equal(kills, 0);
|
||||
});
|
||||
|
||||
test('failed spawned stop retains the child identity so a later safe retry can finish', async t => {
|
||||
let allowExit = false;
|
||||
const child = new EventEmitter();
|
||||
child.stdin = { end() { if (allowExit) child.emit('exit', 0, null); } };
|
||||
child.stderr = new EventEmitter();
|
||||
child.kill = () => { if (allowExit) child.emit('exit', 0, 'SIGTERM'); return true; };
|
||||
const runtime = fixture(t, {
|
||||
fetch: async () => { throw new Error('endpoint unavailable'); },
|
||||
spawn: () => child,
|
||||
waitForEngine: async () => ({ available: true, version: '5.4.19' }),
|
||||
enginePortProbe: async () => false,
|
||||
});
|
||||
|
||||
await runtime.startEngine();
|
||||
assert.deepEqual(await runtime.stopEngine(), {
|
||||
stopped: false,
|
||||
endpointReleased: false,
|
||||
identityGone: false,
|
||||
reason: 'engine-process-still-active',
|
||||
});
|
||||
allowExit = true;
|
||||
assert.deepEqual(await runtime.stopEngine(), {
|
||||
stopped: true,
|
||||
endpointReleased: true,
|
||||
identityGone: true,
|
||||
forced: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('failed engine readiness performs the same proven bounded cleanup', async t => {
|
||||
const child = new EventEmitter();
|
||||
child.stdin = { end() { child.emit('exit', 0, null); } };
|
||||
child.stderr = new EventEmitter();
|
||||
child.kill = () => true;
|
||||
const runtime = fixture(t, {
|
||||
fetch: async () => { throw new Error('endpoint unavailable'); },
|
||||
spawn: () => child,
|
||||
waitForEngine: async () => ({ available: false, version: '', reason: 'unreachable' }),
|
||||
enginePortProbe: async () => false,
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => runtime.startEngine(),
|
||||
error => error.code === 'PS5UPLOAD_ENGINE_START_FAILED'
|
||||
&& error.details?.cleanup?.stopped === true
|
||||
&& error.details.cleanup.endpointReleased === true
|
||||
&& error.details.cleanup.identityGone === true,
|
||||
);
|
||||
});
|
||||
|
||||
test('Linux failed readiness re-attests the spawned listener before proven pidfd cleanup', async t => {
|
||||
const guardPath = path.join(os.tmpdir(), 'chimera-process-guard-test');
|
||||
let running = false;
|
||||
let childKills = 0;
|
||||
const terminations = [];
|
||||
const child = new EventEmitter();
|
||||
child.pid = 4242;
|
||||
child.stdin = { end() {} };
|
||||
child.stderr = new EventEmitter();
|
||||
child.kill = () => { childKills += 1; return true; };
|
||||
const runtime = fixture(t, {
|
||||
fetch: async url => {
|
||||
if (!running || !String(url).endsWith('/api/version')) throw new Error('not listening');
|
||||
return { ok: true, status: 200, json: async () => ({ version: 'wrong-profile' }) };
|
||||
},
|
||||
processPlatform: 'linux',
|
||||
processGuardPath: guardPath,
|
||||
verifyProcessGuard: () => ({ verified: true, path: guardPath }),
|
||||
spawn: () => { running = true; return child; },
|
||||
waitForEngine: async () => ({ available: false, version: 'wrong-profile', reason: 'runtime-profile-mismatch' }),
|
||||
inspectExistingEngine: async input => listenerIdentity(input),
|
||||
inspectSpawnedProcess: async input => processIdentity(input),
|
||||
terminateProvenProcess: async identity => {
|
||||
terminations.push(identity);
|
||||
running = false;
|
||||
return { terminated: true, identityGone: true, forced: false };
|
||||
},
|
||||
enginePortProbe: async () => running,
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => runtime.startEngine(),
|
||||
error => error.code === 'PS5UPLOAD_ENGINE_START_FAILED'
|
||||
&& error.details?.cleanup?.stopped === true
|
||||
&& error.details.cleanup.endpointReleased === true
|
||||
&& error.details.cleanup.identityGone === true,
|
||||
);
|
||||
assert.equal(terminations.length, 1);
|
||||
assert.equal(terminations[0].pid, 4242);
|
||||
assert.equal(childKills, 0);
|
||||
});
|
||||
|
||||
test('Linux failed readiness terminates an exact spawned PID that never opens a listener', async t => {
|
||||
const guardPath = path.join(os.tmpdir(), 'chimera-process-guard-test');
|
||||
let childKills = 0;
|
||||
const terminations = [];
|
||||
const child = new EventEmitter();
|
||||
child.pid = 4242;
|
||||
child.stdin = { end() {} };
|
||||
child.stderr = new EventEmitter();
|
||||
child.kill = () => { childKills += 1; return true; };
|
||||
const runtime = fixture(t, {
|
||||
fetch: async () => { throw new Error('listener never opened'); },
|
||||
processPlatform: 'linux',
|
||||
processGuardPath: guardPath,
|
||||
verifyProcessGuard: () => ({ verified: true, path: guardPath }),
|
||||
spawn: () => child,
|
||||
waitForEngine: async () => ({ available: false, version: '', reason: 'unreachable' }),
|
||||
inspectExistingEngine: async () => ({ verified: false, reason: 'listener-not-found' }),
|
||||
inspectSpawnedProcess: async input => processIdentity(input),
|
||||
terminateProvenProcess: async identity => {
|
||||
terminations.push(identity);
|
||||
return { terminated: true, identityGone: true, forced: false };
|
||||
},
|
||||
enginePortProbe: async () => false,
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => runtime.startEngine(),
|
||||
error => error.code === 'PS5UPLOAD_ENGINE_START_FAILED'
|
||||
&& error.details?.cleanup?.stopped === true
|
||||
&& error.details.cleanup.endpointReleased === true
|
||||
&& error.details.cleanup.identityGone === true,
|
||||
);
|
||||
assert.equal(terminations.length, 1);
|
||||
assert.equal(terminations[0].pid, 4242);
|
||||
assert.equal(terminations[0].evidence, LINUX_PROCESS_EVIDENCE);
|
||||
assert.equal(childKills, 0);
|
||||
});
|
||||
|
||||
test('payload start sends only the pinned ELF and reports management readiness without user data', async t => {
|
||||
const sends = [];
|
||||
const inspections = [];
|
||||
let spawnCalls = 0;
|
||||
const runtime = fixture(t, {
|
||||
inspectExistingEngine: async input => { inspections.push(input); return listenerIdentity(input); },
|
||||
spawn: () => { spawnCalls += 1; throw new Error('must adopt before payload start'); },
|
||||
tcpSend: async input => { sends.push(input); return { ok: true, bytes: input.bytes.length }; },
|
||||
});
|
||||
const result = await runtime.startPayload();
|
||||
const status = await runtime.status();
|
||||
assert.equal(sends.length, 1);
|
||||
assert.equal(spawnCalls, 0);
|
||||
assert.equal(inspections.length >= 4, true);
|
||||
assert.equal(inspections.every(input => input.host === '127.0.0.1' && input.port === 19113), true);
|
||||
assert.equal(status.engine.processOwned, true);
|
||||
assert.equal(status.engine.processOwnership, 'adopted');
|
||||
assert.equal(sends[0].host, '192.0.2.55');
|
||||
assert.equal(sends[0].port, 9021);
|
||||
assert.equal(sends[0].bytes.toString(), 'elf-5.4.19');
|
||||
assert.deepEqual(result, { sent: true, bytes: 10, managementReady: true, managementPort: 9114 });
|
||||
});
|
||||
|
||||
test('payload start ignores a stale ready daemon until the new management instance is observable', async t => {
|
||||
let sent = false;
|
||||
let postSendStatusReads = 0;
|
||||
const runtime = fixture(t, {
|
||||
fetch: async url => {
|
||||
const href = String(url);
|
||||
if (href.endsWith('/api/version')) return { ok: true, status: 200, json: async () => ({ version: '5.4.19' }) };
|
||||
if (href.includes('/api/ps5/status')) {
|
||||
if (sent) postSendStatusReads += 1;
|
||||
const transitioned = sent && postSendStatusReads >= 3;
|
||||
return { ok: true, status: 200, json: async () => ({ version: '5.4.19', instance_id: transitioned ? '2' : '1', started_at_unix: transitioned ? 2 : 1 }) };
|
||||
}
|
||||
return { ok: true, status: 200, json: async () => ({ ok: true, users: [] }) };
|
||||
},
|
||||
tcpSend: async input => { sent = true; return { ok: true, bytes: input.bytes.length }; },
|
||||
});
|
||||
const result = await runtime.startPayload();
|
||||
assert.equal(result.managementReady, true);
|
||||
assert.equal(postSendStatusReads, 3);
|
||||
const identity = await runtime.runtimeIdentity();
|
||||
assert.equal(identity.status, 'unproven');
|
||||
assert.equal(identity.payload.evidence, 'loader-input-hash-and-instance-transition');
|
||||
});
|
||||
|
||||
test('payload start does not attest or report readiness when the old instance never transitions', async t => {
|
||||
let statusReads = 0;
|
||||
const runtime = fixture(t, {
|
||||
fetch: async url => {
|
||||
const href = String(url);
|
||||
if (href.endsWith('/api/version')) return { ok: true, status: 200, json: async () => ({ version: '5.4.19' }) };
|
||||
if (href.includes('/api/ps5/status')) {
|
||||
statusReads += 1;
|
||||
return { ok: true, status: 200, json: async () => ({ version: '5.4.19', instance_id: '1', started_at_unix: 1 }) };
|
||||
}
|
||||
return { ok: true, status: 200, json: async () => ({ ok: true, users: [] }) };
|
||||
},
|
||||
});
|
||||
const result = await runtime.startPayload();
|
||||
assert.equal(result.managementReady, false);
|
||||
assert.equal(statusReads, 13);
|
||||
assert.equal((await runtime.runtimeIdentity()).status, 'unproven');
|
||||
});
|
||||
|
||||
test('DPI fallback sends only the pinned standalone daemon and waits for port 9040', async t => {
|
||||
const probes = []; const sends = [];
|
||||
const runtime = fixture(t, {
|
||||
tcpProbe: async input => { probes.push(input); return input.port === 9021 || probes.filter(item => item.port === 9040).length > 1; },
|
||||
tcpSend: async input => { sends.push(input); return { ok: true, bytes: input.bytes.length }; },
|
||||
});
|
||||
const result = await runtime.ensureDpi();
|
||||
assert.equal(result.ready, true);
|
||||
assert.equal(result.port, 9040);
|
||||
assert.equal(result.alreadyRunning, false);
|
||||
assert.equal(sends.length, 1);
|
||||
assert.equal(sends[0].port, 9021);
|
||||
assert.equal(sends[0].bytes.toString(), 'dpi-5.4.19');
|
||||
assert.equal(probes.some(item => item.port === 9040), true);
|
||||
});
|
||||
|
||||
test('missing console host blocks engine and payload setup', async t => {
|
||||
const runtime = fixture(t, { consoleHost: () => '' });
|
||||
await assert.rejects(() => runtime.startEngine(), error => error.code === 'PS5UPLOAD_CONSOLE_HOST');
|
||||
const result = await runtime.preflight();
|
||||
assert.equal(result.ready, false);
|
||||
assert.equal(result.console.reason, 'host-missing');
|
||||
});
|
||||
|
||||
test('A5 runtime identity stays unproven until Chimera owns the exact engine and payload transition', async t => {
|
||||
const child = new EventEmitter();
|
||||
child.stdin = { end() {} }; child.stderr = new EventEmitter(); child.kill = () => true;
|
||||
let engineReady = false; let payloadLoaded = false;
|
||||
const runtime = fixture(t, {
|
||||
fetch: async url => {
|
||||
const href = String(url);
|
||||
if (href.endsWith('/api/version')) {
|
||||
if (!engineReady) throw new Error('not listening');
|
||||
return { ok: true, status: 200, json: async () => ({ version: '5.4.19' }) };
|
||||
}
|
||||
if (href.includes('/api/ps5/status')) {
|
||||
return { ok: true, status: 200, json: async () => ({ version: '5.4.19', instance_id: payloadLoaded ? '2' : '1', started_at_unix: payloadLoaded ? 2 : 1 }) };
|
||||
}
|
||||
return { ok: true, status: 200, json: async () => ({ ok: true, users: [] }) };
|
||||
},
|
||||
spawn: () => { engineReady = true; return child; },
|
||||
waitForEngine: async () => ({ available: true, version: '5.4.19' }),
|
||||
tcpProbe: async input => input.port !== 9040,
|
||||
tcpSend: async input => { payloadLoaded = true; return { ok: true, bytes: input.bytes.length }; },
|
||||
});
|
||||
assert.equal((await runtime.runtimeIdentity()).status, 'unproven');
|
||||
await runtime.startEngine();
|
||||
assert.equal((await runtime.runtimeIdentity()).status, 'unproven');
|
||||
await runtime.startPayload();
|
||||
const exact = await runtime.runtimeIdentity();
|
||||
assert.equal(exact.status, 'exact');
|
||||
assert.equal(exact.mutationAllowed, true);
|
||||
assert.equal(exact.engine.evidence, 'chimera-owned-process-artifact');
|
||||
assert.equal(exact.payload.evidence, 'loader-input-hash-and-instance-transition');
|
||||
});
|
||||
|
||||
test('an exact same-namespace listener can be re-attested after a server restart', async t => {
|
||||
let spawnCalls = 0;
|
||||
const runtime = fixture(t, {
|
||||
spawn: () => { spawnCalls += 1; throw new Error('must not spawn over an exact existing listener'); },
|
||||
tcpProbe: async input => input.port !== 9040,
|
||||
inspectExistingEngine: async input => listenerIdentity(input),
|
||||
});
|
||||
|
||||
const adopted = await runtime.startEngine();
|
||||
assert.equal(adopted.available, true);
|
||||
assert.equal(adopted.processOwned, true);
|
||||
assert.equal(adopted.adopted, true);
|
||||
assert.equal(spawnCalls, 0);
|
||||
|
||||
const beforePayload = await runtime.runtimeIdentity();
|
||||
assert.equal(beforePayload.status, 'unproven');
|
||||
assert.equal(beforePayload.engine.evidence, LINUX_LISTENER_PROCESS_EVIDENCE);
|
||||
await runtime.startPayload();
|
||||
const exact = await runtime.runtimeIdentity();
|
||||
assert.equal(exact.status, 'exact');
|
||||
assert.equal(exact.mutationAllowed, true);
|
||||
assert.equal(exact.engine.evidence, LINUX_LISTENER_PROCESS_EVIDENCE);
|
||||
});
|
||||
|
||||
test('an existing endpoint is never adopted without exact local process provenance', async t => {
|
||||
let spawnCalls = 0;
|
||||
const runtime = fixture(t, {
|
||||
spawn: () => { spawnCalls += 1; throw new Error('must not spawn over an unowned endpoint'); },
|
||||
inspectExistingEngine: async () => ({ verified: false, reason: 'listener-owner-not-found' }),
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => runtime.startEngine(),
|
||||
error => error.code === 'PS5UPLOAD_ENGINE_UNOWNED'
|
||||
&& error.details?.reason === 'listener-owner-not-found',
|
||||
);
|
||||
assert.equal(spawnCalls, 0);
|
||||
assert.equal((await runtime.runtimeIdentity()).status, 'unproven');
|
||||
});
|
||||
|
||||
test('a reachable wrong-lane profile is never inspected, signaled or spawned over', async t => {
|
||||
let inspections = 0;
|
||||
let spawnCalls = 0;
|
||||
let terminations = 0;
|
||||
const runtime = fixture(t, {
|
||||
fetch: async url => {
|
||||
if (String(url).endsWith('/api/version')) {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ version: '5.4.19', diagnostic_identity: 'chimera-p15-shellui-observe-v1' }),
|
||||
};
|
||||
}
|
||||
throw new Error('unexpected request');
|
||||
},
|
||||
inspectExistingEngine: async input => { inspections += 1; return listenerIdentity(input); },
|
||||
spawn: () => { spawnCalls += 1; throw new Error('must not spawn over wrong lane'); },
|
||||
terminateProvenProcess: async () => { terminations += 1; return { terminated: true, identityGone: true }; },
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => runtime.startEngine(),
|
||||
error => error.code === 'PS5UPLOAD_ENGINE_UNOWNED'
|
||||
&& error.details?.reason === 'runtime-profile-mismatch',
|
||||
);
|
||||
assert.equal(inspections, 0);
|
||||
assert.equal(spawnCalls, 0);
|
||||
assert.equal(terminations, 0);
|
||||
});
|
||||
|
||||
test('an adopted engine is revalidated before a bounded graceful stop', async t => {
|
||||
let terminated = false;
|
||||
const terminations = [];
|
||||
const runtime = fixture(t, {
|
||||
fetch: async url => {
|
||||
const href = String(url);
|
||||
if (href.endsWith('/api/version')) {
|
||||
if (terminated) throw new Error('released');
|
||||
return { ok: true, status: 200, json: async () => ({ version: '5.4.19' }) };
|
||||
}
|
||||
if (href.includes('/api/ps5/status')) return { ok: true, status: 200, json: async () => ({ version: '5.4.19', instance_id: '1', started_at_unix: 1 }) };
|
||||
return { ok: true, status: 200, json: async () => ({ ok: true, users: [] }) };
|
||||
},
|
||||
inspectExistingEngine: async input => listenerIdentity(input),
|
||||
terminateProvenProcess: async identity => {
|
||||
terminations.push(identity);
|
||||
terminated = true;
|
||||
return { terminated: true, identityGone: true, forced: false };
|
||||
},
|
||||
enginePortProbe: async () => !terminated,
|
||||
});
|
||||
|
||||
await runtime.startEngine();
|
||||
const stopped = await runtime.stopEngine();
|
||||
assert.deepEqual(stopped, { stopped: true, endpointReleased: true, identityGone: true, forced: false, adopted: true });
|
||||
assert.equal(terminations.length, 1);
|
||||
assert.equal(terminations[0].pid, 4242);
|
||||
});
|
||||
|
||||
test('adopted process identity drift fails closed without signaling any process', async t => {
|
||||
let inspections = 0;
|
||||
const terminations = [];
|
||||
const runtime = fixture(t, {
|
||||
inspectExistingEngine: async input => {
|
||||
inspections += 1;
|
||||
return listenerIdentity(input, { pid: inspections <= 2 ? 4242 : 5252 });
|
||||
},
|
||||
terminateProvenProcess: async identity => { terminations.push(identity); return { terminated: true, identityGone: true }; },
|
||||
});
|
||||
|
||||
await runtime.startEngine();
|
||||
const stopped = await runtime.stopEngine();
|
||||
assert.deepEqual(stopped, { stopped: false, endpointReleased: false, identityGone: false, reason: 'adopted-process-identity-drift' });
|
||||
assert.deepEqual(terminations, []);
|
||||
});
|
||||
|
||||
test('status clears adopted ownership and attestation immediately on identity drift', async t => {
|
||||
let inspections = 0;
|
||||
const runtime = fixture(t, {
|
||||
inspectExistingEngine: async input => {
|
||||
inspections += 1;
|
||||
return listenerIdentity(input, { startTimeTicks: inspections <= 2 ? '123456' : '654321' });
|
||||
},
|
||||
});
|
||||
|
||||
await runtime.startEngine();
|
||||
const status = await runtime.status();
|
||||
assert.equal(status.engine.available, true);
|
||||
assert.equal(status.engine.processOwned, false);
|
||||
assert.equal(status.engine.processOwnership, null);
|
||||
const identity = await runtime.runtimeIdentity();
|
||||
assert.equal(identity.status, 'unproven');
|
||||
assert.equal(identity.mutationAllowed, false);
|
||||
});
|
||||
|
||||
test('adopted helper failure remains fail-closed and keeps ownership available for retry', async t => {
|
||||
let terminations = 0;
|
||||
const runtime = fixture(t, {
|
||||
terminateProvenProcess: async () => {
|
||||
terminations += 1;
|
||||
return { terminated: false, identityGone: false, reason: 'pidfd-exit-timeout' };
|
||||
},
|
||||
});
|
||||
|
||||
await runtime.startEngine();
|
||||
const stopped = await runtime.stopEngine();
|
||||
assert.deepEqual(stopped, {
|
||||
stopped: false,
|
||||
endpointReleased: false,
|
||||
identityGone: false,
|
||||
reason: 'pidfd-exit-timeout',
|
||||
});
|
||||
assert.equal(terminations, 1);
|
||||
assert.equal((await runtime.status()).engine.processOwned, true);
|
||||
});
|
||||
Reference in new issue
Block a user