compressor.js (18286B)
1 var expect = require('chai').expect; 2 var util = require('./util'); 3 4 var compressor = require('../lib/protocol/compressor'); 5 var HeaderTable = compressor.HeaderTable; 6 var HuffmanTable = compressor.HuffmanTable; 7 var HeaderSetCompressor = compressor.HeaderSetCompressor; 8 var HeaderSetDecompressor = compressor.HeaderSetDecompressor; 9 var Compressor = compressor.Compressor; 10 var Decompressor = compressor.Decompressor; 11 12 var test_integers = [{ 13 N: 5, 14 I: 10, 15 buffer: Buffer.from([10]) 16 }, { 17 N: 0, 18 I: 10, 19 buffer: Buffer.from([10]) 20 }, { 21 N: 5, 22 I: 1337, 23 buffer: Buffer.from([31, 128 + 26, 10]) 24 }, { 25 N: 0, 26 I: 1337, 27 buffer: Buffer.from([128 + 57, 10]) 28 }]; 29 30 var test_strings = [{ 31 string: 'www.foo.com', 32 buffer: Buffer.from('89f1e3c2f29ceb90f4ff', 'hex') 33 }, { 34 string: 'éáűőúöüó€', 35 buffer: Buffer.from('13c3a9c3a1c5b1c591c3bac3b6c3bcc3b3e282ac', 'hex') 36 }]; 37 38 test_huffman_request = { 39 'GET': 'c5837f', 40 'http': '9d29af', 41 '/': '63', 42 'www.foo.com': 'f1e3c2f29ceb90f4ff', 43 'https': '9d29ad1f', 44 'www.bar.com': 'f1e3c2f18ec5c87a7f', 45 'no-cache': 'a8eb10649cbf', 46 '/custom-path.css': '6096a127a56ac699d72211', 47 'custom-key': '25a849e95ba97d7f', 48 'custom-value': '25a849e95bb8e8b4bf' 49 }; 50 51 test_huffman_response = { 52 '302': '6402', 53 'private': 'aec3771a4b', 54 'Mon, 21 OCt 2013 20:13:21 GMT': 'd07abe941054d5792a0801654102e059b820a98b46ff', 55 ': https://www.bar.com': 'b8a4e94d68b8c31e3c785e31d8b90f4f', 56 '200': '1001', 57 'Mon, 21 OCt 2013 20:13:22 GMT': 'd07abe941054d5792a0801654102e059b821298b46ff', 58 'https://www.bar.com': '9d29ad171863c78f0bc63b1721e9', 59 'gzip': '9bd9ab', 60 'foo=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ 61 AAAAAAAAAAAAAAAAAAAAAAAAAALASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKHQWOEIUAL\ 62 QWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKH\ 63 QWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEO\ 64 IUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOP\ 65 IUAXQWEOIUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234ZZZZZZZZZZ\ 66 ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ1234 m\ 67 ax-age=3600; version=1': '94e7821861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861873c3bafe5cd8f666bbfbf9ab672c1ab5e4e10fe6ce583564e10fe67cb9b1ece5ab064e10e7d9cb06ac9c21fccfb307087f33e7cd961dd7f672c1ab86487f34844cb59e1dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab27087f33e5cd8f672d583270873ece583564e10fe67d983843f99f3e6cb0eebfb3960d5c3243f9a42265acf0eebf97363d99aefefe6ad9cb06ad793843f9b3960d593843f99f2e6c7b396ac1938439f672c1ab27087f33ecc1c21fccf9f3658775fd9cb06ae1921fcd21132d678775fcb9b1eccd77f7f356ce58356bc9c21fcd9cb06ac9c21fccf97363d9cb560c9c21cfb3960d593843f99f660e10fe67cf9b2c3bafece583570c90fe6908996bf7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f42265a5291f9587316065c003ed4ee5b1063d5007f', 68 'foo=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\ 69 ZZZZZZZZZZZZZZZZZZZZZZZZZZLASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKHQWOEIUAL\ 70 QWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKH\ 71 QWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEO\ 72 IUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOP\ 73 IUAXQWEOIUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234AAAAAAAAAA\ 74 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234 m\ 75 ax-age=3600; version=1': '94e783f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f73c3bafe5cd8f666bbfbf9ab672c1ab5e4e10fe6ce583564e10fe67cb9b1ece5ab064e10e7d9cb06ac9c21fccfb307087f33e7cd961dd7f672c1ab86487f34844cb59e1dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab27087f33e5cd8f672d583270873ece583564e10fe67d983843f99f3e6cb0eebfb3960d5c3243f9a42265acf0eebf97363d99aefefe6ad9cb06ad793843f9b3960d593843f99f2e6c7b396ac1938439f672c1ab27087f33ecc1c21fccf9f3658775fd9cb06ae1921fcd21132d678775fcb9b1eccd77f7f356ce58356bc9c21fcd9cb06ac9c21fccf97363d9cb560c9c21cfb3960d593843f99f660e10fe67cf9b2c3bafece583570c90fe6908996a1861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861842265a5291f9587316065c003ed4ee5b1063d5007f' 76 }; 77 78 var test_headers = [{ 79 // index 80 header: { 81 name: 1, 82 value: 1, 83 index: false, 84 mustNeverIndex: false, 85 contextUpdate: false, 86 newMaxSize: 0 87 }, 88 buffer: Buffer.from('82', 'hex') 89 }, { 90 // index 91 header: { 92 name: 5, 93 value: 5, 94 index: false, 95 mustNeverIndex: false, 96 contextUpdate: false, 97 newMaxSize: 0 98 }, 99 buffer: Buffer.from('86', 'hex') 100 }, { 101 // index 102 header: { 103 name: 3, 104 value: 3, 105 index: false, 106 mustNeverIndex: false, 107 contextUpdate: false, 108 newMaxSize: 0 109 }, 110 buffer: Buffer.from('84', 'hex') 111 }, { 112 // literal w/index, name index 113 header: { 114 name: 0, 115 value: 'www.foo.com', 116 index: true, 117 mustNeverIndex: false, 118 contextUpdate: false, 119 newMaxSize: 0 120 }, 121 buffer: Buffer.from('41' + '89f1e3c2f29ceb90f4ff', 'hex') 122 }, { 123 // indexed 124 header: { 125 name: 1, 126 value: 1, 127 index: false, 128 mustNeverIndex: false, 129 contextUpdate: false, 130 newMaxSize: 0 131 }, 132 buffer: Buffer.from('82', 'hex') 133 }, { 134 // indexed 135 header: { 136 name: 6, 137 value: 6, 138 index: false, 139 mustNeverIndex: false, 140 contextUpdate: false, 141 newMaxSize: 0 142 }, 143 buffer: Buffer.from('87', 'hex') 144 }, { 145 // indexed 146 header: { 147 name: 3, 148 value: 3, 149 index: false, 150 mustNeverIndex: false, 151 contextUpdate: false, 152 newMaxSize: 0 153 }, 154 buffer: Buffer.from('84', 'hex') 155 }, { 156 // literal w/index, name index 157 header: { 158 name: 0, 159 value: 'www.bar.com', 160 index: true, 161 mustNeverIndex: false, 162 contextUpdate: false, 163 newMaxSize: 0 164 }, 165 buffer: Buffer.from('41' + '89f1e3c2f18ec5c87a7f', 'hex') 166 }, { 167 // literal w/index, name index 168 header: { 169 name: 23, 170 value: 'no-cache', 171 index: true, 172 mustNeverIndex: false, 173 contextUpdate: false, 174 newMaxSize: 0 175 }, 176 buffer: Buffer.from('58' + '86a8eb10649cbf', 'hex') 177 }, { 178 // index 179 header: { 180 name: 1, 181 value: 1, 182 index: false, 183 mustNeverIndex: false, 184 contextUpdate: false, 185 newMaxSize: 0 186 }, 187 buffer: Buffer.from('82', 'hex') 188 }, { 189 // index 190 header: { 191 name: 6, 192 value: 6, 193 index: false, 194 mustNeverIndex: false, 195 contextUpdate: false, 196 newMaxSize: 0 197 }, 198 buffer: Buffer.from('87', 'hex') 199 }, { 200 // literal w/index, name index 201 header: { 202 name: 3, 203 value: '/custom-path.css', 204 index: true, 205 mustNeverIndex: false, 206 contextUpdate: false, 207 newMaxSize: 0 208 }, 209 buffer: Buffer.from('44' + '8b6096a127a56ac699d72211', 'hex') 210 }, { 211 // index 212 header: { 213 name: 63, 214 value: 63, 215 index: false, 216 mustNeverIndex: false, 217 contextUpdate: false, 218 newMaxSize: 0 219 }, 220 buffer: Buffer.from('C0', 'hex') 221 }, { 222 // literal w/index, new name & value 223 header: { 224 name: 'custom-key', 225 value: 'custom-value', 226 index: true, 227 mustNeverIndex: false, 228 contextUpdate: false, 229 newMaxSize: 0 230 }, 231 buffer: Buffer.from('40' + '8825a849e95ba97d7f' + '8925a849e95bb8e8b4bf', 'hex') 232 }, { 233 // index 234 header: { 235 name: 1, 236 value: 1, 237 index: false, 238 mustNeverIndex: false, 239 contextUpdate: false, 240 newMaxSize: 0 241 }, 242 buffer: Buffer.from('82', 'hex') 243 }, { 244 // index 245 header: { 246 name: 6, 247 value: 6, 248 index: false, 249 mustNeverIndex: false, 250 contextUpdate: false, 251 newMaxSize: 0 252 }, 253 buffer: Buffer.from('87', 'hex') 254 }, { 255 // index 256 header: { 257 name: 62, 258 value: 62, 259 index: false, 260 mustNeverIndex: false, 261 contextUpdate: false, 262 newMaxSize: 0 263 }, 264 buffer: Buffer.from('BF', 'hex') 265 }, { 266 // index 267 header: { 268 name: 65, 269 value: 65, 270 index: false, 271 mustNeverIndex: false, 272 contextUpdate: false, 273 newMaxSize: 0 274 }, 275 buffer: Buffer.from('C2', 'hex') 276 }, { 277 // index 278 header: { 279 name: 64, 280 value: 64, 281 index: false, 282 mustNeverIndex: false, 283 contextUpdate: false, 284 newMaxSize: 0 285 }, 286 buffer: Buffer.from('C1', 'hex') 287 }, { 288 // index 289 header: { 290 name: 61, 291 value: 61, 292 index: false, 293 mustNeverIndex: false, 294 contextUpdate: false, 295 newMaxSize: 0 296 }, 297 buffer: Buffer.from('BE', 'hex') 298 }, { 299 // Literal w/o index, name index 300 header: { 301 name: 6, 302 value: "whatever", 303 index: false, 304 mustNeverIndex: false, 305 contextUpdate: false, 306 newMaxSize: 0 307 }, 308 buffer: Buffer.from('07' + '86f138d25ee5b3', 'hex') 309 }, { 310 // Literal w/o index, new name & value 311 header: { 312 name: "foo", 313 value: "bar", 314 index: false, 315 mustNeverIndex: false, 316 contextUpdate: false, 317 newMaxSize: 0 318 }, 319 buffer: Buffer.from('00' + '8294e7' + '03626172', 'hex') 320 }, { 321 // Literal never indexed, name index 322 header: { 323 name: 6, 324 value: "whatever", 325 index: false, 326 mustNeverIndex: true, 327 contextUpdate: false, 328 newMaxSize: 0 329 }, 330 buffer: Buffer.from('17' + '86f138d25ee5b3', 'hex') 331 }, { 332 // Literal never indexed, new name & value 333 header: { 334 name: "foo", 335 value: "bar", 336 index: false, 337 mustNeverIndex: true, 338 contextUpdate: false, 339 newMaxSize: 0 340 }, 341 buffer: Buffer.from('10' + '8294e7' + '03626172', 'hex') 342 }, { 343 header: { 344 name: -1, 345 value: -1, 346 index: false, 347 mustNeverIndex: false, 348 contextUpdate: true, 349 newMaxSize: 100 350 }, 351 buffer: Buffer.from('3F45', 'hex') 352 }]; 353 354 var test_header_sets = [{ 355 headers: { 356 ':method': 'GET', 357 ':scheme': 'http', 358 ':path': '/', 359 ':authority': 'www.foo.com' 360 }, 361 buffer: util.concat(test_headers.slice(0, 4).map(function(test) { return test.buffer; })) 362 }, { 363 headers: { 364 ':method': 'GET', 365 ':scheme': 'https', 366 ':path': '/', 367 ':authority': 'www.bar.com', 368 'cache-control': 'no-cache' 369 }, 370 buffer: util.concat(test_headers.slice(4, 9).map(function(test) { return test.buffer; })) 371 }, { 372 headers: { 373 ':method': 'GET', 374 ':scheme': 'https', 375 ':path': '/custom-path.css', 376 ':authority': 'www.bar.com', 377 'custom-key': 'custom-value' 378 }, 379 buffer: util.concat(test_headers.slice(9, 14).map(function(test) { return test.buffer; })) 380 }, { 381 headers: { 382 ':method': 'GET', 383 ':scheme': 'https', 384 ':path': '/custom-path.css', 385 ':authority': ['www.foo.com', 'www.bar.com'], 386 'custom-key': 'custom-value' 387 }, 388 buffer: util.concat(test_headers.slice(14, 19).map(function(test) { return test.buffer; })) 389 }]; 390 391 describe('compressor.js', function() { 392 describe('HeaderTable', function() { 393 }); 394 395 describe('HuffmanTable', function() { 396 describe('method encode(buffer)', function() { 397 it('should return the Huffman encoded version of the input buffer', function() { 398 var table = HuffmanTable.huffmanTable; 399 for (var decoded in test_huffman_request) { 400 var encoded = test_huffman_request[decoded]; 401 expect(table.encode(Buffer.from(decoded)).toString('hex')).to.equal(encoded); 402 } 403 table = HuffmanTable.huffmanTable; 404 for (decoded in test_huffman_response) { 405 encoded = test_huffman_response[decoded]; 406 expect(table.encode(Buffer.from(decoded)).toString('hex')).to.equal(encoded); 407 } 408 }); 409 }); 410 describe('method decode(buffer)', function() { 411 it('should return the Huffman decoded version of the input buffer', function() { 412 var table = HuffmanTable.huffmanTable; 413 for (var decoded in test_huffman_request) { 414 var encoded = test_huffman_request[decoded]; 415 expect(table.decode(Buffer.from(encoded, 'hex')).toString()).to.equal(decoded); 416 } 417 table = HuffmanTable.huffmanTable; 418 for (decoded in test_huffman_response) { 419 encoded = test_huffman_response[decoded]; 420 expect(table.decode(Buffer.from(encoded, 'hex')).toString()).to.equal(decoded); 421 } 422 }); 423 }); 424 }); 425 426 describe('HeaderSetCompressor', function() { 427 describe('static method .integer(I, N)', function() { 428 it('should return an array of buffers that represent the N-prefix coded form of the integer I', function() { 429 for (var i = 0; i < test_integers.length; i++) { 430 var test = test_integers[i]; 431 test.buffer.cursor = 0; 432 expect(util.concat(HeaderSetCompressor.integer(test.I, test.N))).to.deep.equal(test.buffer); 433 } 434 }); 435 }); 436 describe('static method .string(string)', function() { 437 it('should return an array of buffers that represent the encoded form of the string', function() { 438 var table = HuffmanTable.huffmanTable; 439 for (var i = 0; i < test_strings.length; i++) { 440 var test = test_strings[i]; 441 expect(util.concat(HeaderSetCompressor.string(test.string, table))).to.deep.equal(test.buffer); 442 } 443 }); 444 }); 445 describe('static method .header({ name, value, index })', function() { 446 it('should return an array of buffers that represent the encoded form of the header', function() { 447 var table = HuffmanTable.huffmanTable; 448 for (var i = 0; i < test_headers.length; i++) { 449 var test = test_headers[i]; 450 expect(util.concat(HeaderSetCompressor.header(test.header, table))).to.deep.equal(test.buffer); 451 } 452 }); 453 }); 454 }); 455 456 describe('HeaderSetDecompressor', function() { 457 describe('static method .integer(buffer, N)', function() { 458 it('should return the parsed N-prefix coded number and increase the cursor property of buffer', function() { 459 for (var i = 0; i < test_integers.length; i++) { 460 var test = test_integers[i]; 461 test.buffer.cursor = 0; 462 expect(HeaderSetDecompressor.integer(test.buffer, test.N)).to.equal(test.I); 463 expect(test.buffer.cursor).to.equal(test.buffer.length); 464 } 465 }); 466 }); 467 describe('static method .string(buffer)', function() { 468 it('should return the parsed string and increase the cursor property of buffer', function() { 469 var table = HuffmanTable.huffmanTable; 470 for (var i = 0; i < test_strings.length; i++) { 471 var test = test_strings[i]; 472 test.buffer.cursor = 0; 473 expect(HeaderSetDecompressor.string(test.buffer, table)).to.equal(test.string); 474 expect(test.buffer.cursor).to.equal(test.buffer.length); 475 } 476 }); 477 }); 478 describe('static method .header(buffer)', function() { 479 it('should return the parsed header and increase the cursor property of buffer', function() { 480 var table = HuffmanTable.huffmanTable; 481 for (var i = 0; i < test_headers.length; i++) { 482 var test = test_headers[i]; 483 test.buffer.cursor = 0; 484 expect(HeaderSetDecompressor.header(test.buffer, table)).to.deep.equal(test.header); 485 expect(test.buffer.cursor).to.equal(test.buffer.length); 486 } 487 }); 488 }); 489 }); 490 describe('Decompressor', function() { 491 describe('method decompress(buffer)', function() { 492 it('should return the parsed header set in { name1: value1, name2: [value2, value3], ... } format', function() { 493 var decompressor = new Decompressor(util.log, 'REQUEST'); 494 for (var i = 0; i < test_header_sets.length - 1; i++) { 495 var header_set = test_header_sets[i]; 496 expect(decompressor.decompress(header_set.buffer)).to.deep.equal(header_set.headers); 497 } 498 }); 499 }); 500 describe('transform stream', function() { 501 it('should emit an error event if a series of header frames is interleaved with other frames', function() { 502 var decompressor = new Decompressor(util.log, 'REQUEST'); 503 var error_occured = false; 504 decompressor.on('error', function() { 505 error_occured = true; 506 }); 507 decompressor.write({ 508 type: 'HEADERS', 509 flags: { 510 END_HEADERS: false 511 }, 512 data: Buffer.alloc(5) 513 }); 514 decompressor.write({ 515 type: 'DATA', 516 flags: {}, 517 data: Buffer.alloc(5) 518 }); 519 expect(error_occured).to.be.equal(true); 520 }); 521 }); 522 }); 523 524 describe('invariant', function() { 525 describe('decompressor.decompress(compressor.compress(headerset)) === headerset', function() { 526 it('should be true for any header set if the states are synchronized', function() { 527 var compressor = new Compressor(util.log, 'REQUEST'); 528 var decompressor = new Decompressor(util.log, 'REQUEST'); 529 var n = test_header_sets.length; 530 for (var i = 0; i < 10; i++) { 531 var headers = test_header_sets[i%n].headers; 532 var compressed = compressor.compress(headers); 533 var decompressed = decompressor.decompress(compressed); 534 expect(decompressed).to.deep.equal(headers); 535 expect(compressor._table).to.deep.equal(decompressor._table); 536 } 537 }); 538 }); 539 describe('source.pipe(compressor).pipe(decompressor).pipe(destination)', function() { 540 it('should behave like source.pipe(destination) for a stream of frames', function(done) { 541 var compressor = new Compressor(util.log, 'RESPONSE'); 542 var decompressor = new Decompressor(util.log, 'RESPONSE'); 543 var n = test_header_sets.length; 544 compressor.pipe(decompressor); 545 for (var i = 0; i < 10; i++) { 546 compressor.write({ 547 type: i%2 ? 'HEADERS' : 'PUSH_PROMISE', 548 flags: {}, 549 headers: test_header_sets[i%n].headers 550 }); 551 } 552 setTimeout(function() { 553 for (var j = 0; j < 10; j++) { 554 expect(decompressor.read().headers).to.deep.equal(test_header_sets[j%n].headers); 555 } 556 done(); 557 }, 10); 558 }); 559 }); 560 describe('huffmanTable.decompress(huffmanTable.compress(buffer)) === buffer', function() { 561 it('should be true for any buffer', function() { 562 for (var i = 0; i < 10; i++) { 563 var buffer = []; 564 while (Math.random() > 0.1) { 565 buffer.push(Math.floor(Math.random() * 256)) 566 } 567 buffer = Buffer.from(buffer); 568 var table = HuffmanTable.huffmanTable; 569 var result = table.decode(table.encode(buffer)); 570 expect(result).to.deep.equal(buffer); 571 } 572 }); 573 }); 574 }); 575 });