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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +14 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +7 -0
  6. data/LICENSE +21 -0
  7. data/README.md +14 -0
  8. data/Rakefile +12 -0
  9. data/bin/.gitkeep +0 -0
  10. data/examples/local_server.rb +206 -0
  11. data/examples/static_server.rb +157 -0
  12. data/lib/plum.rb +21 -0
  13. data/lib/plum/binary_string.rb +74 -0
  14. data/lib/plum/connection.rb +201 -0
  15. data/lib/plum/connection_utils.rb +38 -0
  16. data/lib/plum/errors.rb +35 -0
  17. data/lib/plum/event_emitter.rb +19 -0
  18. data/lib/plum/flow_control.rb +97 -0
  19. data/lib/plum/frame.rb +163 -0
  20. data/lib/plum/frame_factory.rb +53 -0
  21. data/lib/plum/frame_utils.rb +50 -0
  22. data/lib/plum/hpack/constants.rb +331 -0
  23. data/lib/plum/hpack/context.rb +55 -0
  24. data/lib/plum/hpack/decoder.rb +145 -0
  25. data/lib/plum/hpack/encoder.rb +105 -0
  26. data/lib/plum/hpack/huffman.rb +42 -0
  27. data/lib/plum/http_connection.rb +33 -0
  28. data/lib/plum/https_connection.rb +24 -0
  29. data/lib/plum/stream.rb +217 -0
  30. data/lib/plum/stream_utils.rb +58 -0
  31. data/lib/plum/version.rb +3 -0
  32. data/plum.gemspec +29 -0
  33. data/test/plum/connection/test_handle_frame.rb +70 -0
  34. data/test/plum/hpack/test_context.rb +63 -0
  35. data/test/plum/hpack/test_decoder.rb +291 -0
  36. data/test/plum/hpack/test_encoder.rb +49 -0
  37. data/test/plum/hpack/test_huffman.rb +36 -0
  38. data/test/plum/stream/test_handle_frame.rb +262 -0
  39. data/test/plum/test_binary_string.rb +64 -0
  40. data/test/plum/test_connection.rb +96 -0
  41. data/test/plum/test_connection_utils.rb +29 -0
  42. data/test/plum/test_error.rb +13 -0
  43. data/test/plum/test_flow_control.rb +167 -0
  44. data/test/plum/test_frame.rb +59 -0
  45. data/test/plum/test_frame_factory.rb +56 -0
  46. data/test/plum/test_frame_utils.rb +46 -0
  47. data/test/plum/test_https_connection.rb +37 -0
  48. data/test/plum/test_stream.rb +32 -0
  49. data/test/plum/test_stream_utils.rb +16 -0
  50. data/test/server.crt +19 -0
  51. data/test/server.csr +16 -0
  52. data/test/server.key +27 -0
  53. data/test/test_helper.rb +28 -0
  54. data/test/utils/assertions.rb +60 -0
  55. data/test/utils/server.rb +63 -0
  56. 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