// Echivalenta formelor liniare din 0.2.0 cu formele din 0.1.0 (2026-09-29, revizuirea adversariala): aceleasi gasiri si biblioteci // pe arbori reali, si aceleasi rezultate ale lexerului si ale cautarii PEM pe texte generate din jetoanele care conteaza. // 0.1.0 se ia din git (revizia AERE_INV_REV_VECHE, implicit db9d9543), in forma comisa; intr-o copie fara istoric iese SARIT. // node test/echivalenta-0.1.0.mjs [--iteratii N] [dosar...] (implicit: fixturile si dosarul uneltei) // -> 0 identic, 1 o diferenta (tiparita), 2 NEMASURAT (revizia veche lipseste sau nimic comparat) // Singura diferenta asteptata e textul motivului pentru "unknown" (spune acum si plafonul de variabile); se normalizeaza. import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { spawnSync } from 'node:child_process'; import { tmpdir } from 'node:os'; const AICI = dirname(fileURLToPath(import.meta.url)); const RAD = join(AICI, '..'); const REV = process.env.AERE_INV_REV_VECHE || 'db9d9543'; let ITER = 20000; const dosare = []; for (let k = 2; k < process.argv.length; k++) { if (process.argv[k] === '--iteratii') ITER = Number(process.argv[++k]); else dosare.push(process.argv[k]); } const DOSARE = dosare.length ? dosare : [join(AICI, 'fixturi'), RAD]; const git = (...a) => spawnSync('git', ['-c', 'core.autocrlf=false', ...a], { cwd: RAD, encoding: 'buffer', maxBuffer: 64 << 20 }); const ls = git('ls-tree', '-r', '--name-only', '--full-tree', REV, 'tools/crypto-inventory/inventar.mjs', 'tools/crypto-inventory/lib'); const fis = ls.status === 0 ? ls.stdout.toString().split('\n').filter(Boolean) : []; if (!fis.length) { console.log(`SARIT: revizia ${REV} nu e in istoricul acestui depozit; NEMASURAT aici`); process.exit(2); } const vechi = mkdtempSync(join(tmpdir(), 'ci-v010-')); let dif = 0, comparate = 0; try { for (const f of fis) { const b = git('show', `${REV}:${f}`); if (b.status !== 0) { console.log(`NEMASURAT: git show ${f}`); process.exit(2); } const t = join(vechi, f.slice('tools/crypto-inventory/'.length)); mkdirSync(dirname(t), { recursive: true }); writeFileSync(t, b.stdout); } const imp = (r, m) => import(pathToFileURL(join(r, 'lib', m)).href); const [SV, SN, LV, LN, PN] = await Promise.all([imp(vechi, 'scan.mjs'), imp(RAD, 'scan.mjs'), imp(vechi, 'lexer.mjs'), imp(RAD, 'lexer.mjs'), imp(RAD, 'pem.mjs')]); if (SV.VERSIUNE !== '0.1.0') { console.log(`NEMASURAT: revizia ${REV} are versiunea ${SV.VERSIUNE}, nu 0.1.0`); process.exit(2); } // 1. arbori reali const norm = (g) => JSON.stringify({ ...g, reason: String(g.reason).replace(/, which (cannot be resolved statically|this tool does not resolve statically).*$/, ', which [motiv]') }); for (const d of DOSARE) { const a = SV.scaneaza(d), b = SN.scaneaza(d); const ka = a.findings.map(norm), kb = b.findings.map(norm); const sa = new Set(ka), sb = new Set(kb); const doarV = ka.filter((x) => !sb.has(x)), doarN = kb.filter((x) => !sa.has(x)); const ok = !doarV.length && !doarN.length && ka.length === kb.length && JSON.stringify(a.libraries) === JSON.stringify(b.libraries); comparate += ka.length; if (!ok) dif++; console.log(`${ok ? 'IDENTIC ' : 'DIFERIT '} ${d}: ${a.stats.filesSeen} fisiere, gasiri ${ka.length}/${kb.length}, biblioteci ${a.libraries.length}/${b.libraries.length}`); for (const x of [...doarV.slice(0, 3).map((x) => 'numai 0.1.0: ' + x), ...doarN.slice(0, 3).map((x) => 'numai 0.2.0: ' + x)]) console.log(' ' + x.slice(0, 260)); } // 2. texte generate: lexerul pe patru limbaje si cautarea PEM (fata de regexul din 0.1.0) const RE_010 = /-----BEGIN ((?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY|PUBLIC KEY|RSA PUBLIC KEY|CERTIFICATE)-----([A-Za-z0-9+/=\s:,\-\r\n]*?)-----END \1-----/g; let seed = 12345; const rnd = (n) => { seed = (seed * 1103515245 + 12345) & 0x7fffffff; return seed % n; }; const gen = (jet, max) => { let s = ''; const L = rnd(max); for (let i = 0; i < L; i++) s += jet[rnd(jet.length)]; return s; }; const JET = { python: ['"', "'", '"""', "'''", '\\', '\n', ' ', '\t', 'r', 'b', 'f', 'u', 'x', '#', '=', '(', ')', 'rb', '\r\n', 'a1'], javascript: ['=/[', '=/', ' x/', '"', "'", '`', '${', '}', '{', '/', '//', '/*', '*/', '[', ']', '\\', '\n', ' ', '=', '(', ')', 'return', 'x', '1', '.', ',', 'typeof'], java: ['"', "'", '"""', '\\', '\n', ' ', '/', '//', '/*', '*/', 'x', '=', '(', ')'], go: ['"', "'", '`', '\\', '\n', ' ', '/', '//', '/*', '*/', 'x', '=', '(', ')'], }; const JET_PEM = ['-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----', '-----BEGIN PRIVATE KEY-----', '-----END PRIVATE KEY-----', '-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----', '-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----', 'BEGIN CERTIFICATE-----', 'BEGIN PRIVATE KEY-----', 'AAAA', 'QUJD', '\n', ' ', '.', '-', '--', '-----', '"', 'BEGIN', '=', ':', ' ', '\r\n']; const cat = {}; for (let k = 0; k < ITER; k++) { for (const [limbaj, jet] of Object.entries(JET)) { const t = gen(jet, 40); const a = LV.curata(t, limbaj), b = LN.curata(t, limbaj); comparate++; if (a.code !== b.code || JSON.stringify(a.siruri) !== JSON.stringify(b.siruri)) { cat[limbaj] = (cat[limbaj] || 0) + 1; if (dif++ < 5) console.log(`DIFERIT lexer ${limbaj}: ${JSON.stringify(t)}`); } } const t = gen(JET_PEM, 30); const a = [...t.matchAll(RE_010)].map((m) => [m.index, m[1], m[2]]); const b = [...PN.blocuriPem(t)].map((x) => [x.index, x.tip, x.corp]); comparate++; if (JSON.stringify(a) !== JSON.stringify(b)) { cat.pem = (cat.pem || 0) + 1; if (dif++ < 5) console.log(`DIFERIT pem: ${JSON.stringify(t)}`); } } console.log(`texte generate: ${ITER} runde (lexer x4 + PEM), diferente ${JSON.stringify(cat)}`); } finally { rmSync(vechi, { recursive: true, force: true }); } console.log(`\n${dif ? 'DIFERIT' : 'IDENTIC'}: ${dif} diferente in ${comparate} comparatii (0.1.0 din ${REV} fata de 0.2.0)`); process.exitCode = dif ? 1 : comparate ? 0 : 2;