mieps_http-2 0.8.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.
@@ -0,0 +1,3 @@
1
+ module HTTP2
2
+ VERSION = '0.8.0'.freeze
3
+ end
data/lib/http/2.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'http/2/version'
2
+ require 'http/2/error'
3
+ require 'http/2/emitter'
4
+ require 'http/2/buffer'
5
+ require 'http/2/flow_buffer'
6
+ require 'http/2/huffman'
7
+ require 'http/2/huffman_statemachine'
8
+ require 'http/2/compressor'
9
+ require 'http/2/framer'
10
+ require 'http/2/connection'
11
+ require 'http/2/client'
12
+ require 'http/2/server'
13
+ require 'http/2/stream'
@@ -0,0 +1,166 @@
1
+ desc 'Generate Huffman precompiled table in huffman_statemachine.rb'
2
+ task :generate_table do
3
+ HuffmanTable::Node.generate_state_table
4
+ end
5
+
6
+ require_relative '../http/2/huffman'
7
+
8
+ # @private
9
+ module HuffmanTable
10
+ BITS_AT_ONCE = HTTP2::Header::Huffman::BITS_AT_ONCE
11
+ EOS = 256
12
+
13
+ class Node
14
+ attr_accessor :next, :emit, :final, :depth
15
+ attr_accessor :transitions
16
+ attr_accessor :id
17
+ @@id = 0 # rubocop:disable Style/ClassVars
18
+ def initialize(depth)
19
+ @next = [nil, nil]
20
+ @id = @@id
21
+ @@id += 1 # rubocop:disable Style/ClassVars
22
+ @final = false
23
+ @depth = depth
24
+ end
25
+
26
+ def add(code, len, chr)
27
+ self.final = true if chr == EOS && @depth <= 7
28
+ if len == 0
29
+ @emit = chr
30
+ else
31
+ bit = (code & (1 << (len - 1))) == 0 ? 0 : 1
32
+ node = @next[bit] ||= Node.new(@depth + 1)
33
+ node.add(code, len - 1, chr)
34
+ end
35
+ end
36
+
37
+ class Transition
38
+ attr_accessor :emit, :node
39
+ def initialize(emit, node)
40
+ @emit = emit
41
+ @node = node
42
+ end
43
+ end
44
+
45
+ def self.generate_tree
46
+ @root = new(0)
47
+ HTTP2::Header::Huffman::CODES.each_with_index do |c, chr|
48
+ code, len = c
49
+ @root.add(code, len, chr)
50
+ end
51
+ puts "#{@@id} nodes"
52
+ @root
53
+ end
54
+
55
+ def self.generate_machine
56
+ generate_tree
57
+ togo = Set[@root]
58
+ @states = Set[@root]
59
+
60
+ until togo.empty?
61
+ node = togo.first
62
+ togo.delete(node)
63
+
64
+ next if node.transitions
65
+ node.transitions = Array[1 << BITS_AT_ONCE]
66
+
67
+ (1 << BITS_AT_ONCE).times do |input|
68
+ n = node
69
+ emit = ''
70
+ (BITS_AT_ONCE - 1).downto(0) do |i|
71
+ bit = (input & (1 << i)) == 0 ? 0 : 1
72
+ n = n.next[bit]
73
+ next unless n.emit
74
+ if n.emit == EOS
75
+ emit = EOS # cause error on decoding
76
+ else
77
+ emit << n.emit.chr(Encoding::BINARY) unless emit == EOS
78
+ end
79
+ n = @root
80
+ end
81
+ node.transitions[input] = Transition.new(emit, n)
82
+ togo << n
83
+ @states << n
84
+ end
85
+ end
86
+ puts "#{@states.size} states"
87
+ @root
88
+ end
89
+
90
+ def self.generate_state_table
91
+ generate_machine
92
+ state_id = {}
93
+ id_state = {}
94
+ state_id[@root] = 0
95
+ id_state[0] = @root
96
+ max_final = 0
97
+ id = 1
98
+ (@states - [@root]).sort_by { |s| s.final ? 0 : 1 }.each do |s|
99
+ state_id[s] = id
100
+ id_state[id] = s
101
+ max_final = id if s.final
102
+ id += 1
103
+ end
104
+
105
+ File.open(File.expand_path('../http/2/huffman_statemachine.rb', File.dirname(__FILE__)), 'w') do |f|
106
+ f.print <<HEADER
107
+ # Machine generated Huffman decoder state machine.
108
+ # DO NOT EDIT THIS FILE.
109
+
110
+ # The following task generates this file.
111
+ # rake generate_huffman_table
112
+
113
+ module HTTP2
114
+ module Header
115
+ class Huffman
116
+ # :nodoc:
117
+ MAX_FINAL_STATE = #{max_final}
118
+ MACHINE = [
119
+ HEADER
120
+ id.times do |i|
121
+ n = id_state[i]
122
+ f.print ' ['
123
+ string = (1 << BITS_AT_ONCE).times.map do |t|
124
+ transition = n.transitions.fetch(t)
125
+ emit = transition.emit
126
+ unless emit == EOS
127
+ bytes = emit.bytes
128
+ fail ArgumentError if bytes.size > 1
129
+ emit = bytes.first
130
+ end
131
+ "[#{emit.inspect}, #{state_id.fetch(transition.node)}]"
132
+ end.join(', ')
133
+ f.print(string)
134
+ f.print "],\n"
135
+ end
136
+ f.print <<TAILER
137
+ ].each { |arr| arr.each { |subarr| subarr.each(&:freeze) }.freeze }.freeze
138
+ end
139
+ end
140
+ end
141
+ TAILER
142
+ end
143
+ end
144
+
145
+ class << self
146
+ attr_reader :root
147
+ end
148
+
149
+ # Test decoder
150
+ def self.decode(input)
151
+ emit = ''
152
+ n = root
153
+ nibbles = input.unpack('C*').flat_map { |b| [((b & 0xf0) >> 4), b & 0xf] }
154
+ until nibbles.empty?
155
+ nb = nibbles.shift
156
+ t = n.transitions[nb]
157
+ emit << t.emit
158
+ n = t.node
159
+ end
160
+ unless n.final && nibbles.all? { |x| x == 0xf }
161
+ puts "len = #{emit.size} n.final = #{n.final} nibbles = #{nibbles}"
162
+ end
163
+ emit
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,21 @@
1
+ require 'helper'
2
+
3
+ RSpec.describe HTTP2::Buffer do
4
+ let(:b) { Buffer.new('émalgré') }
5
+
6
+ it 'should force 8-bit encoding' do
7
+ expect(b.encoding.to_s).to eq 'ASCII-8BIT'
8
+ end
9
+
10
+ it 'should return bytesize of the buffer' do
11
+ expect(b.size).to eq 9
12
+ end
13
+
14
+ it 'should read single byte at a time' do
15
+ 9.times { expect(b.read(1)).not_to be_nil }
16
+ end
17
+
18
+ it 'should unpack an unsigned 32-bit int' do
19
+ expect(Buffer.new([256].pack('N')).read_uint32).to eq 256
20
+ end
21
+ end
@@ -0,0 +1,92 @@
1
+ require 'helper'
2
+
3
+ RSpec.describe HTTP2::Client do
4
+ before(:each) do
5
+ @client = Client.new
6
+ end
7
+
8
+ let(:f) { Framer.new }
9
+
10
+ context 'initialization and settings' do
11
+ it 'should return odd stream IDs' do
12
+ expect(@client.new_stream.id).not_to be_even
13
+ end
14
+
15
+ it 'should emit connection header and SETTINGS on new client connection' do
16
+ frames = []
17
+ @client.on(:frame) { |bytes| frames << bytes }
18
+ @client.ping('12345678')
19
+
20
+ expect(frames[0]).to eq CONNECTION_PREFACE_MAGIC
21
+ expect(f.parse(frames[1])[:type]).to eq :settings
22
+ end
23
+
24
+ it 'should initialize client with custom connection settings' do
25
+ frames = []
26
+
27
+ @client = Client.new(settings_max_concurrent_streams: 200)
28
+ @client.on(:frame) { |bytes| frames << bytes }
29
+ @client.ping('12345678')
30
+
31
+ frame = f.parse(frames[1])
32
+ expect(frame[:type]).to eq :settings
33
+ expect(frame[:payload]).to include([:settings_max_concurrent_streams, 200])
34
+ end
35
+ end
36
+
37
+ context 'push' do
38
+ it 'should disallow client initiated push' do
39
+ expect do
40
+ @client.promise({}) {}
41
+ end.to raise_error(NoMethodError)
42
+ end
43
+
44
+ it 'should raise error on PUSH_PROMISE against stream 0' do
45
+ expect do
46
+ @client << set_stream_id(f.generate(PUSH_PROMISE.dup), 0)
47
+ end.to raise_error(ProtocolError)
48
+ end
49
+
50
+ it 'should raise error on PUSH_PROMISE against bogus stream' do
51
+ expect do
52
+ @client << set_stream_id(f.generate(PUSH_PROMISE.dup), 31_415)
53
+ end.to raise_error(ProtocolError)
54
+ end
55
+
56
+ it 'should raise error on PUSH_PROMISE against non-idle stream' do
57
+ expect do
58
+ s = @client.new_stream
59
+ s.send HEADERS
60
+
61
+ @client << set_stream_id(f.generate(PUSH_PROMISE), s.id)
62
+ @client << set_stream_id(f.generate(PUSH_PROMISE), s.id)
63
+ end.to raise_error(ProtocolError)
64
+ end
65
+
66
+ it 'should emit stream object for received PUSH_PROMISE' do
67
+ s = @client.new_stream
68
+ s.send HEADERS.deep_dup
69
+
70
+ promise = nil
71
+ @client.on(:promise) { |stream| promise = stream }
72
+ @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
73
+
74
+ expect(promise.id).to eq 2
75
+ expect(promise.state).to eq :reserved_remote
76
+ end
77
+
78
+ it 'should auto RST_STREAM promises against locally-RST stream' do
79
+ s = @client.new_stream
80
+ s.send HEADERS.deep_dup
81
+ s.close
82
+
83
+ allow(@client).to receive(:send)
84
+ expect(@client).to receive(:send) do |frame|
85
+ expect(frame[:type]).to eq :rst_stream
86
+ expect(frame[:stream]).to eq 2
87
+ end
88
+
89
+ @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
90
+ end
91
+ end
92
+ end