decompression-corrupt-input.tentative.any.js (11405B)
1 // META: global=window,worker 2 3 "use strict"; 4 5 // The zstd-compressed bytes for the string "expected output" 6 // where the optional checksum value has been included in the data. 7 // 8 // Each section of the data is labeled according to RFC 8878. 9 // https://datatracker.ietf.org/doc/html/rfc8878 10 const compressedZstdBytesWithChecksum = new Uint8Array([ 11 // Section 3.1.1 - Magic Number (4 bytes) 12 0x28, 0xb5, 0x2f, 0xfd, 13 // Section 3.1.1.1 - Frame Header (2 to 14 bytes) 14 0x04, // Section 3.1.1.1.1 - Frame Header Descriptor 15 0x58, // Section 3.1.1.1.2 - Window Descriptor 16 0x79, 0x00, // Section 3.1.1.1.3 - Dictionary ID 17 // Section 3.1.1.2 - Block Data (16 bytes) 18 0x00, 0x65, 0x78, 0x70, 19 0x65, 0x63, 0x74, 0x65, 20 0x64, 0x20, 0x6f, 0x75, 21 0x74, 0x70, 0x75, 0x74, 22 // Section 3.1.1 - Content Checksum (4 bytes) 23 0x5b, 0x11, 0xc6, 0x85, 24 ]); 25 26 // The zstd-compressed bytes for the string "expected output", 27 // where the optional checksum value has been omitted from the data. 28 // 29 // Each section of the data is labeled according to RFC 8878. 30 // https://datatracker.ietf.org/doc/html/rfc8878 31 const compressedZstdBytesNoChecksum = new Uint8Array([ 32 // Section 3.1.1 - Magic Number (4 bytes) 33 0x28, 0xb5, 0x2f, 0xfd, 34 // Section 3.1.1.1 - Frame Header (2 to 14 bytes) 35 0x00, // Section 3.1.1.1.1 - Frame Header Descriptor 36 0x58, // Section 3.1.1.1.2 - Window Descriptor 37 0x79, 0x00, // Section 3.1.1.1.3 - Dictionary ID 38 // Section 3.1.1.2 - Block Data (16 bytes) 39 0x00, 0x65, 0x78, 0x70, 40 0x65, 0x63, 0x74, 0x65, 41 0x64, 0x20, 0x6f, 0x75, 42 0x74, 0x70, 0x75, 0x74, 43 ]); 44 45 // We define all tests with fields: 46 // 47 // - name: descriptive string 48 // - type: "unchanged", "extendStart", "extendEnd", "truncateStart", "truncateEnd", or "field" 49 // - removeBytes / extraBytes / offset / length / value as needed 50 // 51 // - expectedResultWithChecksum: "success" | "error" | "corrupt" 52 // - expectedResultNoChecksum: "success" | "error" | "corrupt" 53 // (if omitted/undefined, the no-checksum path won't be tested) 54 // 55 const expectations = [ 56 { 57 name: "Unchanged", 58 type: "unchanged", 59 expectedResultWithChecksum: "success", 60 expectedResultNoChecksum: "success", 61 compressedZstdBytesWithChecksum, 62 compressedZstdBytesNoChecksum, 63 }, 64 { 65 name: "Truncate 1 byte from start", 66 type: "truncateStart", 67 removeBytes: 1, 68 expectedResultWithChecksum: "error", 69 expectedResultNoChecksum: "error", 70 compressedZstdBytesWithChecksum, 71 compressedZstdBytesNoChecksum, 72 }, 73 { 74 name: "Truncate 1 byte from end", 75 type: "truncateEnd", 76 removeBytes: 1, 77 expectedResultWithChecksum: "error", 78 expectedResultNoChecksum: "error", 79 compressedZstdBytesWithChecksum, 80 compressedZstdBytesNoChecksum, 81 }, 82 { 83 name: "Truncate 2 bytes from start", 84 type: "truncateStart", 85 removeBytes: 2, 86 expectedResultWithChecksum: "error", 87 expectedResultNoChecksum: "error", 88 compressedZstdBytesWithChecksum, 89 compressedZstdBytesNoChecksum, 90 }, 91 { 92 name: "Truncate 2 bytes from end", 93 type: "truncateEnd", 94 removeBytes: 2, 95 expectedResultWithChecksum: "error", 96 expectedResultNoChecksum: "error", 97 compressedZstdBytesWithChecksum, 98 compressedZstdBytesNoChecksum, 99 }, 100 { 101 name: "Extend from start with [0x00]", 102 type: "extendStart", 103 extraBytes: [0x00], 104 expectedResultWithChecksum: "error", 105 expectedResultNoChecksum: "error", 106 compressedZstdBytesWithChecksum, 107 compressedZstdBytesNoChecksum, 108 }, 109 { 110 name: "Extend from end with [0x00]", 111 type: "extendEnd", 112 extraBytes: [0x00], 113 expectedResultWithChecksum: "error", 114 expectedResultNoChecksum: "error", 115 compressedZstdBytesWithChecksum, 116 compressedZstdBytesNoChecksum, 117 }, 118 { 119 name: "Extend from end [0xff]", 120 type: "extendEnd", 121 extraBytes: [0xff], 122 expectedResultWithChecksum: "error", 123 expectedResultNoChecksum: "error", 124 compressedZstdBytesWithChecksum, 125 compressedZstdBytesNoChecksum, 126 }, 127 { 128 name: "Extend from start with [0xff]", 129 type: "extendStart", 130 extraBytes: [0xff], 131 expectedResultWithChecksum: "error", 132 expectedResultNoChecksum: "error", 133 compressedZstdBytesWithChecksum, 134 compressedZstdBytesNoChecksum, 135 }, 136 { 137 name: "Extend from end with [0xff]", 138 type: "extendEnd", 139 extraBytes: [0xff], 140 expectedResultWithChecksum: "error", 141 expectedResultNoChecksum: "error", 142 compressedZstdBytesWithChecksum, 143 compressedZstdBytesNoChecksum, 144 }, 145 { 146 name: "Extend from start with [0x00, 0x00]", 147 type: "extendStart", 148 extraBytes: [0x00, 0x00], 149 expectedResultWithChecksum: "error", 150 expectedResultNoChecksum: "error", 151 compressedZstdBytesWithChecksum, 152 compressedZstdBytesNoChecksum, 153 }, 154 { 155 name: "Extend from end with [0x00, 0x00]", 156 type: "extendEnd", 157 extraBytes: [0x00, 0x00], 158 expectedResultWithChecksum: "error", 159 expectedResultNoChecksum: "error", 160 compressedZstdBytesWithChecksum, 161 compressedZstdBytesNoChecksum, 162 }, 163 { 164 name: "Magic Number: offset=0 replace with 0xff", 165 type: "field", 166 offset: 0, 167 length: 1, 168 value: 0xff, 169 expectedResultWithChecksum: "error", 170 expectedResultNoChecksum: "error", 171 compressedZstdBytesWithChecksum, 172 compressedZstdBytesNoChecksum, 173 }, 174 { 175 name: "Magic Number: offset=1 replace with 0xff", 176 type: "field", 177 offset: 1, 178 length: 1, 179 value: 0xff, 180 expectedResultWithChecksum: "error", 181 expectedResultNoChecksum: "error", 182 compressedZstdBytesWithChecksum, 183 compressedZstdBytesNoChecksum, 184 }, 185 { 186 name: "Magic Number: offset=2 replace with 0xff", 187 type: "field", 188 offset: 2, 189 length: 1, 190 value: 0xff, 191 expectedResultWithChecksum: "error", 192 expectedResultNoChecksum: "error", 193 compressedZstdBytesWithChecksum, 194 compressedZstdBytesNoChecksum, 195 }, 196 { 197 name: "Magic Number: offset=3 replace with 0xff", 198 type: "field", 199 offset: 3, 200 length: 1, 201 value: 0xff, 202 expectedResultWithChecksum: "error", 203 expectedResultNoChecksum: "error", 204 compressedZstdBytesWithChecksum, 205 compressedZstdBytesNoChecksum, 206 }, 207 { 208 name: "Frame Header Descriptor: offset=4 replace with 0x05", 209 type: "field", 210 offset: 4, 211 length: 1, 212 value: 0x05, 213 expectedResultWithChecksum: "error", 214 expectedResultNoChecksum: "error", 215 compressedZstdBytesWithChecksum, 216 compressedZstdBytesNoChecksum, 217 }, 218 { 219 name: "Window Descriptor: offset=5 replace with 0xff", 220 type: "field", 221 offset: 5, 222 length: 1, 223 value: 0xff, 224 expectedResultWithChecksum: "error", 225 expectedResultNoChecksum: "error", 226 compressedZstdBytesWithChecksum, 227 compressedZstdBytesNoChecksum, 228 }, 229 { 230 name: "Dictionary ID: offset=6 length=2 replace with 0x00", 231 type: "field", 232 offset: 6, 233 length: 2, 234 value: 0x00, 235 expectedResultWithChecksum: "error", 236 expectedResultNoChecksum: "error", 237 compressedZstdBytesWithChecksum, 238 compressedZstdBytesNoChecksum, 239 }, 240 { 241 name: "Block Data: offset=18 replace with 0x64", 242 type: "field", 243 offset: 18, 244 length: 1, 245 value: 0x64, 246 expectedResultWithChecksum: "error", 247 expectedResultNoChecksum: "corrupt", 248 compressedZstdBytesWithChecksum, 249 compressedZstdBytesNoChecksum, 250 }, 251 { 252 name: "Block Data: offset=23 replace with 0x73", 253 type: "field", 254 offset: 23, 255 length: 1, 256 value: 0x73, 257 expectedResultWithChecksum: "error", 258 expectedResultNoChecksum: "corrupt", 259 compressedZstdBytesWithChecksum, 260 compressedZstdBytesNoChecksum, 261 }, 262 // The following test cases mutate the checksum itself, so there are no expected 263 // results for the no-checksum scenario. 264 { 265 name: "Content Checksum: offset=-4 replace with 0x00", 266 type: "field", 267 offset: -4, 268 length: 1, 269 value: 0x00, 270 expectedResultWithChecksum: "error", 271 compressedZstdBytesWithChecksum, 272 }, 273 { 274 name: "Content Checksum: offset=-3 replace with 0xff", 275 type: "field", 276 offset: -3, 277 length: 1, 278 value: 0xff, 279 expectedResultWithChecksum: "error", 280 compressedZstdBytesWithChecksum, 281 }, 282 { 283 name: "Content Checksum: offset=-2 replace with 0x00", 284 type: "field", 285 offset: -2, 286 length: 1, 287 value: 0x00, 288 expectedResultWithChecksum: "error", 289 compressedZstdBytesWithChecksum, 290 }, 291 { 292 name: "Content Checksum: offset=-1 replace with 0xff", 293 type: "field", 294 offset: -1, 295 length: 1, 296 value: 0xff, 297 expectedResultWithChecksum: "error", 298 compressedZstdBytesWithChecksum, 299 }, 300 ]; 301 302 async function tryDecompress(input) { 303 const ds = new DecompressionStream("zstd"); 304 const reader = ds.readable.getReader(); 305 const writer = ds.writable.getWriter(); 306 307 const writePromise = writer.write(input).catch(() => {}); 308 const writerClosePromise = writer.close().catch(() => {}); 309 310 let out = []; 311 while (true) { 312 try { 313 const { value, done } = await reader.read(); 314 if (done) { 315 break; 316 } 317 out = out.concat(Array.from(value)); 318 } catch (e) { 319 if (e.name === "TypeError") { 320 return { result: "error" }; 321 } 322 return { result: e.name }; 323 } 324 } 325 326 await writePromise; 327 await writerClosePromise; 328 329 const textDecoder = new TextDecoder(); 330 const text = textDecoder.decode(new Uint8Array(out)); 331 if (text !== "expected output") { 332 return { result: "corrupt" }; 333 } 334 return { result: "success" }; 335 } 336 337 function produceTestInput(testCase, compressedBytes) { 338 switch (testCase.type) { 339 case "unchanged": 340 return compressedBytes; 341 342 case "truncateStart": { 343 return compressedBytes.slice(testCase.removeBytes); 344 } 345 case "truncateEnd": { 346 return compressedBytes.slice( 347 0, 348 compressedBytes.length - testCase.removeBytes 349 ); 350 } 351 case "extendStart": { 352 return new Uint8Array([...testCase.extraBytes, ...compressedBytes]); 353 } 354 case "extendEnd": { 355 return new Uint8Array([...compressedBytes, ...testCase.extraBytes]); 356 } 357 case "field": { 358 const output = new Uint8Array(compressedBytes); 359 let realOffset = testCase.offset; 360 if (realOffset < 0) { 361 realOffset = output.length + realOffset; 362 } 363 for (let i = 0; i < testCase.length; i++) { 364 output[realOffset + i] = testCase.value; 365 } 366 return output; 367 } 368 default: { 369 throw new Error(`Unknown testCase type: ${testCase.type}`); 370 } 371 } 372 } 373 374 for (const testCase of expectations) { 375 promise_test(async t => { 376 const inputWithChecksum = produceTestInput( 377 testCase, 378 testCase.compressedZstdBytesWithChecksum 379 ); 380 const { result: resultWithChecksum } = 381 await tryDecompress(inputWithChecksum); 382 383 assert_equals( 384 resultWithChecksum, 385 testCase.expectedResultWithChecksum, 386 `${testCase.name} (with checksum)` 387 ); 388 389 let resultNoChecksum; 390 if (testCase.expectedResultNoChecksum !== undefined) { 391 const inputNoChecksum = produceTestInput( 392 testCase, 393 testCase.compressedZstdBytesNoChecksum 394 ); 395 const { result } = await tryDecompress(inputNoChecksum); 396 resultNoChecksum = result; 397 } 398 399 if (testCase.expectedResultNoChecksum !== undefined) { 400 assert_equals( 401 resultNoChecksum, 402 testCase.expectedResultNoChecksum, 403 `${testCase.name} (no checksum)` 404 ); 405 } 406 }, testCase.name); 407 }