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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4fae3d5fc60fdcda63be201fe0349e497ed0c4a2
4
- data.tar.gz: e9a1ac852f57acfef9d1eb9585c5b5cca427ce3c
3
+ metadata.gz: a3f442db964b9973db192c37a12de8bb3bc496aa
4
+ data.tar.gz: 9281279f7c35289c91222329ac09efcd6cfcf4b4
5
5
  SHA512:
6
- metadata.gz: 0f66ed1a0dda7c39d14b0126d871b0c9978069c2b1a27bcb541d9dc9e0e5c0654d86439ff5c27a650d877f827a3a924ee20028f38245d5a9ceb09f3f69a1998c
7
- data.tar.gz: 95a3eb71873bb321e4ee683820b4057205a2fde167d38b1e09f605f0dd70e606f0ba6f67e1b3fd6d9a6a08e12c77167d1aee32cd7bec034278892f33651c682f
6
+ metadata.gz: 1d11fb0f5189dc3b6e33b24aa92a4c6325ac4e58104f44acfa4ec5ba900a14eea58cd6cca6f0c3e4de31e406aac88a9be64eeceae7901a175819b9637b5f28fb
7
+ data.tar.gz: 97d592aee10b6a329791c25a388e329957deaac1178f40258adc5f907ab00ee44a0c2c8e924b5197167acacc4b4f5842369cf2640112f00fcac8a24582d20483
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in plum.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -1,16 +1,45 @@
1
1
  # Plum [![Build Status](https://travis-ci.org/rhenium/plum.png?branch=master)](https://travis-ci.org/rhenium/plum) [![Code Climate](https://codeclimate.com/github/rhenium/plum/badges/gpa.svg)](https://codeclimate.com/github/rhenium/plum) [![Test Coverage](https://codeclimate.com/github/rhenium/plum/badges/coverage.svg)](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
- * [http-parser.rb gem](https://rubygems.org/gems/http_parser.rb) (HTTP/1.1 parser; if you use "http" URI scheme)
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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
3
+ require "plum/rack"
4
+ require "plum/rack/cli"
5
+
6
+ cli = Plum::Rack::CLI.new(ARGV)
7
+ cli.run
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
@@ -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
- byteslice(pos, 1).unpack("C")[0]
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
- (uint16(pos) << 8) | uint8(pos + 2)
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 << [val].pack("C")
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
- push_uint16(val >> 8)
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.
@@ -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, :io
22
+ attr_reader :state, :streams
23
23
 
24
- def initialize(io, local_settings = {})
25
- @io = io
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
- # Starts communication with the peer. It blocks until the io is closed, or reaches EOF.
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
- @io.close
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 = (@streams.keys.select(&:even?).max || 0) + 2
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
- @io.write(frame.assemble)
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.flags.include?(:end_headers)
116
+ if frame.end_headers?
118
117
  @state = :open
119
118
  @continuation_id = nil
120
119
  end
121
120
  end
122
121
 
123
- if [:headers].include?(frame.type)
124
- if !frame.flags.include?(:end_headers)
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? || (@streams.size > 0 && @streams.keys.select(&:odd?).max >= frame.stream_id)
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.flags.include?(:ack)
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.flags.include?(:ack)
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 = @streams.keys.max || 0
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
@@ -17,7 +17,7 @@ module Plum
17
17
  enhance_your_calm: 0x0b,
18
18
  inadequate_security: 0x0c,
19
19
  http_1_1_required: 0x0d
20
- }
20
+ }.freeze
21
21
 
22
22
  attr_reader :http2_error_type
23
23
 
@@ -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 [String] The name of event.
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
- private
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 ||= Hash.new {|hash, key| hash[key] = [] }
18
+ @callbacks ||= {}
17
19
  end
18
20
  end
19
21
  end
@@ -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
- case frame.type
12
- when :data
10
+ if frame.type == :data
13
11
  @send_buffer << frame
14
- callback(:send_deferred, frame) if @send_remaining_window < frame.length
15
- consume_send_buffer
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 & ~(1 << 31))
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
- case frame.type
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
- callback(:window_update, wsi)
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. unsigned 31-bit integer
76
- attr_accessor :stream_id
77
- # [String] The payload.
78
- attr_accessor :payload
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
- 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")
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
- @payload.bytesize
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
- FRAME_TYPES.key(type_value) || ("unknown_%02x" % type_value).to_sym
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
- self.type_value = FRAME_TYPES[value] or raise ArgumentError.new("unknown frame type: #{value}")
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 value [Array<Symbol>] The flags.
116
- def flags=(value)
117
- self.flags_value = (value && value.map {|flag| FRAME_FLAGS[self.type][flag] }.inject(:|) || 0)
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
- 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
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.force_encoding(Encoding::BINARY)
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.size < 9 + length
147
-
148
- bhead = buffer.byteshift(9)
149
- payload = buffer.byteshift(length)
160
+ return nil if buffer.bytesize < 9 + length
150
161
 
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)
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: payload).freeze
170
+ payload: cur.byteslice(9, length)).freeze
161
171
  end
162
172
  end
163
173
  end