hpack 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +40 -0
- data/Rakefile +7 -0
- data/hpack.gemspec +25 -0
- data/lib/hpack.rb +10 -0
- data/lib/hpack/decoder.rb +110 -0
- data/lib/hpack/encoder.rb +4 -0
- data/lib/hpack/field.rb +11 -0
- data/lib/hpack/huffman.rb +326 -0
- data/lib/hpack/integer_reader.rb +19 -0
- data/lib/hpack/lookup_table.rb +172 -0
- data/lib/hpack/version.rb +3 -0
- data/spec/decoder_spec.rb +312 -0
- data/spec/integer_reader_spec.rb +30 -0
- data/spec/spec_helper.rb +87 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b6783e15162742ae6a5234b6246e5e6a3e79a62d
|
4
|
+
data.tar.gz: 706528046402ce381f19218dd67155f705b96028
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 17e698d980a2a6db5c6fe473d057694e131483539917a3ecd1c8cc922eedf25fa951b204f5c0fcf67239af34f691d7367bf2ba4c7bf4d4da6d771cb521028ca3
|
7
|
+
data.tar.gz: 89b6189be4973cc58e71620f1b8119b70d8004af34033a590674bd896ce768f7b8c84267ddae0bcb24c4ce89ed48c786478941e8640c829c9ef2a1c9301072a1
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Konstantin Burnaev
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# Hpack
|
2
|
+
|
3
|
+
Ruby implementation of the HPACK (Header Compression for HTTP/2) standard.
|
4
|
+
|
5
|
+
http://http2.github.io/http2-spec/compression.html
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'hpack'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install hpack
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
```
|
24
|
+
require 'hpack'
|
25
|
+
|
26
|
+
io_stream = ... something which gives you IO object with data to be processed ...
|
27
|
+
|
28
|
+
decoder = Hpack::Decoder.new
|
29
|
+
decoder.decode io_stream do |header, value, metadata|
|
30
|
+
...
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
## Contributing
|
35
|
+
|
36
|
+
1. Fork it ( https://github.com/[my-github-username]/hpack/fork )
|
37
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
38
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
39
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
40
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/hpack.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hpack/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hpack"
|
8
|
+
spec.version = Hpack::VERSION
|
9
|
+
spec.authors = ["Konstantin Burnaev"]
|
10
|
+
spec.email = ["kbourn@gmail.com"]
|
11
|
+
spec.summary = %q{HPACK implementation for Ruby.}
|
12
|
+
spec.description = """Ruby implementation of the HPACK (Header Compression for HTTP/2) standard available at http://http2.github.io/http2-spec/compression.html"""
|
13
|
+
spec.homepage = "https://github.com/bkon/hpack"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.4", ">= 10.4.2"
|
23
|
+
spec.add_development_dependency "simplecov", "~> 0.9", ">= 0.9.1"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.1", ">= 3.1.0"
|
25
|
+
end
|
data/lib/hpack.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module Hpack
|
2
|
+
class Decoder
|
3
|
+
class DecodingError < StandardError; end
|
4
|
+
class ZeroIndexedFieldIndex < DecodingError; end
|
5
|
+
|
6
|
+
attr_reader :lookup_table
|
7
|
+
|
8
|
+
def initialize lookup_table_size: LookupTable::SETTINGS_HEADER_TABLE_SIZE
|
9
|
+
@lookup_table = LookupTable.new max_size: lookup_table_size
|
10
|
+
end
|
11
|
+
|
12
|
+
def decode input
|
13
|
+
read_fields_from input do |field|
|
14
|
+
yield field.header, field.value, field
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def read_fields_from input
|
21
|
+
while not input.eof?
|
22
|
+
header_byte = input.readbyte
|
23
|
+
|
24
|
+
# indexed
|
25
|
+
if header_byte & 0b1000_0000 == 0b1000_0000
|
26
|
+
index = read_integer_with_prefix length: 7, input: input, start_with: header_byte
|
27
|
+
|
28
|
+
if index == 0
|
29
|
+
raise ZeroIndexedFieldIndex
|
30
|
+
end
|
31
|
+
|
32
|
+
header = @lookup_table[index].name
|
33
|
+
value = @lookup_table[index].value
|
34
|
+
|
35
|
+
yield Field.new header, value
|
36
|
+
|
37
|
+
# literal indexed
|
38
|
+
elsif header_byte & 0b1100_0000 == 0b0100_0000
|
39
|
+
name_index = read_integer_with_prefix length: 6, input: input, start_with: header_byte
|
40
|
+
if name_index == 0
|
41
|
+
header = read_string input
|
42
|
+
else
|
43
|
+
header = @lookup_table[name_index].name
|
44
|
+
end
|
45
|
+
|
46
|
+
value = read_string input
|
47
|
+
|
48
|
+
@lookup_table << LookupTable::Entry.new(header, value)
|
49
|
+
|
50
|
+
yield Field.new header, value
|
51
|
+
|
52
|
+
elsif header_byte & 0b1111_0000 == 0b0000_0000
|
53
|
+
name_index = read_integer_with_prefix length: 4, input: input, start_with: header_byte
|
54
|
+
|
55
|
+
if name_index == 0
|
56
|
+
header = read_string input
|
57
|
+
else
|
58
|
+
header = @lookup_table[name_index].name
|
59
|
+
end
|
60
|
+
|
61
|
+
value = read_string input
|
62
|
+
|
63
|
+
yield Field.new header, value
|
64
|
+
|
65
|
+
elsif header_byte & 0b1111_0000 == 0b0001_0000
|
66
|
+
name_index = read_integer_with_prefix length: 4, input: input, start_with: header_byte
|
67
|
+
|
68
|
+
if name_index == 0
|
69
|
+
header = read_string input
|
70
|
+
else
|
71
|
+
header = @lookup_table[name_index].name
|
72
|
+
end
|
73
|
+
|
74
|
+
value = read_string input
|
75
|
+
|
76
|
+
yield Field.new header, value
|
77
|
+
|
78
|
+
elsif header_byte & 0b1110_0000 == 0b0010_0000
|
79
|
+
new_size = read_integer_with_prefix length: 5, input: input, start_with: header_byte
|
80
|
+
|
81
|
+
@lookup_table.max_size = new_size
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def read_integer_with_prefix *options
|
87
|
+
integer_reader.read(*options)
|
88
|
+
end
|
89
|
+
|
90
|
+
def read_string input
|
91
|
+
header_byte = input.readbyte
|
92
|
+
huffman = (header_byte & 0b1000_0000 != 0)
|
93
|
+
length = read_integer_with_prefix length: 7, input: input, start_with: header_byte
|
94
|
+
|
95
|
+
if huffman
|
96
|
+
huffman_decoder.decode input: input, length: length
|
97
|
+
else
|
98
|
+
input.read length
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def huffman_decoder
|
103
|
+
@huffman_decode ||= Hpack::Huffman.new
|
104
|
+
end
|
105
|
+
|
106
|
+
def integer_reader
|
107
|
+
@integer_reader ||= Hpack::IntegerReader.new
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/hpack/field.rb
ADDED
@@ -0,0 +1,326 @@
|
|
1
|
+
module Hpack
|
2
|
+
class Huffman
|
3
|
+
HUFFMAN = [
|
4
|
+
"1111111111000",
|
5
|
+
"11111111111111111011000",
|
6
|
+
"1111111111111111111111100010",
|
7
|
+
"1111111111111111111111100011",
|
8
|
+
"1111111111111111111111100100",
|
9
|
+
"1111111111111111111111100101",
|
10
|
+
"1111111111111111111111100110",
|
11
|
+
"1111111111111111111111100111",
|
12
|
+
"1111111111111111111111101000",
|
13
|
+
"111111111111111111101010",
|
14
|
+
"111111111111111111111111111100",
|
15
|
+
"1111111111111111111111101001",
|
16
|
+
"1111111111111111111111101010",
|
17
|
+
"111111111111111111111111111101",
|
18
|
+
"1111111111111111111111101011",
|
19
|
+
"1111111111111111111111101100",
|
20
|
+
"1111111111111111111111101101",
|
21
|
+
"1111111111111111111111101110",
|
22
|
+
"1111111111111111111111101111",
|
23
|
+
"1111111111111111111111110000",
|
24
|
+
"1111111111111111111111110001",
|
25
|
+
"1111111111111111111111110010",
|
26
|
+
"111111111111111111111111111110",
|
27
|
+
"1111111111111111111111110011",
|
28
|
+
"1111111111111111111111110100",
|
29
|
+
"1111111111111111111111110101",
|
30
|
+
"1111111111111111111111110110",
|
31
|
+
"1111111111111111111111110111",
|
32
|
+
"1111111111111111111111111000",
|
33
|
+
"1111111111111111111111111001",
|
34
|
+
"1111111111111111111111111010",
|
35
|
+
"1111111111111111111111111011",
|
36
|
+
"010100",
|
37
|
+
"1111111000",
|
38
|
+
"1111111001",
|
39
|
+
"111111111010",
|
40
|
+
"1111111111001",
|
41
|
+
"010101",
|
42
|
+
"11111000",
|
43
|
+
"11111111010",
|
44
|
+
"1111111010",
|
45
|
+
"1111111011",
|
46
|
+
"11111001",
|
47
|
+
"11111111011",
|
48
|
+
"11111010",
|
49
|
+
"010110",
|
50
|
+
"010111",
|
51
|
+
"011000",
|
52
|
+
"00000",
|
53
|
+
"00001",
|
54
|
+
"00010",
|
55
|
+
"011001",
|
56
|
+
"011010",
|
57
|
+
"011011",
|
58
|
+
"011100",
|
59
|
+
"011101",
|
60
|
+
"011110",
|
61
|
+
"011111",
|
62
|
+
"1011100",
|
63
|
+
"11111011",
|
64
|
+
"111111111111100",
|
65
|
+
"100000",
|
66
|
+
"111111111011",
|
67
|
+
"1111111100",
|
68
|
+
"1111111111010",
|
69
|
+
"100001",
|
70
|
+
"1011101",
|
71
|
+
"1011110",
|
72
|
+
"1011111",
|
73
|
+
"1100000",
|
74
|
+
"1100001",
|
75
|
+
"1100010",
|
76
|
+
"1100011",
|
77
|
+
"1100100",
|
78
|
+
"1100101",
|
79
|
+
"1100110",
|
80
|
+
"1100111",
|
81
|
+
"1101000",
|
82
|
+
"1101001",
|
83
|
+
"1101010",
|
84
|
+
"1101011",
|
85
|
+
"1101100",
|
86
|
+
"1101101",
|
87
|
+
"1101110",
|
88
|
+
"1101111",
|
89
|
+
"1110000",
|
90
|
+
"1110001",
|
91
|
+
"1110010",
|
92
|
+
"11111100",
|
93
|
+
"1110011",
|
94
|
+
"11111101",
|
95
|
+
"1111111111011",
|
96
|
+
"1111111111111110000",
|
97
|
+
"1111111111100",
|
98
|
+
"11111111111100",
|
99
|
+
"100010",
|
100
|
+
"111111111111101",
|
101
|
+
"00011",
|
102
|
+
"100011",
|
103
|
+
"00100",
|
104
|
+
"100100",
|
105
|
+
"00101",
|
106
|
+
"100101",
|
107
|
+
"100110",
|
108
|
+
"100111",
|
109
|
+
"00110",
|
110
|
+
"1110100",
|
111
|
+
"1110101",
|
112
|
+
"101000",
|
113
|
+
"101001",
|
114
|
+
"101010",
|
115
|
+
"00111",
|
116
|
+
"101011",
|
117
|
+
"1110110",
|
118
|
+
"101100",
|
119
|
+
"01000",
|
120
|
+
"01001",
|
121
|
+
"101101",
|
122
|
+
"1110111",
|
123
|
+
"1111000",
|
124
|
+
"1111001",
|
125
|
+
"1111010",
|
126
|
+
"1111011",
|
127
|
+
"111111111111110",
|
128
|
+
"11111111100",
|
129
|
+
"11111111111101",
|
130
|
+
"1111111111101",
|
131
|
+
"1111111111111111111111111100",
|
132
|
+
"11111111111111100110",
|
133
|
+
"1111111111111111010010",
|
134
|
+
"11111111111111100111",
|
135
|
+
"11111111111111101000",
|
136
|
+
"1111111111111111010011",
|
137
|
+
"1111111111111111010100",
|
138
|
+
"1111111111111111010101",
|
139
|
+
"11111111111111111011001",
|
140
|
+
"1111111111111111010110",
|
141
|
+
"11111111111111111011010",
|
142
|
+
"11111111111111111011011",
|
143
|
+
"11111111111111111011100",
|
144
|
+
"11111111111111111011101",
|
145
|
+
"11111111111111111011110",
|
146
|
+
"111111111111111111101011",
|
147
|
+
"11111111111111111011111",
|
148
|
+
"111111111111111111101100",
|
149
|
+
"111111111111111111101101",
|
150
|
+
"1111111111111111010111",
|
151
|
+
"11111111111111111100000",
|
152
|
+
"111111111111111111101110",
|
153
|
+
"11111111111111111100001",
|
154
|
+
"11111111111111111100010",
|
155
|
+
"11111111111111111100011",
|
156
|
+
"11111111111111111100100",
|
157
|
+
"111111111111111011100",
|
158
|
+
"1111111111111111011000",
|
159
|
+
"11111111111111111100101",
|
160
|
+
"1111111111111111011001",
|
161
|
+
"11111111111111111100110",
|
162
|
+
"11111111111111111100111",
|
163
|
+
"111111111111111111101111",
|
164
|
+
"1111111111111111011010",
|
165
|
+
"111111111111111011101",
|
166
|
+
"11111111111111101001",
|
167
|
+
"1111111111111111011011",
|
168
|
+
"1111111111111111011100",
|
169
|
+
"11111111111111111101000",
|
170
|
+
"11111111111111111101001",
|
171
|
+
"111111111111111011110",
|
172
|
+
"11111111111111111101010",
|
173
|
+
"1111111111111111011101",
|
174
|
+
"1111111111111111011110",
|
175
|
+
"111111111111111111110000",
|
176
|
+
"111111111111111011111",
|
177
|
+
"1111111111111111011111",
|
178
|
+
"11111111111111111101011",
|
179
|
+
"11111111111111111101100",
|
180
|
+
"111111111111111100000",
|
181
|
+
"111111111111111100001",
|
182
|
+
"1111111111111111100000",
|
183
|
+
"111111111111111100010",
|
184
|
+
"11111111111111111101101",
|
185
|
+
"1111111111111111100001",
|
186
|
+
"11111111111111111101110",
|
187
|
+
"11111111111111111101111",
|
188
|
+
"11111111111111101010",
|
189
|
+
"1111111111111111100010",
|
190
|
+
"1111111111111111100011",
|
191
|
+
"1111111111111111100100",
|
192
|
+
"11111111111111111110000",
|
193
|
+
"1111111111111111100101",
|
194
|
+
"1111111111111111100110",
|
195
|
+
"11111111111111111110001",
|
196
|
+
"11111111111111111111100000",
|
197
|
+
"11111111111111111111100001",
|
198
|
+
"11111111111111101011",
|
199
|
+
"1111111111111110001",
|
200
|
+
"1111111111111111100111",
|
201
|
+
"11111111111111111110010",
|
202
|
+
"1111111111111111101000",
|
203
|
+
"1111111111111111111101100",
|
204
|
+
"11111111111111111111100010",
|
205
|
+
"11111111111111111111100011",
|
206
|
+
"11111111111111111111100100",
|
207
|
+
"111111111111111111111011110",
|
208
|
+
"111111111111111111111011111",
|
209
|
+
"11111111111111111111100101",
|
210
|
+
"111111111111111111110001",
|
211
|
+
"1111111111111111111101101",
|
212
|
+
"1111111111111110010",
|
213
|
+
"111111111111111100011",
|
214
|
+
"11111111111111111111100110",
|
215
|
+
"111111111111111111111100000",
|
216
|
+
"111111111111111111111100001",
|
217
|
+
"11111111111111111111100111",
|
218
|
+
"111111111111111111111100010",
|
219
|
+
"111111111111111111110010",
|
220
|
+
"111111111111111100100",
|
221
|
+
"111111111111111100101",
|
222
|
+
"11111111111111111111101000",
|
223
|
+
"11111111111111111111101001",
|
224
|
+
"1111111111111111111111111101",
|
225
|
+
"111111111111111111111100011",
|
226
|
+
"111111111111111111111100100",
|
227
|
+
"111111111111111111111100101",
|
228
|
+
"11111111111111101100",
|
229
|
+
"111111111111111111110011",
|
230
|
+
"11111111111111101101",
|
231
|
+
"111111111111111100110",
|
232
|
+
"1111111111111111101001",
|
233
|
+
"111111111111111100111",
|
234
|
+
"111111111111111101000",
|
235
|
+
"11111111111111111110011",
|
236
|
+
"1111111111111111101010",
|
237
|
+
"1111111111111111101011",
|
238
|
+
"1111111111111111111101110",
|
239
|
+
"1111111111111111111101111",
|
240
|
+
"111111111111111111110100",
|
241
|
+
"111111111111111111110101",
|
242
|
+
"11111111111111111111101010",
|
243
|
+
"11111111111111111110100",
|
244
|
+
"11111111111111111111101011",
|
245
|
+
"111111111111111111111100110",
|
246
|
+
"11111111111111111111101100",
|
247
|
+
"11111111111111111111101101",
|
248
|
+
"111111111111111111111100111",
|
249
|
+
"111111111111111111111101000",
|
250
|
+
"111111111111111111111101001",
|
251
|
+
"111111111111111111111101010",
|
252
|
+
"111111111111111111111101011",
|
253
|
+
"1111111111111111111111111110",
|
254
|
+
"111111111111111111111101100",
|
255
|
+
"111111111111111111111101101",
|
256
|
+
"111111111111111111111101110",
|
257
|
+
"111111111111111111111101111",
|
258
|
+
"111111111111111111111110000",
|
259
|
+
"11111111111111111111101110",
|
260
|
+
"111111111111111111111111111111" # EOS
|
261
|
+
]
|
262
|
+
|
263
|
+
def initialize
|
264
|
+
@root = {}
|
265
|
+
HUFFMAN.each_with_index do |code, terminal|
|
266
|
+
load_terminal code, terminal
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def load_terminal code, terminal
|
271
|
+
node = @root
|
272
|
+
|
273
|
+
code.each_char do |bit_sym|
|
274
|
+
bit = bit_sym.to_i
|
275
|
+
unless node.include? bit
|
276
|
+
node[bit] = {}
|
277
|
+
end
|
278
|
+
|
279
|
+
node = node[bit]
|
280
|
+
end
|
281
|
+
|
282
|
+
node[:value] = terminal
|
283
|
+
end
|
284
|
+
|
285
|
+
def decode input: nil, length: 0
|
286
|
+
node = @root
|
287
|
+
|
288
|
+
decode_bits input, length do |bit|
|
289
|
+
node = node[bit]
|
290
|
+
|
291
|
+
next nil unless node.include? :value
|
292
|
+
|
293
|
+
char = node[:value]
|
294
|
+
node = @root
|
295
|
+
char
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def decode_bits input, length
|
300
|
+
data = ""
|
301
|
+
padding = []
|
302
|
+
|
303
|
+
length.times do |i|
|
304
|
+
byte = input.readbyte
|
305
|
+
|
306
|
+
8.times do
|
307
|
+
bit = (byte & 0b1000_0000 == 0) && 0 || 1
|
308
|
+
char = yield bit
|
309
|
+
|
310
|
+
if char
|
311
|
+
data << char
|
312
|
+
padding = []
|
313
|
+
else
|
314
|
+
padding << bit
|
315
|
+
end
|
316
|
+
|
317
|
+
byte <<= 1
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# TODO: padding check
|
322
|
+
|
323
|
+
data
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|