@webbuf/webbuf
Version:
Rust/WASM optimized buffers for the web, node.js, deno, and bun.
562 lines (469 loc) • 17.1 kB
text/typescript
/**
* Audit tests for @webbuf/webbuf
*
* These tests verify that WebBuf encoding/decoding matches standard implementations.
* Reference implementations:
* - Hex: Node.js Buffer (or manual byte-by-byte verification)
* - Base64: Web standard atob/btoa
* - UTF-8: Web standard TextEncoder/TextDecoder
*/
import { describe, it, expect } from "vitest";
import { WebBuf } from "../src/webbuf.js";
describe("Audit: Hex Encoding", () => {
describe("comparison with standard implementation", () => {
it("should match manual hex encoding for all byte values 0x00-0xff", () => {
// Create buffer with all possible byte values
const allBytes = new Uint8Array(256);
for (let i = 0; i < 256; i++) {
allBytes[i] = i;
}
const webBuf = WebBuf.fromUint8Array(allBytes);
const hex = webBuf.toHex();
// Verify each byte is correctly encoded
for (let i = 0; i < 256; i++) {
const expected = i.toString(16).padStart(2, "0");
const actual = hex.slice(i * 2, i * 2 + 2);
expect(actual).toBe(expected);
}
});
it("should correctly decode hex back to original bytes", () => {
// Build expected hex string manually
let expectedHex = "";
for (let i = 0; i < 256; i++) {
expectedHex += i.toString(16).padStart(2, "0");
}
const decoded = WebBuf.fromHex(expectedHex);
// Verify each byte
for (let i = 0; i < 256; i++) {
expect(decoded[i]).toBe(i);
}
});
it("should handle uppercase hex input", () => {
const uppercase = "DEADBEEF";
const lowercase = "deadbeef";
const fromUpper = WebBuf.fromHex(uppercase);
const fromLower = WebBuf.fromHex(lowercase);
expect(fromUpper.toHex()).toBe(lowercase);
expect(fromLower.toHex()).toBe(lowercase);
expect(fromUpper.equals(fromLower)).toBe(true);
});
it("should handle mixed case hex input", () => {
const mixed = "DeAdBeEf";
const decoded = WebBuf.fromHex(mixed);
expect(decoded.toHex()).toBe("deadbeef");
});
});
describe("known test vectors", () => {
const hexVectors = [
{ bytes: [], hex: "" },
{ bytes: [0], hex: "00" },
{ bytes: [255], hex: "ff" },
{ bytes: [0, 0, 0, 0], hex: "00000000" },
{ bytes: [255, 255, 255, 255], hex: "ffffffff" },
{ bytes: [0xde, 0xad, 0xbe, 0xef], hex: "deadbeef" },
{ bytes: [0xca, 0xfe, 0xba, 0xbe], hex: "cafebabe" },
{ bytes: [1, 2, 3, 4, 5], hex: "0102030405" },
{
bytes: [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff],
hex: "00112233445566778899aabbccddeeff",
},
];
for (const { bytes, hex } of hexVectors) {
it(`should encode ${JSON.stringify(bytes)} to "${hex}"`, () => {
const buf = WebBuf.fromArray(bytes);
expect(buf.toHex()).toBe(hex);
});
it(`should decode "${hex}" to ${JSON.stringify(bytes)}`, () => {
const buf = WebBuf.fromHex(hex);
expect(Array.from(buf)).toEqual(bytes);
});
}
});
describe("edge cases", () => {
it("should handle empty hex string", () => {
const buf = WebBuf.fromHex("");
expect(buf.length).toBe(0);
expect(buf.toHex()).toBe("");
});
it("should reject odd-length hex string", () => {
expect(() => WebBuf.fromHex("abc")).toThrow();
});
it("should handle large buffers (1MB)", () => {
const size = 1024 * 1024; // 1MB
const buf = WebBuf.alloc(size);
for (let i = 0; i < size; i++) {
buf[i] = i % 256;
}
const hex = buf.toHex();
expect(hex.length).toBe(size * 2);
const decoded = WebBuf.fromHex(hex);
expect(decoded.length).toBe(size);
expect(decoded.equals(buf)).toBe(true);
});
});
describe("round-trip verification", () => {
it("should round-trip random data of various sizes", () => {
const sizes = [0, 1, 2, 3, 15, 16, 17, 31, 32, 33, 63, 64, 65, 100, 1000, 10000];
for (const size of sizes) {
const original = WebBuf.alloc(size);
crypto.getRandomValues(original);
const hex = original.toHex();
const decoded = WebBuf.fromHex(hex);
expect(decoded.equals(original)).toBe(true);
}
});
});
});
describe("Audit: Base64 Encoding", () => {
describe("comparison with Web standard atob/btoa", () => {
it("should match btoa for ASCII strings", () => {
const testStrings = [
"",
"a",
"ab",
"abc",
"abcd",
"Hello, World!",
"The quick brown fox jumps over the lazy dog",
];
for (const str of testStrings) {
const buf = WebBuf.fromUtf8(str);
const webBufBase64 = buf.toBase64();
const standardBase64 = btoa(str);
expect(webBufBase64).toBe(standardBase64);
}
});
it("should match atob for decoding", () => {
const testBase64 = [
"",
"YQ==",
"YWI=",
"YWJj",
"YWJjZA==",
"SGVsbG8sIFdvcmxkIQ==",
];
for (const b64 of testBase64) {
if (b64 === "") {
const webBufDecoded = WebBuf.fromBase64(b64);
expect(webBufDecoded.length).toBe(0);
continue;
}
const webBufDecoded = WebBuf.fromBase64(b64);
const standardDecoded = atob(b64);
expect(webBufDecoded.toUtf8()).toBe(standardDecoded);
}
});
it("should handle binary data that btoa cannot handle", () => {
// btoa only works with Latin-1, but WebBuf should handle any bytes
const binaryData = WebBuf.alloc(256);
for (let i = 0; i < 256; i++) {
binaryData[i] = i;
}
const base64 = binaryData.toBase64();
const decoded = WebBuf.fromBase64(base64);
expect(decoded.equals(binaryData)).toBe(true);
});
});
describe("RFC 4648 test vectors", () => {
// From RFC 4648 Section 10
const rfc4648Vectors = [
{ input: "", base64: "" },
{ input: "f", base64: "Zg==" },
{ input: "fo", base64: "Zm8=" },
{ input: "foo", base64: "Zm9v" },
{ input: "foob", base64: "Zm9vYg==" },
{ input: "fooba", base64: "Zm9vYmE=" },
{ input: "foobar", base64: "Zm9vYmFy" },
];
for (const { input, base64 } of rfc4648Vectors) {
it(`should encode "${input}" to "${base64}" (RFC 4648)`, () => {
const buf = WebBuf.fromUtf8(input);
expect(buf.toBase64()).toBe(base64);
});
it(`should decode "${base64}" to "${input}" (RFC 4648)`, () => {
const buf = WebBuf.fromBase64(base64);
expect(buf.toUtf8()).toBe(input);
});
}
});
describe("padding cases", () => {
it("should handle 0 padding (input length divisible by 3)", () => {
const buf = WebBuf.fromUtf8("abc"); // 3 bytes -> 4 base64 chars, no padding
expect(buf.toBase64()).toBe("YWJj");
});
it("should handle 1 padding char (input length % 3 == 2)", () => {
const buf = WebBuf.fromUtf8("ab"); // 2 bytes -> 3 base64 chars + 1 padding
expect(buf.toBase64()).toBe("YWI=");
});
it("should handle 2 padding chars (input length % 3 == 1)", () => {
const buf = WebBuf.fromUtf8("a"); // 1 byte -> 2 base64 chars + 2 padding
expect(buf.toBase64()).toBe("YQ==");
});
});
describe("edge cases", () => {
it("should handle empty base64 string", () => {
const buf = WebBuf.fromBase64("");
expect(buf.length).toBe(0);
expect(buf.toBase64()).toBe("");
});
it("should handle large buffers (1MB)", () => {
const size = 1024 * 1024; // 1MB
const buf = WebBuf.alloc(size);
for (let i = 0; i < size; i++) {
buf[i] = i % 256;
}
const base64 = buf.toBase64();
const decoded = WebBuf.fromBase64(base64);
expect(decoded.length).toBe(size);
expect(decoded.equals(buf)).toBe(true);
});
it("should handle whitespace when stripWhitespace=true", () => {
const base64WithWhitespace = " SGVs\nbG8s\tIFdv\rcmxkIQ== ";
const decoded = WebBuf.fromBase64(base64WithWhitespace, true);
expect(decoded.toUtf8()).toBe("Hello, World!");
});
});
describe("round-trip verification", () => {
it("should round-trip random data of various sizes", () => {
const sizes = [0, 1, 2, 3, 4, 5, 6, 15, 16, 17, 100, 1000, 10000];
for (const size of sizes) {
const original = WebBuf.alloc(size);
crypto.getRandomValues(original);
const base64 = original.toBase64();
const decoded = WebBuf.fromBase64(base64);
expect(decoded.equals(original)).toBe(true);
}
});
});
});
describe("Audit: UTF-8 Encoding", () => {
describe("comparison with TextEncoder/TextDecoder", () => {
it("should match TextEncoder for encoding", () => {
const encoder = new TextEncoder();
const testStrings = [
"",
"Hello",
"Hello, World!",
"αβγδ", // Greek
"你好世界", // Chinese
"🎉🚀💻", // Emojis
"Mixed: Hello 你好 🎉",
"\u0000\u0001\u0002", // Control characters
"Line1\nLine2\rLine3\r\n", // Newlines
];
for (const str of testStrings) {
const webBufEncoded = WebBuf.fromUtf8(str);
const standardEncoded = encoder.encode(str);
expect(webBufEncoded.length).toBe(standardEncoded.length);
for (let i = 0; i < webBufEncoded.length; i++) {
expect(webBufEncoded[i]).toBe(standardEncoded[i]);
}
}
});
it("should match TextDecoder for decoding", () => {
const decoder = new TextDecoder();
// Test with known UTF-8 byte sequences
const testCases = [
{ bytes: [], expected: "" },
{ bytes: [0x48, 0x65, 0x6c, 0x6c, 0x6f], expected: "Hello" }, // ASCII
{ bytes: [0xce, 0xb1, 0xce, 0xb2], expected: "αβ" }, // Greek (2-byte UTF-8)
{ bytes: [0xe4, 0xb8, 0xad, 0xe6, 0x96, 0x87], expected: "中文" }, // Chinese (3-byte UTF-8)
{ bytes: [0xf0, 0x9f, 0x8e, 0x89], expected: "🎉" }, // Emoji (4-byte UTF-8)
];
for (const { bytes, expected } of testCases) {
const buf = WebBuf.fromArray(bytes);
const webBufDecoded = buf.toUtf8();
const standardDecoded = decoder.decode(new Uint8Array(bytes));
expect(webBufDecoded).toBe(standardDecoded);
expect(webBufDecoded).toBe(expected);
}
});
});
describe("special characters", () => {
it("should handle null bytes", () => {
const str = "a\u0000b";
const buf = WebBuf.fromUtf8(str);
expect(buf.toUtf8()).toBe(str);
expect(buf.length).toBe(3);
});
it("should handle all ASCII printable characters", () => {
let ascii = "";
for (let i = 32; i < 127; i++) {
ascii += String.fromCharCode(i);
}
const buf = WebBuf.fromUtf8(ascii);
expect(buf.toUtf8()).toBe(ascii);
expect(buf.length).toBe(95); // 127 - 32
});
it("should handle multi-byte UTF-8 sequences", () => {
const testCases = [
{ char: "é", bytes: 2 }, // Latin-1 supplement
{ char: "α", bytes: 2 }, // Greek
{ char: "中", bytes: 3 }, // CJK
{ char: "🎉", bytes: 4 }, // Emoji
];
for (const { char, bytes } of testCases) {
const buf = WebBuf.fromUtf8(char);
expect(buf.length).toBe(bytes);
expect(buf.toUtf8()).toBe(char);
}
});
});
describe("edge cases", () => {
it("should handle empty string", () => {
const buf = WebBuf.fromUtf8("");
expect(buf.length).toBe(0);
expect(buf.toUtf8()).toBe("");
});
it("should handle very long strings", () => {
const longString = "x".repeat(100000);
const buf = WebBuf.fromUtf8(longString);
expect(buf.length).toBe(100000);
expect(buf.toUtf8()).toBe(longString);
});
it("should handle strings with only emoji", () => {
const emojis = "🎉🚀💻🔥✨";
const buf = WebBuf.fromUtf8(emojis);
expect(buf.toUtf8()).toBe(emojis);
});
});
describe("round-trip verification", () => {
it("should round-trip various Unicode strings", () => {
const testStrings = [
"Simple ASCII",
"Ümläuts änd áccénts",
"日本語テスト",
"العربية",
"עברית",
"Ελληνικά",
"한국어",
"🇺🇸🇬🇧🇫🇷🇩🇪🇯🇵",
"Mixed: ASCII, 中文, العربية, 🎉",
];
for (const str of testStrings) {
const buf = WebBuf.fromUtf8(str);
const decoded = buf.toUtf8();
expect(decoded).toBe(str);
}
});
});
});
describe("Audit: Additional WebBuf Methods", () => {
describe("concat", () => {
it("should concatenate multiple buffers correctly", () => {
const a = WebBuf.fromHex("0102");
const b = WebBuf.fromHex("0304");
const c = WebBuf.fromHex("05");
const result = WebBuf.concat([a, b, c]);
expect(result.toHex()).toBe("0102030405");
});
it("should handle empty array", () => {
const result = WebBuf.concat([]);
expect(result.length).toBe(0);
});
it("should handle single buffer", () => {
const a = WebBuf.fromHex("deadbeef");
const result = WebBuf.concat([a]);
expect(result.toHex()).toBe("deadbeef");
});
it("should handle empty buffers in array", () => {
const a = WebBuf.fromHex("");
const b = WebBuf.fromHex("0102");
const c = WebBuf.fromHex("");
const result = WebBuf.concat([a, b, c]);
expect(result.toHex()).toBe("0102");
});
});
describe("alloc and fill", () => {
it("should allocate buffer filled with zeros by default", () => {
const buf = WebBuf.alloc(10);
expect(buf.length).toBe(10);
for (let i = 0; i < 10; i++) {
expect(buf[i]).toBe(0);
}
});
it("should allocate buffer filled with specified value", () => {
const buf = WebBuf.alloc(10, 0xff);
expect(buf.length).toBe(10);
for (let i = 0; i < 10; i++) {
expect(buf[i]).toBe(0xff);
}
});
it("should fill buffer with specified value", () => {
const buf = WebBuf.alloc(10);
buf.fill(0xab);
for (let i = 0; i < 10; i++) {
expect(buf[i]).toBe(0xab);
}
});
it("should fill buffer with range", () => {
const buf = WebBuf.alloc(10, 0x00);
buf.fill(0xff, 2, 8);
expect(buf.toHex()).toBe("0000ffffffffffff0000");
});
});
describe("equals and compare", () => {
it("should correctly compare equal buffers", () => {
const a = WebBuf.fromHex("deadbeef");
const b = WebBuf.fromHex("deadbeef");
expect(a.equals(b)).toBe(true);
expect(a.compare(b)).toBe(0);
});
it("should correctly compare different buffers", () => {
const a = WebBuf.fromHex("deadbeef");
const b = WebBuf.fromHex("cafebabe");
expect(a.equals(b)).toBe(false);
expect(a.compare(b)).not.toBe(0);
});
it("should correctly compare buffers of different lengths", () => {
const short = WebBuf.fromHex("dead");
const long = WebBuf.fromHex("deadbeef");
expect(short.equals(long)).toBe(false);
expect(short.compare(long)).toBe(-1);
expect(long.compare(short)).toBe(1);
});
it("should correctly compare empty buffers", () => {
const a = WebBuf.alloc(0);
const b = WebBuf.alloc(0);
expect(a.equals(b)).toBe(true);
expect(a.compare(b)).toBe(0);
});
});
describe("clone and copy", () => {
it("should create independent clone", () => {
const original = WebBuf.fromHex("deadbeef");
const cloned = original.clone();
expect(cloned.equals(original)).toBe(true);
// Modify original, clone should be unaffected
original[0] = 0x00;
expect(cloned[0]).toBe(0xde);
});
it("should copy data correctly", () => {
const source = WebBuf.fromHex("0102030405");
const target = WebBuf.alloc(10);
const copied = source.copy(target, 2);
expect(copied).toBe(5);
expect(target.toHex()).toBe("00000102030405000000");
});
it("should copy partial data", () => {
const source = WebBuf.fromHex("0102030405");
const target = WebBuf.alloc(10);
const copied = source.copy(target, 0, 1, 4);
expect(copied).toBe(3);
expect(target.toHex()).toBe("02030400000000000000");
});
});
describe("reverse", () => {
it("should reverse buffer in place", () => {
const buf = WebBuf.fromHex("01020304");
buf.reverse();
expect(buf.toHex()).toBe("04030201");
});
it("should create reversed copy with toReverse", () => {
const original = WebBuf.fromHex("01020304");
const reversed = original.toReverse();
expect(reversed.toHex()).toBe("04030201");
expect(original.toHex()).toBe("01020304"); // Original unchanged
});
});
});