http-2 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,302 @@
1
+ module HTTP2
2
+
3
+ # Performs encoding, decoding, and validation of binary HTTP 2.0 frames.
4
+ #
5
+ class Framer
6
+ include Error
7
+
8
+ # Maximum frame size (65535 bytes)
9
+ MAX_PAYLOAD_SIZE = 2**16-1
10
+
11
+ # Maximum stream ID (2^31)
12
+ MAX_STREAM_ID = 0x7fffffff
13
+
14
+ # Maximum window increment value (2^31)
15
+ MAX_WINDOWINC = 0x7fffffff
16
+
17
+ # HTTP 2.0 frame type mapping as defined by the spec
18
+ FRAME_TYPES = {
19
+ data: 0x0,
20
+ headers: 0x1,
21
+ priority: 0x2,
22
+ rst_stream: 0x3,
23
+ settings: 0x4,
24
+ push_promise: 0x5,
25
+ ping: 0x6,
26
+ goaway: 0x7,
27
+ window_update: 0x9,
28
+ continuation: 0xa
29
+ }
30
+
31
+ # Per frame flags as defined by the spec
32
+ FRAME_FLAGS = {
33
+ data: {
34
+ end_stream: 0, reserved: 1
35
+ },
36
+ headers: {
37
+ end_stream: 0, reserved: 1,
38
+ end_headers: 2, priority: 3
39
+ },
40
+ priority: {},
41
+ rst_stream: {},
42
+ settings: {},
43
+ push_promise: { end_push_promise: 0 },
44
+ ping: { pong: 0 },
45
+ goaway: {},
46
+ window_update:{},
47
+ continuation: {
48
+ end_stream: 0, end_headers: 1
49
+ }
50
+ }
51
+
52
+ # Default settings as defined by the spec
53
+ DEFINED_SETTINGS = {
54
+ settings_max_concurrent_streams: 4,
55
+ settings_initial_window_size: 7,
56
+ settings_flow_control_options: 10
57
+ }
58
+
59
+ # Default error types as defined by the spec
60
+ DEFINED_ERRORS = {
61
+ no_error: 0,
62
+ protocol_error: 1,
63
+ internal_error: 2,
64
+ flow_control_error: 3,
65
+ stream_closed: 5,
66
+ frame_too_large: 6,
67
+ refused_stream: 7,
68
+ cancel: 8,
69
+ compression_error: 9
70
+ }
71
+
72
+ RBIT = 0x7fffffff
73
+ RBYTE = 0x0fffffff
74
+ HEADERPACK = "SCCL"
75
+ UINT32 = "L"
76
+
77
+ private_constant :RBIT, :RBYTE, :HEADERPACK, :UINT32
78
+
79
+ # Generates common 8-byte frame header.
80
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-04#section-4.1
81
+ #
82
+ # @param frame [Hash]
83
+ # @return [String]
84
+ def commonHeader(frame)
85
+ header = []
86
+
87
+ if !FRAME_TYPES[frame[:type]]
88
+ raise CompressionError.new("Invalid frame type (#{frame[:type]})")
89
+ end
90
+
91
+ if frame[:length] > MAX_PAYLOAD_SIZE
92
+ raise CompressionError.new("Frame size is too large: #{frame[:length]}")
93
+ end
94
+
95
+ if frame[:stream] > MAX_STREAM_ID
96
+ raise CompressionError.new("Stream ID (#{frame[:stream]}) is too large")
97
+ end
98
+
99
+ if frame[:type] == :window_update && frame[:increment] > MAX_WINDOWINC
100
+ raise CompressionError.new("Window increment (#{frame[:increment]}) is too large")
101
+ end
102
+
103
+ header << frame[:length]
104
+ header << FRAME_TYPES[frame[:type]]
105
+ header << frame[:flags].reduce(0) do |acc, f|
106
+ position = FRAME_FLAGS[frame[:type]][f]
107
+ if !position
108
+ raise CompressionError.new("Invalid frame flag (#{f}) for #{frame[:type]}")
109
+ end
110
+
111
+ acc |= (1 << position)
112
+ acc
113
+ end
114
+
115
+ header << frame[:stream]
116
+ header.pack(HEADERPACK) # 16,8,8,32
117
+ end
118
+
119
+ # Decodes common 8-byte header.
120
+ #
121
+ # @param buf [String]
122
+ def readCommonHeader(buf)
123
+ frame = {}
124
+ frame[:length], type, flags, stream = buf.slice(0,8).unpack(HEADERPACK)
125
+
126
+ frame[:type], _ = FRAME_TYPES.select { |t,pos| type == pos }.first
127
+ frame[:flags] = FRAME_FLAGS[frame[:type]].reduce([]) do |acc, (name, pos)|
128
+ acc << name if (flags & (1 << pos)) > 0
129
+ acc
130
+ end
131
+
132
+ frame[:stream] = stream & RBIT
133
+ frame
134
+ end
135
+
136
+ # Generates encoded HTTP 2.0 frame.
137
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-http2
138
+ #
139
+ # @param frame [Hash]
140
+ def generate(frame)
141
+ bytes = ''
142
+ length = 0
143
+
144
+ frame[:flags] ||= []
145
+ frame[:stream] ||= 0
146
+
147
+ case frame[:type]
148
+ when :data
149
+ bytes += frame[:payload]
150
+ length += frame[:payload].bytesize
151
+
152
+ when :headers
153
+ if frame[:priority]
154
+ frame[:flags] += [:priority] if !frame[:flags].include? :priority
155
+ end
156
+
157
+ if frame[:flags].include? :priority
158
+ bytes += [frame[:priority] & RBIT].pack(UINT32)
159
+ length += 4
160
+ end
161
+
162
+ bytes += frame[:payload]
163
+ length += frame[:payload].bytesize
164
+
165
+ when :priority
166
+ bytes += [frame[:priority] & RBIT].pack(UINT32)
167
+ length += 4
168
+
169
+ when :rst_stream
170
+ bytes += pack_error frame[:error]
171
+ length += 4
172
+
173
+ when :settings
174
+ if frame[:stream] != 0
175
+ raise CompressionError.new("Invalid stream ID (#{frame[:stream]})")
176
+ end
177
+
178
+ frame[:payload].each do |(k,v)|
179
+ if !k.is_a? Integer
180
+ k = DEFINED_SETTINGS[k]
181
+
182
+ if k.nil?
183
+ raise CompressionError.new("Unknown settings ID for #{k}")
184
+ end
185
+ end
186
+
187
+ bytes += [k & RBYTE].pack(UINT32)
188
+ bytes += [v].pack(UINT32)
189
+ length += 8
190
+ end
191
+
192
+ when :push_promise
193
+ bytes += [frame[:promise_stream] & RBIT].pack(UINT32)
194
+ bytes += frame[:payload]
195
+ length += 4 + frame[:payload].bytesize
196
+
197
+ when :ping
198
+ if frame[:payload].bytesize != 8
199
+ raise CompressionError.new("Invalid payload size \
200
+ (#{frame[:payload].size} != 8 bytes)")
201
+ end
202
+ bytes += frame[:payload]
203
+ length += 8
204
+
205
+ when :goaway
206
+ bytes += [frame[:last_stream] & RBIT].pack(UINT32)
207
+ bytes += pack_error frame[:error]
208
+ length += 8
209
+
210
+ if frame[:payload]
211
+ bytes += frame[:payload]
212
+ length += frame[:payload].bytesize
213
+ end
214
+
215
+ when :window_update
216
+ bytes += [frame[:increment] & RBIT].pack(UINT32)
217
+ length += 4
218
+
219
+ when :continuation
220
+ bytes += frame[:payload]
221
+ length += frame[:payload].bytesize
222
+ end
223
+
224
+ frame[:length] = length
225
+ commonHeader(frame) + bytes
226
+ end
227
+
228
+ # Decodes complete HTTP 2.0 frame from provided buffer. If the buffer
229
+ # does not contain enough data, no further work is performed.
230
+ #
231
+ # @param buf [String]
232
+ def parse(buf)
233
+ return nil if buf.size < 8
234
+ frame = readCommonHeader(buf)
235
+ return nil if buf.size < 8 + frame[:length]
236
+
237
+ buf.read(8)
238
+ payload = buf.read(frame[:length])
239
+
240
+ case frame[:type]
241
+ when :data
242
+ frame[:payload] = payload.read(frame[:length])
243
+ when :headers
244
+ if frame[:flags].include? :priority
245
+ frame[:priority] = payload.read(4).unpack(UINT32).first & RBIT
246
+ end
247
+ frame[:payload] = payload.read(frame[:length])
248
+ when :priority
249
+ frame[:priority] = payload.read(4).unpack(UINT32).first & RBIT
250
+ when :rst_stream
251
+ frame[:error] = unpack_error payload.read(4).unpack(UINT32).first
252
+
253
+ when :settings
254
+ frame[:payload] = {}
255
+ (frame[:length] / 8).times do
256
+ id = payload.read(4).unpack(UINT32).first & RBYTE
257
+ val = payload.read(4).unpack(UINT32).first
258
+
259
+ name, _ = DEFINED_SETTINGS.select { |name, v| v == id }.first
260
+ frame[:payload][name || id] = val
261
+ end
262
+ when :push_promise
263
+ frame[:promise_stream] = payload.read(4).unpack(UINT32).first & RBIT
264
+ frame[:payload] = payload.read(frame[:length])
265
+ when :ping
266
+ frame[:payload] = payload.read(frame[:length])
267
+ when :goaway
268
+ frame[:last_stream] = payload.read(4).unpack(UINT32).first & RBIT
269
+ frame[:error] = unpack_error payload.read(4).unpack(UINT32).first
270
+
271
+ size = frame[:length] - 8
272
+ frame[:payload] = payload.read(size) if size > 0
273
+ when :window_update
274
+ frame[:increment] = payload.read(4).unpack(UINT32).first & RBIT
275
+ when :continuation
276
+ frame[:payload] = payload.read(frame[:length])
277
+ end
278
+
279
+ frame
280
+ end
281
+
282
+ private
283
+
284
+ def pack_error(e)
285
+ if !e.is_a? Integer
286
+ e = DEFINED_ERRORS[e]
287
+
288
+ if e.nil?
289
+ raise CompressionError.new("Unknown error ID for #{e}")
290
+ end
291
+ end
292
+
293
+ [e].pack(UINT32)
294
+ end
295
+
296
+ def unpack_error(e)
297
+ name, _ = DEFINED_ERRORS.select { |name, v| v == e }.first
298
+ name || error
299
+ end
300
+
301
+ end
302
+ end