1import { describe, expect, test } from "vitest";2import {3 decodePeachScript,4 encodePeachScript,5 isPeachScript,6 normalisePeachScript,7} from "./peachScript";8import { IMPORT_LIMITS } from "../shared/import-limits";910describe("peachScript decode size bound (PB-26-010)", () => {11 test("decode rejects input above MAX_JSON_BYTES before allocating", () => {12 13 14 const chunk = String.fromCodePoint(0xe000).repeat(1024);15 const tooLarge = chunk.repeat(16 Math.floor(IMPORT_LIMITS.MAX_JSON_BYTES / 1024) + 1,17 );18 expect(tooLarge.length).toBeGreaterThan(IMPORT_LIMITS.MAX_JSON_BYTES);19 expect(() => decodePeachScript(tooLarge)).toThrow(20 "PEACHSCRIPT_TOO_LARGE",21 );22 });2324 test("decode accepts input at the bound", () => {25 const atLimit = String.fromCodePoint(26 ...Array.from({ length: 64 }, () => 0xe041),27 );28 expect(decodePeachScript(atLimit).length).toBe(64);29 });30});3132describe("peachScript encode size bound (PB-26-113)", () => {33 34 35 36 const encodedFileBytes = (bytes: Uint8Array) =>37 new TextEncoder().encode(encodePeachScript(bytes)).length;3839 test("an envelope just above the import ceiling fails at export", () => {40 const overCeiling = Math.floor(IMPORT_LIMITS.MAX_JSON_BYTES / 3) + 1;41 const bytes = new Uint8Array(overCeiling);4243 44 expect(bytes.length).toBeLessThan(IMPORT_LIMITS.MAX_JSON_BYTES);45 expect(() => encodePeachScript(bytes)).toThrow("PEACHSCRIPT_TOO_LARGE");46 });4748 test("the encoded file costs three bytes per envelope byte", () => {49 50 51 const probe = new Uint8Array(1024);52 for (let i = 0; i < probe.length; i++) probe[i] = (i * 37 + 13) % 256;53 expect(encodedFileBytes(probe)).toBe(probe.length * 3);54 expect(decodePeachScript(encodePeachScript(probe))).toEqual(probe);55 });5657 test("an envelope at the ceiling exports to a file the importer accepts", () => {58 const atCeiling = Math.floor(IMPORT_LIMITS.MAX_JSON_BYTES / 3);59 const bytes = new Uint8Array(atCeiling);60 for (let i = 0; i < atCeiling; i++) bytes[i] = (i * 37 + 13) % 256;6162 const encoded = encodePeachScript(bytes);63 64 expect(encodedFileBytes(bytes)).toBeLessThanOrEqual(IMPORT_LIMITS.MAX_JSON_BYTES);65 66 67 expect(encoded.length).toBe(atCeiling);68 expect(decodePeachScript(encoded)[atCeiling - 1]).toBe(bytes[atCeiling - 1]);69 });7071 test("a 4 MiB envelope is refused — it would download as ~12 MiB", () => {72 const fourMiB = 4 * 1024 * 1024;73 expect(() => encodePeachScript(new Uint8Array(fourMiB))).toThrow(74 "PEACHSCRIPT_TOO_LARGE",75 );76 });77});7879describe("peachScript", () => {80 test("byte 0 maps to U+E000", () => {81 const out = encodePeachScript(new Uint8Array([0]));82 expect(out.codePointAt(0)).toBe(0xe000);83 });8485 test("byte 255 maps to U+E0FF", () => {86 const out = encodePeachScript(new Uint8Array([255]));87 expect(out.codePointAt(0)).toBe(0xe0ff);88 });8990 test("round-trip identity for all 256 byte values", () => {91 const bytes = new Uint8Array(256);92 for (let i = 0; i < 256; i++) bytes[i] = i;93 const encoded = encodePeachScript(bytes);94 const decoded = decodePeachScript(encoded);95 expect(decoded).toEqual(bytes);96 });9798 test("empty bytes round-trip to empty string and back", () => {99 expect(encodePeachScript(new Uint8Array())).toBe("");100 expect(decodePeachScript("")).toEqual(new Uint8Array());101 });102103 test("decode throws on character outside PUA range", () => {104 expect(() => decodePeachScript("hello")).toThrow("INVALID_PEACHSCRIPT_CHARACTER");105 expect(() => decodePeachScript(String.fromCodePoint(0xe100))).toThrow(106 "INVALID_PEACHSCRIPT_CHARACTER"107 );108 expect(() => decodePeachScript(String.fromCodePoint(0xdfff))).toThrow(109 "INVALID_PEACHSCRIPT_CHARACTER"110 );111 });112113 test("random-payload round-trip preserves bytes", () => {114 const payload = crypto.getRandomValues(new Uint8Array(2048));115 const decoded = decodePeachScript(encodePeachScript(payload));116 expect(decoded).toEqual(payload);117 });118119 test("normalisePeachScript strips whitespace", () => {120 const encoded = encodePeachScript(new Uint8Array([1, 2, 3, 4]));121 const wrapped = encoded.slice(0, 2) + "\n " + encoded.slice(2);122 expect(normalisePeachScript(wrapped)).toBe(encoded);123 });124125 test("isPeachScript accepts only PUA strings", () => {126 expect(isPeachScript(encodePeachScript(new Uint8Array([1, 2, 3])))).toBe(true);127 expect(isPeachScript("")).toBe(false);128 expect(isPeachScript("hello")).toBe(false);129 const mixed = encodePeachScript(new Uint8Array([1])) + "x";130 expect(isPeachScript(mixed)).toBe(false);131 });132});133134describe("peachScript fuzz corpus", () => {135 136 test("rejects U+E100 (off-by-one out of range)", () => {137 expect(() => decodePeachScript(String.fromCodePoint(0xe100))).toThrow(138 "INVALID_PEACHSCRIPT_CHARACTER"139 );140 });141142 test("rejects U+E200 (far out of range)", () => {143 expect(() => decodePeachScript(String.fromCodePoint(0xe200))).toThrow(144 "INVALID_PEACHSCRIPT_CHARACTER"145 );146 });147148 149 test("rejects lone low surrogate U+DFFF", () => {150 expect(() => decodePeachScript(String.fromCodePoint(0xdfff))).toThrow(151 "INVALID_PEACHSCRIPT_CHARACTER"152 );153 });154155 test("rejects lone high surrogate U+D800", () => {156 expect(() => decodePeachScript(String.fromCodePoint(0xd800))).toThrow(157 "INVALID_PEACHSCRIPT_CHARACTER"158 );159 });160161 162 test("rejects U+1D2E0 (real Mayan numeral — outside codec range)", () => {163 expect(() => decodePeachScript("\u{1D2E0}")).toThrow(164 "INVALID_PEACHSCRIPT_CHARACTER"165 );166 });167168 169 test("rejects BOM (U+FEFF) mid-stream", () => {170 const encoded = encodePeachScript(new Uint8Array([0x41, 0x42]));171 172 const withBOM = encoded[0] + String.fromCodePoint(0xfeff) + encoded[1];173 expect(() => decodePeachScript(withBOM)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");174 });175176 test("rejects RTL mark (U+200F) mid-stream", () => {177 const encoded = encodePeachScript(new Uint8Array([0x41]));178 179 expect(() => decodePeachScript(encoded + String.fromCodePoint(0x200f))).toThrow(180 "INVALID_PEACHSCRIPT_CHARACTER"181 );182 });183184 test("rejects zero-width non-joiner (U+200C)", () => {185 const encoded = encodePeachScript(new Uint8Array([0x41]));186 187 expect(() => decodePeachScript(encoded + String.fromCodePoint(0x200c))).toThrow(188 "INVALID_PEACHSCRIPT_CHARACTER"189 );190 });191192 193 test("rejects space between valid rune characters", () => {194 const encoded = encodePeachScript(new Uint8Array([0x41, 0x42]));195 const withSpace = encoded[0] + " " + encoded[1];196 expect(() => decodePeachScript(withSpace)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");197 });198199 test("rejects newline between valid rune characters", () => {200 const encoded = encodePeachScript(new Uint8Array([0x41, 0x42]));201 const withNewline = encoded[0] + "\n" + encoded[1];202 expect(() => decodePeachScript(withNewline)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");203 });204205 206 test("byte 0x00 round-trips correctly", () => {207 const data = new Uint8Array([0x00]);208 expect(decodePeachScript(encodePeachScript(data))).toEqual(data);209 });210211 test("byte 0xFE round-trips correctly", () => {212 const data = new Uint8Array([0xfe]);213 expect(decodePeachScript(encodePeachScript(data))).toEqual(data);214 });215216 test("byte 0xFF round-trips correctly", () => {217 const data = new Uint8Array([0xff]);218 expect(decodePeachScript(encodePeachScript(data))).toEqual(data);219 });220221 222 test("1 KiB deterministic payload round-trips", () => {223 const data = new Uint8Array(1024);224 for (let i = 0; i < 1024; i++) data[i] = (i * 37 + 13) % 256;225 expect(decodePeachScript(encodePeachScript(data))).toEqual(data);226 });227228 229 230 test("rejects large all-invalid input in well under 50 ms", () => {231 const invalid = String.fromCodePoint(0xe100).repeat(10_000);232 const start = Date.now();233 expect(() => decodePeachScript(invalid)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");234 expect(Date.now() - start).toBeLessThan(50);235 });236237 238 test("rejects left double quotation mark (U+201C) prefix", () => {239 const encoded = encodePeachScript(new Uint8Array([0x41]));240 expect(() => decodePeachScript(String.fromCodePoint(0x201c) + encoded)).toThrow(241 "INVALID_PEACHSCRIPT_CHARACTER"242 );243 });244});