// Proba de cost: inventarul trebuie sa ramana LINIAR pe fisiere facute anume (2026-09-29, revizuirea adversariala). Formele vechi // ale cautarii PEM, ale literalului regex JS, ale docstring-ului Python, ale rezolvarii variabilelor si ale variabilei atribuite // in Java costau patratic: un singur fisier de 1 MB (marimea maxima implicita) tinea scanarea minute intregi. // Fiecare caz se scaneaza la doua marimi, a doua de 4 ori prima (64 si 256 KB; PEM 256 KB si 1 MB), fiecare intr-un proces // separat cu termen; liniar inseamna ~4x, patratic ~16x. Verdict pe caz: VERDE daca t2 <= 6 * t1 + 250 ms si t2 < 10 s; ROSU // altfel (si la depasirea termenului). // node test/proba-timp.mjs [--doar caz1,caz2] -> 0 toate verzi, 1 unul rosu, 2 STRICAT (o masurare nu a pornit) // Modulul masurat se schimba cu CRYPTO_INVENTORY_MODULE=/inventar.mjs (asa il foloseste controlul negativ). import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'; import { join, resolve, 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 CALE_MODUL = process.env.CRYPTO_INVENTORY_MODULE ? resolve(process.env.CRYPTO_INVENTORY_MODULE) : join(AICI, '..', 'inventar.mjs'); const TERMEN_MS = 20000; // unitatea se repeta pana la marime; '@' se inlocuieste cu un contor (nume distincte) export const CAZURI = { // pem: cautarea sfarsitului e nativa (indexOf), deci forma patratica se vede abia spre marimea maxima implicita 'pem.txt': ['-----BEGIN CERTIFICATE-----AAAA\n', 256, 1024], 'py-triplu.py': 'x="""a"""+', 'py-linie.py': 'hashlib.new(x)+', 'js-regex.js': '=/[', 'js-linie.js': 'crypto.createHash(x)+', 'js-func.js': 'crypto.createHash(q)\nfunction (', 'java-linie.java': 'Cipher.getInstance(x)+', 'java-kpg.java': 'k = KeyPairGenerator.getInstance("EC")+', }; const marimi = (nume) => (Array.isArray(CAZURI[nume]) ? CAZURI[nume].slice(1) : [64, 256]); const unitate = (nume) => (Array.isArray(CAZURI[nume]) ? CAZURI[nume][0] : CAZURI[nume]); const genereaza = (unit, kb) => unit.repeat(Math.max(1, Math.floor((kb * 1024) / unit.length))); const arg = (f) => { const i = process.argv.indexOf(f); return i >= 0 ? process.argv[i + 1] : null; }; if (arg('--caz')) { // copilul: scaneaza un singur fisier si tipareste milisecundele const nume = arg('--caz'); const kb = Number(arg('--kb')); const inv = await import(pathToFileURL(CALE_MODUL).href); const d = mkdtempSync(join(tmpdir(), 'ci-timp-')); try { writeFileSync(join(d, nume), genereaza(unitate(nume), kb)); const t0 = process.hrtime.bigint(); const rez = inv.scan(d); const ms = Number(process.hrtime.bigint() - t0) / 1e6; if (rez.stats.filesSeen !== 1 || rez.stats.codeFilesTotal + rez.stats.textFilesPemOnly !== 1) throw new Error('fisierul nu a fost citit'); console.log(JSON.stringify({ ms })); } finally { rmSync(d, { recursive: true, force: true }); } } else { const doar = arg('--doar') ? arg('--doar').split(',') : Object.keys(CAZURI); const masoara = (nume, kb) => { const r = spawnSync(process.execPath, [fileURLToPath(import.meta.url), '--caz', nume, '--kb', String(kb)], { encoding: 'utf8', timeout: TERMEN_MS, env: process.env }); if (r.error && r.error.code === 'ETIMEDOUT') return { termen: true }; try { return { ms: JSON.parse((r.stdout || '').trim().split('\n').pop()).ms }; } catch { return { stricat: `cod ${r.status}: ${(r.stderr || '').trim().split('\n').slice(-1)[0]}` }; } }; let rosii = 0, stricate = 0; for (const nume of doar) { if (!CAZURI[nume]) { stricate++; console.log(`STRICAT ${nume}: caz necunoscut`); continue; } const [k1, k2] = marimi(nume); const a = masoara(nume, k1); const b = a.ms !== undefined ? masoara(nume, k2) : a.termen ? {} : { stricat: `nemasurat (${k1} KB a cazut)` }; if (a.stricat || b.stricat) { stricate++; console.log(`STRICAT ${nume}: ${a.stricat || b.stricat}`); continue; } if (a.termen || b.termen) { rosii++; console.log(`ROSU ${nume}: peste ${TERMEN_MS / 1000} s la ${a.termen ? k1 : k2} KB`); continue; } const ok = b.ms <= 6 * a.ms + 250 && b.ms < 10000; if (!ok) rosii++; console.log(`${ok ? 'VERDE ' : 'ROSU '} ${nume.padEnd(16)} ${String(k1).padStart(4)} KB ${a.ms.toFixed(0).padStart(6)} ms | ${String(k2).padStart(4)} KB ${b.ms.toFixed(0).padStart(6)} ms x${(b.ms / Math.max(a.ms, 1)).toFixed(1)}`); } console.log(`\ncostul inventarului: ${doar.length - rosii - stricate}/${doar.length} liniare, ${rosii} rosii, ${stricate} stricate (modul: ${CALE_MODUL})`); process.exitCode = stricate ? 2 : rosii ? 1 : 0; }