plum 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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