http-2 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,325 @@
1
+ require "helper"
2
+
3
+ describe HTTP2::Framer do
4
+
5
+ let(:f) { Framer.new }
6
+
7
+ context "common header" do
8
+ let(:frame) {
9
+ {
10
+ length: 4,
11
+ type: :headers,
12
+ flags: [:end_stream, :reserved, :end_headers],
13
+ stream: 15,
14
+ }
15
+ }
16
+
17
+ let(:bytes) { [0x04, 0x01, 0x7, 0x0000000F].pack("SCCL") }
18
+
19
+ it "should generate common 8 byte header" do
20
+ f.commonHeader(frame).should eq bytes
21
+ end
22
+
23
+ it "should parse common 8 byte header" do
24
+ f.readCommonHeader(Buffer.new(bytes)).should eq frame
25
+ end
26
+
27
+ it "should raise exception on invalid frame type" do
28
+ expect {
29
+ frame[:type] = :bogus
30
+ f.commonHeader(frame)
31
+ }.to raise_error(CompressionError, /invalid.*type/i)
32
+ end
33
+
34
+ it "should raise exception on invalid stream ID" do
35
+ expect {
36
+ frame[:stream] = Framer::MAX_STREAM_ID + 1
37
+ f.commonHeader(frame)
38
+ }.to raise_error(CompressionError, /stream/i)
39
+ end
40
+
41
+ it "should raise exception on invalid frame flag" do
42
+ expect {
43
+ frame[:flags] = [:bogus]
44
+ f.commonHeader(frame)
45
+ }.to raise_error(CompressionError, /frame flag/)
46
+ end
47
+
48
+ it "should raise exception on invalid frame size" do
49
+ expect {
50
+ frame[:length] = 2**16
51
+ f.commonHeader(frame)
52
+ }.to raise_error(CompressionError, /too large/)
53
+ end
54
+ end
55
+
56
+ context "DATA" do
57
+ it "should generate and parse bytes" do
58
+ frame = {
59
+ length: 4,
60
+ type: :data,
61
+ flags: [:end_stream, :reserved],
62
+ stream: 1,
63
+ payload: 'text'
64
+ }
65
+
66
+ bytes = f.generate(frame)
67
+ bytes.should eq [0x4,0x0,0x3,0x1,*'text'.bytes].pack("SCCLC*")
68
+
69
+ f.parse(Buffer.new(bytes)).should eq frame
70
+ end
71
+ end
72
+
73
+ context "HEADERS" do
74
+ it "should generate and parse bytes" do
75
+ frame = {
76
+ length: 12,
77
+ type: :headers,
78
+ flags: [:end_stream, :reserved, :end_headers],
79
+ stream: 1,
80
+ payload: 'header-block'
81
+ }
82
+
83
+ bytes = f.generate(frame)
84
+ bytes.should eq [0xc,0x1,0x7,0x1,*'header-block'.bytes].pack("SCCLC*")
85
+ f.parse(Buffer.new(bytes)).should eq frame
86
+ end
87
+
88
+ it "should carry an optional stream priority" do
89
+ frame = {
90
+ length: 16,
91
+ type: :headers,
92
+ flags: [:end_headers, :priority],
93
+ stream: 1,
94
+ priority: 15,
95
+ payload: 'header-block'
96
+ }
97
+
98
+ bytes = f.generate(frame)
99
+ bytes.should eq [0x10,0x1,0xc,0x1,0xf,*'header-block'.bytes].pack("SCCLLC*")
100
+ f.parse(Buffer.new(bytes)).should eq frame
101
+ end
102
+ end
103
+
104
+ context "PRIORITY" do
105
+ it "should generate and parse bytes" do
106
+ frame = {
107
+ length: 4,
108
+ type: :priority,
109
+ stream: 1,
110
+ priority: 15
111
+ }
112
+
113
+ bytes = f.generate(frame)
114
+ bytes.should eq [0x4,0x2,0x0,0x1,0xf].pack("SCCLL")
115
+ f.parse(Buffer.new(bytes)).should eq frame
116
+ end
117
+ end
118
+
119
+ context "RST_STREAM" do
120
+ it "should generate and parse bytes" do
121
+ frame = {
122
+ length: 4,
123
+ type: :rst_stream,
124
+ stream: 1,
125
+ error: :stream_closed
126
+ }
127
+
128
+ bytes = f.generate(frame)
129
+ bytes.should eq [0x4,0x3,0x0,0x1,0x5].pack("SCCLL")
130
+ f.parse(Buffer.new(bytes)).should eq frame
131
+ end
132
+ end
133
+
134
+ context "SETTINGS" do
135
+ let(:frame) {
136
+ {
137
+ length: 8,
138
+ type: :settings,
139
+ stream: 0,
140
+ payload: {
141
+ settings_max_concurrent_streams: 10
142
+ }
143
+ }
144
+ }
145
+
146
+ it "should generate and parse bytes" do
147
+
148
+ bytes = f.generate(frame)
149
+ bytes.should eq [0x8,0x4,0x0,0x0,0x4,0xa].pack("SCCLLL")
150
+ f.parse(Buffer.new(bytes)).should eq frame
151
+ end
152
+
153
+ it "should encode custom settings" do
154
+ frame[:length] = 8*3
155
+ frame[:payload] = {
156
+ settings_max_concurrent_streams: 10,
157
+ settings_initial_window_size: 20,
158
+ 55 => 30
159
+ }
160
+
161
+ f.parse(Buffer.new(f.generate(frame))).should eq frame
162
+
163
+ end
164
+
165
+ it "should raise exception on invalid stream ID" do
166
+ expect {
167
+ frame[:stream] = 1
168
+ f.generate(frame)
169
+ }.to raise_error(CompressionError, /Invalid stream ID/)
170
+ end
171
+
172
+ it "should raise exception on invalid setting" do
173
+ expect {
174
+ frame[:payload] = {random: 23}
175
+ f.generate(frame)
176
+ }.to raise_error(CompressionError, /Unknown settings ID/)
177
+ end
178
+ end
179
+
180
+ context "PUSH_PROMISE" do
181
+ it "should generate and parse bytes" do
182
+ frame = {
183
+ length: 11,
184
+ type: :push_promise,
185
+ flags: [:end_push_promise],
186
+ stream: 1,
187
+ promise_stream: 2,
188
+ payload: 'headers'
189
+ }
190
+
191
+ bytes = f.generate(frame)
192
+ bytes.should eq [0xb,0x5,0x1,0x1,0x2,*'headers'.bytes].pack("SCCLLC*")
193
+ f.parse(Buffer.new(bytes)).should eq frame
194
+ end
195
+ end
196
+
197
+ context "PING" do
198
+ let(:frame) {
199
+ {
200
+ length: 8,
201
+ stream: 1,
202
+ type: :ping,
203
+ flags: [:pong],
204
+ payload: '12345678'
205
+ }
206
+ }
207
+
208
+ it "should generate and parse bytes" do
209
+ bytes = f.generate(frame)
210
+ bytes.should eq [0x8,0x6,0x1,0x1,*'12345678'.bytes].pack("SCCLC*")
211
+ f.parse(Buffer.new(bytes)).should eq frame
212
+ end
213
+
214
+ it "should raise exception on invalid payload" do
215
+ expect {
216
+ frame[:payload] = "1234"
217
+ f.generate(frame)
218
+ }.to raise_error(CompressionError, /Invalid payload size/)
219
+ end
220
+ end
221
+
222
+ context "GOAWAY" do
223
+ let(:frame) {
224
+ {
225
+ length: 13,
226
+ stream: 1,
227
+ type: :goaway,
228
+ last_stream: 2,
229
+ error: :no_error,
230
+ payload: 'debug'
231
+ }
232
+ }
233
+
234
+ it "should generate and parse bytes" do
235
+ bytes = f.generate(frame)
236
+ bytes.should eq [0xd,0x7,0x0,0x1,0x2,0x0,*'debug'.bytes].pack("SCCLLLC*")
237
+ f.parse(Buffer.new(bytes)).should eq frame
238
+ end
239
+
240
+ it "should treat debug payload as optional" do
241
+ frame.delete :payload
242
+ frame[:length] = 0x8
243
+
244
+ bytes = f.generate(frame)
245
+ bytes.should eq [0x8,0x7,0x0,0x1,0x2,0x0].pack("SCCLLL")
246
+ f.parse(Buffer.new(bytes)).should eq frame
247
+ end
248
+ end
249
+
250
+ context "WINDOW_UPDATE" do
251
+ it "should generate and parse bytes" do
252
+ frame = {
253
+ length: 4,
254
+ type: :window_update,
255
+ increment: 10
256
+ }
257
+
258
+ bytes = f.generate(frame)
259
+ bytes.should eq [0x4,0x9,0x0,0x0,0xa].pack("SCCLL")
260
+ f.parse(Buffer.new(bytes)).should eq frame
261
+ end
262
+ end
263
+
264
+ context "CONTINUATION" do
265
+ it "should generate and parse bytes" do
266
+ frame = {
267
+ length: 12,
268
+ type: :continuation,
269
+ stream: 1,
270
+ flags: [:end_stream, :end_headers],
271
+ payload: 'header-block'
272
+ }
273
+
274
+ bytes = f.generate(frame)
275
+ bytes.should eq [0xc,0xa,0x3,0x1,*'header-block'.bytes].pack("SCCLC*")
276
+ f.parse(Buffer.new(bytes)).should eq frame
277
+ end
278
+ end
279
+
280
+ it "should determine frame length" do
281
+ frames = [
282
+ [{type: :data, stream: 1, flags: [:end_stream], payload: "abc"}, 3],
283
+ [{type: :headers, stream: 1, payload: "abc"}, 3],
284
+ [{type: :priority, stream: 3, priority: 30}, 4],
285
+ [{type: :rst_stream, stream: 3, error: 100}, 4],
286
+ [{type: :settings, payload: {settings_max_concurrent_streams: 10}}, 8],
287
+ [{type: :push_promise, promise_stream: 5, payload: "abc"}, 7],
288
+ [{type: :ping, payload: "blob"*2}, 8],
289
+ [{type: :goaway, last_stream: 5, error: 20, payload: "blob"}, 12],
290
+ [{type: :window_update, stream: 1, increment: 1024}, 4],
291
+ [{type: :continuation, stream: 1, payload: "abc"}, 3]
292
+ ]
293
+
294
+ frames.each do |(frame, size)|
295
+ bytes = f.generate(frame)
296
+ Buffer.new(bytes).slice(0,2).unpack("S").first.should eq size
297
+ end
298
+ end
299
+
300
+ it "should parse single frame at a time" do
301
+ frames = [
302
+ {type: :headers, stream: 1, payload: "headers"},
303
+ {type: :data, stream: 1, flags: [:end_stream], payload: "abc"}
304
+ ]
305
+
306
+ buf = Buffer.new(f.generate(frames[0]) + f.generate(frames[1]))
307
+
308
+ f.parse(buf).should eq frames[0]
309
+ f.parse(buf).should eq frames[1]
310
+ end
311
+
312
+ it "should process full frames only" do
313
+ frame = {type: :headers, stream: 1, payload: "headers"}
314
+ bytes = f.generate(frame)
315
+
316
+ buf = Buffer.new(bytes[0...-1])
317
+ f.parse(buf).should be_nil
318
+ buf.should eq bytes[0...-1]
319
+
320
+ buf = Buffer.new(bytes)
321
+ f.parse(buf).should eq frame
322
+ buf.should be_empty
323
+ end
324
+
325
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,98 @@
1
+ require 'http/2'
2
+
3
+ include HTTP2
4
+ include HTTP2::Header
5
+ include HTTP2::Error
6
+
7
+ DATA = {
8
+ type: :data,
9
+ flags: [:end_stream],
10
+ stream: 1,
11
+ payload: 'text'
12
+ }
13
+
14
+ HEADERS = {
15
+ type: :headers,
16
+ flags: [:end_headers],
17
+ stream: 1,
18
+ payload: 'header-block'
19
+ }
20
+
21
+ HEADERS_END_STREAM = {
22
+ type: :headers,
23
+ flags: [:end_headers, :end_stream],
24
+ stream: 1,
25
+ payload: 'header-block'
26
+ }
27
+
28
+ PRIORITY = {
29
+ type: :priority,
30
+ stream: 1,
31
+ priority: 15
32
+ }
33
+
34
+ RST_STREAM = {
35
+ type: :rst_stream,
36
+ stream: 1,
37
+ error: :stream_closed
38
+ }
39
+
40
+ SETTINGS = {
41
+ type: :settings,
42
+ stream: 0,
43
+ payload: {
44
+ settings_max_concurrent_streams: 10,
45
+ settings_flow_control_options: 1
46
+ }
47
+ }
48
+
49
+ PUSH_PROMISE = {
50
+ type: :push_promise,
51
+ flags: [:end_push_promise],
52
+ stream: 1,
53
+ promise_stream: 2,
54
+ payload: 'headers'
55
+ }
56
+
57
+ PING = {
58
+ stream: 0,
59
+ type: :ping,
60
+ payload: '12345678'
61
+ }
62
+
63
+ PONG = {
64
+ stream: 0,
65
+ type: :ping,
66
+ flags: [:pong],
67
+ payload: '12345678'
68
+ }
69
+
70
+ GOAWAY = {
71
+ type: :goaway,
72
+ last_stream: 2,
73
+ error: :no_error,
74
+ payload: 'debug'
75
+ }
76
+
77
+ WINDOW_UPDATE = {
78
+ type: :window_update,
79
+ increment: 10
80
+ }
81
+
82
+ CONTINUATION = {
83
+ type: :continuation,
84
+ flags: [:end_headers],
85
+ payload: '-second-block'
86
+ }
87
+
88
+ FRAME_TYPES = [
89
+ DATA, HEADERS, PRIORITY, RST_STREAM, SETTINGS, PUSH_PROMISE,
90
+ PING, GOAWAY, WINDOW_UPDATE, CONTINUATION
91
+ ]
92
+
93
+ def set_stream_id(bytes, id)
94
+ head = bytes.slice!(0,8).unpack("SCCL")
95
+ head[3] = id
96
+
97
+ head.pack("SCCL") + bytes
98
+ end