michael@0: var expect = require('chai').expect; michael@0: var util = require('./util'); michael@0: michael@0: var compressor = require('../lib/compressor'); michael@0: var HeaderTable = compressor.HeaderTable; michael@0: var HuffmanTable = compressor.HuffmanTable; michael@0: var HeaderSetCompressor = compressor.HeaderSetCompressor; michael@0: var HeaderSetDecompressor = compressor.HeaderSetDecompressor; michael@0: var Compressor = compressor.Compressor; michael@0: var Decompressor = compressor.Decompressor; michael@0: michael@0: var test_integers = [{ michael@0: N: 5, michael@0: I: 10, michael@0: buffer: new Buffer([10]) michael@0: }, { michael@0: N: 0, michael@0: I: 10, michael@0: buffer: new Buffer([10]) michael@0: }, { michael@0: N: 5, michael@0: I: 1337, michael@0: buffer: new Buffer([31, 128 + 26, 10]) michael@0: }, { michael@0: N: 0, michael@0: I: 1337, michael@0: buffer: new Buffer([128 + 57, 10]) michael@0: }]; michael@0: michael@0: var test_strings = [{ michael@0: string: 'www.foo.com', michael@0: buffer: new Buffer('88db6d898b5a44b74f', 'hex') michael@0: }, { michael@0: string: 'éáűőúöüó€', michael@0: buffer: new Buffer('13C3A9C3A1C5B1C591C3BAC3B6C3BCC3B3E282AC', 'hex') michael@0: }]; michael@0: michael@0: test_huffman_request = { michael@0: 'GET': 'f77778ff', michael@0: 'http': 'ce3177', michael@0: '/': '0f', michael@0: 'www.foo.com': 'db6d898b5a44b74f', michael@0: 'https': 'ce31743f', michael@0: 'www.bar.com': 'db6d897a1e44b74f', michael@0: 'no-cache': '63654a1398ff', michael@0: '/custom-path.css': '04eb08b7495c88e644c21f', michael@0: 'custom-key': '4eb08b749790fa7f', michael@0: 'custom-value': '4eb08b74979a17a8ff' michael@0: }; michael@0: michael@0: test_huffman_response = { michael@0: '302': '98a7', michael@0: 'private': '73d5cd111f', michael@0: 'Mon, 21 OCt 2013 20:13:21 GMT': 'ef6b3a7a0e6e8fa7647a0e534dd072fb0d37b0e6e8f777f8ff', michael@0: ': https://www.bar.com': 'f6746718ba1ec00db6d897a1e44b74', michael@0: '200': '394b', michael@0: 'Mon, 21 OCt 2013 20:13:22 GMT': 'ef6b3a7a0e6e8fa7647a0e534dd072fb0d37b0e7e8f777f8ff', michael@0: 'https://www.bar.com': 'ce31743d801b6db12f43c896e9', michael@0: 'gzip': 'cbd54e', michael@0: 'foo=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ michael@0: AAAAAAAAAAAAAAAAAAAAAAAAAALASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKHQWOEIUAL\ michael@0: QWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKH\ michael@0: QWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEO\ michael@0: IUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOP\ michael@0: IUAXQWEOIUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234ZZZZZZZZZZ\ michael@0: ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ1234 m\ michael@0: ax-age=3600; version=1': 'c5adb77efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7\ michael@0: efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfb\ michael@0: f7efdfbf7efdfbf7efdfbfe5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5ddf4fafc3f1bf\ michael@0: f7f6fd777d3e1f8dffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c3f1bff7f2fb7\ michael@0: 77e37fefe5f2fefe3bfc3b7edfaeefa7e3e1bff7f331e69fe5bfc3b7e3fdfbfedfdf\ michael@0: 5ff9fbfa7dbf5ddf4fafc3f1bff7f6fd777d3e1f8dffbf97c7fbf7fdbf5f4eef87e3\ michael@0: 7fcbedfaeefa7c3f1bff7f2fb777e37fefe5f2fefe3bfc3b7edfaeefa7e3e1bff7f3\ michael@0: 31e69fe5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5ddf4fafc3f1bff7f6fd777d3e1f8d\ michael@0: ffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c3f1bff7f2fb777e37fefe5f2fefe\ michael@0: 3bfc3b7edfaeefa7e3e1bff7f331e69fe5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5ddf\ michael@0: 4fafc3f1bff7f6fd777d3e1f8dffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c3f\ michael@0: 1bff7f2fb777e37fefe5f2fefe3bfc3b7edfaeefa7e3e1bff7f331e69ffcff3fcff3\ michael@0: fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcf\ michael@0: f3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3f\ michael@0: cff3fcff3fcff3fcff3fcff3fcff3fcff0c79a7e8d11e72a321b66a4a5eae8e62f82\ michael@0: 9acb4d', michael@0: 'foo=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\ michael@0: ZZZZZZZZZZZZZZZZZZZZZZZZZZLASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKHQWOEIUAL\ michael@0: QWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKH\ michael@0: QWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEO\ michael@0: IUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOP\ michael@0: IUAXQWEOIUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234AAAAAAAAAA\ michael@0: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234 m\ michael@0: ax-age=3600; version=1': 'c5adb7fcff3fcff3fcff3fcff3fcff3fcff3fcff3f\ michael@0: cff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff\ michael@0: 3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fc\ michael@0: ff3fcff3e5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5ddf4fafc3f1bff7f6fd777d3e1f\ michael@0: 8dffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c3f1bff7f2fb777e37fefe5f2fe\ michael@0: fe3bfc3b7edfaeefa7e3e1bff7f331e69fe5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5d\ michael@0: df4fafc3f1bff7f6fd777d3e1f8dffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c\ michael@0: 3f1bff7f2fb777e37fefe5f2fefe3bfc3b7edfaeefa7e3e1bff7f331e69fe5bfc3b7\ michael@0: e3fdfbfedfdf5ff9fbfa7dbf5ddf4fafc3f1bff7f6fd777d3e1f8dffbf97c7fbf7fd\ michael@0: bf5f4eef87e37fcbedfaeefa7c3f1bff7f2fb777e37fefe5f2fefe3bfc3b7edfaeef\ michael@0: a7e3e1bff7f331e69fe5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5ddf4fafc3f1bff7f6\ michael@0: fd777d3e1f8dffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c3f1bff7f2fb777e3\ michael@0: 7fefe5f2fefe3bfc3b7edfaeefa7e3e1bff7f331e69f7efdfbf7efdfbf7efdfbf7ef\ michael@0: dfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7\ michael@0: efdfbf7efdfbf7efdfbf7efdfbf7efdfbcc79a7e8d11e72a321b66a4a5eae8e62f82\ michael@0: 9acb4d' michael@0: }; michael@0: michael@0: var test_headers = [{ michael@0: header: { michael@0: name: 1, michael@0: value: 'GET', michael@0: index: true michael@0: }, michael@0: buffer: new Buffer('02' + '03474554', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 6, michael@0: value: 'http', michael@0: index: true michael@0: }, michael@0: buffer: new Buffer('07' + '83ce3177', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 5, michael@0: value: '/', michael@0: index: true michael@0: }, michael@0: buffer: new Buffer('06' + '012f', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 3, michael@0: value: 'www.foo.com', michael@0: index: true michael@0: }, michael@0: buffer: new Buffer('04' + '88db6d898b5a44b74f', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 2, michael@0: value: 'https', michael@0: index: true michael@0: }, michael@0: buffer: new Buffer('03' + '84ce31743f', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 1, michael@0: value: 'www.bar.com', michael@0: index: true michael@0: }, michael@0: buffer: new Buffer('02' + '88db6d897a1e44b74f', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 28, michael@0: value: 'no-cache', michael@0: index: true michael@0: }, michael@0: buffer: new Buffer('1d' + '8663654a1398ff', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 3, michael@0: value: 3, michael@0: index: false michael@0: }, michael@0: buffer: new Buffer('84', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 5, michael@0: value: 5, michael@0: index: false michael@0: }, michael@0: buffer: new Buffer('86', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 4, michael@0: value: '/custom-path.css', michael@0: index: true michael@0: }, michael@0: buffer: new Buffer('05' + '8b04eb08b7495c88e644c21f', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 'custom-key', michael@0: value: 'custom-value', michael@0: index: true michael@0: }, michael@0: buffer: new Buffer('00' + '884eb08b749790fa7f' + '894eb08b74979a17a8ff', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 2, michael@0: value: 2, michael@0: index: false michael@0: }, michael@0: buffer: new Buffer('83', 'hex') michael@0: }, { michael@0: header: { michael@0: name: 6, michael@0: value: 6, michael@0: index: false michael@0: }, michael@0: buffer: new Buffer('87', 'hex') michael@0: }, { michael@0: header: { michael@0: name: -1, michael@0: value: -1, michael@0: index: true michael@0: }, michael@0: buffer: new Buffer('8080', 'hex') michael@0: }]; michael@0: michael@0: var test_header_sets = [{ michael@0: headers: { michael@0: ':method': 'GET', michael@0: ':scheme': 'http', michael@0: ':path': '/', michael@0: ':authority': 'www.foo.com' michael@0: }, michael@0: buffer: util.concat(test_headers.slice(0, 4).map(function(test) { return test.buffer; })) michael@0: }, { michael@0: headers: { michael@0: ':method': 'GET', michael@0: ':scheme': 'https', michael@0: ':path': '/', michael@0: ':authority': 'www.bar.com', michael@0: 'cache-control': 'no-cache' michael@0: }, michael@0: buffer: util.concat(test_headers.slice(4, 9).map(function(test) { return test.buffer; })) michael@0: }, { michael@0: headers: { michael@0: ':method': 'GET', michael@0: ':scheme': 'https', michael@0: ':path': '/custom-path.css', michael@0: ':authority': 'www.bar.com', michael@0: 'custom-key': 'custom-value' michael@0: }, michael@0: buffer: util.concat(test_headers.slice(9, 13).map(function(test) { return test.buffer; })) michael@0: }, { michael@0: headers: { michael@0: ':method': 'GET', michael@0: ':scheme': 'https', michael@0: ':path': '/custom-path.css', michael@0: ':authority': ['www.foo.com', 'www.bar.com'], michael@0: 'custom-key': 'custom-value' michael@0: }, michael@0: buffer: test_headers[3].buffer michael@0: }, { michael@0: headers: {}, michael@0: buffer: test_headers[13].buffer michael@0: }, { michael@0: headers: { michael@0: ':status': '200', michael@0: 'user-agent': 'my-user-agent', michael@0: 'cookie': 'first; second; third; third; fourth', michael@0: 'multiple': ['first', 'second', 'third', 'third; fourth'], michael@0: 'verylong': (new Buffer(9000)).toString('hex') michael@0: } michael@0: }]; michael@0: michael@0: describe('compressor.js', function() { michael@0: describe('HeaderTable', function() { michael@0: }); michael@0: michael@0: describe('HuffmanTable', function() { michael@0: describe('method encode(buffer)', function() { michael@0: it('should return the Huffman encoded version of the input buffer', function() { michael@0: var table = HuffmanTable.huffmanTable; michael@0: for (var decoded in test_huffman_request) { michael@0: var encoded = test_huffman_request[decoded]; michael@0: expect(table.encode(new Buffer(decoded)).toString('hex')).to.equal(encoded); michael@0: } michael@0: table = HuffmanTable.huffmanTable; michael@0: for (decoded in test_huffman_response) { michael@0: encoded = test_huffman_response[decoded]; michael@0: expect(table.encode(new Buffer(decoded)).toString('hex')).to.equal(encoded); michael@0: } michael@0: }); michael@0: }) michael@0: describe('method decode(buffer)', function() { michael@0: it('should return the Huffman decoded version of the input buffer', function() { michael@0: var table = HuffmanTable.huffmanTable; michael@0: for (var decoded in test_huffman_request) { michael@0: var encoded = test_huffman_request[decoded]; michael@0: expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded) michael@0: } michael@0: table = HuffmanTable.huffmanTable; michael@0: for (decoded in test_huffman_response) { michael@0: encoded = test_huffman_response[decoded]; michael@0: expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded) michael@0: } michael@0: }); michael@0: }) michael@0: }); michael@0: michael@0: describe('HeaderSetCompressor', function() { michael@0: describe('static method .integer(I, N)', function() { michael@0: it('should return an array of buffers that represent the N-prefix coded form of the integer I', function() { michael@0: for (var i = 0; i < test_integers.length; i++) { michael@0: var test = test_integers[i]; michael@0: test.buffer.cursor = 0; michael@0: expect(util.concat(HeaderSetCompressor.integer(test.I, test.N))).to.deep.equal(test.buffer); michael@0: } michael@0: }); michael@0: }); michael@0: describe('static method .string(string)', function() { michael@0: it('should return an array of buffers that represent the encoded form of the string', function() { michael@0: var table = HuffmanTable.huffmanTable; michael@0: for (var i = 0; i < test_strings.length; i++) { michael@0: var test = test_strings[i]; michael@0: expect(util.concat(HeaderSetCompressor.string(test.string, table))).to.deep.equal(test.buffer); michael@0: } michael@0: }); michael@0: }); michael@0: describe('static method .header({ name, value, index })', function() { michael@0: it('should return an array of buffers that represent the encoded form of the header', function() { michael@0: var table = HuffmanTable.huffmanTable; michael@0: for (var i = 0; i < test_headers.length; i++) { michael@0: var test = test_headers[i]; michael@0: expect(util.concat(HeaderSetCompressor.header(test.header, table))).to.deep.equal(test.buffer); michael@0: } michael@0: }); michael@0: }); michael@0: }); michael@0: michael@0: describe('HeaderSetDecompressor', function() { michael@0: describe('static method .integer(buffer, N)', function() { michael@0: it('should return the parsed N-prefix coded number and increase the cursor property of buffer', function() { michael@0: for (var i = 0; i < test_integers.length; i++) { michael@0: var test = test_integers[i]; michael@0: test.buffer.cursor = 0; michael@0: expect(HeaderSetDecompressor.integer(test.buffer, test.N)).to.equal(test.I); michael@0: expect(test.buffer.cursor).to.equal(test.buffer.length); michael@0: } michael@0: }); michael@0: }); michael@0: describe('static method .string(buffer)', function() { michael@0: it('should return the parsed string and increase the cursor property of buffer', function() { michael@0: var table = HuffmanTable.huffmanTable; michael@0: for (var i = 0; i < test_strings.length; i++) { michael@0: var test = test_strings[i]; michael@0: test.buffer.cursor = 0; michael@0: expect(HeaderSetDecompressor.string(test.buffer, table)).to.equal(test.string); michael@0: expect(test.buffer.cursor).to.equal(test.buffer.length); michael@0: } michael@0: }); michael@0: }); michael@0: describe('static method .header(buffer)', function() { michael@0: it('should return the parsed header and increase the cursor property of buffer', function() { michael@0: var table = HuffmanTable.huffmanTable; michael@0: for (var i = 0; i < test_headers.length; i++) { michael@0: var test = test_headers[i]; michael@0: test.buffer.cursor = 0; michael@0: expect(HeaderSetDecompressor.header(test.buffer, table)).to.deep.equal(test.header); michael@0: expect(test.buffer.cursor).to.equal(test.buffer.length); michael@0: } michael@0: }); michael@0: }); michael@0: }); michael@0: describe('Decompressor', function() { michael@0: describe('method decompress(buffer)', function() { michael@0: it('should return the parsed header set in { name1: value1, name2: [value2, value3], ... } format', function() { michael@0: var decompressor = new Decompressor(util.log, 'REQUEST'); michael@0: for (var i = 0; i < 5; i++) { michael@0: var header_set = test_header_sets[i]; michael@0: expect(decompressor.decompress(header_set.buffer)).to.deep.equal(header_set.headers); michael@0: } michael@0: }); michael@0: }); michael@0: describe('transform stream', function() { michael@0: it('should emit an error event if a series of header frames is interleaved with other frames', function() { michael@0: var decompressor = new Decompressor(util.log, 'REQUEST'); michael@0: var error_occured = false; michael@0: decompressor.on('error', function() { michael@0: error_occured = true; michael@0: }); michael@0: decompressor.write({ michael@0: type: 'HEADERS', michael@0: flags: { michael@0: END_HEADERS: false michael@0: }, michael@0: data: new Buffer(5) michael@0: }); michael@0: decompressor.write({ michael@0: type: 'DATA', michael@0: flags: {}, michael@0: data: new Buffer(5) michael@0: }); michael@0: expect(error_occured).to.be.equal(true); michael@0: }); michael@0: }); michael@0: }); michael@0: michael@0: describe('invariant', function() { michael@0: describe('decompressor.decompress(compressor.compress(headerset)) === headerset', function() { michael@0: it('should be true for any header set if the states are synchronized', function() { michael@0: var compressor = new Compressor(util.log, 'REQUEST'); michael@0: var decompressor = new Decompressor(util.log, 'REQUEST'); michael@0: var n = test_header_sets.length; michael@0: for (var i = 0; i < 10; i++) { michael@0: var headers = test_header_sets[i%n].headers; michael@0: var compressed = compressor.compress(headers); michael@0: var decompressed = decompressor.decompress(compressed); michael@0: expect(decompressed).to.deep.equal(headers); michael@0: expect(compressor._table).to.deep.equal(decompressor._table); michael@0: } michael@0: }); michael@0: }); michael@0: describe('source.pipe(compressor).pipe(decompressor).pipe(destination)', function() { michael@0: it('should behave like source.pipe(destination) for a stream of frames', function(done) { michael@0: var compressor = new Compressor(util.log, 'RESPONSE'); michael@0: var decompressor = new Decompressor(util.log, 'RESPONSE'); michael@0: var n = test_header_sets.length; michael@0: compressor.pipe(decompressor); michael@0: for (var i = 0; i < 10; i++) { michael@0: compressor.write({ michael@0: type: i%2 ? 'HEADERS' : 'PUSH_PROMISE', michael@0: flags: {}, michael@0: headers: test_header_sets[i%n].headers michael@0: }); michael@0: } michael@0: setTimeout(function() { michael@0: for (var j = 0; j < 10; j++) { michael@0: expect(decompressor.read().headers).to.deep.equal(test_header_sets[j%n].headers); michael@0: } michael@0: done(); michael@0: }, 10); michael@0: }); michael@0: }); michael@0: describe('huffmanTable.decompress(huffmanTable.compress(buffer)) === buffer', function() { michael@0: it('should be true for any buffer', function() { michael@0: for (var i = 0; i < 10; i++) { michael@0: var buffer = []; michael@0: while (Math.random() > 0.1) { michael@0: buffer.push(Math.floor(Math.random() * 256)) michael@0: } michael@0: buffer = new Buffer(buffer); michael@0: var table = HuffmanTable.huffmanTable; michael@0: var result = table.decode(table.encode(buffer)); michael@0: expect(result).to.deep.equal(buffer); michael@0: } michael@0: }) michael@0: }) michael@0: }); michael@0: });