http-2-next 0.1.0
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.
- checksums.yaml +7 -0
- data/README.md +302 -0
- data/lib/http/2/next.rb +15 -0
- data/lib/http/2/next/buffer.rb +78 -0
- data/lib/http/2/next/client.rb +72 -0
- data/lib/http/2/next/compressor.rb +619 -0
- data/lib/http/2/next/connection.rb +757 -0
- data/lib/http/2/next/emitter.rb +48 -0
- data/lib/http/2/next/error.rb +59 -0
- data/lib/http/2/next/flow_buffer.rb +119 -0
- data/lib/http/2/next/framer.rb +445 -0
- data/lib/http/2/next/huffman.rb +325 -0
- data/lib/http/2/next/huffman_statemachine.rb +274 -0
- data/lib/http/2/next/server.rb +135 -0
- data/lib/http/2/next/stream.rb +692 -0
- data/lib/http/2/next/version.rb +5 -0
- data/lib/tasks/generate_huffman_table.rb +169 -0
- metadata +61 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTP2Next
|
4
|
+
# Basic event emitter implementation with support for persistent and
|
5
|
+
# one-time event callbacks.
|
6
|
+
#
|
7
|
+
module Emitter
|
8
|
+
# Subscribe to all future events for specified type.
|
9
|
+
#
|
10
|
+
# @param event [Symbol]
|
11
|
+
# @param block [Proc] callback function
|
12
|
+
def add_listener(event, &block)
|
13
|
+
raise ArgumentError, "must provide callback" unless block_given?
|
14
|
+
|
15
|
+
listeners(event.to_sym).push block
|
16
|
+
end
|
17
|
+
alias on add_listener
|
18
|
+
|
19
|
+
# Subscribe to next event (at most once) for specified type.
|
20
|
+
#
|
21
|
+
# @param event [Symbol]
|
22
|
+
# @param block [Proc] callback function
|
23
|
+
def once(event, &block)
|
24
|
+
add_listener(event) do |*args, &callback|
|
25
|
+
block.call(*args, &callback)
|
26
|
+
:delete
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Emit event with provided arguments.
|
31
|
+
#
|
32
|
+
# @param event [Symbol]
|
33
|
+
# @param args [Array] arguments to be passed to the callbacks
|
34
|
+
# @param block [Proc] callback function
|
35
|
+
def emit(event, *args, &block)
|
36
|
+
listeners(event).delete_if do |cb|
|
37
|
+
cb.call(*args, &block) == :delete
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def listeners(event)
|
44
|
+
@listeners ||= Hash.new { |hash, key| hash[key] = [] }
|
45
|
+
@listeners[event]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTP2Next
|
4
|
+
# Stream, connection, and compressor exceptions.
|
5
|
+
module Error
|
6
|
+
@types = {}
|
7
|
+
class << self
|
8
|
+
attr_reader :types
|
9
|
+
end
|
10
|
+
|
11
|
+
class Error < StandardError
|
12
|
+
def self.inherited(klass)
|
13
|
+
type = klass.name.split("::").last
|
14
|
+
type = type.gsub(/([^\^])([A-Z])/, '\1_\2').downcase.to_sym
|
15
|
+
HTTP2Next::Error.types[type] = klass
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Raised if connection header is missing or invalid indicating that
|
20
|
+
# this is an invalid HTTP 2.0 request - no frames are emitted and the
|
21
|
+
# connection must be aborted.
|
22
|
+
class HandshakeError < Error; end
|
23
|
+
|
24
|
+
# Raised by stream or connection handlers, results in GOAWAY frame
|
25
|
+
# which signals termination of the current connection. You *cannot*
|
26
|
+
# recover from this exception, or any exceptions subclassed from it.
|
27
|
+
class ProtocolError < Error; end
|
28
|
+
|
29
|
+
# Raised on any header encoding / decoding exception.
|
30
|
+
#
|
31
|
+
# @see ProtocolError
|
32
|
+
class CompressionError < ProtocolError; end
|
33
|
+
|
34
|
+
# Raised on invalid flow control frame or command.
|
35
|
+
#
|
36
|
+
# @see ProtocolError
|
37
|
+
class FlowControlError < ProtocolError; end
|
38
|
+
|
39
|
+
# Raised on invalid stream processing: invalid frame type received or
|
40
|
+
# sent, or invalid command issued.
|
41
|
+
class InternalError < ProtocolError; end
|
42
|
+
|
43
|
+
#
|
44
|
+
# -- Recoverable errors -------------------------------------------------
|
45
|
+
#
|
46
|
+
|
47
|
+
# Raised if stream has been closed and new frames cannot be sent.
|
48
|
+
class StreamClosed < Error; end
|
49
|
+
|
50
|
+
# Raised if connection has been closed (or draining) and new stream
|
51
|
+
# cannot be opened.
|
52
|
+
class ConnectionClosed < Error; end
|
53
|
+
|
54
|
+
# Raised if stream limit has been reached and new stream cannot be opened.
|
55
|
+
class StreamLimitExceeded < Error; end
|
56
|
+
|
57
|
+
class FrameSizeError < Error; end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTP2Next
|
4
|
+
# Implementation of stream and connection DATA flow control: frames may
|
5
|
+
# be split and / or may be buffered based on current flow control window.
|
6
|
+
#
|
7
|
+
module FlowBuffer
|
8
|
+
include Error
|
9
|
+
|
10
|
+
MAX_WINDOW_SIZE = 2**31 - 1
|
11
|
+
|
12
|
+
# Amount of buffered data. Only DATA payloads are subject to flow stream
|
13
|
+
# and connection flow control.
|
14
|
+
#
|
15
|
+
# @return [Integer]
|
16
|
+
def buffered_amount
|
17
|
+
@send_buffer.map { |f| f[:length] }.reduce(0, :+)
|
18
|
+
end
|
19
|
+
|
20
|
+
def flush
|
21
|
+
send_data
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def update_local_window(frame)
|
27
|
+
frame_size = frame[:payload].bytesize
|
28
|
+
frame_size += frame[:padding] || 0
|
29
|
+
@local_window -= frame_size
|
30
|
+
end
|
31
|
+
|
32
|
+
def calculate_window_update(window_max_size)
|
33
|
+
# If DATA frame is received with length > 0 and
|
34
|
+
# current received window size + delta length is strictly larger than
|
35
|
+
# local window size, it throws a flow control error.
|
36
|
+
#
|
37
|
+
error(:flow_control_error) if @local_window < 0
|
38
|
+
|
39
|
+
# Send WINDOW_UPDATE if the received window size goes over
|
40
|
+
# the local window size / 2.
|
41
|
+
#
|
42
|
+
# The HTTP/2 spec mandates that every DATA frame received
|
43
|
+
# generates a WINDOW_UPDATE to send. In some cases however,
|
44
|
+
# (ex: DATA frames with short payloads),
|
45
|
+
# the noise generated by flow control frames creates enough
|
46
|
+
# congestion for this to be deemed very inefficient.
|
47
|
+
#
|
48
|
+
# This heuristic was inherited from nghttp, which delays the
|
49
|
+
# WINDOW_UPDATE until at least half the window is exhausted.
|
50
|
+
# This works because the sender doesn't need those increments
|
51
|
+
# until the receiver window is exhausted, after which he'll be
|
52
|
+
# waiting for the WINDOW_UPDATE frame.
|
53
|
+
return unless @local_window <= (window_max_size / 2)
|
54
|
+
|
55
|
+
window_update(window_max_size - @local_window)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Buffers outgoing DATA frames and applies flow control logic to split
|
59
|
+
# and emit DATA frames based on current flow control window. If the
|
60
|
+
# window is large enough, the data is sent immediately. Otherwise, the
|
61
|
+
# data is buffered until the flow control window is updated.
|
62
|
+
#
|
63
|
+
# Buffered DATA frames are emitted in FIFO order.
|
64
|
+
#
|
65
|
+
# @param frame [Hash]
|
66
|
+
# @param encode [Boolean] set to true by co
|
67
|
+
def send_data(frame = nil, encode = false)
|
68
|
+
@send_buffer.push frame unless frame.nil?
|
69
|
+
|
70
|
+
# FIXME: Frames with zero length with the END_STREAM flag set (that
|
71
|
+
# is, an empty DATA frame) MAY be sent if there is no available space
|
72
|
+
# in either flow control window.
|
73
|
+
while @remote_window > 0 && !@send_buffer.empty?
|
74
|
+
frame = @send_buffer.shift
|
75
|
+
|
76
|
+
sent = 0
|
77
|
+
frame_size = frame[:payload].bytesize
|
78
|
+
|
79
|
+
if frame_size > @remote_window
|
80
|
+
payload = frame.delete(:payload)
|
81
|
+
chunk = frame.dup
|
82
|
+
|
83
|
+
# Split frame so that it fits in the window
|
84
|
+
# TODO: consider padding!
|
85
|
+
frame[:payload] = payload.slice!(0, @remote_window)
|
86
|
+
frame[:length] = frame[:payload].bytesize
|
87
|
+
chunk[:length] = payload.bytesize
|
88
|
+
chunk[:payload] = payload
|
89
|
+
|
90
|
+
# if no longer last frame in sequence...
|
91
|
+
frame[:flags] -= [:end_stream] if frame[:flags].include? :end_stream
|
92
|
+
|
93
|
+
@send_buffer.unshift chunk
|
94
|
+
sent = @remote_window
|
95
|
+
else
|
96
|
+
sent = frame_size
|
97
|
+
end
|
98
|
+
|
99
|
+
manage_state(frame) do
|
100
|
+
frames = encode ? encode(frame) : [frame]
|
101
|
+
frames.each { |f| emit(:frame, f) }
|
102
|
+
@remote_window -= sent
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def process_window_update(frame:, encode: false)
|
108
|
+
return if frame[:ignore]
|
109
|
+
|
110
|
+
if frame[:increment]
|
111
|
+
raise ProtocolError, "increment MUST be higher than zero" if frame[:increment].zero?
|
112
|
+
|
113
|
+
@remote_window += frame[:increment]
|
114
|
+
error(:flow_control_error, msg: "window size too large") if @remote_window > MAX_WINDOW_SIZE
|
115
|
+
end
|
116
|
+
send_data(nil, encode)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,445 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTP2Next
|
4
|
+
# Performs encoding, decoding, and validation of binary HTTP/2 frames.
|
5
|
+
#
|
6
|
+
class Framer
|
7
|
+
include Error
|
8
|
+
|
9
|
+
# Default value of max frame size (16384 bytes)
|
10
|
+
DEFAULT_MAX_FRAME_SIZE = 2**14
|
11
|
+
|
12
|
+
# maximum frame size
|
13
|
+
attr_accessor :local_max_frame_size, :remote_max_frame_size
|
14
|
+
|
15
|
+
# Maximum stream ID (2^31)
|
16
|
+
MAX_STREAM_ID = 0x7fffffff
|
17
|
+
|
18
|
+
# Maximum window increment value (2^31)
|
19
|
+
MAX_WINDOWINC = 0x7fffffff
|
20
|
+
|
21
|
+
# HTTP/2 frame type mapping as defined by the spec
|
22
|
+
FRAME_TYPES = {
|
23
|
+
data: 0x0,
|
24
|
+
headers: 0x1,
|
25
|
+
priority: 0x2,
|
26
|
+
rst_stream: 0x3,
|
27
|
+
settings: 0x4,
|
28
|
+
push_promise: 0x5,
|
29
|
+
ping: 0x6,
|
30
|
+
goaway: 0x7,
|
31
|
+
window_update: 0x8,
|
32
|
+
continuation: 0x9,
|
33
|
+
altsvc: 0xa
|
34
|
+
}.freeze
|
35
|
+
|
36
|
+
FRAME_TYPES_WITH_PADDING = %i[data headers push_promise].freeze
|
37
|
+
|
38
|
+
# Per frame flags as defined by the spec
|
39
|
+
FRAME_FLAGS = {
|
40
|
+
data: {
|
41
|
+
end_stream: 0,
|
42
|
+
padded: 3,
|
43
|
+
compressed: 5
|
44
|
+
},
|
45
|
+
headers: {
|
46
|
+
end_stream: 0,
|
47
|
+
end_headers: 2,
|
48
|
+
padded: 3,
|
49
|
+
priority: 5
|
50
|
+
},
|
51
|
+
priority: {},
|
52
|
+
rst_stream: {},
|
53
|
+
settings: { ack: 0 },
|
54
|
+
push_promise: {
|
55
|
+
end_headers: 2,
|
56
|
+
padded: 3
|
57
|
+
},
|
58
|
+
ping: { ack: 0 },
|
59
|
+
goaway: {},
|
60
|
+
window_update: {},
|
61
|
+
continuation: { end_headers: 2 },
|
62
|
+
altsvc: {}
|
63
|
+
}.each_value(&:freeze).freeze
|
64
|
+
|
65
|
+
# Default settings as defined by the spec
|
66
|
+
DEFINED_SETTINGS = {
|
67
|
+
settings_header_table_size: 1,
|
68
|
+
settings_enable_push: 2,
|
69
|
+
settings_max_concurrent_streams: 3,
|
70
|
+
settings_initial_window_size: 4,
|
71
|
+
settings_max_frame_size: 5,
|
72
|
+
settings_max_header_list_size: 6
|
73
|
+
}.freeze
|
74
|
+
|
75
|
+
# Default error types as defined by the spec
|
76
|
+
DEFINED_ERRORS = {
|
77
|
+
no_error: 0,
|
78
|
+
protocol_error: 1,
|
79
|
+
internal_error: 2,
|
80
|
+
flow_control_error: 3,
|
81
|
+
settings_timeout: 4,
|
82
|
+
stream_closed: 5,
|
83
|
+
frame_size_error: 6,
|
84
|
+
refused_stream: 7,
|
85
|
+
cancel: 8,
|
86
|
+
compression_error: 9,
|
87
|
+
connect_error: 10,
|
88
|
+
enhance_your_calm: 11,
|
89
|
+
inadequate_security: 12,
|
90
|
+
http_1_1_required: 13
|
91
|
+
}.freeze
|
92
|
+
|
93
|
+
RBIT = 0x7fffffff
|
94
|
+
RBYTE = 0x0fffffff
|
95
|
+
EBIT = 0x80000000
|
96
|
+
UINT32 = "N"
|
97
|
+
UINT16 = "n"
|
98
|
+
UINT8 = "C"
|
99
|
+
HEADERPACK = (UINT8 + UINT16 + UINT8 + UINT8 + UINT32).freeze
|
100
|
+
FRAME_LENGTH_HISHIFT = 16
|
101
|
+
FRAME_LENGTH_LOMASK = 0xFFFF
|
102
|
+
|
103
|
+
private_constant :RBIT, :RBYTE, :EBIT, :HEADERPACK, :UINT32, :UINT16, :UINT8
|
104
|
+
|
105
|
+
# Initializes new framer object.
|
106
|
+
#
|
107
|
+
def initialize(local_max_frame_size = DEFAULT_MAX_FRAME_SIZE,
|
108
|
+
remote_max_frame_size = DEFAULT_MAX_FRAME_SIZE)
|
109
|
+
@local_max_frame_size = local_max_frame_size
|
110
|
+
@remote_max_frame_size = remote_max_frame_size
|
111
|
+
end
|
112
|
+
|
113
|
+
# Generates common 9-byte frame header.
|
114
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1
|
115
|
+
#
|
116
|
+
# @param frame [Hash]
|
117
|
+
# @return [String]
|
118
|
+
def common_header(frame)
|
119
|
+
header = []
|
120
|
+
|
121
|
+
raise CompressionError, "Invalid frame type (#{frame[:type]})" unless FRAME_TYPES[frame[:type]]
|
122
|
+
|
123
|
+
raise CompressionError, "Frame size is too large: #{frame[:length]}" if frame[:length] > @remote_max_frame_size
|
124
|
+
|
125
|
+
raise CompressionError, "Frame size is invalid: #{frame[:length]}" if frame[:length] < 0
|
126
|
+
|
127
|
+
raise CompressionError, "Stream ID (#{frame[:stream]}) is too large" if frame[:stream] > MAX_STREAM_ID
|
128
|
+
|
129
|
+
if frame[:type] == :window_update && frame[:increment] > MAX_WINDOWINC
|
130
|
+
raise CompressionError, "Window increment (#{frame[:increment]}) is too large"
|
131
|
+
end
|
132
|
+
|
133
|
+
header << (frame[:length] >> FRAME_LENGTH_HISHIFT)
|
134
|
+
header << (frame[:length] & FRAME_LENGTH_LOMASK)
|
135
|
+
header << FRAME_TYPES[frame[:type]]
|
136
|
+
header << frame[:flags].reduce(0) do |acc, f|
|
137
|
+
position = FRAME_FLAGS[frame[:type]][f]
|
138
|
+
raise CompressionError, "Invalid frame flag (#{f}) for #{frame[:type]}" unless position
|
139
|
+
|
140
|
+
acc | (1 << position)
|
141
|
+
end
|
142
|
+
|
143
|
+
header << frame[:stream]
|
144
|
+
header.pack(HEADERPACK) # 8+16,8,8,32
|
145
|
+
end
|
146
|
+
|
147
|
+
# Decodes common 9-byte header.
|
148
|
+
#
|
149
|
+
# @param buf [Buffer]
|
150
|
+
def read_common_header(buf)
|
151
|
+
frame = {}
|
152
|
+
len_hi, len_lo, type, flags, stream = buf.slice(0, 9).unpack(HEADERPACK)
|
153
|
+
|
154
|
+
frame[:length] = (len_hi << FRAME_LENGTH_HISHIFT) | len_lo
|
155
|
+
frame[:type], = FRAME_TYPES.find { |_t, pos| type == pos }
|
156
|
+
if frame[:type]
|
157
|
+
frame[:flags] = FRAME_FLAGS[frame[:type]].each_with_object([]) do |(name, pos), acc|
|
158
|
+
acc << name if (flags & (1 << pos)) > 0
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
frame[:stream] = stream & RBIT
|
163
|
+
frame
|
164
|
+
end
|
165
|
+
|
166
|
+
# Generates encoded HTTP/2 frame.
|
167
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2
|
168
|
+
#
|
169
|
+
# @param frame [Hash]
|
170
|
+
def generate(frame)
|
171
|
+
bytes = Buffer.new
|
172
|
+
length = 0
|
173
|
+
|
174
|
+
frame[:flags] ||= []
|
175
|
+
frame[:stream] ||= 0
|
176
|
+
|
177
|
+
case frame[:type]
|
178
|
+
when :data
|
179
|
+
bytes << frame[:payload]
|
180
|
+
length += frame[:payload].bytesize
|
181
|
+
|
182
|
+
when :headers
|
183
|
+
if frame[:weight] || frame[:dependency] || !frame[:exclusive].nil?
|
184
|
+
unless frame[:weight] && frame[:dependency] && !frame[:exclusive].nil?
|
185
|
+
raise CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
|
186
|
+
end
|
187
|
+
|
188
|
+
frame[:flags] += [:priority] unless frame[:flags].include? :priority
|
189
|
+
end
|
190
|
+
|
191
|
+
if frame[:flags].include? :priority
|
192
|
+
bytes << [(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)].pack(UINT32)
|
193
|
+
bytes << [frame[:weight] - 1].pack(UINT8)
|
194
|
+
length += 5
|
195
|
+
end
|
196
|
+
|
197
|
+
bytes << frame[:payload]
|
198
|
+
length += frame[:payload].bytesize
|
199
|
+
|
200
|
+
when :priority
|
201
|
+
unless frame[:weight] && frame[:dependency] && !frame[:exclusive].nil?
|
202
|
+
raise CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
|
203
|
+
end
|
204
|
+
|
205
|
+
bytes << [(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)].pack(UINT32)
|
206
|
+
bytes << [frame[:weight] - 1].pack(UINT8)
|
207
|
+
length += 5
|
208
|
+
|
209
|
+
when :rst_stream
|
210
|
+
bytes << pack_error(frame[:error])
|
211
|
+
length += 4
|
212
|
+
|
213
|
+
when :settings
|
214
|
+
raise CompressionError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
|
215
|
+
|
216
|
+
frame[:payload].each do |(k, v)|
|
217
|
+
if k.is_a? Integer
|
218
|
+
DEFINED_SETTINGS.value?(k) || next
|
219
|
+
else
|
220
|
+
k = DEFINED_SETTINGS[k]
|
221
|
+
|
222
|
+
raise CompressionError, "Unknown settings ID for #{k}" if k.nil?
|
223
|
+
end
|
224
|
+
|
225
|
+
bytes << [k].pack(UINT16)
|
226
|
+
bytes << [v].pack(UINT32)
|
227
|
+
length += 6
|
228
|
+
end
|
229
|
+
|
230
|
+
when :push_promise
|
231
|
+
bytes << [frame[:promise_stream] & RBIT].pack(UINT32)
|
232
|
+
bytes << frame[:payload]
|
233
|
+
length += 4 + frame[:payload].bytesize
|
234
|
+
|
235
|
+
when :ping
|
236
|
+
if frame[:payload].bytesize != 8
|
237
|
+
raise CompressionError, "Invalid payload size (#{frame[:payload].size} != 8 bytes)"
|
238
|
+
end
|
239
|
+
|
240
|
+
bytes << frame[:payload]
|
241
|
+
length += 8
|
242
|
+
|
243
|
+
when :goaway
|
244
|
+
bytes << [frame[:last_stream] & RBIT].pack(UINT32)
|
245
|
+
bytes << pack_error(frame[:error])
|
246
|
+
length += 8
|
247
|
+
|
248
|
+
if frame[:payload]
|
249
|
+
bytes << frame[:payload]
|
250
|
+
length += frame[:payload].bytesize
|
251
|
+
end
|
252
|
+
|
253
|
+
when :window_update
|
254
|
+
bytes << [frame[:increment] & RBIT].pack(UINT32)
|
255
|
+
length += 4
|
256
|
+
|
257
|
+
when :continuation
|
258
|
+
bytes << frame[:payload]
|
259
|
+
length += frame[:payload].bytesize
|
260
|
+
|
261
|
+
when :altsvc
|
262
|
+
bytes << [frame[:max_age], frame[:port]].pack(UINT32 + UINT16)
|
263
|
+
length += 6
|
264
|
+
if frame[:proto]
|
265
|
+
raise CompressionError, "Proto too long" if frame[:proto].bytesize > 255
|
266
|
+
|
267
|
+
bytes << [frame[:proto].bytesize].pack(UINT8)
|
268
|
+
bytes << frame[:proto]
|
269
|
+
length += 1 + frame[:proto].bytesize
|
270
|
+
else
|
271
|
+
bytes << [0].pack(UINT8)
|
272
|
+
length += 1
|
273
|
+
end
|
274
|
+
if frame[:host]
|
275
|
+
raise CompressionError, "Host too long" if frame[:host].bytesize > 255
|
276
|
+
|
277
|
+
bytes << [frame[:host].bytesize].pack(UINT8)
|
278
|
+
bytes << frame[:host]
|
279
|
+
length += 1 + frame[:host].bytesize
|
280
|
+
else
|
281
|
+
bytes << [0].pack(UINT8)
|
282
|
+
length += 1
|
283
|
+
end
|
284
|
+
if frame[:origin]
|
285
|
+
bytes << frame[:origin]
|
286
|
+
length += frame[:origin].bytesize
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Process padding.
|
291
|
+
# frame[:padding] gives number of extra octets to be added.
|
292
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.1
|
293
|
+
if frame[:padding]
|
294
|
+
unless FRAME_TYPES_WITH_PADDING.include?(frame[:type])
|
295
|
+
raise CompressionError, "Invalid padding flag for #{frame[:type]}"
|
296
|
+
end
|
297
|
+
|
298
|
+
padlen = frame[:padding]
|
299
|
+
|
300
|
+
if padlen <= 0 || padlen > 256 || padlen + length > @remote_max_frame_size
|
301
|
+
raise CompressionError, "Invalid padding #{padlen}"
|
302
|
+
end
|
303
|
+
|
304
|
+
length += padlen
|
305
|
+
bytes.prepend([padlen -= 1].pack(UINT8))
|
306
|
+
frame[:flags] << :padded
|
307
|
+
|
308
|
+
# Padding: Padding octets that contain no application semantic value.
|
309
|
+
# Padding octets MUST be set to zero when sending and ignored when
|
310
|
+
# receiving.
|
311
|
+
bytes << "\0" * padlen
|
312
|
+
end
|
313
|
+
|
314
|
+
frame[:length] = length
|
315
|
+
bytes.prepend(common_header(frame))
|
316
|
+
end
|
317
|
+
|
318
|
+
# Decodes complete HTTP/2 frame from provided buffer. If the buffer
|
319
|
+
# does not contain enough data, no further work is performed.
|
320
|
+
#
|
321
|
+
# @param buf [Buffer]
|
322
|
+
def parse(buf)
|
323
|
+
return nil if buf.size < 9
|
324
|
+
|
325
|
+
frame = read_common_header(buf)
|
326
|
+
return nil if buf.size < 9 + frame[:length]
|
327
|
+
|
328
|
+
raise ProtocolError, "payload too large" if frame[:length] > @local_max_frame_size
|
329
|
+
|
330
|
+
buf.read(9)
|
331
|
+
payload = buf.read(frame[:length])
|
332
|
+
|
333
|
+
# Implementations MUST discard frames
|
334
|
+
# that have unknown or unsupported types.
|
335
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.5
|
336
|
+
return frame if frame[:type].nil?
|
337
|
+
|
338
|
+
# Process padding
|
339
|
+
padlen = 0
|
340
|
+
if FRAME_TYPES_WITH_PADDING.include?(frame[:type])
|
341
|
+
padded = frame[:flags].include?(:padded)
|
342
|
+
if padded
|
343
|
+
padlen = payload.read(1).unpack(UINT8).first
|
344
|
+
frame[:padding] = padlen + 1
|
345
|
+
raise ProtocolError, "padding too long" if padlen > payload.bytesize
|
346
|
+
|
347
|
+
payload.slice!(-padlen, padlen) if padlen > 0
|
348
|
+
frame[:length] -= frame[:padding]
|
349
|
+
frame[:flags].delete(:padded)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
case frame[:type]
|
354
|
+
when :data
|
355
|
+
frame[:payload] = payload.read(frame[:length])
|
356
|
+
when :headers
|
357
|
+
if frame[:flags].include? :priority
|
358
|
+
e_sd = payload.read_uint32
|
359
|
+
frame[:dependency] = e_sd & RBIT
|
360
|
+
frame[:exclusive] = (e_sd & EBIT) != 0
|
361
|
+
frame[:weight] = payload.getbyte + 1
|
362
|
+
end
|
363
|
+
frame[:payload] = payload.read(frame[:length])
|
364
|
+
when :priority
|
365
|
+
raise FrameSizeError, "Invalid length for PRIORITY_STREAM (#{frame[:length]} != 5)" if frame[:length] != 5
|
366
|
+
|
367
|
+
e_sd = payload.read_uint32
|
368
|
+
frame[:dependency] = e_sd & RBIT
|
369
|
+
frame[:exclusive] = (e_sd & EBIT) != 0
|
370
|
+
frame[:weight] = payload.getbyte + 1
|
371
|
+
when :rst_stream
|
372
|
+
raise FrameSizeError, "Invalid length for RST_STREAM (#{frame[:length]} != 4)" if frame[:length] != 4
|
373
|
+
|
374
|
+
frame[:error] = unpack_error payload.read_uint32
|
375
|
+
|
376
|
+
when :settings
|
377
|
+
# NOTE: frame[:length] might not match the number of frame[:payload]
|
378
|
+
# because unknown extensions are ignored.
|
379
|
+
frame[:payload] = []
|
380
|
+
raise ProtocolError, "Invalid settings payload length" unless (frame[:length] % 6).zero?
|
381
|
+
|
382
|
+
raise ProtocolError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
|
383
|
+
|
384
|
+
(frame[:length] / 6).times do
|
385
|
+
id = payload.read(2).unpack(UINT16).first
|
386
|
+
val = payload.read_uint32
|
387
|
+
|
388
|
+
# Unsupported or unrecognized settings MUST be ignored.
|
389
|
+
# Here we send it along.
|
390
|
+
name, = DEFINED_SETTINGS.find { |_name, v| v == id }
|
391
|
+
frame[:payload] << [name, val] if name
|
392
|
+
end
|
393
|
+
when :push_promise
|
394
|
+
frame[:promise_stream] = payload.read_uint32 & RBIT
|
395
|
+
frame[:payload] = payload.read(frame[:length])
|
396
|
+
when :ping
|
397
|
+
frame[:payload] = payload.read(frame[:length])
|
398
|
+
when :goaway
|
399
|
+
frame[:last_stream] = payload.read_uint32 & RBIT
|
400
|
+
frame[:error] = unpack_error payload.read_uint32
|
401
|
+
|
402
|
+
size = frame[:length] - 8 # for last_stream and error
|
403
|
+
frame[:payload] = payload.read(size) if size > 0
|
404
|
+
when :window_update
|
405
|
+
if frame[:length] % 4 != 0
|
406
|
+
raise FrameSizeError, "Invalid length for WINDOW_UPDATE (#{frame[:length]} not multiple of 4)"
|
407
|
+
end
|
408
|
+
|
409
|
+
frame[:increment] = payload.read_uint32 & RBIT
|
410
|
+
when :continuation
|
411
|
+
frame[:payload] = payload.read(frame[:length])
|
412
|
+
when :altsvc
|
413
|
+
frame[:max_age], frame[:port] = payload.read(6).unpack(UINT32 + UINT16)
|
414
|
+
|
415
|
+
len = payload.getbyte
|
416
|
+
frame[:proto] = payload.read(len) if len > 0
|
417
|
+
|
418
|
+
len = payload.getbyte
|
419
|
+
frame[:host] = payload.read(len) if len > 0
|
420
|
+
|
421
|
+
frame[:origin] = payload.read(payload.size) unless payload.empty?
|
422
|
+
# else # Unknown frame type is explicitly allowed
|
423
|
+
end
|
424
|
+
|
425
|
+
frame
|
426
|
+
end
|
427
|
+
|
428
|
+
private
|
429
|
+
|
430
|
+
def pack_error(e)
|
431
|
+
unless e.is_a? Integer
|
432
|
+
raise CompressionError, "Unknown error ID for #{e}" if DEFINED_ERRORS[e].nil?
|
433
|
+
|
434
|
+
e = DEFINED_ERRORS[e]
|
435
|
+
end
|
436
|
+
|
437
|
+
[e].pack(UINT32)
|
438
|
+
end
|
439
|
+
|
440
|
+
def unpack_error(error)
|
441
|
+
name, = DEFINED_ERRORS.find { |_name, v| v == error }
|
442
|
+
name || error
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|