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
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