bindata 1.5.1 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bindata might be problematic. Click here for more details.
- data/ChangeLog.rdoc +7 -0
- data/NEWS.rdoc +11 -0
- data/Rakefile +6 -1
- data/bindata.gemspec +2 -1
- data/doc/manual.md +17 -9
- data/examples/gzip.rb +2 -2
- data/examples/list.rb +2 -2
- data/lib/bindata/alignment.rb +4 -9
- data/lib/bindata/array.rb +57 -51
- data/lib/bindata/base.rb +13 -110
- data/lib/bindata/base_primitive.rb +130 -75
- data/lib/bindata/bits.rb +5 -7
- data/lib/bindata/choice.rb +24 -32
- data/lib/bindata/dsl.rb +1 -6
- data/lib/bindata/framework.rb +81 -0
- data/lib/bindata/int.rb +5 -7
- data/lib/bindata/name.rb +28 -0
- data/lib/bindata/offset.rb +42 -53
- data/lib/bindata/params.rb +33 -38
- data/lib/bindata/struct.rb +2 -6
- data/lib/bindata/trace.rb +16 -16
- data/lib/bindata/version.rb +1 -1
- data/lib/bindata/virtual.rb +3 -3
- data/{spec/alignment_spec.rb → test/alignment_test.rb} +17 -16
- data/test/array_test.rb +371 -0
- data/test/base_primitive_test.rb +312 -0
- data/test/base_test.rb +183 -0
- data/{spec/bits_spec.rb → test/bits_test.rb} +59 -59
- data/test/choice_test.rb +260 -0
- data/{spec/spec_common.rb → test/common.rb} +33 -18
- data/test/count_bytes_remaining_test.rb +41 -0
- data/{spec/deprecated_spec.rb → test/deprecated_test.rb} +5 -7
- data/test/float_test.rb +72 -0
- data/{spec/int_spec.rb → test/int_test.rb} +34 -43
- data/{spec/io_spec.rb → test/io_test.rb} +70 -71
- data/{spec/lazy_spec.rb → test/lazy_test.rb} +38 -39
- data/test/offset_test.rb +93 -0
- data/test/params_test.rb +144 -0
- data/{spec/primitive_spec.rb → test/primitive_test.rb} +42 -54
- data/{spec/record_spec.rb → test/record_test.rb} +133 -154
- data/test/registry_test.rb +104 -0
- data/test/rest_test.rb +29 -0
- data/test/skip_test.rb +28 -0
- data/{spec/string_spec.rb → test/string_test.rb} +96 -97
- data/test/stringz_test.rb +127 -0
- data/{spec/struct_spec.rb → test/struct_test.rb} +119 -120
- data/{spec/system_spec.rb → test/system_test.rb} +66 -106
- metadata +39 -38
- data/lib/a.rb +0 -24
- data/spec/array_spec.rb +0 -331
- data/spec/base_primitive_spec.rb +0 -238
- data/spec/base_spec.rb +0 -376
- data/spec/choice_spec.rb +0 -263
- data/spec/count_bytes_remaining_spec.rb +0 -38
- data/spec/example.rb +0 -21
- data/spec/float_spec.rb +0 -37
- data/spec/registry_spec.rb +0 -108
- data/spec/rest_spec.rb +0 -26
- data/spec/skip_spec.rb +0 -27
- data/spec/stringz_spec.rb +0 -118
- data/tasks/rspec.rake +0 -17
data/ChangeLog.rdoc
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
= BinData Changelog
|
2
2
|
|
3
|
+
== Version 1.6.0 (2013-09-02)
|
4
|
+
|
5
|
+
* Added license to .gemspec
|
6
|
+
* Moved test suite from RSpec to Minitest.
|
7
|
+
* Added :assert and :asserted_value.
|
8
|
+
* :check_value has been deprecated. Use :assert instead.
|
9
|
+
|
3
10
|
== Version 1.5.1 (2013-08-16)
|
4
11
|
|
5
12
|
* Rework build system and include .gemspec. Requested by Simon Shortman.
|
data/NEWS.rdoc
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
= 1.6.0
|
2
|
+
|
3
|
+
Added :assert as a replacement for :check_value. Note that :assert performs
|
4
|
+
the checking on assignment as well as reading.
|
5
|
+
|
6
|
+
The parameter :asserted_value is a new shortcut for combining :assert and :value
|
7
|
+
int8 :magic, :assert => 42, :value => 42
|
8
|
+
|
9
|
+
Can be written more concisely as
|
10
|
+
int8 :magic, :asserted_value => 42
|
11
|
+
|
1
12
|
= 1.5.0
|
2
13
|
|
3
14
|
Finally moved the source code to github.
|
data/Rakefile
CHANGED
@@ -3,11 +3,16 @@ Bundler.setup
|
|
3
3
|
Bundler::GemHelper.install_tasks
|
4
4
|
|
5
5
|
require 'rake/clean'
|
6
|
+
require 'rake/testtask'
|
6
7
|
|
7
8
|
task :clobber do
|
8
9
|
rm_rf 'pkg'
|
9
10
|
end
|
10
11
|
|
11
|
-
|
12
|
+
Rake::TestTask.new do |t|
|
13
|
+
t.pattern = "test/**/*_test.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => :test
|
12
17
|
|
13
18
|
Dir['tasks/**/*.rake'].each { |t| load t }
|
data/bindata.gemspec
CHANGED
@@ -15,9 +15,10 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.extra_rdoc_files = ['NEWS.rdoc']
|
16
16
|
s.rdoc_options << '--main' << 'NEWS.rdoc'
|
17
17
|
s.files = `git ls-files`.split("\n")
|
18
|
+
s.license = 'Ruby'
|
18
19
|
|
19
20
|
s.add_development_dependency('rake')
|
20
|
-
s.add_development_dependency('
|
21
|
+
s.add_development_dependency('minitest', "> 5.0.0")
|
21
22
|
s.add_development_dependency('haml', ["< 4.0.0"])
|
22
23
|
s.add_development_dependency('maruku')
|
23
24
|
s.add_development_dependency('syntax')
|
data/doc/manual.md
CHANGED
@@ -40,7 +40,7 @@ manipulating.
|
|
40
40
|
It supports all the common datatypes that are found in structured binary
|
41
41
|
data. Support for dependent and variable length fields is built in.
|
42
42
|
|
43
|
-
Last updated: 2013-
|
43
|
+
Last updated: 2013-09-02
|
44
44
|
|
45
45
|
## Source code
|
46
46
|
|
@@ -389,20 +389,28 @@ There are several parameters that are specific to all primitives.
|
|
389
389
|
list.len #=> 3
|
390
390
|
{:ruby}
|
391
391
|
|
392
|
-
`:
|
392
|
+
`:assert`
|
393
393
|
|
394
|
-
: When reading, will raise a `ValidityError` if the value
|
395
|
-
not match the value of this parameter.
|
396
|
-
[debugging](#debugging), rather than as a general error detection
|
397
|
-
system.
|
394
|
+
: When reading or assigning, will raise a `ValidityError` if the value
|
395
|
+
read or assigned does not match the value of this parameter.
|
398
396
|
|
399
|
-
obj = BinData::String.new(:
|
397
|
+
obj = BinData::String.new(:assert => lambda { /aaa/ =~ value })
|
400
398
|
obj.read("baaa!") #=> "baaa!"
|
401
399
|
obj.read("bbb") #=> raises ValidityError
|
402
400
|
|
403
|
-
obj = BinData::String.new(:
|
401
|
+
obj = BinData::String.new(:assert => "foo")
|
404
402
|
obj.read("foo") #=> "foo"
|
405
|
-
obj.
|
403
|
+
obj.assign("bar") #=> raises ValidityError
|
404
|
+
{:ruby}
|
405
|
+
|
406
|
+
`:asserted_value`
|
407
|
+
|
408
|
+
: A combination of `:assert` and `:value`. Used as a shortcut when
|
409
|
+
both `:assert` and `:value` have the same values. The following
|
410
|
+
are equivalent.
|
411
|
+
|
412
|
+
obj = BinData::Uint32Be.new(:assert => 42, :value => 42)
|
413
|
+
obj = BinData::Uint32Be.new(:asserted_value => 42)
|
406
414
|
{:ruby}
|
407
415
|
|
408
416
|
## Numerics
|
data/examples/gzip.rb
CHANGED
@@ -19,10 +19,10 @@ class Gzip
|
|
19
19
|
class Header < BinData::Record
|
20
20
|
endian :little
|
21
21
|
|
22
|
-
uint16 :ident, :
|
22
|
+
uint16 :ident, :asserted_value => 0x8b1f
|
23
23
|
uint8 :compression_method, :initial_value => DEFLATE
|
24
24
|
|
25
|
-
bit3 :freserved, :
|
25
|
+
bit3 :freserved, :asserted_value => 0
|
26
26
|
bit1 :fcomment, :value => lambda { comment.length > 0 ? 1 : 0 }
|
27
27
|
bit1 :ffile_name, :value => lambda { file_name.length > 0 ? 1 : 0 }
|
28
28
|
bit1 :fextra, :value => lambda { extra.len > 0 ? 1 : 0 }
|
data/examples/list.rb
CHANGED
@@ -20,12 +20,12 @@ require 'bindata'
|
|
20
20
|
# A first attempt at a declaration would be:
|
21
21
|
#
|
22
22
|
# class Atom < BinData::Record
|
23
|
-
# string :tag, :length => 1, :
|
23
|
+
# string :tag, :length => 1, :assert => 'a'
|
24
24
|
# int32be :val
|
25
25
|
# end
|
26
26
|
#
|
27
27
|
# class List < BinData::Record
|
28
|
-
# string :tag, :length => 1, :
|
28
|
+
# string :tag, :length => 1, :assert => 'l'
|
29
29
|
# int32be :num, :value => lambda { vals.length }
|
30
30
|
# array :vals, :initial_length => :num do
|
31
31
|
# choice :selection => ??? do
|
data/lib/bindata/alignment.rb
CHANGED
@@ -13,7 +13,6 @@ module BinData
|
|
13
13
|
# MyRec.read("\x12\x34") #=> {"a" => 1, "b" => 3}
|
14
14
|
#
|
15
15
|
class ResumeByteAlignment < BinData::Base
|
16
|
-
def clear; end
|
17
16
|
def clear?; true; end
|
18
17
|
def assign(val); end
|
19
18
|
def snapshot; nil; end
|
@@ -69,15 +68,11 @@ module BinData
|
|
69
68
|
end
|
70
69
|
end
|
71
70
|
|
72
|
-
|
73
|
-
|
74
|
-
include BitAligned
|
75
|
-
end
|
71
|
+
def BasePrimitive.bit_aligned
|
72
|
+
include BitAligned
|
76
73
|
end
|
77
74
|
|
78
|
-
|
79
|
-
|
80
|
-
fail "'bit_aligned' is not needed for BinData::Primitives"
|
81
|
-
end
|
75
|
+
def Primitive.bit_aligned
|
76
|
+
fail "'bit_aligned' is not needed for BinData::Primitives"
|
82
77
|
end
|
83
78
|
end
|
data/lib/bindata/array.rb
CHANGED
@@ -79,6 +79,15 @@ module BinData
|
|
79
79
|
|
80
80
|
def initialize_shared_instance
|
81
81
|
@element_prototype = get_parameter(:type)
|
82
|
+
if get_parameter(:read_until) == :eof
|
83
|
+
extend ReadUntilEOFPlugin
|
84
|
+
elsif has_parameter?(:read_until)
|
85
|
+
extend ReadUntilPlugin
|
86
|
+
elsif has_parameter?(:initial_length)
|
87
|
+
extend InitialLengthPlugin
|
88
|
+
end
|
89
|
+
|
90
|
+
super
|
82
91
|
end
|
83
92
|
|
84
93
|
def initialize_instance
|
@@ -89,10 +98,6 @@ module BinData
|
|
89
98
|
@element_list.nil? or elements.all? { |el| el.clear? }
|
90
99
|
end
|
91
100
|
|
92
|
-
def clear
|
93
|
-
initialize_instance
|
94
|
-
end
|
95
|
-
|
96
101
|
def assign(array)
|
97
102
|
raise ArgumentError, "can't set a nil value for #{debug_name}" if array.nil?
|
98
103
|
|
@@ -234,14 +239,6 @@ module BinData
|
|
234
239
|
child.do_num_bytes.is_a?(Integer) ? sum.ceil : sum.floor
|
235
240
|
end
|
236
241
|
|
237
|
-
def do_read(io) #:nodoc:
|
238
|
-
if has_parameter?(:initial_length)
|
239
|
-
elements.each { |el| el.do_read(io) }
|
240
|
-
elsif has_parameter?(:read_until)
|
241
|
-
read_until(io)
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
242
|
def do_write(io) #:nodoc:
|
246
243
|
elements.each { |el| el.do_write(io) }
|
247
244
|
end
|
@@ -264,46 +261,8 @@ module BinData
|
|
264
261
|
els.collect { |el| new_element(el) }
|
265
262
|
end
|
266
263
|
|
267
|
-
def read_until(io)
|
268
|
-
if get_parameter(:read_until) == :eof
|
269
|
-
read_until_eof(io)
|
270
|
-
else
|
271
|
-
read_until_condition(io)
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
def read_until_eof(io)
|
276
|
-
loop do
|
277
|
-
element = append_new_element
|
278
|
-
begin
|
279
|
-
element.do_read(io)
|
280
|
-
rescue EOFError, IOError
|
281
|
-
elements.pop
|
282
|
-
break
|
283
|
-
end
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
def read_until_condition(io)
|
288
|
-
loop do
|
289
|
-
element = append_new_element
|
290
|
-
element.do_read(io)
|
291
|
-
variables = { :index => self.length - 1, :element => self.last,
|
292
|
-
:array => self }
|
293
|
-
break if eval_parameter(:read_until, variables)
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
264
|
def elements
|
298
|
-
|
299
|
-
@element_list = []
|
300
|
-
if has_parameter?(:initial_length)
|
301
|
-
eval_parameter(:initial_length).times do
|
302
|
-
@element_list << new_element
|
303
|
-
end
|
304
|
-
end
|
305
|
-
end
|
306
|
-
@element_list
|
265
|
+
@element_list ||= []
|
307
266
|
end
|
308
267
|
|
309
268
|
def append_new_element
|
@@ -332,4 +291,51 @@ module BinData
|
|
332
291
|
end
|
333
292
|
end
|
334
293
|
end
|
294
|
+
|
295
|
+
# Logic for the :read_until parameter
|
296
|
+
module ReadUntilPlugin
|
297
|
+
def do_read(io)
|
298
|
+
loop do
|
299
|
+
element = append_new_element
|
300
|
+
element.do_read(io)
|
301
|
+
variables = { :index => self.length - 1, :element => self.last,
|
302
|
+
:array => self }
|
303
|
+
break if eval_parameter(:read_until, variables)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
end
|
308
|
+
|
309
|
+
# Logic for the :read_until => :eof parameter
|
310
|
+
module ReadUntilEOFPlugin
|
311
|
+
def do_read(io)
|
312
|
+
loop do
|
313
|
+
element = append_new_element
|
314
|
+
begin
|
315
|
+
element.do_read(io)
|
316
|
+
rescue EOFError, IOError
|
317
|
+
elements.pop
|
318
|
+
break
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Logic for the :initial_length parameter
|
325
|
+
module InitialLengthPlugin
|
326
|
+
def do_read(io)
|
327
|
+
elements.each { |el| el.do_read(io) }
|
328
|
+
end
|
329
|
+
|
330
|
+
def elements
|
331
|
+
if @element_list.nil?
|
332
|
+
@element_list = []
|
333
|
+
eval_parameter(:initial_length).times do
|
334
|
+
@element_list << new_element
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
@element_list
|
339
|
+
end
|
340
|
+
end
|
335
341
|
end
|
data/lib/bindata/base.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
+
require 'bindata/framework'
|
1
2
|
require 'bindata/io'
|
2
3
|
require 'bindata/lazy'
|
4
|
+
require 'bindata/name'
|
3
5
|
require 'bindata/offset'
|
4
6
|
require 'bindata/params'
|
5
7
|
require 'bindata/registry'
|
6
8
|
require 'bindata/sanitize'
|
7
9
|
|
8
10
|
module BinData
|
9
|
-
# Error raised when unexpected results occur when reading data from IO.
|
10
|
-
class ValidityError < StandardError ; end
|
11
|
-
|
12
11
|
# ArgExtractors take the arguments passed to BinData::Base.new and
|
13
|
-
#
|
12
|
+
# separate them into [value, parameters, parent].
|
14
13
|
class BaseArgExtractor
|
15
14
|
@@empty_hash = Hash.new.freeze
|
16
15
|
|
@@ -38,8 +37,10 @@ module BinData
|
|
38
37
|
|
39
38
|
# This is the abstract base class for all data objects.
|
40
39
|
class Base
|
41
|
-
|
42
|
-
include
|
40
|
+
extend AcceptedParametersPlugin
|
41
|
+
include Framework
|
42
|
+
include CheckOrAdjustOffsetPlugin
|
43
|
+
include RegisterNamePlugin
|
43
44
|
|
44
45
|
class << self
|
45
46
|
|
@@ -82,9 +83,6 @@ module BinData
|
|
82
83
|
# Register all subclasses of this class.
|
83
84
|
register_subclasses
|
84
85
|
|
85
|
-
# The registered name may be provided explicitly.
|
86
|
-
optional_parameter :name
|
87
|
-
|
88
86
|
# Creates a new data object.
|
89
87
|
#
|
90
88
|
# Args are optional, but if present, must be in the following order.
|
@@ -97,28 +95,12 @@ module BinData
|
|
97
95
|
# +parent+ is the parent data object (e.g. struct, array, choice) this
|
98
96
|
# object resides under.
|
99
97
|
#
|
100
|
-
# == Parameters
|
101
|
-
#
|
102
|
-
# Parameters may be provided at initialisation to control the behaviour of
|
103
|
-
# an object. These params are:
|
104
|
-
#
|
105
|
-
# <tt>:name</tt>:: The name that this object can be referred to may be
|
106
|
-
# set explicitly. This is only useful when dynamically
|
107
|
-
# generating types.
|
108
|
-
# <code><pre>
|
109
|
-
# BinData::Struct.new(:name => :my_struct, :fields => ...)
|
110
|
-
# array = BinData::Array.new(:type => :my_struct)
|
111
|
-
# </pre></code>
|
112
|
-
#
|
113
98
|
def initialize(*args)
|
114
99
|
value, parameters, parent = extract_args(args)
|
115
100
|
|
116
101
|
@params = SanitizedParameters.sanitize(parameters, self.class)
|
117
102
|
@parent = parent
|
118
103
|
|
119
|
-
register_prototype
|
120
|
-
add_methods_for_check_or_adjust_offset
|
121
|
-
|
122
104
|
initialize_shared_instance
|
123
105
|
initialize_instance
|
124
106
|
assign(value) if value
|
@@ -172,6 +154,11 @@ module BinData
|
|
172
154
|
@params.has_parameter?(key)
|
173
155
|
end
|
174
156
|
|
157
|
+
# Resets the internal state to that of a newly created object.
|
158
|
+
def clear
|
159
|
+
initialize_instance
|
160
|
+
end
|
161
|
+
|
175
162
|
# Reads data into this data object.
|
176
163
|
def read(io)
|
177
164
|
io = BinData::IO.new(io) unless BinData::IO === io
|
@@ -274,7 +261,7 @@ module BinData
|
|
274
261
|
def safe_respond_to?(symbol, include_private = false) #:nodoc:
|
275
262
|
respond_to?(symbol, include_private)
|
276
263
|
end
|
277
|
-
alias_method :
|
264
|
+
alias_method :base_respond_to?, :respond_to? #:nodoc:
|
278
265
|
|
279
266
|
#---------------
|
280
267
|
private
|
@@ -283,12 +270,6 @@ module BinData
|
|
283
270
|
self.class.arg_extractor.extract(self.class, the_args)
|
284
271
|
end
|
285
272
|
|
286
|
-
def register_prototype
|
287
|
-
if has_parameter?(:name)
|
288
|
-
RegisteredClasses.register(get_parameter(:name), self)
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
273
|
def furthest_ancestor
|
293
274
|
if parent.nil?
|
294
275
|
self
|
@@ -306,83 +287,5 @@ module BinData
|
|
306
287
|
str.dup
|
307
288
|
end
|
308
289
|
end
|
309
|
-
|
310
|
-
###########################################################################
|
311
|
-
# To be implemented by subclasses
|
312
|
-
|
313
|
-
# Performs sanity checks on the given parameters. This method converts
|
314
|
-
# the parameters to the form expected by this data object.
|
315
|
-
def self.sanitize_parameters!(parameters) #:nodoc:
|
316
|
-
end
|
317
|
-
|
318
|
-
# Initializes the state of the object. All instance variables that
|
319
|
-
# are used by the object must be initialized here.
|
320
|
-
def initialize_instance
|
321
|
-
end
|
322
|
-
|
323
|
-
# Initialises state that is shared by objects with the same parameters.
|
324
|
-
#
|
325
|
-
# This should only be used when optimising for performance. Instance
|
326
|
-
# variables set here, and changes to the singleton class will be shared
|
327
|
-
# between all objects that are initialized with the same parameters.
|
328
|
-
# This method is called only once for a particular set of parameters.
|
329
|
-
def initialize_shared_instance
|
330
|
-
end
|
331
|
-
|
332
|
-
# Resets the internal state to that of a newly created object.
|
333
|
-
def clear
|
334
|
-
raise NotImplementedError
|
335
|
-
end
|
336
|
-
|
337
|
-
# Returns true if the object has not been changed since creation.
|
338
|
-
def clear?
|
339
|
-
raise NotImplementedError
|
340
|
-
end
|
341
|
-
|
342
|
-
# Assigns the value of +val+ to this data object. Note that +val+ must
|
343
|
-
# always be deep copied to ensure no aliasing problems can occur.
|
344
|
-
def assign(val)
|
345
|
-
raise NotImplementedError
|
346
|
-
end
|
347
|
-
|
348
|
-
# Returns a snapshot of this data object.
|
349
|
-
def snapshot
|
350
|
-
raise NotImplementedError
|
351
|
-
end
|
352
|
-
|
353
|
-
# Returns the debug name of +child+. This only needs to be implemented
|
354
|
-
# by objects that contain child objects.
|
355
|
-
def debug_name_of(child) #:nodoc:
|
356
|
-
debug_name
|
357
|
-
end
|
358
|
-
|
359
|
-
# Returns the offset of +child+. This only needs to be implemented
|
360
|
-
# by objects that contain child objects.
|
361
|
-
def offset_of(child) #:nodoc:
|
362
|
-
0
|
363
|
-
end
|
364
|
-
|
365
|
-
# Reads the data for this data object from +io+.
|
366
|
-
def do_read(io) #:nodoc:
|
367
|
-
raise NotImplementedError
|
368
|
-
end
|
369
|
-
|
370
|
-
# Writes the value for this data to +io+.
|
371
|
-
def do_write(io) #:nodoc:
|
372
|
-
raise NotImplementedError
|
373
|
-
end
|
374
|
-
|
375
|
-
# Returns the number of bytes it will take to write this data.
|
376
|
-
def do_num_bytes #:nodoc:
|
377
|
-
raise NotImplementedError
|
378
|
-
end
|
379
|
-
|
380
|
-
# Set visibility requirements of methods to implement
|
381
|
-
public :clear, :clear?, :assign, :snapshot, :debug_name_of, :offset_of
|
382
|
-
protected :initialize_instance, :initialize_shared_instance
|
383
|
-
protected :do_read, :do_write, :do_num_bytes
|
384
|
-
|
385
|
-
# End To be implemented by subclasses
|
386
|
-
###########################################################################
|
387
290
|
end
|
388
291
|
end
|