|
1 var expect = require('chai').expect; |
|
2 var util = require('./util'); |
|
3 |
|
4 var compressor = require('../lib/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: new Buffer([10]) |
|
16 }, { |
|
17 N: 0, |
|
18 I: 10, |
|
19 buffer: new Buffer([10]) |
|
20 }, { |
|
21 N: 5, |
|
22 I: 1337, |
|
23 buffer: new Buffer([31, 128 + 26, 10]) |
|
24 }, { |
|
25 N: 0, |
|
26 I: 1337, |
|
27 buffer: new Buffer([128 + 57, 10]) |
|
28 }]; |
|
29 |
|
30 var test_strings = [{ |
|
31 string: 'www.foo.com', |
|
32 buffer: new Buffer('88db6d898b5a44b74f', 'hex') |
|
33 }, { |
|
34 string: 'éáűőúöüó€', |
|
35 buffer: new Buffer('13C3A9C3A1C5B1C591C3BAC3B6C3BCC3B3E282AC', 'hex') |
|
36 }]; |
|
37 |
|
38 test_huffman_request = { |
|
39 'GET': 'f77778ff', |
|
40 'http': 'ce3177', |
|
41 '/': '0f', |
|
42 'www.foo.com': 'db6d898b5a44b74f', |
|
43 'https': 'ce31743f', |
|
44 'www.bar.com': 'db6d897a1e44b74f', |
|
45 'no-cache': '63654a1398ff', |
|
46 '/custom-path.css': '04eb08b7495c88e644c21f', |
|
47 'custom-key': '4eb08b749790fa7f', |
|
48 'custom-value': '4eb08b74979a17a8ff' |
|
49 }; |
|
50 |
|
51 test_huffman_response = { |
|
52 '302': '98a7', |
|
53 'private': '73d5cd111f', |
|
54 'Mon, 21 OCt 2013 20:13:21 GMT': 'ef6b3a7a0e6e8fa7647a0e534dd072fb0d37b0e6e8f777f8ff', |
|
55 ': https://www.bar.com': 'f6746718ba1ec00db6d897a1e44b74', |
|
56 '200': '394b', |
|
57 'Mon, 21 OCt 2013 20:13:22 GMT': 'ef6b3a7a0e6e8fa7647a0e534dd072fb0d37b0e7e8f777f8ff', |
|
58 'https://www.bar.com': 'ce31743d801b6db12f43c896e9', |
|
59 'gzip': 'cbd54e', |
|
60 'foo=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ |
|
61 AAAAAAAAAAAAAAAAAAAAAAAAAALASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKHQWOEIUAL\ |
|
62 QWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKH\ |
|
63 QWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEO\ |
|
64 IUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOP\ |
|
65 IUAXQWEOIUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234ZZZZZZZZZZ\ |
|
66 ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ1234 m\ |
|
67 ax-age=3600; version=1': 'c5adb77efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7\ |
|
68 efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfb\ |
|
69 f7efdfbf7efdfbf7efdfbfe5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5ddf4fafc3f1bf\ |
|
70 f7f6fd777d3e1f8dffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c3f1bff7f2fb7\ |
|
71 77e37fefe5f2fefe3bfc3b7edfaeefa7e3e1bff7f331e69fe5bfc3b7e3fdfbfedfdf\ |
|
72 5ff9fbfa7dbf5ddf4fafc3f1bff7f6fd777d3e1f8dffbf97c7fbf7fdbf5f4eef87e3\ |
|
73 7fcbedfaeefa7c3f1bff7f2fb777e37fefe5f2fefe3bfc3b7edfaeefa7e3e1bff7f3\ |
|
74 31e69fe5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5ddf4fafc3f1bff7f6fd777d3e1f8d\ |
|
75 ffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c3f1bff7f2fb777e37fefe5f2fefe\ |
|
76 3bfc3b7edfaeefa7e3e1bff7f331e69fe5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5ddf\ |
|
77 4fafc3f1bff7f6fd777d3e1f8dffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c3f\ |
|
78 1bff7f2fb777e37fefe5f2fefe3bfc3b7edfaeefa7e3e1bff7f331e69ffcff3fcff3\ |
|
79 fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcf\ |
|
80 f3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3f\ |
|
81 cff3fcff3fcff3fcff3fcff3fcff3fcff0c79a7e8d11e72a321b66a4a5eae8e62f82\ |
|
82 9acb4d', |
|
83 'foo=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\ |
|
84 ZZZZZZZZZZZZZZZZZZZZZZZZZZLASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKHQWOEIUAL\ |
|
85 QWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEOIUAXLJKH\ |
|
86 QWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEO\ |
|
87 IUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOP\ |
|
88 IUAXQWEOIUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234AAAAAAAAAA\ |
|
89 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234 m\ |
|
90 ax-age=3600; version=1': 'c5adb7fcff3fcff3fcff3fcff3fcff3fcff3fcff3f\ |
|
91 cff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff\ |
|
92 3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fcff3fc\ |
|
93 ff3fcff3e5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5ddf4fafc3f1bff7f6fd777d3e1f\ |
|
94 8dffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c3f1bff7f2fb777e37fefe5f2fe\ |
|
95 fe3bfc3b7edfaeefa7e3e1bff7f331e69fe5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5d\ |
|
96 df4fafc3f1bff7f6fd777d3e1f8dffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c\ |
|
97 3f1bff7f2fb777e37fefe5f2fefe3bfc3b7edfaeefa7e3e1bff7f331e69fe5bfc3b7\ |
|
98 e3fdfbfedfdf5ff9fbfa7dbf5ddf4fafc3f1bff7f6fd777d3e1f8dffbf97c7fbf7fd\ |
|
99 bf5f4eef87e37fcbedfaeefa7c3f1bff7f2fb777e37fefe5f2fefe3bfc3b7edfaeef\ |
|
100 a7e3e1bff7f331e69fe5bfc3b7e3fdfbfedfdf5ff9fbfa7dbf5ddf4fafc3f1bff7f6\ |
|
101 fd777d3e1f8dffbf97c7fbf7fdbf5f4eef87e37fcbedfaeefa7c3f1bff7f2fb777e3\ |
|
102 7fefe5f2fefe3bfc3b7edfaeefa7e3e1bff7f331e69f7efdfbf7efdfbf7efdfbf7ef\ |
|
103 dfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7efdfbf7\ |
|
104 efdfbf7efdfbf7efdfbf7efdfbf7efdfbcc79a7e8d11e72a321b66a4a5eae8e62f82\ |
|
105 9acb4d' |
|
106 }; |
|
107 |
|
108 var test_headers = [{ |
|
109 header: { |
|
110 name: 1, |
|
111 value: 'GET', |
|
112 index: true |
|
113 }, |
|
114 buffer: new Buffer('02' + '03474554', 'hex') |
|
115 }, { |
|
116 header: { |
|
117 name: 6, |
|
118 value: 'http', |
|
119 index: true |
|
120 }, |
|
121 buffer: new Buffer('07' + '83ce3177', 'hex') |
|
122 }, { |
|
123 header: { |
|
124 name: 5, |
|
125 value: '/', |
|
126 index: true |
|
127 }, |
|
128 buffer: new Buffer('06' + '012f', 'hex') |
|
129 }, { |
|
130 header: { |
|
131 name: 3, |
|
132 value: 'www.foo.com', |
|
133 index: true |
|
134 }, |
|
135 buffer: new Buffer('04' + '88db6d898b5a44b74f', 'hex') |
|
136 }, { |
|
137 header: { |
|
138 name: 2, |
|
139 value: 'https', |
|
140 index: true |
|
141 }, |
|
142 buffer: new Buffer('03' + '84ce31743f', 'hex') |
|
143 }, { |
|
144 header: { |
|
145 name: 1, |
|
146 value: 'www.bar.com', |
|
147 index: true |
|
148 }, |
|
149 buffer: new Buffer('02' + '88db6d897a1e44b74f', 'hex') |
|
150 }, { |
|
151 header: { |
|
152 name: 28, |
|
153 value: 'no-cache', |
|
154 index: true |
|
155 }, |
|
156 buffer: new Buffer('1d' + '8663654a1398ff', 'hex') |
|
157 }, { |
|
158 header: { |
|
159 name: 3, |
|
160 value: 3, |
|
161 index: false |
|
162 }, |
|
163 buffer: new Buffer('84', 'hex') |
|
164 }, { |
|
165 header: { |
|
166 name: 5, |
|
167 value: 5, |
|
168 index: false |
|
169 }, |
|
170 buffer: new Buffer('86', 'hex') |
|
171 }, { |
|
172 header: { |
|
173 name: 4, |
|
174 value: '/custom-path.css', |
|
175 index: true |
|
176 }, |
|
177 buffer: new Buffer('05' + '8b04eb08b7495c88e644c21f', 'hex') |
|
178 }, { |
|
179 header: { |
|
180 name: 'custom-key', |
|
181 value: 'custom-value', |
|
182 index: true |
|
183 }, |
|
184 buffer: new Buffer('00' + '884eb08b749790fa7f' + '894eb08b74979a17a8ff', 'hex') |
|
185 }, { |
|
186 header: { |
|
187 name: 2, |
|
188 value: 2, |
|
189 index: false |
|
190 }, |
|
191 buffer: new Buffer('83', 'hex') |
|
192 }, { |
|
193 header: { |
|
194 name: 6, |
|
195 value: 6, |
|
196 index: false |
|
197 }, |
|
198 buffer: new Buffer('87', 'hex') |
|
199 }, { |
|
200 header: { |
|
201 name: -1, |
|
202 value: -1, |
|
203 index: true |
|
204 }, |
|
205 buffer: new Buffer('8080', 'hex') |
|
206 }]; |
|
207 |
|
208 var test_header_sets = [{ |
|
209 headers: { |
|
210 ':method': 'GET', |
|
211 ':scheme': 'http', |
|
212 ':path': '/', |
|
213 ':authority': 'www.foo.com' |
|
214 }, |
|
215 buffer: util.concat(test_headers.slice(0, 4).map(function(test) { return test.buffer; })) |
|
216 }, { |
|
217 headers: { |
|
218 ':method': 'GET', |
|
219 ':scheme': 'https', |
|
220 ':path': '/', |
|
221 ':authority': 'www.bar.com', |
|
222 'cache-control': 'no-cache' |
|
223 }, |
|
224 buffer: util.concat(test_headers.slice(4, 9).map(function(test) { return test.buffer; })) |
|
225 }, { |
|
226 headers: { |
|
227 ':method': 'GET', |
|
228 ':scheme': 'https', |
|
229 ':path': '/custom-path.css', |
|
230 ':authority': 'www.bar.com', |
|
231 'custom-key': 'custom-value' |
|
232 }, |
|
233 buffer: util.concat(test_headers.slice(9, 13).map(function(test) { return test.buffer; })) |
|
234 }, { |
|
235 headers: { |
|
236 ':method': 'GET', |
|
237 ':scheme': 'https', |
|
238 ':path': '/custom-path.css', |
|
239 ':authority': ['www.foo.com', 'www.bar.com'], |
|
240 'custom-key': 'custom-value' |
|
241 }, |
|
242 buffer: test_headers[3].buffer |
|
243 }, { |
|
244 headers: {}, |
|
245 buffer: test_headers[13].buffer |
|
246 }, { |
|
247 headers: { |
|
248 ':status': '200', |
|
249 'user-agent': 'my-user-agent', |
|
250 'cookie': 'first; second; third; third; fourth', |
|
251 'multiple': ['first', 'second', 'third', 'third; fourth'], |
|
252 'verylong': (new Buffer(9000)).toString('hex') |
|
253 } |
|
254 }]; |
|
255 |
|
256 describe('compressor.js', function() { |
|
257 describe('HeaderTable', function() { |
|
258 }); |
|
259 |
|
260 describe('HuffmanTable', function() { |
|
261 describe('method encode(buffer)', function() { |
|
262 it('should return the Huffman encoded version of the input buffer', function() { |
|
263 var table = HuffmanTable.huffmanTable; |
|
264 for (var decoded in test_huffman_request) { |
|
265 var encoded = test_huffman_request[decoded]; |
|
266 expect(table.encode(new Buffer(decoded)).toString('hex')).to.equal(encoded); |
|
267 } |
|
268 table = HuffmanTable.huffmanTable; |
|
269 for (decoded in test_huffman_response) { |
|
270 encoded = test_huffman_response[decoded]; |
|
271 expect(table.encode(new Buffer(decoded)).toString('hex')).to.equal(encoded); |
|
272 } |
|
273 }); |
|
274 }) |
|
275 describe('method decode(buffer)', function() { |
|
276 it('should return the Huffman decoded version of the input buffer', function() { |
|
277 var table = HuffmanTable.huffmanTable; |
|
278 for (var decoded in test_huffman_request) { |
|
279 var encoded = test_huffman_request[decoded]; |
|
280 expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded) |
|
281 } |
|
282 table = HuffmanTable.huffmanTable; |
|
283 for (decoded in test_huffman_response) { |
|
284 encoded = test_huffman_response[decoded]; |
|
285 expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded) |
|
286 } |
|
287 }); |
|
288 }) |
|
289 }); |
|
290 |
|
291 describe('HeaderSetCompressor', function() { |
|
292 describe('static method .integer(I, N)', function() { |
|
293 it('should return an array of buffers that represent the N-prefix coded form of the integer I', function() { |
|
294 for (var i = 0; i < test_integers.length; i++) { |
|
295 var test = test_integers[i]; |
|
296 test.buffer.cursor = 0; |
|
297 expect(util.concat(HeaderSetCompressor.integer(test.I, test.N))).to.deep.equal(test.buffer); |
|
298 } |
|
299 }); |
|
300 }); |
|
301 describe('static method .string(string)', function() { |
|
302 it('should return an array of buffers that represent the encoded form of the string', function() { |
|
303 var table = HuffmanTable.huffmanTable; |
|
304 for (var i = 0; i < test_strings.length; i++) { |
|
305 var test = test_strings[i]; |
|
306 expect(util.concat(HeaderSetCompressor.string(test.string, table))).to.deep.equal(test.buffer); |
|
307 } |
|
308 }); |
|
309 }); |
|
310 describe('static method .header({ name, value, index })', function() { |
|
311 it('should return an array of buffers that represent the encoded form of the header', function() { |
|
312 var table = HuffmanTable.huffmanTable; |
|
313 for (var i = 0; i < test_headers.length; i++) { |
|
314 var test = test_headers[i]; |
|
315 expect(util.concat(HeaderSetCompressor.header(test.header, table))).to.deep.equal(test.buffer); |
|
316 } |
|
317 }); |
|
318 }); |
|
319 }); |
|
320 |
|
321 describe('HeaderSetDecompressor', function() { |
|
322 describe('static method .integer(buffer, N)', function() { |
|
323 it('should return the parsed N-prefix coded number and increase the cursor property of buffer', function() { |
|
324 for (var i = 0; i < test_integers.length; i++) { |
|
325 var test = test_integers[i]; |
|
326 test.buffer.cursor = 0; |
|
327 expect(HeaderSetDecompressor.integer(test.buffer, test.N)).to.equal(test.I); |
|
328 expect(test.buffer.cursor).to.equal(test.buffer.length); |
|
329 } |
|
330 }); |
|
331 }); |
|
332 describe('static method .string(buffer)', function() { |
|
333 it('should return the parsed string and increase the cursor property of buffer', function() { |
|
334 var table = HuffmanTable.huffmanTable; |
|
335 for (var i = 0; i < test_strings.length; i++) { |
|
336 var test = test_strings[i]; |
|
337 test.buffer.cursor = 0; |
|
338 expect(HeaderSetDecompressor.string(test.buffer, table)).to.equal(test.string); |
|
339 expect(test.buffer.cursor).to.equal(test.buffer.length); |
|
340 } |
|
341 }); |
|
342 }); |
|
343 describe('static method .header(buffer)', function() { |
|
344 it('should return the parsed header and increase the cursor property of buffer', function() { |
|
345 var table = HuffmanTable.huffmanTable; |
|
346 for (var i = 0; i < test_headers.length; i++) { |
|
347 var test = test_headers[i]; |
|
348 test.buffer.cursor = 0; |
|
349 expect(HeaderSetDecompressor.header(test.buffer, table)).to.deep.equal(test.header); |
|
350 expect(test.buffer.cursor).to.equal(test.buffer.length); |
|
351 } |
|
352 }); |
|
353 }); |
|
354 }); |
|
355 describe('Decompressor', function() { |
|
356 describe('method decompress(buffer)', function() { |
|
357 it('should return the parsed header set in { name1: value1, name2: [value2, value3], ... } format', function() { |
|
358 var decompressor = new Decompressor(util.log, 'REQUEST'); |
|
359 for (var i = 0; i < 5; i++) { |
|
360 var header_set = test_header_sets[i]; |
|
361 expect(decompressor.decompress(header_set.buffer)).to.deep.equal(header_set.headers); |
|
362 } |
|
363 }); |
|
364 }); |
|
365 describe('transform stream', function() { |
|
366 it('should emit an error event if a series of header frames is interleaved with other frames', function() { |
|
367 var decompressor = new Decompressor(util.log, 'REQUEST'); |
|
368 var error_occured = false; |
|
369 decompressor.on('error', function() { |
|
370 error_occured = true; |
|
371 }); |
|
372 decompressor.write({ |
|
373 type: 'HEADERS', |
|
374 flags: { |
|
375 END_HEADERS: false |
|
376 }, |
|
377 data: new Buffer(5) |
|
378 }); |
|
379 decompressor.write({ |
|
380 type: 'DATA', |
|
381 flags: {}, |
|
382 data: new Buffer(5) |
|
383 }); |
|
384 expect(error_occured).to.be.equal(true); |
|
385 }); |
|
386 }); |
|
387 }); |
|
388 |
|
389 describe('invariant', function() { |
|
390 describe('decompressor.decompress(compressor.compress(headerset)) === headerset', function() { |
|
391 it('should be true for any header set if the states are synchronized', function() { |
|
392 var compressor = new Compressor(util.log, 'REQUEST'); |
|
393 var decompressor = new Decompressor(util.log, 'REQUEST'); |
|
394 var n = test_header_sets.length; |
|
395 for (var i = 0; i < 10; i++) { |
|
396 var headers = test_header_sets[i%n].headers; |
|
397 var compressed = compressor.compress(headers); |
|
398 var decompressed = decompressor.decompress(compressed); |
|
399 expect(decompressed).to.deep.equal(headers); |
|
400 expect(compressor._table).to.deep.equal(decompressor._table); |
|
401 } |
|
402 }); |
|
403 }); |
|
404 describe('source.pipe(compressor).pipe(decompressor).pipe(destination)', function() { |
|
405 it('should behave like source.pipe(destination) for a stream of frames', function(done) { |
|
406 var compressor = new Compressor(util.log, 'RESPONSE'); |
|
407 var decompressor = new Decompressor(util.log, 'RESPONSE'); |
|
408 var n = test_header_sets.length; |
|
409 compressor.pipe(decompressor); |
|
410 for (var i = 0; i < 10; i++) { |
|
411 compressor.write({ |
|
412 type: i%2 ? 'HEADERS' : 'PUSH_PROMISE', |
|
413 flags: {}, |
|
414 headers: test_header_sets[i%n].headers |
|
415 }); |
|
416 } |
|
417 setTimeout(function() { |
|
418 for (var j = 0; j < 10; j++) { |
|
419 expect(decompressor.read().headers).to.deep.equal(test_header_sets[j%n].headers); |
|
420 } |
|
421 done(); |
|
422 }, 10); |
|
423 }); |
|
424 }); |
|
425 describe('huffmanTable.decompress(huffmanTable.compress(buffer)) === buffer', function() { |
|
426 it('should be true for any buffer', function() { |
|
427 for (var i = 0; i < 10; i++) { |
|
428 var buffer = []; |
|
429 while (Math.random() > 0.1) { |
|
430 buffer.push(Math.floor(Math.random() * 256)) |
|
431 } |
|
432 buffer = new Buffer(buffer); |
|
433 var table = HuffmanTable.huffmanTable; |
|
434 var result = table.decode(table.encode(buffer)); |
|
435 expect(result).to.deep.equal(buffer); |
|
436 } |
|
437 }) |
|
438 }) |
|
439 }); |
|
440 }); |