plum 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
data/lib/plum/frame.rb ADDED
@@ -0,0 +1,163 @@
1
+ using Plum::BinaryString
2
+
3
+ module Plum
4
+ class Frame
5
+ extend FrameFactory
6
+ include FrameUtils
7
+
8
+ FRAME_TYPES = {
9
+ data: 0x00,
10
+ headers: 0x01,
11
+ priority: 0x02,
12
+ rst_stream: 0x03,
13
+ settings: 0x04,
14
+ push_promise: 0x05,
15
+ ping: 0x06,
16
+ goaway: 0x07,
17
+ window_update: 0x08,
18
+ continuation: 0x09
19
+ }
20
+
21
+ FRAME_FLAGS = {
22
+ data: {
23
+ end_stream: 0x01,
24
+ padded: 0x08
25
+ },
26
+ headers: {
27
+ end_stream: 0x01,
28
+ end_headers: 0x04,
29
+ padded: 0x08,
30
+ priority: 0x20
31
+ },
32
+ priority: {},
33
+ rst_stream: {},
34
+ settings: {
35
+ ack: 0x01
36
+ },
37
+ push_promise: {
38
+ end_headers: 0x04,
39
+ padded: 0x08
40
+ },
41
+ ping: {
42
+ ack: 0x01
43
+ },
44
+ goaway: {},
45
+ window_update: {},
46
+ continuation: {
47
+ end_headers: 0x04
48
+ }
49
+ }
50
+
51
+ SETTINGS_TYPE = {
52
+ header_table_size: 0x01,
53
+ enable_push: 0x02,
54
+ max_concurrent_streams: 0x03,
55
+ initial_window_size: 0x04,
56
+ max_frame_size: 0x05,
57
+ max_header_list_size: 0x06
58
+ }
59
+
60
+ # RFC7540: 4.1 Frame format
61
+ # +-----------------------------------------------+
62
+ # | Length (24) |
63
+ # +---------------+---------------+---------------+
64
+ # | Type (8) | Flags (8) |
65
+ # +-+-------------+---------------+-------------------------------+
66
+ # |R| Stream Identifier (31) |
67
+ # +=+=============================================================+
68
+ # | Frame Payload (0...) ...
69
+ # +---------------------------------------------------------------+
70
+
71
+ # [Integer] Frame type. 8-bit
72
+ attr_accessor :type_value
73
+ # [Integer] Flags. 8-bit
74
+ attr_accessor :flags_value
75
+ # [Integer] Stream Identifier. unsigned 31-bit integer
76
+ attr_accessor :stream_id
77
+ # [String] The payload.
78
+ attr_accessor :payload
79
+
80
+ def initialize(type: nil, type_value: nil, flags: nil, flags_value: nil, stream_id: nil, payload: nil)
81
+ self.payload = (payload || "")
82
+ self.type_value = type_value or self.type = type
83
+ self.flags_value = flags_value or self.flags = flags
84
+ self.stream_id = stream_id or raise ArgumentError.new("stream_id is necessary")
85
+ end
86
+
87
+ # Returns the length of payload.
88
+ # @return [Integer] The length.
89
+ def length
90
+ @payload.bytesize
91
+ end
92
+
93
+ # Returns the type of the frame in Symbol.
94
+ # @return [Symbol] The type.
95
+ def type
96
+ FRAME_TYPES.key(type_value) || ("unknown_%02x" % type_value).to_sym
97
+ end
98
+
99
+ # Sets the frame type.
100
+ # @param value [Symbol] The type.
101
+ def type=(value)
102
+ self.type_value = FRAME_TYPES[value] or raise ArgumentError.new("unknown frame type: #{value}")
103
+ end
104
+
105
+ # Returns the set flags on the frame.
106
+ # @return [Array<Symbol>] The flags.
107
+ def flags
108
+ fs = FRAME_FLAGS[type]
109
+ [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]
110
+ .select {|v| flags_value & v > 0 }
111
+ .map {|val| fs && fs.key(val) || ("unknown_%02x" % val).to_sym }
112
+ end
113
+
114
+ # Sets the frame flags.
115
+ # @param value [Array<Symbol>] The flags.
116
+ def flags=(value)
117
+ self.flags_value = (value && value.map {|flag| FRAME_FLAGS[self.type][flag] }.inject(:|) || 0)
118
+ end
119
+
120
+ # Assembles the frame into binary representation.
121
+ # @return [String] Binary representation of this frame.
122
+ def assemble
123
+ bytes = "".force_encoding(Encoding::BINARY)
124
+ bytes.push_uint24(length)
125
+ bytes.push_uint8(type_value)
126
+ bytes.push_uint8(flags_value)
127
+ bytes.push_uint32(stream_id & ~(1 << 31)) # first bit is reserved (MUST be 0)
128
+ bytes.push(payload)
129
+ bytes
130
+ end
131
+
132
+ # @private
133
+ def inspect
134
+ "#<Plum::Frame:0x%04x} length=%d, type=%p, flags=%p, stream_id=0x%04x, payload=%p>" % [__id__, length, type, flags, stream_id, payload]
135
+ end
136
+
137
+ # Parses a frame from given buffer. It changes given buffer.
138
+ #
139
+ # @param buffer [String] The buffer stored the data received from peer.
140
+ # @return [Frame, nil] The parsed frame or nil if the buffer is imcomplete.
141
+ def self.parse!(buffer)
142
+ buffer.force_encoding(Encoding::BINARY)
143
+
144
+ return nil if buffer.size < 9 # header: 9 bytes
145
+ length = buffer.uint24
146
+ return nil if buffer.size < 9 + length
147
+
148
+ bhead = buffer.byteshift(9)
149
+ payload = buffer.byteshift(length)
150
+
151
+ type_value = bhead.uint8(3)
152
+ flags_value = bhead.uint8(4)
153
+ r_sid = bhead.uint32(5)
154
+ r = r_sid >> 31
155
+ stream_id = r_sid & ~(1 << 31)
156
+
157
+ self.new(type_value: type_value,
158
+ flags_value: flags_value,
159
+ stream_id: stream_id,
160
+ payload: payload).freeze
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,53 @@
1
+ using Plum::BinaryString
2
+
3
+ module Plum
4
+ module FrameFactory
5
+ def rst_stream(stream_id, error_type)
6
+ payload = "".push_uint32(HTTPError::ERROR_CODES[error_type])
7
+ Frame.new(type: :rst_stream, stream_id: stream_id, payload: payload)
8
+ end
9
+
10
+ def goaway(last_id, error_type, message = "")
11
+ payload = "".push_uint32((last_id || 0) | (0 << 31))
12
+ .push_uint32(HTTPError::ERROR_CODES[error_type])
13
+ .push(message)
14
+ Frame.new(type: :goaway, stream_id: 0, payload: payload)
15
+ end
16
+
17
+ def settings(ack = nil, **args)
18
+ payload = args.inject("") {|payload, (key, value)|
19
+ id = Frame::SETTINGS_TYPE[key] or raise ArgumentError.new("invalid settings type")
20
+ payload.push_uint16(id)
21
+ payload.push_uint32(value)
22
+ }
23
+ Frame.new(type: :settings, stream_id: 0, flags: [ack].compact, payload: payload)
24
+ end
25
+
26
+ def ping(arg1 = "plum\x00\x00\x00\x00", arg2 = nil)
27
+ if !arg2
28
+ raise ArgumentError.new("data must be 8 octets") if arg1.bytesize != 8
29
+ Frame.new(type: :ping, stream_id: 0, payload: arg1)
30
+ else
31
+ Frame.new(type: :ping, stream_id: 0, flags: [:ack], payload: arg2)
32
+ end
33
+ end
34
+
35
+ def data(stream_id, payload, *flags)
36
+ Frame.new(type: :data, stream_id: stream_id, flags: flags.compact, payload: payload.to_s)
37
+ end
38
+
39
+ def headers(stream_id, encoded, *flags)
40
+ Frame.new(type: :headers, stream_id: stream_id, flags: flags.compact, payload: encoded)
41
+ end
42
+
43
+ def push_promise(stream_id, new_id, encoded, *flags)
44
+ payload = "".push_uint32(0 << 31 | new_id)
45
+ .push(encoded)
46
+ Frame.new(type: :push_promise, stream_id: stream_id, flags: flags.compact, payload: payload)
47
+ end
48
+
49
+ def continuation(stream_id, payload, *flags)
50
+ Frame.new(type: :continuation, stream_id: stream_id, flags: flags.compact, payload: payload)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,50 @@
1
+ using Plum::BinaryString
2
+
3
+ module Plum
4
+ module FrameUtils
5
+ # Splits the DATA frame into multiple frames if the payload size exceeds max size.
6
+ #
7
+ # @param max [Integer] The maximum size of a frame payload.
8
+ # @return [Array<Frame>] The splitted frames.
9
+ def split_data(max)
10
+ return [self] if self.length <= max
11
+ raise "Frame type must be DATA" unless self.type == :data
12
+
13
+ fragments = self.payload.each_byteslice(max).to_a
14
+ frames = fragments.map {|fragment| Frame.new(type: :data, flags: [], stream_id: self.stream_id, payload: fragment) }
15
+ frames.first.flags = self.flags - [:end_stream]
16
+ frames.last.flags = self.flags & [:end_stream]
17
+ frames
18
+ end
19
+
20
+ # Splits the HEADERS or PUSH_PROMISE frame into multiple frames if the payload size exceeds max size.
21
+ #
22
+ # @param max [Integer] The maximum size of a frame payload.
23
+ # @return [Array<Frame>] The splitted frames.
24
+ def split_headers(max)
25
+ return [self] if self.length <= max
26
+ raise "Frame type must be HEADERS or PUSH_PROMISE" unless [:headers, :push_promise].include?(self.type)
27
+
28
+ fragments = self.payload.each_byteslice(max).to_a
29
+ frames = fragments.map {|fragment| Frame.new(type: :continuation, flags: [], stream_id: self.stream_id, payload: fragment) }
30
+ frames.first.type_value = self.type_value
31
+ frames.first.flags = self.flags - [:end_headers]
32
+ frames.last.flags = self.flags & [:end_headers]
33
+ frames
34
+ end
35
+
36
+ # Parses SETTINGS frame payload. Ignores unknown settings type (see RFC7540 6.5.2).
37
+ #
38
+ # @return [Hash<Symbol, Integer>] The parsed strings.
39
+ def parse_settings
40
+ settings = {}
41
+ payload.each_byteslice(6) do |param|
42
+ id = param.uint16
43
+ name = Frame::SETTINGS_TYPE.key(id)
44
+ # ignore unknown settings type
45
+ settings[name] = param.uint32(2) if name
46
+ end
47
+ settings
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,331 @@
1
+ module Plum
2
+ module HPACK
3
+ # RFC7541 Appendix A
4
+ # index is starting from 0
5
+ STATIC_TABLE = [
6
+ [":authority"],
7
+ [":method", "GET"],
8
+ [":method", "POST"],
9
+ [":path", "/"],
10
+ [":path", "/index.html"],
11
+ [":scheme", "http"],
12
+ [":scheme", "https"],
13
+ [":status", "200"],
14
+ [":status", "204"],
15
+ [":status", "206"],
16
+ [":status", "304"],
17
+ [":status", "400"],
18
+ [":status", "404"],
19
+ [":status", "500"],
20
+ ["accept-charset"],
21
+ ["accept-encoding", "gzip, deflate"],
22
+ ["accept-language"],
23
+ ["accept-ranges"],
24
+ ["accept"],
25
+ ["access-control-allow-origin"],
26
+ ["age"],
27
+ ["allow"],
28
+ ["authorization"],
29
+ ["cache-control"],
30
+ ["content-disposition"],
31
+ ["content-encoding"],
32
+ ["content-language"],
33
+ ["content-length"],
34
+ ["content-location"],
35
+ ["content-range"],
36
+ ["content-type"],
37
+ ["cookie"],
38
+ ["date"],
39
+ ["etag"],
40
+ ["expect"],
41
+ ["expires"],
42
+ ["from"],
43
+ ["host"],
44
+ ["if-match"],
45
+ ["if-modified-since"],
46
+ ["if-none-match"],
47
+ ["if-range"],
48
+ ["if-unmodified-since"],
49
+ ["last-modified"],
50
+ ["link"],
51
+ ["location"],
52
+ ["max-forwards"],
53
+ ["proxy-authenticate"],
54
+ ["proxy-authorization"],
55
+ ["range"],
56
+ ["referer"],
57
+ ["refresh"],
58
+ ["retry-after"],
59
+ ["server"],
60
+ ["set-cookie"],
61
+ ["strict-transport-security"],
62
+ ["transfer-encoding"],
63
+ ["user-agent"],
64
+ ["vary"],
65
+ ["via"],
66
+ ["www-authenticate"],
67
+ ]
68
+
69
+ HUFFMAN_TABLE = [
70
+ "1111111111000",
71
+ "11111111111111111011000",
72
+ "1111111111111111111111100010",
73
+ "1111111111111111111111100011",
74
+ "1111111111111111111111100100",
75
+ "1111111111111111111111100101",
76
+ "1111111111111111111111100110",
77
+ "1111111111111111111111100111",
78
+ "1111111111111111111111101000",
79
+ "111111111111111111101010",
80
+ "111111111111111111111111111100",
81
+ "1111111111111111111111101001",
82
+ "1111111111111111111111101010",
83
+ "111111111111111111111111111101",
84
+ "1111111111111111111111101011",
85
+ "1111111111111111111111101100",
86
+ "1111111111111111111111101101",
87
+ "1111111111111111111111101110",
88
+ "1111111111111111111111101111",
89
+ "1111111111111111111111110000",
90
+ "1111111111111111111111110001",
91
+ "1111111111111111111111110010",
92
+ "111111111111111111111111111110",
93
+ "1111111111111111111111110011",
94
+ "1111111111111111111111110100",
95
+ "1111111111111111111111110101",
96
+ "1111111111111111111111110110",
97
+ "1111111111111111111111110111",
98
+ "1111111111111111111111111000",
99
+ "1111111111111111111111111001",
100
+ "1111111111111111111111111010",
101
+ "1111111111111111111111111011",
102
+ "010100",
103
+ "1111111000",
104
+ "1111111001",
105
+ "111111111010",
106
+ "1111111111001",
107
+ "010101",
108
+ "11111000",
109
+ "11111111010",
110
+ "1111111010",
111
+ "1111111011",
112
+ "11111001",
113
+ "11111111011",
114
+ "11111010",
115
+ "010110",
116
+ "010111",
117
+ "011000",
118
+ "00000",
119
+ "00001",
120
+ "00010",
121
+ "011001",
122
+ "011010",
123
+ "011011",
124
+ "011100",
125
+ "011101",
126
+ "011110",
127
+ "011111",
128
+ "1011100",
129
+ "11111011",
130
+ "111111111111100",
131
+ "100000",
132
+ "111111111011",
133
+ "1111111100",
134
+ "1111111111010",
135
+ "100001",
136
+ "1011101",
137
+ "1011110",
138
+ "1011111",
139
+ "1100000",
140
+ "1100001",
141
+ "1100010",
142
+ "1100011",
143
+ "1100100",
144
+ "1100101",
145
+ "1100110",
146
+ "1100111",
147
+ "1101000",
148
+ "1101001",
149
+ "1101010",
150
+ "1101011",
151
+ "1101100",
152
+ "1101101",
153
+ "1101110",
154
+ "1101111",
155
+ "1110000",
156
+ "1110001",
157
+ "1110010",
158
+ "11111100",
159
+ "1110011",
160
+ "11111101",
161
+ "1111111111011",
162
+ "1111111111111110000",
163
+ "1111111111100",
164
+ "11111111111100",
165
+ "100010",
166
+ "111111111111101",
167
+ "00011",
168
+ "100011",
169
+ "00100",
170
+ "100100",
171
+ "00101",
172
+ "100101",
173
+ "100110",
174
+ "100111",
175
+ "00110",
176
+ "1110100",
177
+ "1110101",
178
+ "101000",
179
+ "101001",
180
+ "101010",
181
+ "00111",
182
+ "101011",
183
+ "1110110",
184
+ "101100",
185
+ "01000",
186
+ "01001",
187
+ "101101",
188
+ "1110111",
189
+ "1111000",
190
+ "1111001",
191
+ "1111010",
192
+ "1111011",
193
+ "111111111111110",
194
+ "11111111100",
195
+ "11111111111101",
196
+ "1111111111101",
197
+ "1111111111111111111111111100",
198
+ "11111111111111100110",
199
+ "1111111111111111010010",
200
+ "11111111111111100111",
201
+ "11111111111111101000",
202
+ "1111111111111111010011",
203
+ "1111111111111111010100",
204
+ "1111111111111111010101",
205
+ "11111111111111111011001",
206
+ "1111111111111111010110",
207
+ "11111111111111111011010",
208
+ "11111111111111111011011",
209
+ "11111111111111111011100",
210
+ "11111111111111111011101",
211
+ "11111111111111111011110",
212
+ "111111111111111111101011",
213
+ "11111111111111111011111",
214
+ "111111111111111111101100",
215
+ "111111111111111111101101",
216
+ "1111111111111111010111",
217
+ "11111111111111111100000",
218
+ "111111111111111111101110",
219
+ "11111111111111111100001",
220
+ "11111111111111111100010",
221
+ "11111111111111111100011",
222
+ "11111111111111111100100",
223
+ "111111111111111011100",
224
+ "1111111111111111011000",
225
+ "11111111111111111100101",
226
+ "1111111111111111011001",
227
+ "11111111111111111100110",
228
+ "11111111111111111100111",
229
+ "111111111111111111101111",
230
+ "1111111111111111011010",
231
+ "111111111111111011101",
232
+ "11111111111111101001",
233
+ "1111111111111111011011",
234
+ "1111111111111111011100",
235
+ "11111111111111111101000",
236
+ "11111111111111111101001",
237
+ "111111111111111011110",
238
+ "11111111111111111101010",
239
+ "1111111111111111011101",
240
+ "1111111111111111011110",
241
+ "111111111111111111110000",
242
+ "111111111111111011111",
243
+ "1111111111111111011111",
244
+ "11111111111111111101011",
245
+ "11111111111111111101100",
246
+ "111111111111111100000",
247
+ "111111111111111100001",
248
+ "1111111111111111100000",
249
+ "111111111111111100010",
250
+ "11111111111111111101101",
251
+ "1111111111111111100001",
252
+ "11111111111111111101110",
253
+ "11111111111111111101111",
254
+ "11111111111111101010",
255
+ "1111111111111111100010",
256
+ "1111111111111111100011",
257
+ "1111111111111111100100",
258
+ "11111111111111111110000",
259
+ "1111111111111111100101",
260
+ "1111111111111111100110",
261
+ "11111111111111111110001",
262
+ "11111111111111111111100000",
263
+ "11111111111111111111100001",
264
+ "11111111111111101011",
265
+ "1111111111111110001",
266
+ "1111111111111111100111",
267
+ "11111111111111111110010",
268
+ "1111111111111111101000",
269
+ "1111111111111111111101100",
270
+ "11111111111111111111100010",
271
+ "11111111111111111111100011",
272
+ "11111111111111111111100100",
273
+ "111111111111111111111011110",
274
+ "111111111111111111111011111",
275
+ "11111111111111111111100101",
276
+ "111111111111111111110001",
277
+ "1111111111111111111101101",
278
+ "1111111111111110010",
279
+ "111111111111111100011",
280
+ "11111111111111111111100110",
281
+ "111111111111111111111100000",
282
+ "111111111111111111111100001",
283
+ "11111111111111111111100111",
284
+ "111111111111111111111100010",
285
+ "111111111111111111110010",
286
+ "111111111111111100100",
287
+ "111111111111111100101",
288
+ "11111111111111111111101000",
289
+ "11111111111111111111101001",
290
+ "1111111111111111111111111101",
291
+ "111111111111111111111100011",
292
+ "111111111111111111111100100",
293
+ "111111111111111111111100101",
294
+ "11111111111111101100",
295
+ "111111111111111111110011",
296
+ "11111111111111101101",
297
+ "111111111111111100110",
298
+ "1111111111111111101001",
299
+ "111111111111111100111",
300
+ "111111111111111101000",
301
+ "11111111111111111110011",
302
+ "1111111111111111101010",
303
+ "1111111111111111101011",
304
+ "1111111111111111111101110",
305
+ "1111111111111111111101111",
306
+ "111111111111111111110100",
307
+ "111111111111111111110101",
308
+ "11111111111111111111101010",
309
+ "11111111111111111110100",
310
+ "11111111111111111111101011",
311
+ "111111111111111111111100110",
312
+ "11111111111111111111101100",
313
+ "11111111111111111111101101",
314
+ "111111111111111111111100111",
315
+ "111111111111111111111101000",
316
+ "111111111111111111111101001",
317
+ "111111111111111111111101010",
318
+ "111111111111111111111101011",
319
+ "1111111111111111111111111110",
320
+ "111111111111111111111101100",
321
+ "111111111111111111111101101",
322
+ "111111111111111111111101110",
323
+ "111111111111111111111101111",
324
+ "111111111111111111111110000",
325
+ "11111111111111111111101110",
326
+ "111111111111111111111111111111"
327
+ ]
328
+
329
+ HUFFMAN_TABLE_INVERSED = HUFFMAN_TABLE.each_with_index.to_h
330
+ end
331
+ end