import assert from 'node:assert/strict'; import { test } from 'node:test'; import { deflateRawSync } from 'node:zlib'; import { checkApk, loadAlignment, REQUIRED } from './check-apk.mjs'; /** ELF64 с одним PT_LOAD и заданным выравниванием. */ function elf(align) { const b = Buffer.alloc(64 + 56); b.writeUInt32BE(0x7f454c46, 0); b[4] = 2; b.writeBigUInt64LE(64n, 0x20); b.writeUInt16LE(56, 0x36); b.writeUInt16LE(1, 0x38); b.writeUInt32LE(1, 64); b.writeBigUInt64LE(BigInt(align), 64 + 0x30); return b; } /** ZIP из пар [имя, содержимое]. */ function zip(files, compress = true) { const locals = []; const central = []; let offset = 0; for (const [name, data] of files) { const packed = compress ? deflateRawSync(data) : data; const n = Buffer.from(name); const lh = Buffer.alloc(30); lh.writeUInt32LE(0x04034b50, 0); lh.writeUInt16LE(compress ? 8 : 0, 8); lh.writeUInt32LE(packed.length, 18); lh.writeUInt32LE(data.length, 22); lh.writeUInt16LE(n.length, 26); locals.push(lh, n, packed); const ch = Buffer.alloc(46); ch.writeUInt32LE(0x02014b50, 0); ch.writeUInt16LE(compress ? 8 : 0, 10); ch.writeUInt32LE(packed.length, 20); ch.writeUInt32LE(data.length, 24); ch.writeUInt16LE(n.length, 28); ch.writeUInt32LE(offset, 42); central.push(ch, n); offset += 30 + n.length + packed.length; } const cd = Buffer.concat(central); const end = Buffer.alloc(22); end.writeUInt32LE(0x06054b50, 0); end.writeUInt16LE(files.length, 8); end.writeUInt16LE(files.length, 10); end.writeUInt32LE(cd.length, 12); end.writeUInt32LE(offset, 16); return Buffer.concat([...locals, cd, end]); } test('reads the smallest PT_LOAD alignment', () => { assert.equal(loadAlignment(elf(16384)), 16384); assert.equal(loadAlignment(elf(4096)), 4096); }); test('accepts an arm64-only APK aligned to 16 KB', () => { const apk = zip(REQUIRED.map((l) => [`lib/arm64-v8a/${l}`, elf(16384)])); assert.deepEqual(checkApk(apk).problems, []); }); test('rejects direct APK library loading that breaks ARM translation', () => { const apk = zip(REQUIRED.map((l) => [`lib/arm64-v8a/${l}`, elf(16384)]), false); const { problems } = checkApk(apk); assert.equal(problems.length, REQUIRED.length); assert.ok(problems.every((p) => p.includes('useLegacyPackaging'))); }); test('rejects other ABIs, missing libraries and 4 KB alignment', () => { const libs = REQUIRED.filter((l) => l !== 'libexpo-modules-core.so').map((l) => [ `lib/arm64-v8a/${l}`, elf(l === 'librnskia.so' ? 4096 : 16384), ]); const { problems } = checkApk(zip([...libs, ['lib/x86_64/libjsi.so', elf(16384)]])); assert.equal(problems.length, 3); assert.match(problems.join('\n'), /lib\/x86_64/); assert.match(problems.join('\n'), /libexpo-modules-core\.so/); assert.match(problems.join('\n'), /librnskia\.so.*4096/); });