plum 0.0.3 → 0.1.0
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 +4 -4
- data/Gemfile +1 -1
- data/README.md +35 -6
- data/bin/plum +7 -0
- data/examples/rack.ru +22 -0
- data/lib/plum/binary_string.rb +5 -5
- data/lib/plum/connection.rb +24 -25
- data/lib/plum/connection_utils.rb +1 -4
- data/lib/plum/errors.rb +1 -1
- data/lib/plum/event_emitter.rb +7 -5
- data/lib/plum/flow_control.rb +19 -12
- data/lib/plum/frame.rb +60 -50
- data/lib/plum/frame_factory.rb +39 -5
- data/lib/plum/frame_utils.rb +0 -3
- data/lib/plum/hpack/constants.rb +49 -49
- data/lib/plum/hpack/context.rb +4 -3
- data/lib/plum/hpack/decoder.rb +55 -45
- data/lib/plum/hpack/encoder.rb +15 -20
- data/lib/plum/http_connection.rb +19 -3
- data/lib/plum/https_connection.rb +21 -5
- data/lib/plum/rack/cli.rb +130 -0
- data/lib/plum/rack/config.rb +28 -0
- data/lib/plum/rack/connection.rb +169 -0
- data/lib/plum/rack/dsl.rb +44 -0
- data/lib/plum/rack/listener.rb +122 -0
- data/lib/plum/rack/server.rb +68 -0
- data/lib/plum/rack.rb +10 -0
- data/lib/plum/stream.rb +35 -25
- data/lib/plum/stream_utils.rb +0 -4
- data/lib/plum/version.rb +1 -1
- data/lib/plum.rb +1 -0
- data/lib/rack/handler/plum.rb +49 -0
- data/plum.gemspec +1 -0
- data/test/plum/hpack/test_decoder.rb +8 -8
- data/test/plum/hpack/test_encoder.rb +3 -3
- data/test/plum/stream/test_handle_frame.rb +6 -6
- data/test/plum/test_frame.rb +4 -1
- data/test/plum/test_http_connection.rb +1 -3
- data/test/plum/test_https_connection.rb +9 -12
- data/test/utils/server.rb +3 -3
- metadata +28 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3f442db964b9973db192c37a12de8bb3bc496aa
|
4
|
+
data.tar.gz: 9281279f7c35289c91222329ac09efcd6cfcf4b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d11fb0f5189dc3b6e33b24aa92a4c6325ac4e58104f44acfa4ec5ba900a14eea58cd6cca6f0c3e4de31e406aac88a9be64eeceae7901a175819b9637b5f28fb
|
7
|
+
data.tar.gz: 97d592aee10b6a329791c25a388e329957deaac1178f40258adc5f907ab00ee44a0c2c8e924b5197167acacc4b4f5842369cf2640112f00fcac8a24582d20483
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,16 +1,45 @@
|
|
1
1
|
# Plum [](https://travis-ci.org/rhenium/plum) [](https://codeclimate.com/github/rhenium/plum) [](https://codeclimate.com/github/rhenium/plum/coverage)
|
2
|
-
A minimal implementation of HTTP/2 server.
|
3
|
-
|
4
|
-
## Examples
|
5
|
-
* examples/ - Minimal usage.
|
6
|
-
* [rhenium/plum-server](https://github.com/rhenium/plum-server) - A example server for https://rhe.jp and http://rhe.jp.
|
2
|
+
A minimal pure Ruby implementation of HTTP/2 library / server.
|
7
3
|
|
8
4
|
## Requirements
|
9
5
|
* Ruby
|
10
6
|
* Ruby 2.2 with [ALPN support patch](https://gist.github.com/rhenium/b1711edcc903e8887a51) and [ECDH support patch (r51348)](https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/51348/diff?format=diff)
|
11
7
|
* or latest Ruby 2.3.0-dev
|
12
8
|
* OpenSSL 1.0.2 or newer (HTTP/2 requires ALPN)
|
13
|
-
*
|
9
|
+
* Optional:
|
10
|
+
* [http-parser.rb gem](https://rubygems.org/gems/http_parser.rb) (HTTP/1.1 parser; if you use "http" URI scheme)
|
11
|
+
* [rack gem](https://rubygems.org/gems/rack) if you use Plum as Rack server.
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
### As a library
|
15
|
+
* See documentation: http://www.rubydoc.info/gems/plum
|
16
|
+
* See examples in `examples/`
|
17
|
+
* [rhenium/plum-server](https://github.com/rhenium/plum-server) - A static-file server for https://rhe.jp and http://rhe.jp.
|
18
|
+
|
19
|
+
### As a Rack-compatible server
|
20
|
+
Insert `require "plum/rack"` to your `config.ru`
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
require "plum/rack"
|
24
|
+
|
25
|
+
App = -> env {
|
26
|
+
[
|
27
|
+
200,
|
28
|
+
{ "Content-Type" => "text/plain" },
|
29
|
+
[" request: #{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"]
|
30
|
+
]
|
31
|
+
}
|
32
|
+
|
33
|
+
run App
|
34
|
+
```
|
35
|
+
|
36
|
+
Then run it with:
|
37
|
+
|
38
|
+
```sh
|
39
|
+
% plum -e production -p 8080 --https config.ru
|
40
|
+
```
|
41
|
+
|
42
|
+
By default, Plum generates a dummy server certificate if `--cert` and `--key` options are not specified.
|
14
43
|
|
15
44
|
## TODO
|
16
45
|
* **Better API**
|
data/bin/plum
ADDED
data/examples/rack.ru
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path("../../lib", __FILE__)
|
2
|
+
require "plum/rack"
|
3
|
+
|
4
|
+
class App2
|
5
|
+
def call(env)
|
6
|
+
if env["REQUEST_METHOD"] == "GET" && env["PATH_INFO"] == "/"
|
7
|
+
[
|
8
|
+
200,
|
9
|
+
{ "Content-Type" => "text/html" },
|
10
|
+
["*10 bytes*"*400]
|
11
|
+
]
|
12
|
+
else
|
13
|
+
[
|
14
|
+
404,
|
15
|
+
{ "Content-Type" => "text/html" },
|
16
|
+
[""]
|
17
|
+
]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
run App2.new
|
data/lib/plum/binary_string.rb
CHANGED
@@ -4,7 +4,7 @@ module Plum
|
|
4
4
|
# Reads a 8-bit unsigned integer.
|
5
5
|
# @param pos [Integer] The start position to read.
|
6
6
|
def uint8(pos = 0)
|
7
|
-
|
7
|
+
getbyte(pos)
|
8
8
|
end
|
9
9
|
|
10
10
|
# Reads a 16-bit unsigned integer.
|
@@ -16,7 +16,8 @@ module Plum
|
|
16
16
|
# Reads a 24-bit unsigned integer.
|
17
17
|
# @param pos [Integer] The start position to read.
|
18
18
|
def uint24(pos = 0)
|
19
|
-
|
19
|
+
a, b = byteslice(pos, 3).unpack("nC")
|
20
|
+
(a * 0x100) + b
|
20
21
|
end
|
21
22
|
|
22
23
|
# Reads a 32-bit unsigned integer.
|
@@ -27,7 +28,7 @@ module Plum
|
|
27
28
|
|
28
29
|
# Appends a 8-bit unsigned integer to this string.
|
29
30
|
def push_uint8(val)
|
30
|
-
self <<
|
31
|
+
self << val.chr
|
31
32
|
end
|
32
33
|
|
33
34
|
# Appends a 16-bit unsigned integer to this string.
|
@@ -37,8 +38,7 @@ module Plum
|
|
37
38
|
|
38
39
|
# Appends a 24-bit unsigned integer to this string.
|
39
40
|
def push_uint24(val)
|
40
|
-
|
41
|
-
push_uint8(val & ((1 << 8) - 1))
|
41
|
+
self << [val / 0x100, val % 0x100].pack("nC")
|
42
42
|
end
|
43
43
|
|
44
44
|
# Appends a 32-bit unsigned integer to this string.
|
data/lib/plum/connection.rb
CHANGED
@@ -6,7 +6,7 @@ module Plum
|
|
6
6
|
include FlowControl
|
7
7
|
include ConnectionUtils
|
8
8
|
|
9
|
-
CLIENT_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
9
|
+
CLIENT_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
|
10
10
|
|
11
11
|
DEFAULT_SETTINGS = {
|
12
12
|
header_table_size: 4096, # octets
|
@@ -15,14 +15,14 @@ module Plum
|
|
15
15
|
initial_window_size: 65535, # octets; <= 2 ** 31 - 1
|
16
16
|
max_frame_size: 16384, # octets; <= 2 ** 24 - 1
|
17
17
|
max_header_list_size: (1 << 32) - 1 # Fixnum
|
18
|
-
}
|
18
|
+
}.freeze
|
19
19
|
|
20
20
|
attr_reader :hpack_encoder, :hpack_decoder
|
21
21
|
attr_reader :local_settings, :remote_settings
|
22
|
-
attr_reader :state, :streams
|
22
|
+
attr_reader :state, :streams
|
23
23
|
|
24
|
-
def initialize(
|
25
|
-
@
|
24
|
+
def initialize(writer, local_settings = {})
|
25
|
+
@writer = writer
|
26
26
|
@local_settings = Hash.new {|hash, key| DEFAULT_SETTINGS[key] }.merge!(local_settings)
|
27
27
|
@remote_settings = Hash.new {|hash, key| DEFAULT_SETTINGS[key] }
|
28
28
|
@buffer = "".force_encoding(Encoding::BINARY)
|
@@ -32,24 +32,18 @@ module Plum
|
|
32
32
|
@hpack_encoder = HPACK::Encoder.new(@remote_settings[:header_table_size])
|
33
33
|
initialize_flow_control(send: @remote_settings[:initial_window_size],
|
34
34
|
recv: @local_settings[:initial_window_size])
|
35
|
+
@max_odd_stream_id = 0
|
36
|
+
@max_even_stream_id = 0
|
35
37
|
end
|
36
38
|
private :initialize
|
37
39
|
|
38
|
-
#
|
39
|
-
def run
|
40
|
-
while !@io.closed? && !@io.eof?
|
41
|
-
receive @io.readpartial(1024)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Closes the io.
|
40
|
+
# Emits :close event. Doesn't actually close socket.
|
46
41
|
def close
|
47
42
|
# TODO: server MAY wait streams
|
48
|
-
|
43
|
+
callback(:close)
|
49
44
|
end
|
50
45
|
|
51
46
|
# Receives the specified data and process.
|
52
|
-
#
|
53
47
|
# @param new_data [String] The data received from the peer.
|
54
48
|
def receive(new_data)
|
55
49
|
return if new_data.empty?
|
@@ -71,10 +65,9 @@ module Plum
|
|
71
65
|
alias << receive
|
72
66
|
|
73
67
|
# Reserves a new stream to server push.
|
74
|
-
#
|
75
68
|
# @param args [Hash] The argument to pass to Stram.new.
|
76
69
|
def reserve_stream(**args)
|
77
|
-
next_id =
|
70
|
+
next_id = @max_even_stream_id + 2
|
78
71
|
stream = new_stream(next_id, state: :reserved_local, **args)
|
79
72
|
stream
|
80
73
|
end
|
@@ -82,7 +75,7 @@ module Plum
|
|
82
75
|
private
|
83
76
|
def send_immediately(frame)
|
84
77
|
callback(:send_frame, frame)
|
85
|
-
@
|
78
|
+
@writer.call(frame.assemble)
|
86
79
|
end
|
87
80
|
|
88
81
|
def negotiate!
|
@@ -98,9 +91,15 @@ module Plum
|
|
98
91
|
end
|
99
92
|
|
100
93
|
def new_stream(stream_id, **args)
|
94
|
+
if stream_id.even?
|
95
|
+
@max_even_stream_id = stream_id
|
96
|
+
else
|
97
|
+
@max_odd_stream_id = stream_id
|
98
|
+
end
|
99
|
+
|
101
100
|
stream = Stream.new(self, stream_id, **args)
|
102
|
-
callback(:stream, stream)
|
103
101
|
@streams[stream_id] = stream
|
102
|
+
callback(:stream, stream)
|
104
103
|
stream
|
105
104
|
end
|
106
105
|
|
@@ -114,14 +113,14 @@ module Plum
|
|
114
113
|
raise ConnectionError.new(:protocol_error)
|
115
114
|
end
|
116
115
|
|
117
|
-
if frame.
|
116
|
+
if frame.end_headers?
|
118
117
|
@state = :open
|
119
118
|
@continuation_id = nil
|
120
119
|
end
|
121
120
|
end
|
122
121
|
|
123
|
-
if
|
124
|
-
if !frame.
|
122
|
+
if frame.type == :headers
|
123
|
+
if !frame.end_headers?
|
125
124
|
@state = :waiting_continuation
|
126
125
|
@continuation_id = frame.stream_id
|
127
126
|
end
|
@@ -138,7 +137,7 @@ module Plum
|
|
138
137
|
if @streams.key?(frame.stream_id)
|
139
138
|
stream = @streams[frame.stream_id]
|
140
139
|
else
|
141
|
-
if frame.stream_id.even? ||
|
140
|
+
if frame.stream_id.even? || @max_odd_stream_id >= frame.stream_id
|
142
141
|
raise Plum::ConnectionError.new(:protocol_error)
|
143
142
|
end
|
144
143
|
|
@@ -172,7 +171,7 @@ module Plum
|
|
172
171
|
end
|
173
172
|
|
174
173
|
def receive_settings(frame, send_ack: true)
|
175
|
-
if frame.
|
174
|
+
if frame.ack?
|
176
175
|
raise ConnectionError.new(:frame_size_error) if frame.length != 0
|
177
176
|
callback(:settings_ack)
|
178
177
|
return
|
@@ -202,7 +201,7 @@ module Plum
|
|
202
201
|
def receive_ping(frame)
|
203
202
|
raise Plum::ConnectionError.new(:frame_size_error) if frame.length != 8
|
204
203
|
|
205
|
-
if frame.
|
204
|
+
if frame.ack?
|
206
205
|
callback(:ping_ack)
|
207
206
|
else
|
208
207
|
opaque_data = frame.payload
|
@@ -3,7 +3,6 @@ using Plum::BinaryString
|
|
3
3
|
module Plum
|
4
4
|
module ConnectionUtils
|
5
5
|
# Sends local settings to the peer.
|
6
|
-
#
|
7
6
|
# @param kwargs [Hash<Symbol, Integer>]
|
8
7
|
def settings(**kwargs)
|
9
8
|
send_immediately Frame.settings(**kwargs)
|
@@ -11,7 +10,6 @@ module Plum
|
|
11
10
|
end
|
12
11
|
|
13
12
|
# Sends a PING frame to the peer.
|
14
|
-
#
|
15
13
|
# @param data [String] Must be 8 octets.
|
16
14
|
# @raise [ArgumentError] If the data is not 8 octets.
|
17
15
|
def ping(data = "plum\x00\x00\x00\x00")
|
@@ -19,10 +17,9 @@ module Plum
|
|
19
17
|
end
|
20
18
|
|
21
19
|
# Sends GOAWAY frame to the peer and closes the connection.
|
22
|
-
#
|
23
20
|
# @param error_type [Symbol] The error type to be contained in the GOAWAY frame.
|
24
21
|
def goaway(error_type = :no_error)
|
25
|
-
last_id = @
|
22
|
+
last_id = @max_odd_stream_id > @max_even_stream_id ? @max_odd_stream_id : @max_even_stream_id
|
26
23
|
send_immediately Frame.goaway(last_id, error_type)
|
27
24
|
end
|
28
25
|
|
data/lib/plum/errors.rb
CHANGED
data/lib/plum/event_emitter.rb
CHANGED
@@ -1,19 +1,21 @@
|
|
1
1
|
module Plum
|
2
2
|
module EventEmitter
|
3
3
|
# Registers an event handler to specified event. An event can have multiple handlers.
|
4
|
-
# @param name [
|
4
|
+
# @param name [Symbol] The name of event.
|
5
5
|
# @yield Gives event-specific parameters.
|
6
6
|
def on(name, &blk)
|
7
|
-
callbacks[name] << blk
|
7
|
+
(callbacks[name] ||= []) << blk
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
# Invokes an event and call handlers with args.
|
11
|
+
# @param name [Symbol] The identifier of event.
|
11
12
|
def callback(name, *args)
|
12
|
-
callbacks[name].each {|cb| cb.call(*args) }
|
13
|
+
(cbs = callbacks[name]) && cbs.each {|cb| cb.call(*args) }
|
13
14
|
end
|
14
15
|
|
16
|
+
private
|
15
17
|
def callbacks
|
16
|
-
@callbacks ||=
|
18
|
+
@callbacks ||= {}
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
data/lib/plum/flow_control.rb
CHANGED
@@ -5,25 +5,29 @@ module Plum
|
|
5
5
|
attr_reader :send_remaining_window, :recv_remaining_window
|
6
6
|
|
7
7
|
# Sends frame respecting inner-stream flow control.
|
8
|
-
#
|
9
8
|
# @param frame [Frame] The frame to be sent.
|
10
9
|
def send(frame)
|
11
|
-
|
12
|
-
when :data
|
10
|
+
if frame.type == :data
|
13
11
|
@send_buffer << frame
|
14
|
-
|
15
|
-
|
12
|
+
if @send_remaining_window < frame.length
|
13
|
+
if Stream === self
|
14
|
+
connection.callback(:send_deferred, self, frame)
|
15
|
+
else
|
16
|
+
callback(:send_deferred, self, frame)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
consume_send_buffer
|
20
|
+
end
|
16
21
|
else
|
17
22
|
send_immediately frame
|
18
23
|
end
|
19
24
|
end
|
20
25
|
|
21
26
|
# Increases receiving window size. Sends WINDOW_UPDATE frame to the peer.
|
22
|
-
#
|
23
27
|
# @param wsi [Integer] The amount to increase receiving window size. The legal range is 1 to 2^32-1.
|
24
28
|
def window_update(wsi)
|
25
29
|
@recv_remaining_window += wsi
|
26
|
-
payload = "".push_uint32(wsi
|
30
|
+
payload = "".push_uint32(wsi)
|
27
31
|
sid = (Stream === self) ? self.id : 0
|
28
32
|
send_immediately Frame.new(type: :window_update, stream_id: sid, payload: payload)
|
29
33
|
end
|
@@ -57,8 +61,7 @@ module Plum
|
|
57
61
|
end
|
58
62
|
|
59
63
|
def consume_recv_window(frame)
|
60
|
-
|
61
|
-
when :data
|
64
|
+
if frame.type == :data
|
62
65
|
@recv_remaining_window -= frame.length
|
63
66
|
if @recv_remaining_window < 0
|
64
67
|
local_error = (Connection === self) ? ConnectionError : StreamError
|
@@ -82,15 +85,19 @@ module Plum
|
|
82
85
|
end
|
83
86
|
|
84
87
|
r_wsi = frame.payload.uint32
|
85
|
-
r = r_wsi >> 31
|
86
|
-
wsi = r_wsi & ~(1 << 31)
|
88
|
+
# r = r_wsi >> 31 # currently not used
|
89
|
+
wsi = r_wsi # & ~(1 << 31)
|
87
90
|
|
88
91
|
if wsi == 0
|
89
92
|
local_error = (Connection === self) ? ConnectionError : StreamError
|
90
93
|
raise local_error.new(:protocol_error)
|
91
94
|
end
|
92
95
|
|
93
|
-
|
96
|
+
if Stream === self
|
97
|
+
connection.callback(:window_update, self, wsi)
|
98
|
+
else
|
99
|
+
callback(:window_update, self, wsi)
|
100
|
+
end
|
94
101
|
|
95
102
|
@send_remaining_window += wsi
|
96
103
|
consume_send_buffer
|
data/lib/plum/frame.rb
CHANGED
@@ -16,37 +16,43 @@ module Plum
|
|
16
16
|
goaway: 0x07,
|
17
17
|
window_update: 0x08,
|
18
18
|
continuation: 0x09
|
19
|
-
}
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# @!visibility private
|
22
|
+
FRAME_TYPES_INVERSE = FRAME_TYPES.invert.freeze
|
20
23
|
|
21
24
|
FRAME_FLAGS = {
|
22
25
|
data: {
|
23
26
|
end_stream: 0x01,
|
24
27
|
padded: 0x08
|
25
|
-
},
|
28
|
+
}.freeze,
|
26
29
|
headers: {
|
27
30
|
end_stream: 0x01,
|
28
31
|
end_headers: 0x04,
|
29
32
|
padded: 0x08,
|
30
33
|
priority: 0x20
|
31
|
-
},
|
32
|
-
priority: {},
|
33
|
-
rst_stream: {},
|
34
|
+
}.freeze,
|
35
|
+
priority: {}.freeze,
|
36
|
+
rst_stream: {}.freeze,
|
34
37
|
settings: {
|
35
38
|
ack: 0x01
|
36
|
-
},
|
39
|
+
}.freeze,
|
37
40
|
push_promise: {
|
38
41
|
end_headers: 0x04,
|
39
42
|
padded: 0x08
|
40
|
-
},
|
43
|
+
}.freeze,
|
41
44
|
ping: {
|
42
45
|
ack: 0x01
|
43
|
-
},
|
44
|
-
goaway: {},
|
45
|
-
window_update: {},
|
46
|
+
}.freeze,
|
47
|
+
goaway: {}.freeze,
|
48
|
+
window_update: {}.freeze,
|
46
49
|
continuation: {
|
47
50
|
end_headers: 0x04
|
48
|
-
}
|
49
|
-
}
|
51
|
+
}.freeze
|
52
|
+
}.freeze
|
53
|
+
|
54
|
+
# @!visibility private
|
55
|
+
FRAME_FLAGS_MAP = FRAME_FLAGS.values.inject(:merge).freeze
|
50
56
|
|
51
57
|
SETTINGS_TYPE = {
|
52
58
|
header_table_size: 0x01,
|
@@ -55,7 +61,7 @@ module Plum
|
|
55
61
|
initial_window_size: 0x04,
|
56
62
|
max_frame_size: 0x05,
|
57
63
|
max_header_list_size: 0x06
|
58
|
-
}
|
64
|
+
}.freeze
|
59
65
|
|
60
66
|
# RFC7540: 4.1 Frame format
|
61
67
|
# +-----------------------------------------------+
|
@@ -72,34 +78,35 @@ module Plum
|
|
72
78
|
attr_accessor :type_value
|
73
79
|
# [Integer] Flags. 8-bit
|
74
80
|
attr_accessor :flags_value
|
75
|
-
# [Integer] Stream Identifier.
|
76
|
-
|
77
|
-
# [String] The payload.
|
78
|
-
|
81
|
+
# [Integer] Stream Identifier. Unsigned 31-bit integer
|
82
|
+
attr_reader :stream_id
|
83
|
+
# [String] The payload. Value is frozen.
|
84
|
+
attr_reader :payload
|
79
85
|
|
80
86
|
def initialize(type: nil, type_value: nil, flags: nil, flags_value: nil, stream_id: nil, payload: nil)
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
87
|
+
@payload = payload || ""
|
88
|
+
@length = @payload.bytesize
|
89
|
+
@type_value = type_value or self.type = type
|
90
|
+
@flags_value = flags_value or self.flags = flags
|
91
|
+
@stream_id = stream_id or raise ArgumentError.new("stream_id is necessary")
|
85
92
|
end
|
86
93
|
|
87
94
|
# Returns the length of payload.
|
88
95
|
# @return [Integer] The length.
|
89
96
|
def length
|
90
|
-
@
|
97
|
+
@length
|
91
98
|
end
|
92
99
|
|
93
100
|
# Returns the type of the frame in Symbol.
|
94
101
|
# @return [Symbol] The type.
|
95
102
|
def type
|
96
|
-
|
103
|
+
FRAME_TYPES_INVERSE[@type_value] || ("unknown_%02x" % @type_value).to_sym
|
97
104
|
end
|
98
105
|
|
99
106
|
# Sets the frame type.
|
100
107
|
# @param value [Symbol] The type.
|
101
108
|
def type=(value)
|
102
|
-
|
109
|
+
@type_value = FRAME_TYPES[value] or raise ArgumentError.new("unknown frame type: #{value}")
|
103
110
|
end
|
104
111
|
|
105
112
|
# Returns the set flags on the frame.
|
@@ -107,26 +114,36 @@ module Plum
|
|
107
114
|
def flags
|
108
115
|
fs = FRAME_FLAGS[type]
|
109
116
|
[0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]
|
110
|
-
.select {|v| flags_value & v > 0 }
|
117
|
+
.select {|v| @flags_value & v > 0 }
|
111
118
|
.map {|val| fs && fs.key(val) || ("unknown_%02x" % val).to_sym }
|
112
119
|
end
|
113
120
|
|
114
121
|
# Sets the frame flags.
|
115
|
-
# @param
|
116
|
-
def flags=(
|
117
|
-
|
122
|
+
# @param values [Array<Symbol>] The flags.
|
123
|
+
def flags=(values)
|
124
|
+
val = 0
|
125
|
+
FRAME_FLAGS_MAP.values_at(*values).each { |c|
|
126
|
+
val |= c if c
|
127
|
+
}
|
128
|
+
@flags_value = val
|
118
129
|
end
|
119
130
|
|
131
|
+
# Frame#flag_name?() == Frame#flags().include?(:flag_name)
|
132
|
+
FRAME_FLAGS_MAP.each { |name, value|
|
133
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
134
|
+
def #{name}?
|
135
|
+
@flags_value & #{value} > 0
|
136
|
+
end
|
137
|
+
EOS
|
138
|
+
}
|
139
|
+
|
120
140
|
# Assembles the frame into binary representation.
|
121
141
|
# @return [String] Binary representation of this frame.
|
122
142
|
def assemble
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
bytes.push_uint32(stream_id & ~(1 << 31)) # first bit is reserved (MUST be 0)
|
128
|
-
bytes.push(payload)
|
129
|
-
bytes
|
143
|
+
[length / 0x100, length % 0x100,
|
144
|
+
@type_value,
|
145
|
+
@flags_value,
|
146
|
+
@stream_id].pack("nCCCN") << @payload
|
130
147
|
end
|
131
148
|
|
132
149
|
# @private
|
@@ -135,29 +152,22 @@ module Plum
|
|
135
152
|
end
|
136
153
|
|
137
154
|
# Parses a frame from given buffer. It changes given buffer.
|
138
|
-
#
|
139
|
-
# @param buffer [String] The buffer stored the data received from peer.
|
155
|
+
# @param buffer [String] The buffer stored the data received from peer. Encoding must be Encoding::BINARY.
|
140
156
|
# @return [Frame, nil] The parsed frame or nil if the buffer is imcomplete.
|
141
157
|
def self.parse!(buffer)
|
142
|
-
buffer.
|
143
|
-
|
144
|
-
return nil if buffer.size < 9 # header: 9 bytes
|
158
|
+
return nil if buffer.bytesize < 9 # header: 9 bytes
|
145
159
|
length = buffer.uint24
|
146
|
-
return nil if buffer.
|
147
|
-
|
148
|
-
bhead = buffer.byteshift(9)
|
149
|
-
payload = buffer.byteshift(length)
|
160
|
+
return nil if buffer.bytesize < 9 + length
|
150
161
|
|
151
|
-
|
152
|
-
flags_value =
|
153
|
-
|
154
|
-
|
155
|
-
stream_id = r_sid & ~(1 << 31)
|
162
|
+
cur = buffer.byteshift(9 + length)
|
163
|
+
type_value, flags_value, r_sid = cur.byteslice(3, 6).unpack("CCN")
|
164
|
+
# r = r_sid >> 31 # currently not used
|
165
|
+
stream_id = r_sid # & ~(1 << 31)
|
156
166
|
|
157
167
|
self.new(type_value: type_value,
|
158
168
|
flags_value: flags_value,
|
159
169
|
stream_id: stream_id,
|
160
|
-
payload:
|
170
|
+
payload: cur.byteslice(9, length)).freeze
|
161
171
|
end
|
162
172
|
end
|
163
173
|
end
|