mieps_http-2 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,535 @@
1
+ require 'helper'
2
+
3
+ RSpec.describe HTTP2::Header do
4
+ let(:c) { Compressor.new }
5
+ let(:d) { Decompressor.new }
6
+
7
+ context 'literal representation' do
8
+ context 'integer' do
9
+ it 'should encode 10 using a 5-bit prefix' do
10
+ buf = c.integer(10, 5)
11
+ expect(buf).to eq [10].pack('C')
12
+ expect(d.integer(Buffer.new(buf), 5)).to eq 10
13
+ end
14
+
15
+ it 'should encode 10 using a 0-bit prefix' do
16
+ buf = c.integer(10, 0)
17
+ expect(buf).to eq [10].pack('C')
18
+ expect(d.integer(Buffer.new(buf), 0)).to eq 10
19
+ end
20
+
21
+ it 'should encode 1337 using a 5-bit prefix' do
22
+ buf = c.integer(1337, 5)
23
+ expect(buf).to eq [31, 128 + 26, 10].pack('C*')
24
+ expect(d.integer(Buffer.new(buf), 5)).to eq 1337
25
+ end
26
+
27
+ it 'should encode 1337 using a 0-bit prefix' do
28
+ buf = c.integer(1337, 0)
29
+ expect(buf).to eq [128 + 57, 10].pack('C*')
30
+ expect(d.integer(Buffer.new(buf), 0)).to eq 1337
31
+ end
32
+ end
33
+
34
+ context 'string' do
35
+ [
36
+ ['with huffman', :always, 0x80],
37
+ ['without huffman', :never, 0],
38
+ ].each do |desc, option, msb|
39
+ let(:trailer) { 'trailer' }
40
+
41
+ [
42
+ ['ascii codepoints', 'abcdefghij'],
43
+ ['utf-8 codepoints', 'éáűőúöüó€'],
44
+ ['long utf-8 strings', 'éáűőúöüó€' * 100],
45
+ ].each do |datatype, plain|
46
+ it "should handle #{datatype} #{desc}" do
47
+ # NOTE: don't put this new in before{} because of test case shuffling
48
+ @c = Compressor.new(huffman: option)
49
+ str = @c.string(plain)
50
+ expect(str.getbyte(0) & 0x80).to eq msb
51
+
52
+ buf = Buffer.new(str + trailer)
53
+ expect(d.string(buf)).to eq plain
54
+ expect(buf).to eq trailer
55
+ end
56
+ end
57
+ end
58
+ context 'choosing shorter representation' do
59
+ [['日本語', :plain],
60
+ ['200', :huffman],
61
+ ['xq', :plain], # prefer plain if equal size
62
+ ].each do |string, choice|
63
+ before { @c = Compressor.new(huffman: :shorter) }
64
+
65
+ it "should return #{choice} representation" do
66
+ wire = @c.string(string)
67
+ expect(wire.getbyte(0) & 0x80).to eq(choice == :plain ? 0 : 0x80)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ context 'header representation' do
75
+ it 'should handle indexed representation' do
76
+ h = { name: 10, type: :indexed }
77
+ wire = c.header(h)
78
+ expect(wire.readbyte(0) & 0x80).to eq 0x80
79
+ expect(wire.readbyte(0) & 0x7f).to eq h[:name] + 1
80
+ expect(d.header(wire)).to eq h
81
+ end
82
+ it 'should raise when decoding indexed representation with index zero' do
83
+ h = { name: 10, type: :indexed }
84
+ wire = c.header(h)
85
+ wire[0] = 0x80.chr(Encoding::BINARY)
86
+ expect { d.header(wire) }.to raise_error CompressionError
87
+ end
88
+
89
+ context 'literal w/o indexing representation' do
90
+ it 'should handle indexed header' do
91
+ h = { name: 10, value: 'my-value', type: :noindex }
92
+ wire = c.header(h)
93
+ expect(wire.readbyte(0) & 0xf0).to eq 0x0
94
+ expect(wire.readbyte(0) & 0x0f).to eq h[:name] + 1
95
+ expect(d.header(wire)).to eq h
96
+ end
97
+
98
+ it 'should handle literal header' do
99
+ h = { name: 'x-custom', value: 'my-value', type: :noindex }
100
+ wire = c.header(h)
101
+ expect(wire.readbyte(0) & 0xf0).to eq 0x0
102
+ expect(wire.readbyte(0) & 0x0f).to eq 0
103
+ expect(d.header(wire)).to eq h
104
+ end
105
+ end
106
+
107
+ context 'literal w/ incremental indexing' do
108
+ it 'should handle indexed header' do
109
+ h = { name: 10, value: 'my-value', type: :incremental }
110
+ wire = c.header(h)
111
+ expect(wire.readbyte(0) & 0xc0).to eq 0x40
112
+ expect(wire.readbyte(0) & 0x3f).to eq h[:name] + 1
113
+ expect(d.header(wire)).to eq h
114
+ end
115
+
116
+ it 'should handle literal header' do
117
+ h = { name: 'x-custom', value: 'my-value', type: :incremental }
118
+ wire = c.header(h)
119
+ expect(wire.readbyte(0) & 0xc0).to eq 0x40
120
+ expect(wire.readbyte(0) & 0x3f).to eq 0
121
+ expect(d.header(wire)).to eq h
122
+ end
123
+ end
124
+
125
+ context 'literal never indexed' do
126
+ it 'should handle indexed header' do
127
+ h = { name: 10, value: 'my-value', type: :neverindexed }
128
+ wire = c.header(h)
129
+ expect(wire.readbyte(0) & 0xf0).to eq 0x10
130
+ expect(wire.readbyte(0) & 0x0f).to eq h[:name] + 1
131
+ expect(d.header(wire)).to eq h
132
+ end
133
+
134
+ it 'should handle literal header' do
135
+ h = { name: 'x-custom', value: 'my-value', type: :neverindexed }
136
+ wire = c.header(h)
137
+ expect(wire.readbyte(0) & 0xf0).to eq 0x10
138
+ expect(wire.readbyte(0) & 0x0f).to eq 0
139
+ expect(d.header(wire)).to eq h
140
+ end
141
+ end
142
+ end
143
+
144
+ context 'shared compression context' do
145
+ before(:each) { @cc = EncodingContext.new }
146
+
147
+ it 'should be initialized with empty headers' do
148
+ cc = EncodingContext.new
149
+ expect(cc.table).to be_empty
150
+ end
151
+
152
+ context 'processing' do
153
+ [
154
+ ['no indexing', :noindex],
155
+ ['never indexed', :neverindexed],
156
+ ].each do |desc, type|
157
+ context "#{desc}" do
158
+ it 'should process indexed header with literal value' do
159
+ original_table = @cc.table.dup
160
+
161
+ emit = @cc.process(name: 4, value: '/path', type: type)
162
+ expect(emit).to eq [':path', '/path']
163
+ expect(@cc.table).to eq original_table
164
+ end
165
+
166
+ it 'should process literal header with literal value' do
167
+ original_table = @cc.table.dup
168
+
169
+ emit = @cc.process(name: 'x-custom', value: 'random', type: type)
170
+ expect(emit).to eq ['x-custom', 'random']
171
+ expect(@cc.table).to eq original_table
172
+ end
173
+ end
174
+ end
175
+
176
+ context 'incremental indexing' do
177
+ it 'should process indexed header with literal value' do
178
+ original_table = @cc.table.dup
179
+
180
+ emit = @cc.process(name: 4, value: '/path', type: :incremental)
181
+ expect(emit).to eq [':path', '/path']
182
+ expect(@cc.table - original_table).to eq [[':path', '/path']]
183
+ end
184
+
185
+ it 'should process literal header with literal value' do
186
+ original_table = @cc.table.dup
187
+
188
+ @cc.process(name: 'x-custom', value: 'random', type: :incremental)
189
+ expect(@cc.table - original_table).to eq [['x-custom', 'random']]
190
+ end
191
+ end
192
+
193
+ context 'size bounds' do
194
+ it 'should drop headers from end of table' do
195
+ cc = EncodingContext.new(table_size: 2048)
196
+ cc.instance_eval do
197
+ add_to_table(['test1', '1' * 1024])
198
+ add_to_table(['test2', '2' * 500])
199
+ end
200
+
201
+ original_table = cc.table.dup
202
+ original_size = original_table.join.bytesize + original_table.size * 32
203
+
204
+ cc.process(name: 'x-custom',
205
+ value: 'a' * (2048 - original_size),
206
+ type: :incremental)
207
+
208
+ expect(cc.table.first[0]).to eq 'x-custom'
209
+ expect(cc.table.size).to eq original_table.size # number of entries
210
+ end
211
+ end
212
+
213
+ it 'should clear table if entry exceeds table size' do
214
+ cc = EncodingContext.new(table_size: 2048)
215
+ cc.instance_eval do
216
+ add_to_table(['test1', '1' * 1024])
217
+ add_to_table(['test2', '2' * 500])
218
+ end
219
+
220
+ h = { name: 'x-custom', value: 'a', index: 0, type: :incremental }
221
+ e = { name: 'large', value: 'a' * 2048, index: 0 }
222
+
223
+ cc.process(h)
224
+ cc.process(e.merge(type: :incremental))
225
+ expect(cc.table).to be_empty
226
+ end
227
+
228
+ it 'should shrink table if set smaller size' do
229
+ cc = EncodingContext.new(table_size: 2048)
230
+ cc.instance_eval do
231
+ add_to_table(['test1', '1' * 1024])
232
+ add_to_table(['test2', '2' * 500])
233
+ end
234
+
235
+ cc.process(type: :changetablesize, value: 1500)
236
+ expect(cc.table.size).to be 1
237
+ expect(cc.table.first[0]).to eq 'test2'
238
+ end
239
+ end
240
+ end
241
+
242
+ spec_examples = [
243
+ { title: 'D.3. Request Examples without Huffman',
244
+ type: :request,
245
+ table_size: 4096,
246
+ huffman: :never,
247
+ streams: [
248
+ { wire: "8286 8441 0f77 7777 2e65 7861 6d70 6c65
249
+ 2e63 6f6d",
250
+ emitted: [
251
+ [':method', 'GET'],
252
+ [':scheme', 'http'],
253
+ [':path', '/'],
254
+ [':authority', 'www.example.com'],
255
+ ],
256
+ table: [
257
+ [':authority', 'www.example.com'],
258
+ ],
259
+ table_size: 57,
260
+ },
261
+ { wire: '8286 84be 5808 6e6f 2d63 6163 6865',
262
+ emitted: [
263
+ [':method', 'GET'],
264
+ [':scheme', 'http'],
265
+ [':path', '/'],
266
+ [':authority', 'www.example.com'],
267
+ ['cache-control', 'no-cache'],
268
+ ],
269
+ table: [
270
+ ['cache-control', 'no-cache'],
271
+ [':authority', 'www.example.com'],
272
+ ],
273
+ table_size: 110,
274
+ },
275
+ { wire: "8287 85bf 400a 6375 7374 6f6d 2d6b 6579
276
+ 0c63 7573 746f 6d2d 7661 6c75 65",
277
+ emitted: [
278
+ [':method', 'GET'],
279
+ [':scheme', 'https'],
280
+ [':path', '/index.html'],
281
+ [':authority', 'www.example.com'],
282
+ ['custom-key', 'custom-value'],
283
+ ],
284
+ table: [
285
+ ['custom-key', 'custom-value'],
286
+ ['cache-control', 'no-cache'],
287
+ [':authority', 'www.example.com'],
288
+ ],
289
+ table_size: 164,
290
+ },
291
+ ],
292
+ },
293
+ { title: 'D.4. Request Examples with Huffman',
294
+ type: :request,
295
+ table_size: 4096,
296
+ huffman: :always,
297
+ streams: [
298
+ { wire: '8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff',
299
+ emitted: [
300
+ [':method', 'GET'],
301
+ [':scheme', 'http'],
302
+ [':path', '/'],
303
+ [':authority', 'www.example.com'],
304
+ ],
305
+ table: [
306
+ [':authority', 'www.example.com'],
307
+ ],
308
+ table_size: 57,
309
+ },
310
+ { wire: '8286 84be 5886 a8eb 1064 9cbf',
311
+ emitted: [
312
+ [':method', 'GET'],
313
+ [':scheme', 'http'],
314
+ [':path', '/'],
315
+ [':authority', 'www.example.com'],
316
+ ['cache-control', 'no-cache'],
317
+ ],
318
+ table: [
319
+ ['cache-control', 'no-cache'],
320
+ [':authority', 'www.example.com'],
321
+ ],
322
+ table_size: 110,
323
+ },
324
+ { wire: "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925
325
+ a849 e95b b8e8 b4bf",
326
+ emitted: [
327
+ [':method', 'GET'],
328
+ [':scheme', 'https'],
329
+ [':path', '/index.html'],
330
+ [':authority', 'www.example.com'],
331
+ ['custom-key', 'custom-value'],
332
+ ],
333
+ table: [
334
+ ['custom-key', 'custom-value'],
335
+ ['cache-control', 'no-cache'],
336
+ [':authority', 'www.example.com'],
337
+ ],
338
+ table_size: 164,
339
+ },
340
+ ],
341
+ },
342
+ { title: 'D.5. Response Examples without Huffman',
343
+ type: :response,
344
+ table_size: 256,
345
+ huffman: :never,
346
+ streams: [
347
+ { wire: "4803 3330 3258 0770 7269 7661 7465 611d
348
+ 4d6f 6e2c 2032 3120 4f63 7420 3230 3133
349
+ 2032 303a 3133 3a32 3120 474d 546e 1768
350
+ 7474 7073 3a2f 2f77 7777 2e65 7861 6d70
351
+ 6c65 2e63 6f6d",
352
+ emitted: [
353
+ [':status', '302'],
354
+ ['cache-control', 'private'],
355
+ ['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
356
+ ['location', 'https://www.example.com'],
357
+ ],
358
+ table: [
359
+ ['location', 'https://www.example.com'],
360
+ ['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
361
+ ['cache-control', 'private'],
362
+ [':status', '302'],
363
+ ],
364
+ table_size: 222,
365
+ },
366
+ { wire: '4803 3330 37c1 c0bf',
367
+ emitted: [
368
+ [':status', '307'],
369
+ ['cache-control', 'private'],
370
+ ['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
371
+ ['location', 'https://www.example.com'],
372
+ ],
373
+ table: [
374
+ [':status', '307'],
375
+ ['location', 'https://www.example.com'],
376
+ ['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
377
+ ['cache-control', 'private'],
378
+ ],
379
+ table_size: 222,
380
+ },
381
+ { wire: "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420
382
+ 3230 3133 2032 303a 3133 3a32 3220 474d
383
+ 54c0 5a04 677a 6970 7738 666f 6f3d 4153
384
+ 444a 4b48 514b 425a 584f 5157 454f 5049
385
+ 5541 5851 5745 4f49 553b 206d 6178 2d61
386
+ 6765 3d33 3630 303b 2076 6572 7369 6f6e
387
+ 3d31",
388
+ emitted: [
389
+ [':status', '200'],
390
+ ['cache-control', 'private'],
391
+ ['date', 'Mon, 21 Oct 2013 20:13:22 GMT'],
392
+ ['location', 'https://www.example.com'],
393
+ ['content-encoding', 'gzip'],
394
+ ['set-cookie', 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'],
395
+ ],
396
+ table: [
397
+ ['set-cookie', 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'],
398
+ ['content-encoding', 'gzip'],
399
+ ['date', 'Mon, 21 Oct 2013 20:13:22 GMT'],
400
+ ],
401
+ table_size: 215,
402
+ },
403
+ ],
404
+ },
405
+ { title: 'D.6. Response Examples with Huffman',
406
+ type: :response,
407
+ table_size: 256,
408
+ huffman: :always,
409
+ streams: [
410
+ { wire: "4882 6402 5885 aec3 771a 4b61 96d0 7abe
411
+ 9410 54d4 44a8 2005 9504 0b81 66e0 82a6
412
+ 2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8
413
+ e9ae 82ae 43d3",
414
+ emitted: [
415
+ [':status', '302'],
416
+ ['cache-control', 'private'],
417
+ ['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
418
+ ['location', 'https://www.example.com'],
419
+ ],
420
+ table: [
421
+ ['location', 'https://www.example.com'],
422
+ ['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
423
+ ['cache-control', 'private'],
424
+ [':status', '302'],
425
+ ],
426
+ table_size: 222,
427
+ },
428
+ { wire: '4883 640e ffc1 c0bf',
429
+ emitted: [
430
+ [':status', '307'],
431
+ ['cache-control', 'private'],
432
+ ['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
433
+ ['location', 'https://www.example.com'],
434
+ ],
435
+ table: [
436
+ [':status', '307'],
437
+ ['location', 'https://www.example.com'],
438
+ ['date', 'Mon, 21 Oct 2013 20:13:21 GMT'],
439
+ ['cache-control', 'private'],
440
+ ],
441
+ table_size: 222,
442
+ },
443
+ { wire: "88c1 6196 d07a be94 1054 d444 a820 0595
444
+ 040b 8166 e084 a62d 1bff c05a 839b d9ab
445
+ 77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b
446
+ 3960 d5af 2708 7f36 72c1 ab27 0fb5 291f
447
+ 9587 3160 65c0 03ed 4ee5 b106 3d50 07",
448
+ emitted: [
449
+ [':status', '200'],
450
+ ['cache-control', 'private'],
451
+ ['date', 'Mon, 21 Oct 2013 20:13:22 GMT'],
452
+ ['location', 'https://www.example.com'],
453
+ ['content-encoding', 'gzip'],
454
+ ['set-cookie', 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'],
455
+ ],
456
+ table: [
457
+ ['set-cookie', 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'],
458
+ ['content-encoding', 'gzip'],
459
+ ['date', 'Mon, 21 Oct 2013 20:13:22 GMT'],
460
+ ],
461
+ table_size: 215,
462
+ },
463
+ ],
464
+ },
465
+ ]
466
+
467
+ context 'decode' do
468
+ spec_examples.each do |ex|
469
+ context "spec example #{ex[:title]}" do
470
+ ex[:streams].size.times do |nth|
471
+ context "request #{nth + 1}" do
472
+ before { @dc = Decompressor.new(table_size: ex[:table_size]) }
473
+ before do
474
+ (0...nth).each do |i|
475
+ bytes = [ex[:streams][i][:wire].delete(" \n")].pack('H*')
476
+ @dc.decode(HTTP2::Buffer.new(bytes))
477
+ end
478
+ end
479
+ subject do
480
+ bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
481
+ @emitted = @dc.decode(HTTP2::Buffer.new(bytes))
482
+ end
483
+ it 'should emit expected headers' do
484
+ subject
485
+ # order-perserving compare
486
+ expect(@emitted).to eq ex[:streams][nth][:emitted]
487
+ end
488
+ it 'should update header table' do
489
+ subject
490
+ expect(@dc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
491
+ end
492
+ it 'should compute header table size' do
493
+ subject
494
+ expect(@dc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
495
+ end
496
+ end
497
+ end
498
+ end
499
+ end
500
+ end
501
+
502
+ context 'encode' do
503
+ spec_examples.each do |ex|
504
+ context "spec example #{ex[:title]}" do
505
+ ex[:streams].size.times do |nth|
506
+ context "request #{nth + 1}" do
507
+ before do
508
+ @cc = Compressor.new(table_size: ex[:table_size],
509
+ huffman: ex[:huffman])
510
+ end
511
+ before do
512
+ (0...nth).each do |i|
513
+ @cc.encode(ex[:streams][i][:emitted])
514
+ end
515
+ end
516
+ subject do
517
+ @cc.encode(ex[:streams][nth][:emitted])
518
+ end
519
+ it 'should emit expected bytes on wire' do
520
+ expect(subject.unpack('H*').first).to eq ex[:streams][nth][:wire].delete(" \n")
521
+ end
522
+ it 'should update header table' do
523
+ subject
524
+ expect(@cc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
525
+ end
526
+ it 'should compute header table size' do
527
+ subject
528
+ expect(@cc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
529
+ end
530
+ end
531
+ end
532
+ end
533
+ end
534
+ end
535
+ end