plum 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/Guardfile +7 -0
- data/LICENSE +21 -0
- data/README.md +14 -0
- data/Rakefile +12 -0
- data/bin/.gitkeep +0 -0
- data/examples/local_server.rb +206 -0
- data/examples/static_server.rb +157 -0
- data/lib/plum.rb +21 -0
- data/lib/plum/binary_string.rb +74 -0
- data/lib/plum/connection.rb +201 -0
- data/lib/plum/connection_utils.rb +38 -0
- data/lib/plum/errors.rb +35 -0
- data/lib/plum/event_emitter.rb +19 -0
- data/lib/plum/flow_control.rb +97 -0
- data/lib/plum/frame.rb +163 -0
- data/lib/plum/frame_factory.rb +53 -0
- data/lib/plum/frame_utils.rb +50 -0
- data/lib/plum/hpack/constants.rb +331 -0
- data/lib/plum/hpack/context.rb +55 -0
- data/lib/plum/hpack/decoder.rb +145 -0
- data/lib/plum/hpack/encoder.rb +105 -0
- data/lib/plum/hpack/huffman.rb +42 -0
- data/lib/plum/http_connection.rb +33 -0
- data/lib/plum/https_connection.rb +24 -0
- data/lib/plum/stream.rb +217 -0
- data/lib/plum/stream_utils.rb +58 -0
- data/lib/plum/version.rb +3 -0
- data/plum.gemspec +29 -0
- data/test/plum/connection/test_handle_frame.rb +70 -0
- data/test/plum/hpack/test_context.rb +63 -0
- data/test/plum/hpack/test_decoder.rb +291 -0
- data/test/plum/hpack/test_encoder.rb +49 -0
- data/test/plum/hpack/test_huffman.rb +36 -0
- data/test/plum/stream/test_handle_frame.rb +262 -0
- data/test/plum/test_binary_string.rb +64 -0
- data/test/plum/test_connection.rb +96 -0
- data/test/plum/test_connection_utils.rb +29 -0
- data/test/plum/test_error.rb +13 -0
- data/test/plum/test_flow_control.rb +167 -0
- data/test/plum/test_frame.rb +59 -0
- data/test/plum/test_frame_factory.rb +56 -0
- data/test/plum/test_frame_utils.rb +46 -0
- data/test/plum/test_https_connection.rb +37 -0
- data/test/plum/test_stream.rb +32 -0
- data/test/plum/test_stream_utils.rb +16 -0
- data/test/server.crt +19 -0
- data/test/server.csr +16 -0
- data/test/server.key +27 -0
- data/test/test_helper.rb +28 -0
- data/test/utils/assertions.rb +60 -0
- data/test/utils/server.rb +63 -0
- metadata +234 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
module Plum
|
2
|
+
module HPACK
|
3
|
+
module Context
|
4
|
+
attr_reader :dynamic_table, :limit, :size
|
5
|
+
|
6
|
+
def limit=(value)
|
7
|
+
@limit = value
|
8
|
+
evict
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def initialize(dynamic_table_limit)
|
13
|
+
@limit = dynamic_table_limit
|
14
|
+
@dynamic_table = []
|
15
|
+
@size = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def store(name, value)
|
19
|
+
@dynamic_table.unshift([name, value])
|
20
|
+
@size += name.bytesize + value.to_s.bytesize + 32
|
21
|
+
evict
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch(index)
|
25
|
+
if index == 0
|
26
|
+
raise HPACKError.new("index can't be 0")
|
27
|
+
elsif index <= STATIC_TABLE.size
|
28
|
+
STATIC_TABLE[index - 1]
|
29
|
+
elsif index <= STATIC_TABLE.size + @dynamic_table.size
|
30
|
+
@dynamic_table[index - STATIC_TABLE.size - 1]
|
31
|
+
else
|
32
|
+
raise HPACKError.new("invalid index: #{index}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def search(name, value)
|
37
|
+
pr = proc {|n, v|
|
38
|
+
n == name && (!value || v == value)
|
39
|
+
}
|
40
|
+
|
41
|
+
si = STATIC_TABLE.index &pr
|
42
|
+
return si + 1 if si
|
43
|
+
di = @dynamic_table.index &pr
|
44
|
+
return di + STATIC_TABLE.size + 1 if di
|
45
|
+
end
|
46
|
+
|
47
|
+
def evict
|
48
|
+
while @limit && @size > @limit
|
49
|
+
name, value = @dynamic_table.pop
|
50
|
+
@size -= name.bytesize + value.to_s.bytesize + 32
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
using Plum::BinaryString
|
2
|
+
|
3
|
+
module Plum
|
4
|
+
module HPACK
|
5
|
+
class Decoder
|
6
|
+
include HPACK::Context
|
7
|
+
|
8
|
+
def initialize(dynamic_table_limit)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def decode(str)
|
13
|
+
str = str.dup
|
14
|
+
headers = []
|
15
|
+
headers << parse!(str) while str.size > 0
|
16
|
+
headers.compact
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def parse!(str)
|
21
|
+
first_byte = str.uint8
|
22
|
+
if first_byte >= 128 # 0b1XXXXXXX
|
23
|
+
parse_indexed!(str)
|
24
|
+
elsif first_byte >= 64 # 0b01XXXXXX
|
25
|
+
parse_indexing!(str)
|
26
|
+
elsif first_byte >= 32 # 0b001XXXXX
|
27
|
+
self.limit = read_integer!(str, 5)
|
28
|
+
nil
|
29
|
+
else # 0b0000XXXX (without indexing) or 0b0001XXXX (never indexing)
|
30
|
+
parse_no_indexing!(str)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def read_integer!(str, prefix_length)
|
35
|
+
first_byte = str.byteshift(1).uint8
|
36
|
+
raise HPACKError.new("integer: end of buffer") unless first_byte
|
37
|
+
|
38
|
+
mask = (1 << prefix_length) - 1
|
39
|
+
ret = first_byte & mask
|
40
|
+
return ret if ret < mask
|
41
|
+
|
42
|
+
octets = 0
|
43
|
+
while next_value = str.byteshift(1).uint8
|
44
|
+
ret += (next_value & 0b01111111) << (7 * octets)
|
45
|
+
octets += 1
|
46
|
+
|
47
|
+
if next_value < 128
|
48
|
+
return ret
|
49
|
+
elsif octets == 4 # RFC 7541 5.1 tells us that we MUST have limitation. at least > 2 ** 28
|
50
|
+
raise HPACKError.new("integer: too large integer")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
raise HPACKError.new("integer: end of buffer")
|
55
|
+
end
|
56
|
+
|
57
|
+
def read_string!(str)
|
58
|
+
first_byte = str.uint8
|
59
|
+
raise HPACKError.new("string: end of buffer") unless first_byte
|
60
|
+
|
61
|
+
huffman = (first_byte >> 7) == 1
|
62
|
+
length = read_integer!(str, 7)
|
63
|
+
bin = str.byteshift(length)
|
64
|
+
|
65
|
+
raise HTTPError.new("string: end of buffer") if bin.bytesize < length
|
66
|
+
bin = Huffman.decode(bin) if huffman
|
67
|
+
bin
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_indexed!(str)
|
71
|
+
# indexed
|
72
|
+
# +---+---+---+---+---+---+---+---+
|
73
|
+
# | 1 | Index (7+) |
|
74
|
+
# +---+---------------------------+
|
75
|
+
index = read_integer!(str, 7)
|
76
|
+
fetch(index)
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse_indexing!(str)
|
80
|
+
# +---+---+---+---+---+---+---+---+
|
81
|
+
# | 0 | 1 | Index (6+) |
|
82
|
+
# +---+---+-----------------------+
|
83
|
+
# | H | Value Length (7+) |
|
84
|
+
# +---+---------------------------+
|
85
|
+
# | Value String (Length octets) |
|
86
|
+
# +-------------------------------+
|
87
|
+
# or
|
88
|
+
# +---+---+---+---+---+---+---+---+
|
89
|
+
# | 0 | 1 | 0 |
|
90
|
+
# +---+---+-----------------------+
|
91
|
+
# | H | Name Length (7+) |
|
92
|
+
# +---+---------------------------+
|
93
|
+
# | Name String (Length octets) |
|
94
|
+
# +---+---------------------------+
|
95
|
+
# | H | Value Length (7+) |
|
96
|
+
# +---+---------------------------+
|
97
|
+
# | Value String (Length octets) |
|
98
|
+
# +-------------------------------+
|
99
|
+
index = read_integer!(str, 6)
|
100
|
+
if index == 0
|
101
|
+
name = read_string!(str)
|
102
|
+
else
|
103
|
+
name, = fetch(index)
|
104
|
+
end
|
105
|
+
|
106
|
+
val = read_string!(str)
|
107
|
+
store(name, val)
|
108
|
+
|
109
|
+
[name, val]
|
110
|
+
end
|
111
|
+
|
112
|
+
def parse_no_indexing!(str)
|
113
|
+
# +---+---+---+---+---+---+---+---+
|
114
|
+
# | 0 | 0 | 0 |0,1| Index (4+) |
|
115
|
+
# +---+---+-----------------------+
|
116
|
+
# | H | Value Length (7+) |
|
117
|
+
# +---+---------------------------+
|
118
|
+
# | Value String (Length octets) |
|
119
|
+
# +-------------------------------+
|
120
|
+
# or
|
121
|
+
# +---+---+---+---+---+---+---+---+
|
122
|
+
# | 0 | 0 | 0 |0,1| 0 |
|
123
|
+
# +---+---+-----------------------+
|
124
|
+
# | H | Name Length (7+) |
|
125
|
+
# +---+---------------------------+
|
126
|
+
# | Name String (Length octets) |
|
127
|
+
# +---+---------------------------+
|
128
|
+
# | H | Value Length (7+) |
|
129
|
+
# +---+---------------------------+
|
130
|
+
# | Value String (Length octets) |
|
131
|
+
# +-------------------------------+
|
132
|
+
index = read_integer!(str, 4)
|
133
|
+
if index == 0
|
134
|
+
name = read_string!(str)
|
135
|
+
else
|
136
|
+
name, = fetch(index)
|
137
|
+
end
|
138
|
+
|
139
|
+
val = read_string!(str)
|
140
|
+
|
141
|
+
[name, val]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
using Plum::BinaryString
|
2
|
+
|
3
|
+
module Plum
|
4
|
+
module HPACK
|
5
|
+
class Encoder
|
6
|
+
include HPACK::Context
|
7
|
+
|
8
|
+
def initialize(dynamic_table_limit)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def encode(headers)
|
13
|
+
out = ""
|
14
|
+
headers.each do |name, value|
|
15
|
+
name = name.to_s; value = value.to_s
|
16
|
+
if index = search(name, value)
|
17
|
+
out << encode_indexed(index)
|
18
|
+
elsif index = search(name, nil)
|
19
|
+
out << encode_half_indexed(index, value, true) # incremental indexing
|
20
|
+
else
|
21
|
+
out << encode_literal(name, value, true) # incremental indexing
|
22
|
+
end
|
23
|
+
end
|
24
|
+
out.force_encoding(Encoding::BINARY)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
# +---+---+---+---+---+---+---+---+
|
29
|
+
# | 0 | 1 | 0 |
|
30
|
+
# +---+---+-----------------------+
|
31
|
+
# | H | Name Length (7+) |
|
32
|
+
# +---+---------------------------+
|
33
|
+
# | Name String (Length octets) |
|
34
|
+
# +---+---------------------------+
|
35
|
+
# | H | Value Length (7+) |
|
36
|
+
# +---+---------------------------+
|
37
|
+
# | Value String (Length octets) |
|
38
|
+
# +-------------------------------+
|
39
|
+
def encode_literal(name, value, indexing = true)
|
40
|
+
if indexing
|
41
|
+
store(name, value)
|
42
|
+
fb = "\x40"
|
43
|
+
else
|
44
|
+
fb = "\x00"
|
45
|
+
end
|
46
|
+
fb << encode_string(name) << encode_string(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
# +---+---+---+---+---+---+---+---+
|
50
|
+
# | 0 | 1 | Index (6+) |
|
51
|
+
# +---+---+-----------------------+
|
52
|
+
# | H | Value Length (7+) |
|
53
|
+
# +---+---------------------------+
|
54
|
+
# | Value String (Length octets) |
|
55
|
+
# +-------------------------------+
|
56
|
+
def encode_half_indexed(index, value, indexing = true)
|
57
|
+
if indexing
|
58
|
+
store(fetch(index)[0], value)
|
59
|
+
fb = encode_integer(index, 6)
|
60
|
+
fb.setbyte(0, fb.uint8 | 0b01000000)
|
61
|
+
else
|
62
|
+
fb = encode_integer(index, 4)
|
63
|
+
end
|
64
|
+
fb << encode_string(value)
|
65
|
+
end
|
66
|
+
|
67
|
+
# +---+---+---+---+---+---+---+---+
|
68
|
+
# | 1 | Index (7+) |
|
69
|
+
# +---+---------------------------+
|
70
|
+
def encode_indexed(index)
|
71
|
+
s = encode_integer(index, 7)
|
72
|
+
s.setbyte(0, s.uint8 | 0b10000000)
|
73
|
+
s
|
74
|
+
end
|
75
|
+
|
76
|
+
def encode_integer(value, prefix_length)
|
77
|
+
mask = (1 << prefix_length) - 1
|
78
|
+
out = ""
|
79
|
+
|
80
|
+
if value < mask
|
81
|
+
out.push_uint8(value)
|
82
|
+
else
|
83
|
+
value -= mask
|
84
|
+
out.push_uint8(mask)
|
85
|
+
while value >= mask
|
86
|
+
out.push_uint8((value % 0b10000000) + 0b10000000)
|
87
|
+
value >>= 7
|
88
|
+
end
|
89
|
+
out.push_uint8(value)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def encode_string(str)
|
94
|
+
huffman_str = Huffman.encode(str).force_encoding(__ENCODING__)
|
95
|
+
if huffman_str.bytesize < str.bytesize
|
96
|
+
lenstr = encode_integer(huffman_str.bytesize, 7)
|
97
|
+
lenstr.setbyte(0, lenstr.uint8(0) | 0b10000000)
|
98
|
+
lenstr << huffman_str
|
99
|
+
else
|
100
|
+
encode_integer(str.bytesize, 7) << str
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
using Plum::BinaryString
|
2
|
+
|
3
|
+
module Plum
|
4
|
+
module HPACK
|
5
|
+
module Huffman
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# Static-Huffman-encodes the specified String.
|
9
|
+
def encode(bytestr)
|
10
|
+
out = ""
|
11
|
+
bytestr.each_byte do |b|
|
12
|
+
out << HUFFMAN_TABLE[b]
|
13
|
+
end
|
14
|
+
out << "1" * ((8 - out.bytesize) % 8)
|
15
|
+
[out].pack("B*")
|
16
|
+
end
|
17
|
+
|
18
|
+
# Static-Huffman-decodes the specified String.
|
19
|
+
def decode(encoded)
|
20
|
+
bits = encoded.unpack("B*")[0]
|
21
|
+
out = []
|
22
|
+
buf = ""
|
23
|
+
bits.each_char do |cb|
|
24
|
+
buf << cb
|
25
|
+
if c = HUFFMAN_TABLE_INVERSED[buf]
|
26
|
+
raise HPACKError.new("huffman: EOS detected") if c == 256
|
27
|
+
out << c
|
28
|
+
buf = ""
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
if buf.bytesize > 7
|
33
|
+
raise HPACKError.new("huffman: padding is too large (> 7 bits)")
|
34
|
+
elsif buf != "1" * buf.bytesize
|
35
|
+
raise HPACKError.new("huffman: unknown suffix: #{buf}")
|
36
|
+
else
|
37
|
+
out.pack("C*")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Plum
|
2
|
+
class HTTPConnection < Connection
|
3
|
+
def initialize(io, local_settings = {})
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
def negotiate!
|
9
|
+
if @buffer.bytesize >= 4
|
10
|
+
if CLIENT_CONNECTION_PREFACE.start_with?(@buffer)
|
11
|
+
negotiate_with_knowledge
|
12
|
+
else
|
13
|
+
negotiate_with_upgrade
|
14
|
+
end
|
15
|
+
end
|
16
|
+
# next
|
17
|
+
end
|
18
|
+
|
19
|
+
def negotiate_with_knowledge
|
20
|
+
if @buffer.bytesize >= 24
|
21
|
+
if @buffer.byteshift(24) == CLIENT_CONNECTION_PREFACE
|
22
|
+
@state = :waiting_settings
|
23
|
+
settings(@local_settings)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
# next
|
27
|
+
end
|
28
|
+
|
29
|
+
def negotiate_with_upgrade
|
30
|
+
raise NotImplementedError, "Parsing HTTP/1.1 is hard..."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
using Plum::BinaryString
|
2
|
+
|
3
|
+
module Plum
|
4
|
+
class HTTPSConnection < Connection
|
5
|
+
def initialize(io, local_settings = {})
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def negotiate!
|
11
|
+
return if @buffer.empty?
|
12
|
+
|
13
|
+
if CLIENT_CONNECTION_PREFACE.start_with?(@buffer.byteslice(0, 24))
|
14
|
+
if @buffer.bytesize >= 24
|
15
|
+
@buffer.byteshift(24)
|
16
|
+
@state = :waiting_settings
|
17
|
+
settings(@local_settings)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
raise ConnectionError.new(:protocol_error) # (MAY) send GOAWAY. sending.
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/plum/stream.rb
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
using Plum::BinaryString
|
2
|
+
|
3
|
+
module Plum
|
4
|
+
class Stream
|
5
|
+
include EventEmitter
|
6
|
+
include FlowControl
|
7
|
+
include StreamUtils
|
8
|
+
|
9
|
+
attr_reader :id, :state, :connection
|
10
|
+
attr_reader :weight, :exclusive
|
11
|
+
attr_accessor :parent
|
12
|
+
|
13
|
+
def initialize(con, id, state: :idle, weight: 16, parent: nil, exclusive: false)
|
14
|
+
@connection = con
|
15
|
+
@id = id
|
16
|
+
@state = state
|
17
|
+
@continuation = []
|
18
|
+
|
19
|
+
initialize_flow_control(send: @connection.remote_settings[:initial_window_size],
|
20
|
+
recv: @connection.local_settings[:initial_window_size])
|
21
|
+
update_dependency(weight: weight, parent: parent, exclusive: exclusive)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the child (depending on this stream) streams.
|
25
|
+
#
|
26
|
+
# @return [Array<Stream>] The child streams.
|
27
|
+
def children
|
28
|
+
@connection.streams.values.select {|c| c.parent == self }.freeze
|
29
|
+
end
|
30
|
+
|
31
|
+
# Processes received frames for this stream. Internal use.
|
32
|
+
# @private
|
33
|
+
def receive_frame(frame)
|
34
|
+
validate_received_frame(frame)
|
35
|
+
consume_recv_window(frame)
|
36
|
+
|
37
|
+
case frame.type
|
38
|
+
when :data
|
39
|
+
receive_data(frame)
|
40
|
+
when :headers
|
41
|
+
receive_headers(frame)
|
42
|
+
when :priority
|
43
|
+
receive_priority(frame)
|
44
|
+
when :rst_stream
|
45
|
+
receive_rst_stream(frame)
|
46
|
+
when :window_update
|
47
|
+
receive_window_update(frame)
|
48
|
+
when :continuation
|
49
|
+
receive_continuation(frame)
|
50
|
+
when :ping, :goaway, :settings, :push_promise
|
51
|
+
raise ConnectionError.new(:protocol_error) # stream_id MUST be 0x00
|
52
|
+
else
|
53
|
+
# MUST ignore unknown frame
|
54
|
+
end
|
55
|
+
rescue StreamError => e
|
56
|
+
callback(:stream_error, e)
|
57
|
+
close(e.http2_error_type)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Closes this stream. Sends RST_STREAM frame to the peer.
|
61
|
+
#
|
62
|
+
# @param error_type [Symbol] The error type to be contained in the RST_STREAM frame.
|
63
|
+
def close(error_type = :no_error)
|
64
|
+
@state = :closed
|
65
|
+
send_immediately Frame.rst_stream(id, error_type)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def send_immediately(frame)
|
70
|
+
callback(:send_frame, frame)
|
71
|
+
@connection.send(frame)
|
72
|
+
end
|
73
|
+
|
74
|
+
def update_dependency(weight: nil, parent: nil, exclusive: nil)
|
75
|
+
raise StreamError.new(:protocol_error, "A stream cannot depend on itself.") if parent == self
|
76
|
+
@weight = weight unless weight.nil?
|
77
|
+
@parent = parent unless parent.nil?
|
78
|
+
@exclusive = exclusive unless exclusive.nil?
|
79
|
+
|
80
|
+
if exclusive == true
|
81
|
+
parent.children.each do |child|
|
82
|
+
next if child == self
|
83
|
+
child.parent = self
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate_received_frame(frame)
|
89
|
+
if frame.length > @connection.local_settings[:max_frame_size]
|
90
|
+
if [:headers, :push_promise, :continuation].include?(frame.type)
|
91
|
+
raise ConnectionError.new(:frame_size_error)
|
92
|
+
else
|
93
|
+
raise StreamError.new(:frame_size_error)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def receive_end_stream
|
99
|
+
callback(:end_stream)
|
100
|
+
@state = :half_closed_remote
|
101
|
+
end
|
102
|
+
|
103
|
+
def receive_data(frame)
|
104
|
+
if @state != :open && @state != :half_closed_local
|
105
|
+
raise StreamError.new(:stream_closed)
|
106
|
+
end
|
107
|
+
|
108
|
+
if frame.flags.include?(:padded)
|
109
|
+
padding_length = frame.payload.uint8(0)
|
110
|
+
if padding_length >= frame.length
|
111
|
+
raise ConnectionError.new(:protocol_error, "padding is too long")
|
112
|
+
end
|
113
|
+
body = frame.payload.byteslice(1, frame.length - padding_length - 1)
|
114
|
+
else
|
115
|
+
body = frame.payload
|
116
|
+
end
|
117
|
+
callback(:data, body)
|
118
|
+
|
119
|
+
receive_end_stream if frame.flags.include?(:end_stream)
|
120
|
+
end
|
121
|
+
|
122
|
+
def receive_complete_headers(frames)
|
123
|
+
frames = frames.dup
|
124
|
+
first = frames.shift
|
125
|
+
|
126
|
+
payload = first.payload
|
127
|
+
first_length = first.length
|
128
|
+
padding_length = 0
|
129
|
+
|
130
|
+
if first.flags.include?(:padded)
|
131
|
+
padding_length = payload.uint8
|
132
|
+
first_length -= 1 + padding_length
|
133
|
+
payload = payload.byteslice(1, first_length)
|
134
|
+
else
|
135
|
+
payload = payload.dup
|
136
|
+
end
|
137
|
+
|
138
|
+
if first.flags.include?(:priority)
|
139
|
+
receive_priority_payload(payload.byteshift(5))
|
140
|
+
first_length -= 5
|
141
|
+
end
|
142
|
+
|
143
|
+
if padding_length > first_length
|
144
|
+
raise ConnectionError.new(:protocol_error, "padding is too long")
|
145
|
+
end
|
146
|
+
|
147
|
+
frames.each do |frame|
|
148
|
+
payload << frame.payload
|
149
|
+
end
|
150
|
+
|
151
|
+
begin
|
152
|
+
decoded_headers = @connection.hpack_decoder.decode(payload)
|
153
|
+
rescue => e
|
154
|
+
raise ConnectionError.new(:compression_error, e)
|
155
|
+
end
|
156
|
+
|
157
|
+
callback(:headers, decoded_headers)
|
158
|
+
|
159
|
+
receive_end_stream if first.flags.include?(:end_stream)
|
160
|
+
end
|
161
|
+
|
162
|
+
def receive_headers(frame)
|
163
|
+
if @state == :reserved_local
|
164
|
+
raise ConnectionError.new(:protocol_error)
|
165
|
+
elsif @state == :half_closed_remote
|
166
|
+
raise StreamError.new(:stream_closed)
|
167
|
+
elsif @state == :closed
|
168
|
+
raise ConnectionError.new(:stream_closed)
|
169
|
+
end
|
170
|
+
|
171
|
+
@state = :open
|
172
|
+
callback(:open)
|
173
|
+
|
174
|
+
if frame.flags.include?(:end_headers)
|
175
|
+
receive_complete_headers([frame])
|
176
|
+
else
|
177
|
+
@continuation << frame
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def receive_continuation(frame)
|
182
|
+
# state error mustn't happen: server_connection validates
|
183
|
+
@continuation << frame
|
184
|
+
|
185
|
+
if frame.flags.include?(:end_headers)
|
186
|
+
receive_complete_headers(@continuation)
|
187
|
+
@continuation.clear
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def receive_priority(frame)
|
192
|
+
if frame.length != 5
|
193
|
+
raise StreamError.new(:frame_size_error)
|
194
|
+
end
|
195
|
+
receive_priority_payload(frame.payload)
|
196
|
+
end
|
197
|
+
|
198
|
+
def receive_priority_payload(payload)
|
199
|
+
esd = payload.uint32
|
200
|
+
e = esd >> 31
|
201
|
+
dependency_id = e & ~(1 << 31)
|
202
|
+
weight = payload.uint8(4)
|
203
|
+
|
204
|
+
update_dependency(weight: weight, parent: @connection.streams[dependency_id], exclusive: e == 1)
|
205
|
+
end
|
206
|
+
|
207
|
+
def receive_rst_stream(frame)
|
208
|
+
if frame.length != 4
|
209
|
+
raise ConnectionError.new(:frame_size_error)
|
210
|
+
elsif @state == :idle
|
211
|
+
raise ConnectionError.new(:protocol_error)
|
212
|
+
end
|
213
|
+
|
214
|
+
@state = :closed # MUST NOT send RST_STREAM
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|