Menyimpan payload biner atau multi-file di dalam dokumen PDF umum dilakukan dengan memanfaatkan objek /Metadata stream berformat XMP atau stream terenkapsulasi kustom (seperti pendekatan arsitektur pdfx). Namun, struktur format PDF (ISO 32000-1) sangat rentan terhadap desinkronisasi penanda posisi byte. Modifikasi metadata stream sering kali merusak byte offsets pada Cross-Reference Table (XRef) atau memicu kegagalan pembacaan parser akibat transformasi carriage return (CR/LF) yang tidak terduga.

Anatomi Kerentanan Byte Metadata PDF

PDF embedded parser bekerja dengan menyisipkan dictionary object stream yang berisi payload:

12 0 obj
<<
  /Type /Metadata
  /Subtype /XML
  /Length 48
>>
stream
... [raw binary or base64 chunk] ...
endstream
endobj

Risiko kegagalan parsing pada level byte umumnya bersumber dari tiga anomali berikut:

  • CR/LF Normalization: PDF standard mewajibkan keyword stream diikuti langsung oleh carriage return dan line feed (CRLF) atau single newline (LF). Banyak writer library menormalkan sequence 0x0D 0x0A menjadi 0x0A, yang secara instan memotong 1 byte dari payload biner.
  • XRef Offset Shift: Mengubah ukuran metadata stream tanpa merekonstruksi tabel xref atau /XRef stream akan membuat indirect object address tidak valid, mengakibatkan fallback brute-force parser atau kegagalan fatal PDFSyntaxError.
  • Chunking Boundary Glitches: Payload berukuran besar yang dipecah ke dalam multiple metadata stream sering kali kehilangan byte batas (boundary byte) saat disambung kembali (reconstruction), khususnya jika byte tersebut bernilai 0x00 (null byte) atau white-space PDF (0x20, 0x09).

Strategi Property-Based Testing dengan fast-check

Pengujian unit deterministik dengan file PDF statis (fixture) gagal mendeteksi edge case kombinasi byte biner acak. Solusi yang tepat adalah Property-Based Testing (PBT): menghasilkan ratusan permutasi byte payload secara acak dan memverifikasi properti invarian sistem, yaitu Roundtrip Idempotency: extract(inject(pdf, payload)) === payload.

Harness Implementasi TypeScript

Contoh berikut menggunakan library fast-check untuk menguji parser metadata PDF embedded. Harness memvalidasi kestabilan payload acak, integritas boundary chunk, dan penanganan XRef table.

import fc from 'fast-check';
import assert from 'node:assert';

// Representasi parser metadata PDF minimal
interface InjectedPdf {
  buffer: Uint8Array;
  xrefOffset: number;
}

class PdfEmbeddedEngine {
  // Simulasi injeksi payload biner ke dalam format stream PDF
  static injectMetadata(basePdf: Uint8Array, payload: Uint8Array): InjectedPdf {
    const header = Buffer.from(`
99 0 obj
<< /Type /Metadata /Length ${payload.length} >>
stream
`);
    const footer = Buffer.from('
endstream
endobj
');
    
    // Gabungkan base PDF + stream objek baru
    const streamPart = Buffer.concat([header, Buffer.from(payload), footer]);
    const finalBuffer = Buffer.concat([Buffer.from(basePdf), streamPart]);
    
    // Hitung offset objek untuk update XRef entry
    const objectOffset = basePdf.length + 1;
    return {
      buffer: new Uint8Array(finalBuffer),
      xrefOffset: objectOffset,
    };
  }

  // Ekstraksi payload dari stream metadata
  static extractMetadata(pdf: Uint8Array): Uint8Array {
    const buf = Buffer.from(pdf);
    const streamMarker = Buffer.from('stream
');
    const endMarker = Buffer.from('
endstream');

    const startIndex = buf.indexOf(streamMarker);
    if (startIndex === -1) {
      throw new Error('Malformed PDF: stream token not found');
    }
    
    const payloadStart = startIndex + streamMarker.length;
    const payloadEnd = buf.indexOf(endMarker, payloadStart);
    if (payloadEnd === -1) {
      throw new Error('Malformed PDF: endstream token not found or damaged boundary');
    }

    return new Uint8Array(buf.subarray(payloadStart, payloadEnd));
  }
}

// Runnable Test Harness: Property-Based Verification
export function runPdfPropertyTests() {
  const minimalValidPdf = Buffer.from('%PDF-1.7
1 0 obj
<< /Type /Catalog >>
endobj
');

  fc.assert(
    fc.property(
      // Generator: Payload biner acak mencakup null byte, whitespace, dan binary murni
      fc.uint8Array({ minLength: 0, maxLength: 65536 }),
      (payload) => {
        // 1. Injeksi metadata ke PDF
        const { buffer } = PdfEmbeddedEngine.injectMetadata(minimalValidPdf, payload);

        // 2. Ekstraksi kembali
        const extracted = PdfEmbeddedEngine.extractMetadata(buffer);

        // 3. Properti Invarian: Payload hasil ekstraksi harus identik per-byte
        assert.strictEqual(
          Buffer.from(extracted).compare(Buffer.from(payload)),
          0,
          'Payload biner terkorupsi saat siklus injeksi/ekstraksi'
        );
      }
    ),
    { numRuns: 500 }
  );

  console.log('✓ Property-based test passed: Roundtrip byte metadata valid untuk 500 iterasi.');
}

runPdfPropertyTests();

Pengujian Boundary Edge Cases

Selain verifikasi invarian roundtrip, parser harus diuji terhadap boundary cases yang sering merusak state machine tokenizer PDF:

1. Malformed Endstream Token

Beberapa writer mengekspor PDF tanpa pemisah karakter newline sebelum token endstream (misal langsung <byte>endstream alih-alih endstream). Test harness harus memastikan parser mengecek offset via /Length entry jika tersedia, bukan hanya mengandalkan pencarian delimiter string sederhana.

2. Chunk Boundary Splitting

Ketika payload dipecah menjadi chunks sebesar batas tertentu (misal 32KB per object stream untuk kompatibilitas reader lawas), pastikan bahwa edge case potongan yang jatuh tepat di antara delimiter multi-byte (seperti ) tidak terpotong dua kali atau ter-escape ganda.

3. Desinkronisasi Cross-Reference (XRef)

Skenario pengujian kegagalan: Berikan payload valid namun sengaja korupsi entry byte offset pada tabel XRef. Parser yang robust harus melemparkan custom error yang dapat diidentifikasi (misal CorruptXRefTableException) dan tidak jatuh ke kondisi infinite loop atau silent failure.

Trade-off: Memvalidasi kesesuaian spec ISO 32000-1 secara penuh membutuhkan overhead komputasi tinggi pada test runner. Pendekatan isolasi pengujian pada level stream dictionary metadata (seperti pada harness di atas) memberikan rasio coverage-to-speed optimal untuk pipeline CI/CD tanpa perlu menjalankan full-rendering engine seperti PDF.js.