#!/usr/bin/env node // Spike S3 (volet flood) · pause()/resume() node-pty sous 20 Mo de débit, sans claude (zéro quota). // Critères : aucune perte (marqueur final reçu, volume >= attendu), 0 octet pendant pause, // process bash survit à 10 s de pause, écho < 100 ms après le flood. import ptyMod from '@homebridge/node-pty-prebuilt-multiarch'; import { execSync } from 'node:child_process'; import { writeFileSync, mkdirSync } from 'node:fs'; import { join } from 'node:path'; const CAPTURES = new URL('./captures/', import.meta.url).pathname; mkdirSync(CAPTURES, { recursive: true }); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // 20 Mo de base64 (lignes de 76 chars) : la conversion LF→CRLF du TTY est prise en compte execSync('head -c 15728640 /dev/urandom | base64 > /tmp/spike-flood.txt && wc -c < /tmp/spike-flood.txt'); const fileBytes = parseInt(execSync('wc -c < /tmp/spike-flood.txt').toString().trim(), 10); const fileLines = parseInt(execSync('wc -l < /tmp/spike-flood.txt').toString().trim(), 10); const expectedMin = fileBytes; // reçu = fileBytes + fileLines (CR ajoutés) ; on vérifie >= fileBytes const p = ptyMod.spawn('bash', ['--norc'], { name: 'xterm-256color', cols: 120, rows: 32, cwd: '/tmp', env: process.env }); let received = 0; let buf = ''; let pausedBytes = 0; let paused = false; p.onData((d) => { received += Buffer.byteLength(d); if (paused) pausedBytes += Buffer.byteLength(d); buf = (buf + d).slice(-4000); }); const results = {}; const t0 = Date.now(); await sleep(500); p.write('cat /tmp/spike-flood.txt; echo FLOOD-DONE-MARKER\r'); // Après ~2 Mo reçus : pause 10 s while (received < 2 * 1024 * 1024 && Date.now() - t0 < 60000) await sleep(50); const beforePause = received; p.pause(); paused = true; await sleep(10000); p.resume(); paused = false; results.pause = { beforePause, bytesDuringPause: pausedBytes, pauseHeld: pausedBytes < 65536 }; // Attendre la fin du flood const tFlood = Date.now(); while (!buf.includes('FLOOD-DONE-MARKER') && Date.now() - tFlood < 120000) await sleep(100); results.flood = { fileBytes, fileLines, received, receivedAtLeastFile: received >= expectedMin, markerReceived: buf.includes('FLOOD-DONE-MARKER'), totalMs: Date.now() - t0, }; // Écho après flood : write d'un marqueur, mesure du round-trip await sleep(500); const echoT0 = process.hrtime.bigint(); p.write('echo ECHO-RT-TEST'); let echoMs = null; const tEcho = Date.now(); while (Date.now() - tEcho < 5000) { if (buf.includes('ECHO-RT-TEST')) { echoMs = Number(process.hrtime.bigint() - echoT0) / 1e6; break; } await sleep(5); } results.echo = { echoMs, under100ms: echoMs !== null && echoMs < 100 }; p.kill(); writeFileSync(join(CAPTURES, 'flood-results.json'), JSON.stringify(results, null, 1)); console.log(JSON.stringify(results, null, 1));