Source code

Revision control

Copy as Markdown

Other Tools

// META: global=window,worker
"use strict";
// The zstd-compressed bytes for the string "expected output"
// where the optional checksum value has been included in the data.
//
// Each section of the data is labeled according to RFC 8878.
const compressedZstdBytesWithChecksum = new Uint8Array([
// Section 3.1.1 - Magic Number (4 bytes)
0x28, 0xb5, 0x2f, 0xfd,
// Section 3.1.1.1 - Frame Header (2 to 14 bytes)
0x04, // Section 3.1.1.1.1 - Frame Header Descriptor
0x58, // Section 3.1.1.1.2 - Window Descriptor
0x79, 0x00, // Section 3.1.1.1.3 - Dictionary ID
// Section 3.1.1.2 - Block Data (16 bytes)
0x00, 0x65, 0x78, 0x70,
0x65, 0x63, 0x74, 0x65,
0x64, 0x20, 0x6f, 0x75,
0x74, 0x70, 0x75, 0x74,
// Section 3.1.1 - Content Checksum (4 bytes)
0x5b, 0x11, 0xc6, 0x85,
]);
// The zstd-compressed bytes for the string "expected output",
// where the optional checksum value has been omitted from the data.
//
// Each section of the data is labeled according to RFC 8878.
const compressedZstdBytesNoChecksum = new Uint8Array([
// Section 3.1.1 - Magic Number (4 bytes)
0x28, 0xb5, 0x2f, 0xfd,
// Section 3.1.1.1 - Frame Header (2 to 14 bytes)
0x00, // Section 3.1.1.1.1 - Frame Header Descriptor
0x58, // Section 3.1.1.1.2 - Window Descriptor
0x79, 0x00, // Section 3.1.1.1.3 - Dictionary ID
// Section 3.1.1.2 - Block Data (16 bytes)
0x00, 0x65, 0x78, 0x70,
0x65, 0x63, 0x74, 0x65,
0x64, 0x20, 0x6f, 0x75,
0x74, 0x70, 0x75, 0x74,
]);
// We define all tests with fields:
//
// - name: descriptive string
// - type: "unchanged", "extendStart", "extendEnd", "truncateStart", "truncateEnd", or "field"
// - removeBytes / extraBytes / offset / length / value as needed
//
// - expectedResultWithChecksum: "success" | "error" | "corrupt"
// - expectedResultNoChecksum: "success" | "error" | "corrupt"
// (if omitted/undefined, the no-checksum path won't be tested)
//
const expectations = [
{
name: "Unchanged",
type: "unchanged",
expectedResultWithChecksum: "success",
expectedResultNoChecksum: "success",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Truncate 1 byte from start",
type: "truncateStart",
removeBytes: 1,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Truncate 1 byte from end",
type: "truncateEnd",
removeBytes: 1,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Truncate 2 bytes from start",
type: "truncateStart",
removeBytes: 2,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Truncate 2 bytes from end",
type: "truncateEnd",
removeBytes: 2,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Extend from start with [0x00]",
type: "extendStart",
extraBytes: [0x00],
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Extend from end with [0x00]",
type: "extendEnd",
extraBytes: [0x00],
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Extend from end [0xff]",
type: "extendEnd",
extraBytes: [0xff],
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Extend from start with [0xff]",
type: "extendStart",
extraBytes: [0xff],
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Extend from end with [0xff]",
type: "extendEnd",
extraBytes: [0xff],
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Extend from start with [0x00, 0x00]",
type: "extendStart",
extraBytes: [0x00, 0x00],
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Extend from end with [0x00, 0x00]",
type: "extendEnd",
extraBytes: [0x00, 0x00],
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Magic Number: offset=0 replace with 0xff",
type: "field",
offset: 0,
length: 1,
value: 0xff,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Magic Number: offset=1 replace with 0xff",
type: "field",
offset: 1,
length: 1,
value: 0xff,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Magic Number: offset=2 replace with 0xff",
type: "field",
offset: 2,
length: 1,
value: 0xff,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Magic Number: offset=3 replace with 0xff",
type: "field",
offset: 3,
length: 1,
value: 0xff,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Frame Header Descriptor: offset=4 replace with 0x05",
type: "field",
offset: 4,
length: 1,
value: 0x05,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Window Descriptor: offset=5 replace with 0xff",
type: "field",
offset: 5,
length: 1,
value: 0xff,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Dictionary ID: offset=6 length=2 replace with 0x00",
type: "field",
offset: 6,
length: 2,
value: 0x00,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "error",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Block Data: offset=18 replace with 0x64",
type: "field",
offset: 18,
length: 1,
value: 0x64,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "corrupt",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
{
name: "Block Data: offset=23 replace with 0x73",
type: "field",
offset: 23,
length: 1,
value: 0x73,
expectedResultWithChecksum: "error",
expectedResultNoChecksum: "corrupt",
compressedZstdBytesWithChecksum,
compressedZstdBytesNoChecksum,
},
// The following test cases mutate the checksum itself, so there are no expected
// results for the no-checksum scenario.
{
name: "Content Checksum: offset=-4 replace with 0x00",
type: "field",
offset: -4,
length: 1,
value: 0x00,
expectedResultWithChecksum: "error",
compressedZstdBytesWithChecksum,
},
{
name: "Content Checksum: offset=-3 replace with 0xff",
type: "field",
offset: -3,
length: 1,
value: 0xff,
expectedResultWithChecksum: "error",
compressedZstdBytesWithChecksum,
},
{
name: "Content Checksum: offset=-2 replace with 0x00",
type: "field",
offset: -2,
length: 1,
value: 0x00,
expectedResultWithChecksum: "error",
compressedZstdBytesWithChecksum,
},
{
name: "Content Checksum: offset=-1 replace with 0xff",
type: "field",
offset: -1,
length: 1,
value: 0xff,
expectedResultWithChecksum: "error",
compressedZstdBytesWithChecksum,
},
];
async function tryDecompress(input) {
const ds = new DecompressionStream("zstd");
const reader = ds.readable.getReader();
const writer = ds.writable.getWriter();
const writePromise = writer.write(input).catch(() => {});
const writerClosePromise = writer.close().catch(() => {});
let out = [];
while (true) {
try {
const { value, done } = await reader.read();
if (done) {
break;
}
out = out.concat(Array.from(value));
} catch (e) {
if (e.name === "TypeError") {
return { result: "error" };
}
return { result: e.name };
}
}
await writePromise;
await writerClosePromise;
const textDecoder = new TextDecoder();
const text = textDecoder.decode(new Uint8Array(out));
if (text !== "expected output") {
return { result: "corrupt" };
}
return { result: "success" };
}
function produceTestInput(testCase, compressedBytes) {
switch (testCase.type) {
case "unchanged":
return compressedBytes;
case "truncateStart": {
return compressedBytes.slice(testCase.removeBytes);
}
case "truncateEnd": {
return compressedBytes.slice(
0,
compressedBytes.length - testCase.removeBytes
);
}
case "extendStart": {
return new Uint8Array([...testCase.extraBytes, ...compressedBytes]);
}
case "extendEnd": {
return new Uint8Array([...compressedBytes, ...testCase.extraBytes]);
}
case "field": {
const output = new Uint8Array(compressedBytes);
let realOffset = testCase.offset;
if (realOffset < 0) {
realOffset = output.length + realOffset;
}
for (let i = 0; i < testCase.length; i++) {
output[realOffset + i] = testCase.value;
}
return output;
}
default: {
throw new Error(`Unknown testCase type: ${testCase.type}`);
}
}
}
for (const testCase of expectations) {
promise_test(async t => {
const inputWithChecksum = produceTestInput(
testCase,
testCase.compressedZstdBytesWithChecksum
);
const { result: resultWithChecksum } =
await tryDecompress(inputWithChecksum);
assert_equals(
resultWithChecksum,
testCase.expectedResultWithChecksum,
`${testCase.name} (with checksum)`
);
let resultNoChecksum;
if (testCase.expectedResultNoChecksum !== undefined) {
const inputNoChecksum = produceTestInput(
testCase,
testCase.compressedZstdBytesNoChecksum
);
const { result } = await tryDecompress(inputNoChecksum);
resultNoChecksum = result;
}
if (testCase.expectedResultNoChecksum !== undefined) {
assert_equals(
resultNoChecksum,
testCase.expectedResultNoChecksum,
`${testCase.name} (no checksum)`
);
}
}, testCase.name);
}