mieps_http-2 0.8.0

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