'use strict'; const assert = require('node:assert/strict'); const crypto = require('node:crypto'); const fs = require('node:fs/promises'); const os = require('node:os'); const path = require('node:path'); const test = require('node:test'); const { createP15R2InstalledContentVerifier } = require('../../src/services/ps5-p15-r2-installed-content-verifier'); const PARAM = Buffer.from(JSON.stringify({ titleId: 'PPSA99099', contentId: 'UP9000-PPSA99099_00-PROSPERO00000000', contentVersion: '01.000.000', applicationDrmType: 'free', })); const INSTALLED_APP_PKG = Buffer.from('sony-installed-sealed-control-content'); function provider({ online = true, appPkg = INSTALLED_APP_PKG } = {}) { return { probe: async () => ({ available: online, readOnly: true }), list: async remotePath => remotePath.includes('appmeta') ? [{ name: 'param.json', size: PARAM.length }] : [{ name: 'app.pkg', size: appPkg.length }], download: async ({ remotePath, localPath }) => fs.writeFile(localPath, remotePath.endsWith('param.json') ? PARAM : appPkg), }; } test('verifier requires two complete stable reads and exact Sony metadata', async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'chimera-p15-r2-test-')); try { const verifier = createP15R2InstalledContentVerifier({ provider: provider(), tempRoot: root, expected: { version: '01.000.000', installGeometry: { packageSize: INSTALLED_APP_PKG.length, mountImageSize: INSTALLED_APP_PKG.length, installedAppPkgSize: INSTALLED_APP_PKG.length, provenance: 'signed-image:pfsimage.xml' }, sourceManifestEbootPresent: true }, }); const result = await verifier.verify(); assert.equal(result.status, 'installed_complete'); assert.equal(result.transportHashComparisonRequired, false); } finally { await fs.rm(root, { recursive: true, force: true }); } }); test('transport hash inequality is not used as an installed app.pkg failure', async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'chimera-p15-r2-test-')); try { assert.notEqual(crypto.createHash('sha256').update(INSTALLED_APP_PKG).digest('hex'), '00a1563abd7e931e2e2d37b0186d2f479053af2179ebbc18717b36b7a4feebab'); const result = await createP15R2InstalledContentVerifier({ provider: provider(), tempRoot: root, expected: { installGeometry: { packageSize: INSTALLED_APP_PKG.length, mountImageSize: INSTALLED_APP_PKG.length, installedAppPkgSize: INSTALLED_APP_PKG.length, provenance: 'signed-image:pfsimage.xml' }, sourceManifestEbootPresent: true }, }).verify(); assert.equal(result.status, 'installed_complete'); } finally { await fs.rm(root, { recursive: true, force: true }); } }); test('a manual size cannot replace or contradict trusted package geometry', async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'chimera-p15-r2-test-')); try { const overrideOnly = await createP15R2InstalledContentVerifier({ provider: provider(), tempRoot: root, expected: { installedAppPkgSize: INSTALLED_APP_PKG.length, sourceManifestEbootPresent: true }, }).verify(); assert.equal(overrideOnly.status, 'installed_unproven'); assert.ok(overrideOnly.missingFields.includes('appPkgSizeCoherent')); const contradictory = await createP15R2InstalledContentVerifier({ provider: provider(), tempRoot: root, expected: { installedAppPkgSize: INSTALLED_APP_PKG.length + 1, installGeometry: { packageSize: INSTALLED_APP_PKG.length, mountImageSize: INSTALLED_APP_PKG.length, installedAppPkgSize: INSTALLED_APP_PKG.length, provenance: 'signed-image:pfsimage.xml' }, sourceManifestEbootPresent: true, }, }).verify(); assert.equal(contradictory.status, 'installed_incomplete'); assert.ok(contradictory.falseFields.includes('appPkgSizeCoherent')); } finally { await fs.rm(root, { recursive: true, force: true }); } }); test('offline provider leaves historical installation visibility unproven', async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'chimera-p15-r2-test-')); try { const result = await createP15R2InstalledContentVerifier({ provider: provider({ online: false }), tempRoot: root }).verify(); assert.equal(result.status, 'installed_unproven'); } finally { await fs.rm(root, { recursive: true, force: true }); } });