import { describe, expect, test } from "vitest"; import { decodePeachScript, encodePeachScript, isPeachScript, normalisePeachScript, } from "./peachScript"; import { IMPORT_LIMITS } from "../shared/import-limits"; describe("peachScript decode size bound (PB-26-010)", () => { test("decode rejects input above MAX_JSON_BYTES before allocating", () => { // Build an oversized valid PeachScript string without the spread-arg // limit: repeat a 1K-char chunk past the 10 MiB bound. const chunk = String.fromCodePoint(0xe000).repeat(1024); const tooLarge = chunk.repeat( Math.floor(IMPORT_LIMITS.MAX_JSON_BYTES / 1024) + 1, ); expect(tooLarge.length).toBeGreaterThan(IMPORT_LIMITS.MAX_JSON_BYTES); expect(() => decodePeachScript(tooLarge)).toThrow( "PEACHSCRIPT_TOO_LARGE", ); }); test("decode accepts input at the bound", () => { const atLimit = String.fromCodePoint( ...Array.from({ length: 64 }, () => 0xe041), ); expect(decodePeachScript(atLimit).length).toBe(64); }); }); describe("peachScript", () => { test("byte 0 maps to U+E000", () => { const out = encodePeachScript(new Uint8Array([0])); expect(out.codePointAt(0)).toBe(0xe000); }); test("byte 255 maps to U+E0FF", () => { const out = encodePeachScript(new Uint8Array([255])); expect(out.codePointAt(0)).toBe(0xe0ff); }); test("round-trip identity for all 256 byte values", () => { const bytes = new Uint8Array(256); for (let i = 0; i < 256; i++) bytes[i] = i; const encoded = encodePeachScript(bytes); const decoded = decodePeachScript(encoded); expect(decoded).toEqual(bytes); }); test("empty bytes round-trip to empty string and back", () => { expect(encodePeachScript(new Uint8Array())).toBe(""); expect(decodePeachScript("")).toEqual(new Uint8Array()); }); test("decode throws on character outside PUA range", () => { expect(() => decodePeachScript("hello")).toThrow("INVALID_PEACHSCRIPT_CHARACTER"); expect(() => decodePeachScript(String.fromCodePoint(0xe100))).toThrow( "INVALID_PEACHSCRIPT_CHARACTER" ); expect(() => decodePeachScript(String.fromCodePoint(0xdfff))).toThrow( "INVALID_PEACHSCRIPT_CHARACTER" ); }); test("random-payload round-trip preserves bytes", () => { const payload = crypto.getRandomValues(new Uint8Array(2048)); const decoded = decodePeachScript(encodePeachScript(payload)); expect(decoded).toEqual(payload); }); test("normalisePeachScript strips whitespace", () => { const encoded = encodePeachScript(new Uint8Array([1, 2, 3, 4])); const wrapped = encoded.slice(0, 2) + "\n " + encoded.slice(2); expect(normalisePeachScript(wrapped)).toBe(encoded); }); test("isPeachScript accepts only PUA strings", () => { expect(isPeachScript(encodePeachScript(new Uint8Array([1, 2, 3])))).toBe(true); expect(isPeachScript("")).toBe(false); expect(isPeachScript("hello")).toBe(false); const mixed = encodePeachScript(new Uint8Array([1])) + "x"; expect(isPeachScript(mixed)).toBe(false); }); }); describe("peachScript fuzz corpus", () => { // Off-by-one boundary — U+E100 is one above the valid ceiling U+E0FF test("rejects U+E100 (off-by-one out of range)", () => { expect(() => decodePeachScript(String.fromCodePoint(0xe100))).toThrow( "INVALID_PEACHSCRIPT_CHARACTER" ); }); test("rejects U+E200 (far out of range)", () => { expect(() => decodePeachScript(String.fromCodePoint(0xe200))).toThrow( "INVALID_PEACHSCRIPT_CHARACTER" ); }); // Surrogate code points — below the valid PUA range U+E000..U+E0FF test("rejects lone low surrogate U+DFFF", () => { expect(() => decodePeachScript(String.fromCodePoint(0xdfff))).toThrow( "INVALID_PEACHSCRIPT_CHARACTER" ); }); test("rejects lone high surrogate U+D800", () => { expect(() => decodePeachScript(String.fromCodePoint(0xd800))).toThrow( "INVALID_PEACHSCRIPT_CHARACTER" ); }); // Real Mayan numerals are U+1D2E0..U+1D2F3 — outside PUA codec range test("rejects U+1D2E0 (real Mayan numeral — outside codec range)", () => { expect(() => decodePeachScript("\u{1D2E0}")).toThrow( "INVALID_PEACHSCRIPT_CHARACTER" ); }); // BOM and Unicode direction/zero-width marks mid-stream test("rejects BOM (U+FEFF) mid-stream", () => { const encoded = encodePeachScript(new Uint8Array([0x41, 0x42])); // U+FEFF = BOM, inject between two valid runes const withBOM = encoded[0] + String.fromCodePoint(0xfeff) + encoded[1]; expect(() => decodePeachScript(withBOM)).toThrow("INVALID_PEACHSCRIPT_CHARACTER"); }); test("rejects RTL mark (U+200F) mid-stream", () => { const encoded = encodePeachScript(new Uint8Array([0x41])); // U+200F = RIGHT-TO-LEFT MARK expect(() => decodePeachScript(encoded + String.fromCodePoint(0x200f))).toThrow( "INVALID_PEACHSCRIPT_CHARACTER" ); }); test("rejects zero-width non-joiner (U+200C)", () => { const encoded = encodePeachScript(new Uint8Array([0x41])); // U+200C = ZERO WIDTH NON-JOINER expect(() => decodePeachScript(encoded + String.fromCodePoint(0x200c))).toThrow( "INVALID_PEACHSCRIPT_CHARACTER" ); }); // Whitespace — the raw decoder is strict; normalisation is a separate step test("rejects space between valid rune characters", () => { const encoded = encodePeachScript(new Uint8Array([0x41, 0x42])); const withSpace = encoded[0] + " " + encoded[1]; expect(() => decodePeachScript(withSpace)).toThrow("INVALID_PEACHSCRIPT_CHARACTER"); }); test("rejects newline between valid rune characters", () => { const encoded = encodePeachScript(new Uint8Array([0x41, 0x42])); const withNewline = encoded[0] + "\n" + encoded[1]; expect(() => decodePeachScript(withNewline)).toThrow("INVALID_PEACHSCRIPT_CHARACTER"); }); // Boundary bytes must round-trip correctly test("byte 0x00 round-trips correctly", () => { const data = new Uint8Array([0x00]); expect(decodePeachScript(encodePeachScript(data))).toEqual(data); }); test("byte 0xFE round-trips correctly", () => { const data = new Uint8Array([0xfe]); expect(decodePeachScript(encodePeachScript(data))).toEqual(data); }); test("byte 0xFF round-trips correctly", () => { const data = new Uint8Array([0xff]); expect(decodePeachScript(encodePeachScript(data))).toEqual(data); }); // 1 KiB pseudo-random round-trip test("1 KiB deterministic payload round-trips", () => { const data = new Uint8Array(1024); for (let i = 0; i < 1024; i++) data[i] = (i * 37 + 13) % 256; expect(decodePeachScript(encodePeachScript(data))).toEqual(data); }); // DoS resistance — the decoder must fail on the first invalid character, // not after scanning the whole input. U+E100 > 0xE0FF fails on char 0. test("rejects large all-invalid input in well under 50 ms", () => { const invalid = String.fromCodePoint(0xe100).repeat(10_000); const start = Date.now(); expect(() => decodePeachScript(invalid)).toThrow("INVALID_PEACHSCRIPT_CHARACTER"); expect(Date.now() - start).toBeLessThan(50); }); // Copy-paste hostility — smart quote prefix (U+201C = LEFT DOUBLE QUOTATION MARK) test("rejects left double quotation mark (U+201C) prefix", () => { const encoded = encodePeachScript(new Uint8Array([0x41])); expect(() => decodePeachScript(String.fromCodePoint(0x201c) + encoded)).toThrow( "INVALID_PEACHSCRIPT_CHARACTER" ); }); });