plum 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,29 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
using BinaryString
|
4
|
+
|
5
|
+
class ServerConnectionUtilsTest < Minitest::Test
|
6
|
+
def test_server_ping
|
7
|
+
open_server_connection {|con|
|
8
|
+
con.ping("ABCABCAB")
|
9
|
+
|
10
|
+
last = sent_frames.last
|
11
|
+
assert_equal(:ping, last.type)
|
12
|
+
assert_equal([], last.flags)
|
13
|
+
assert_equal("ABCABCAB", last.payload)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_server_goaway
|
18
|
+
open_server_connection {|con|
|
19
|
+
con << Frame.headers(3, "", :end_stream, :end_headers).assemble
|
20
|
+
con.goaway(:stream_closed)
|
21
|
+
|
22
|
+
last = sent_frames.last
|
23
|
+
assert_equal(:goaway, last.type)
|
24
|
+
assert_equal([], last.flags)
|
25
|
+
assert_equal(3, last.payload.uint32)
|
26
|
+
assert_equal(HTTPError::ERROR_CODES[:stream_closed], last.payload.uint32(4))
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class ErrorTest < Minitest::Test
|
4
|
+
def test_http_error_http2_error_code
|
5
|
+
test = -> klass {
|
6
|
+
e = klass.new(:cancel)
|
7
|
+
assert_equal(0x08, e.http2_error_code)
|
8
|
+
}
|
9
|
+
|
10
|
+
test.call ConnectionError
|
11
|
+
test.call StreamError
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
using BinaryString
|
4
|
+
|
5
|
+
class FlowControlTest < Minitest::Test
|
6
|
+
def test_flow_control_window_update_server
|
7
|
+
open_server_connection {|con|
|
8
|
+
before_ws = con.recv_remaining_window
|
9
|
+
con.window_update(500)
|
10
|
+
|
11
|
+
last = sent_frames.last
|
12
|
+
assert_equal(:window_update, last.type)
|
13
|
+
assert_equal(0, last.stream_id)
|
14
|
+
assert_equal(500, last.payload.uint32)
|
15
|
+
assert_equal(before_ws + 500, con.recv_remaining_window)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_flow_control_window_update_stream
|
20
|
+
open_new_stream {|stream|
|
21
|
+
before_ws = stream.recv_remaining_window
|
22
|
+
stream.window_update(500)
|
23
|
+
|
24
|
+
last = sent_frames.last
|
25
|
+
assert_equal(:window_update, last.type)
|
26
|
+
assert_equal(stream.id, last.stream_id)
|
27
|
+
assert_equal(500, last.payload.uint32)
|
28
|
+
assert_equal(before_ws + 500, stream.recv_remaining_window)
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_flow_control_window_update_zero
|
33
|
+
open_new_stream {|stream|
|
34
|
+
assert_stream_error(:protocol_error) {
|
35
|
+
stream.receive_frame Frame.new(type: :window_update,
|
36
|
+
stream_id: stream.id,
|
37
|
+
payload: "".push_uint32(0))
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_flow_control_window_update_frame_size
|
43
|
+
open_new_stream {|stream|
|
44
|
+
assert_connection_error(:frame_size_error) {
|
45
|
+
stream.receive_frame Frame.new(type: :window_update,
|
46
|
+
stream_id: stream.id,
|
47
|
+
payload: "".push_uint16(0))
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_flow_control_dont_send_data_exceeding_send_window
|
53
|
+
open_new_stream {|stream|
|
54
|
+
con = stream.connection
|
55
|
+
con << Frame.new(type: :settings,
|
56
|
+
stream_id: 0,
|
57
|
+
payload: "".push_uint16(Frame::SETTINGS_TYPE[:initial_window_size])
|
58
|
+
.push_uint32(4*2+1)).assemble
|
59
|
+
# only extend stream window size
|
60
|
+
con << Frame.new(type: :window_update,
|
61
|
+
stream_id: stream.id,
|
62
|
+
payload: "".push_uint32(100)).assemble
|
63
|
+
10.times {|i|
|
64
|
+
stream.send Frame.new(type: :data,
|
65
|
+
stream_id: stream.id,
|
66
|
+
payload: "".push_uint32(i))
|
67
|
+
}
|
68
|
+
|
69
|
+
last = sent_frames.last
|
70
|
+
assert_equal(1, last.payload.uint32)
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_flow_control_dont_send_data_upto_updated_send_window
|
75
|
+
open_new_stream {|stream|
|
76
|
+
con = stream.connection
|
77
|
+
con << Frame.new(type: :settings,
|
78
|
+
stream_id: 0,
|
79
|
+
payload: "".push_uint16(Frame::SETTINGS_TYPE[:initial_window_size])
|
80
|
+
.push_uint32(4*2+1)).assemble
|
81
|
+
10.times {|i|
|
82
|
+
stream.send Frame.new(type: :data,
|
83
|
+
stream_id: stream.id,
|
84
|
+
payload: "".push_uint32(i))
|
85
|
+
}
|
86
|
+
# only extend stream window size
|
87
|
+
con << Frame.new(type: :window_update,
|
88
|
+
stream_id: stream.id,
|
89
|
+
payload: "".push_uint32(100)).assemble
|
90
|
+
# and extend connection window size
|
91
|
+
con << Frame.new(type: :window_update,
|
92
|
+
stream_id: 0,
|
93
|
+
payload: "".push_uint32(4*2+1)).assemble
|
94
|
+
|
95
|
+
last = sent_frames.last
|
96
|
+
assert_equal(3, last.payload.uint32)
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_flow_control_update_send_initial_window_size
|
101
|
+
open_new_stream {|stream|
|
102
|
+
con = stream.connection
|
103
|
+
con << Frame.new(type: :settings,
|
104
|
+
stream_id: 0,
|
105
|
+
payload: "".push_uint16(Frame::SETTINGS_TYPE[:initial_window_size])
|
106
|
+
.push_uint32(4*2+1)).assemble
|
107
|
+
10.times {|i|
|
108
|
+
stream.send Frame.new(type: :data,
|
109
|
+
stream_id: stream.id,
|
110
|
+
payload: "".push_uint32(i))
|
111
|
+
}
|
112
|
+
# only extend stream window size
|
113
|
+
con << Frame.new(type: :window_update,
|
114
|
+
stream_id: stream.id,
|
115
|
+
payload: "".push_uint32(100)).assemble
|
116
|
+
# and update initial window size
|
117
|
+
con << Frame.new(type: :settings,
|
118
|
+
stream_id: 0,
|
119
|
+
payload: "".push_uint16(Frame::SETTINGS_TYPE[:initial_window_size])
|
120
|
+
.push_uint32(4*4+1)).assemble
|
121
|
+
|
122
|
+
last = sent_frames.reverse.find {|f| f.type == :data }
|
123
|
+
assert_equal(3, last.payload.uint32)
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_flow_control_recv_window_exceeded
|
128
|
+
prepare = ->(&blk) {
|
129
|
+
open_new_stream {|stream|
|
130
|
+
con = stream.connection
|
131
|
+
con.settings(initial_window_size: 24)
|
132
|
+
blk.call(con, stream)
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
prepare.call {|con, stream|
|
137
|
+
con.window_update(500) # extend only connection
|
138
|
+
con << Frame.headers(stream.id, "", :end_headers).assemble
|
139
|
+
assert_stream_error(:flow_control_error) {
|
140
|
+
con << Frame.data(stream.id, "\x00" * 30, :end_stream).assemble
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
prepare.call {|con, stream|
|
145
|
+
stream.window_update(500) # extend only stream
|
146
|
+
con << Frame.headers(stream.id, "", :end_headers).assemble
|
147
|
+
assert_connection_error(:flow_control_error) {
|
148
|
+
con << Frame.data(stream.id, "\x00" * 30, :end_stream).assemble
|
149
|
+
}
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_flow_control_update_recv_initial_window_size
|
154
|
+
open_new_stream {|stream|
|
155
|
+
con = stream.connection
|
156
|
+
con.settings(initial_window_size: 24)
|
157
|
+
stream.window_update(1)
|
158
|
+
con << Frame.headers(stream.id, "", :end_headers).assemble
|
159
|
+
con << Frame.data(stream.id, "\x00" * 20, :end_stream).assemble
|
160
|
+
assert_equal(4, con.recv_remaining_window)
|
161
|
+
assert_equal(5, stream.recv_remaining_window)
|
162
|
+
con.settings(initial_window_size: 60)
|
163
|
+
assert_equal(40, con.recv_remaining_window)
|
164
|
+
assert_equal(41, stream.recv_remaining_window)
|
165
|
+
}
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class FrameTest < Minitest::Test
|
4
|
+
# Frame.parse!
|
5
|
+
def test_parse_header_uncomplete
|
6
|
+
buffer = "\x00\x00\x00" << "\x00" << "\x00"
|
7
|
+
buffer_orig = buffer.dup
|
8
|
+
assert_nil(Plum::Frame.parse!(buffer))
|
9
|
+
assert_equal(buffer_orig, buffer)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_parse_body_uncomplete
|
13
|
+
buffer = "\x00\x00\x03" << "\x00" << "\x00" << "\x00\x00\x00\x00" << "ab"
|
14
|
+
buffer_orig = buffer.dup
|
15
|
+
assert_nil(Plum::Frame.parse!(buffer))
|
16
|
+
assert_equal(buffer_orig, buffer)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_parse
|
20
|
+
# R 0x1, stream_id 0x4, body "abc"
|
21
|
+
buffer = "\x00\x00\x03" << "\x00" << "\x09" << "\x80\x00\x00\x04" << "abc" << "next_frame_data"
|
22
|
+
frame = Plum::Frame.parse!(buffer)
|
23
|
+
assert_equal(3, frame.length)
|
24
|
+
assert_equal(:data, frame.type)
|
25
|
+
assert_equal([:end_stream, :padded], frame.flags)
|
26
|
+
assert_equal(0x04, frame.stream_id)
|
27
|
+
assert_equal("abc", frame.payload)
|
28
|
+
assert_equal("next_frame_data", buffer)
|
29
|
+
assert_equal(true, frame.frozen?)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Frame#assemble
|
33
|
+
def test_assemble
|
34
|
+
frame = Plum::Frame.new(type: :push_promise, flags: [:end_headers, :padded], stream_id: 0x678, payload: "payl")
|
35
|
+
bin = "\x00\x00\x04" << "\x05" << "\x0c" << "\x00\x00\x06\x78" << "payl"
|
36
|
+
assert_equal(bin, frame.assemble)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Frame#generate
|
40
|
+
def test_new
|
41
|
+
frame = Plum::Frame.new(type: :data,
|
42
|
+
stream_id: 12345,
|
43
|
+
flags: [:end_stream, :padded],
|
44
|
+
payload: "ぺいろーど".encode(Encoding::UTF_8))
|
45
|
+
assert_equal("ぺいろーど", frame.payload)
|
46
|
+
assert_equal("ぺいろーど".bytesize, frame.length)
|
47
|
+
assert_equal(:data, frame.type) # DATA
|
48
|
+
assert_equal([:end_stream, :padded], frame.flags) # 0x01 | 0x08
|
49
|
+
assert_equal(12345, frame.stream_id)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_inspect
|
53
|
+
frame = Plum::Frame.new(type: :data,
|
54
|
+
stream_id: 12345,
|
55
|
+
flags: [:end_stream, :padded],
|
56
|
+
payload: "ぺいろーど")
|
57
|
+
frame.inspect
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
using Plum::BinaryString
|
4
|
+
class FrameFactoryTest < Minitest::Test
|
5
|
+
def test_rst_stream
|
6
|
+
frame = Frame.rst_stream(123, :stream_closed)
|
7
|
+
assert_frame(frame,
|
8
|
+
type: :rst_stream,
|
9
|
+
stream_id: 123)
|
10
|
+
assert_equal(HTTPError::ERROR_CODES[:stream_closed], frame.payload.uint32)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_goaway
|
14
|
+
frame = Frame.goaway(0x55, :stream_closed, "debug")
|
15
|
+
assert_frame(frame,
|
16
|
+
type: :goaway,
|
17
|
+
stream_id: 0,
|
18
|
+
payload: "\x00\x00\x00\x55\x00\x00\x00\x05debug")
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_settings
|
22
|
+
frame = Frame.settings(header_table_size: 0x1010)
|
23
|
+
assert_frame(frame,
|
24
|
+
type: :settings,
|
25
|
+
stream_id: 0,
|
26
|
+
flags: [],
|
27
|
+
payload: "\x00\x01\x00\x00\x10\x10")
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_settings_ack
|
31
|
+
frame = Frame.settings(:ack)
|
32
|
+
assert_frame(frame,
|
33
|
+
type: :settings,
|
34
|
+
stream_id: 0,
|
35
|
+
flags: [:ack],
|
36
|
+
payload: "")
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_ping
|
40
|
+
frame = Frame.ping("12345678")
|
41
|
+
assert_frame(frame,
|
42
|
+
type: :ping,
|
43
|
+
stream_id: 0,
|
44
|
+
flags: [],
|
45
|
+
payload: "12345678")
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_ping_ack
|
49
|
+
frame = Frame.ping(:ack, "12345678")
|
50
|
+
assert_frame(frame,
|
51
|
+
type: :ping,
|
52
|
+
stream_id: 0,
|
53
|
+
flags: [:ack],
|
54
|
+
payload: "12345678")
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class FrameUtilsTest < Minitest::Test
|
4
|
+
def test_frame_enough_short
|
5
|
+
frame = Frame.new(type: :data, stream_id: 1, payload: "123")
|
6
|
+
ret = frame.split_data(3)
|
7
|
+
assert_equal(1, ret.size)
|
8
|
+
assert_equal("123", ret.first.payload)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_frame_unknown
|
12
|
+
frame = Frame.new(type: :settings, stream_id: 1, payload: "123")
|
13
|
+
assert_raises { frame.split_data(2) }
|
14
|
+
assert_raises { frame.split_headers(2) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_frame_data
|
18
|
+
frame = Frame.new(type: :data, flags: [:end_stream], stream_id: 1, payload: "12345")
|
19
|
+
ret = frame.split_data(3)
|
20
|
+
assert_equal(2, ret.size)
|
21
|
+
assert_equal("123", ret.first.payload)
|
22
|
+
assert_equal([], ret.first.flags)
|
23
|
+
assert_equal("45", ret.last.payload)
|
24
|
+
assert_equal([:end_stream], ret.last.flags)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_frame_headers
|
28
|
+
frame = Frame.new(type: :headers, flags: [:priority, :end_stream, :end_headers], stream_id: 1, payload: "1234567")
|
29
|
+
ret = frame.split_headers(3)
|
30
|
+
assert_equal(3, ret.size)
|
31
|
+
assert_equal("123", ret[0].payload)
|
32
|
+
assert_equal([:end_stream, :priority], ret[0].flags)
|
33
|
+
assert_equal("456", ret[1].payload)
|
34
|
+
assert_equal([], ret[1].flags)
|
35
|
+
assert_equal("7", ret[2].payload)
|
36
|
+
assert_equal([:end_headers], ret[2].flags)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_frame_parse_settings
|
40
|
+
# :header_table_size => 0x1010, :enable_push => 0x00, :header_table_size => 0x1011 (overwrite)
|
41
|
+
frame = Frame.new(type: :settings, flags: [], stream_id: 0, payload: "\x00\x01\x00\x00\x10\x10\x00\x02\x00\x00\x00\x00\x00\x01\x00\x00\x10\x11")
|
42
|
+
ret = frame.parse_settings
|
43
|
+
assert_equal(0x1011, ret[:header_table_size])
|
44
|
+
assert_equal(0x0000, ret[:enable_push])
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
using Plum::BinaryString
|
4
|
+
|
5
|
+
class HTTPSConnectionNegotiationTest < Minitest::Test
|
6
|
+
def test_server_must_raise_cprotocol_error_invalid_magic_short
|
7
|
+
con = HTTPSConnection.new(StringIO.new)
|
8
|
+
assert_connection_error(:protocol_error) {
|
9
|
+
con << "HELLO"
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_server_must_raise_cprotocol_error_invalid_magic_long
|
14
|
+
con = HTTPSConnection.new(StringIO.new)
|
15
|
+
assert_connection_error(:protocol_error) {
|
16
|
+
con << ("HELLO" * 100) # over 24
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_server_must_raise_cprotocol_error_non_settings_after_magic
|
21
|
+
con = HTTPSConnection.new(StringIO.new)
|
22
|
+
con << Connection::CLIENT_CONNECTION_PREFACE
|
23
|
+
assert_connection_error(:protocol_error) {
|
24
|
+
con << Frame.new(type: :window_update, stream_id: 0, payload: "".push_uint32(1)).assemble
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_server_accept_fragmented_magic
|
29
|
+
magic = Connection::CLIENT_CONNECTION_PREFACE
|
30
|
+
con = HTTPSConnection.new(StringIO.new)
|
31
|
+
assert_no_error {
|
32
|
+
con << magic[0...5]
|
33
|
+
con << magic[5..-1]
|
34
|
+
con << Frame.new(type: :settings, stream_id: 0).assemble
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
using Plum::BinaryString
|
4
|
+
|
5
|
+
class StreamTest < Minitest::Test
|
6
|
+
def test_stream_illegal_frame_type
|
7
|
+
open_new_stream {|stream|
|
8
|
+
assert_connection_error(:protocol_error) {
|
9
|
+
stream.receive_frame(Frame.new(type: :goaway, stream_id: stream.id, payload: "\x00\x00\x00\x00"))
|
10
|
+
}
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_stream_unknown_frame_type
|
15
|
+
open_new_stream {|stream|
|
16
|
+
assert_no_error {
|
17
|
+
stream.receive_frame(Frame.new(type_value: 0x0f, stream_id: stream.id, payload: "\x00\x00\x00\x00"))
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_stream_close
|
23
|
+
open_new_stream(state: :half_closed_local) {|stream|
|
24
|
+
stream.close(:frame_size_error)
|
25
|
+
|
26
|
+
last = sent_frames.last
|
27
|
+
assert_equal(:rst_stream, last.type)
|
28
|
+
assert_equal(StreamError.new(:frame_size_error).http2_error_code, last.payload.uint32)
|
29
|
+
assert_equal(:closed, stream.state)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|