UNPKG

@webbuf/fixedbuf

Version:

Fixed-sized buffers optimized with Rust/WASM for the web, node.js, deno, and bun.

340 lines (283 loc) 10.7 kB
/** * Audit tests for @webbuf/fixedbuf * * These tests verify that FixedBuf correctly enforces size constraints * and properly wraps WebBuf functionality. */ import { describe, it, expect } from "vitest"; import { WebBuf } from "@webbuf/webbuf"; import { FixedBuf } from "../src/fixedbuf.js"; describe("Audit: Size Enforcement", () => { describe("constructor size validation", () => { it("should accept buffer of exact size", () => { const buf = WebBuf.alloc(32); const fixed = new FixedBuf(32, buf); expect(fixed.buf.length).toBe(32); }); it("should reject buffer smaller than specified size", () => { const buf = WebBuf.alloc(16); expect(() => new FixedBuf(32, buf)).toThrow("invalid size error"); }); it("should reject buffer larger than specified size", () => { const buf = WebBuf.alloc(64); expect(() => new FixedBuf(32, buf)).toThrow("invalid size error"); }); it("should accept zero-size buffer when size is 0", () => { const buf = WebBuf.alloc(0); const fixed = new FixedBuf(0, buf); expect(fixed.buf.length).toBe(0); }); }); describe("fromBuf size validation", () => { it("should accept buffer of exact size", () => { const buf = WebBuf.alloc(16); const fixed = FixedBuf.fromBuf(16, buf); expect(fixed.buf.length).toBe(16); }); it("should reject undersized buffer", () => { const buf = WebBuf.alloc(8); expect(() => FixedBuf.fromBuf(16, buf)).toThrow(); }); it("should reject oversized buffer", () => { const buf = WebBuf.alloc(32); expect(() => FixedBuf.fromBuf(16, buf)).toThrow(); }); }); describe("fromHex size validation", () => { it("should accept hex of exact size", () => { const fixed = FixedBuf.fromHex(4, "deadbeef"); // 4 bytes expect(fixed.buf.length).toBe(4); }); it("should reject hex that decodes to wrong size", () => { expect(() => FixedBuf.fromHex(4, "deadbe")).toThrow(); // 3 bytes expect(() => FixedBuf.fromHex(4, "deadbeefca")).toThrow(); // 5 bytes }); it("should accept empty hex for size 0", () => { const fixed = FixedBuf.fromHex(0, ""); expect(fixed.buf.length).toBe(0); }); }); describe("fromBase64 size validation", () => { it("should accept base64 of exact size", () => { const fixed = FixedBuf.fromBase64(4, "3q2+7w=="); // 4 bytes expect(fixed.buf.length).toBe(4); }); it("should reject base64 that decodes to wrong size", () => { expect(() => FixedBuf.fromBase64(4, "3q0=")).toThrow(); // 2 bytes expect(() => FixedBuf.fromBase64(4, "3q2+7w==AA==")).toThrow(); // invalid }); it("should accept empty base64 for size 0", () => { const fixed = FixedBuf.fromBase64(0, ""); expect(fixed.buf.length).toBe(0); }); }); describe("common cryptographic sizes", () => { const sizes = [ { name: "AES-128 key / IV", size: 16 }, { name: "RIPEMD-160 hash", size: 20 }, { name: "AES-192 key", size: 24 }, { name: "AES-256 key / SHA-256 hash / BLAKE3 hash", size: 32 }, { name: "compressed public key", size: 33 }, { name: "ECDSA signature", size: 64 }, { name: "uncompressed public key", size: 65 }, ]; for (const { name, size } of sizes) { it(`should correctly enforce ${name} size (${String(size)} bytes)`, () => { // Exact size should work const exact = WebBuf.alloc(size); const fixed = FixedBuf.fromBuf(size, exact); expect(fixed.buf.length).toBe(size); // One byte less should fail if (size > 0) { const smaller = WebBuf.alloc(size - 1); expect(() => FixedBuf.fromBuf(size, smaller)).toThrow(); } // One byte more should fail const larger = WebBuf.alloc(size + 1); expect(() => FixedBuf.fromBuf(size, larger)).toThrow(); }); } }); }); describe("Audit: fromRandom", () => { describe("produces correct length", () => { const sizes = [0, 1, 8, 16, 20, 24, 32, 33, 64, 65, 128, 256]; for (const size of sizes) { it(`should produce exactly ${String(size)} bytes`, () => { const fixed = FixedBuf.fromRandom(size); expect(fixed.buf.length).toBe(size); expect(fixed._size).toBe(size); }); } }); describe("produces random data", () => { it("should produce different values on each call", () => { const a = FixedBuf.fromRandom(32); const b = FixedBuf.fromRandom(32); const c = FixedBuf.fromRandom(32); // Extremely unlikely to be equal if truly random expect(a.toHex()).not.toBe(b.toHex()); expect(b.toHex()).not.toBe(c.toHex()); expect(a.toHex()).not.toBe(c.toHex()); }); it("should produce non-zero data (statistically)", () => { // Generate a large random buffer and check it's not all zeros const fixed = FixedBuf.fromRandom(1024); let hasNonZero = false; for (const byte of fixed.buf) { if (byte !== 0) { hasNonZero = true; break; } } expect(hasNonZero).toBe(true); }); it("should use crypto.getRandomValues (statistical distribution check)", () => { // Generate many random bytes and check rough distribution const fixed = FixedBuf.fromRandom(10000); const counts = new Array(256).fill(0) as number[]; for (const byte of fixed.buf) { counts[byte] = (counts[byte] ?? 0) + 1; } // Each byte value should appear roughly 10000/256 ≈ 39 times // Allow for significant variance (10-100 range) for (let i = 0; i < 256; i++) { expect(counts[i]).toBeGreaterThan(5); // Very loose lower bound expect(counts[i]).toBeLessThan(100); // Very loose upper bound } }); }); describe("type safety", () => { it("should preserve generic size type", () => { const fixed16 = FixedBuf.fromRandom<16>(16); const fixed32 = FixedBuf.fromRandom<32>(32); expect(fixed16._size).toBe(16); expect(fixed32._size).toBe(32); }); }); }); describe("Audit: alloc", () => { describe("size and fill", () => { it("should allocate with zeros by default", () => { const fixed = FixedBuf.alloc(16); expect(fixed.buf.length).toBe(16); for (let i = 0; i < 16; i++) { expect(fixed.buf[i]).toBe(0); } }); it("should allocate with specified fill value", () => { const fixed = FixedBuf.alloc(16, 0xff); expect(fixed.buf.length).toBe(16); for (let i = 0; i < 16; i++) { expect(fixed.buf[i]).toBe(0xff); } }); it("should handle zero size", () => { const fixed = FixedBuf.alloc(0); expect(fixed.buf.length).toBe(0); }); }); }); describe("Audit: clone and toReverse", () => { describe("clone", () => { it("should create independent copy", () => { const original = FixedBuf.fromHex(4, "deadbeef"); const cloned = original.clone(); expect(cloned.toHex()).toBe("deadbeef"); expect(cloned._size).toBe(4); // Modify original, clone should be unaffected original.buf[0] = 0x00; expect(cloned.buf[0]).toBe(0xde); // Clone is independent expect(original.buf[0]).toBe(0x00); // Original was modified }); it("should preserve size type", () => { const original = FixedBuf.fromRandom<32>(32); const cloned = original.clone(); expect(cloned._size).toBe(32); }); }); describe("toReverse", () => { it("should create reversed copy without modifying original", () => { const original = FixedBuf.fromHex(4, "01020304"); const reversed = original.toReverse(); expect(reversed.toHex()).toBe("04030201"); expect(original.toHex()).toBe("01020304"); // Original unchanged }); it("should preserve size", () => { const original = FixedBuf.fromRandom<32>(32); const reversed = original.toReverse(); expect(reversed._size).toBe(32); }); it("should handle single byte", () => { const original = FixedBuf.fromHex(1, "ab"); const reversed = original.toReverse(); expect(reversed.toHex()).toBe("ab"); }); it("should handle empty buffer", () => { const original = FixedBuf.alloc(0); const reversed = original.toReverse(); expect(reversed.buf.length).toBe(0); }); }); }); describe("Audit: Encoding round-trips", () => { describe("hex round-trip", () => { it("should round-trip through hex encoding", () => { const original = FixedBuf.fromRandom(32); const hex = original.toHex(); const restored = FixedBuf.fromHex(32, hex); expect(restored.toHex()).toBe(original.toHex()); }); it("should handle all byte values", () => { // Create buffer with all possible byte values (for first 32) const buf = WebBuf.alloc(32); for (let i = 0; i < 32; i++) { buf[i] = i * 8; // 0, 8, 16, ... 248 } const fixed = FixedBuf.fromBuf(32, buf); const hex = fixed.toHex(); const restored = FixedBuf.fromHex(32, hex); for (let i = 0; i < 32; i++) { expect(restored.buf[i]).toBe(i * 8); } }); }); describe("base64 round-trip", () => { it("should round-trip through base64 encoding", () => { const original = FixedBuf.fromRandom(32); const base64 = original.toBase64(); const restored = FixedBuf.fromBase64(32, base64); expect(restored.toHex()).toBe(original.toHex()); }); it("should handle various sizes", () => { const sizes = [1, 2, 3, 4, 5, 6, 15, 16, 17, 32, 33, 64]; for (const size of sizes) { const original = FixedBuf.fromRandom(size); const base64 = original.toBase64(); const restored = FixedBuf.fromBase64(size, base64); expect(restored.toHex()).toBe(original.toHex()); } }); }); }); describe("Audit: buf property access", () => { it("should provide access to underlying WebBuf", () => { const fixed = FixedBuf.fromHex(4, "deadbeef"); const buf = fixed.buf; expect(buf).toBeInstanceOf(WebBuf); expect(buf.length).toBe(4); expect(buf.toHex()).toBe("deadbeef"); }); it("should allow reading individual bytes", () => { const fixed = FixedBuf.fromHex(4, "01020304"); expect(fixed.buf[0]).toBe(0x01); expect(fixed.buf[1]).toBe(0x02); expect(fixed.buf[2]).toBe(0x03); expect(fixed.buf[3]).toBe(0x04); }); it("should allow modifying underlying buffer", () => { const fixed = FixedBuf.fromHex(4, "deadbeef"); fixed.buf[0] = 0x00; expect(fixed.toHex()).toBe("00adbeef"); }); });