bindata 1.2.2 → 1.3.1
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.
Potentially problematic release.
This version of bindata might be problematic. Click here for more details.
- data/ChangeLog +12 -0
- data/NEWS +53 -0
- data/Rakefile +2 -1
- data/examples/NBT.txt +149 -0
- data/examples/ip_address.rb +1 -2
- data/examples/list.rb +124 -0
- data/examples/nbt.rb +195 -0
- data/lib/bindata.rb +4 -3
- data/lib/bindata/alignment.rb +86 -0
- data/lib/bindata/array.rb +21 -29
- data/lib/bindata/base.rb +82 -81
- data/lib/bindata/base_primitive.rb +66 -48
- data/lib/bindata/choice.rb +18 -28
- data/lib/bindata/deprecated.rb +17 -0
- data/lib/bindata/dsl.rb +25 -15
- data/lib/bindata/int.rb +2 -2
- data/lib/bindata/io.rb +8 -6
- data/lib/bindata/offset.rb +91 -0
- data/lib/bindata/primitive.rb +22 -11
- data/lib/bindata/record.rb +40 -10
- data/lib/bindata/sanitize.rb +15 -30
- data/lib/bindata/string.rb +16 -17
- data/lib/bindata/stringz.rb +0 -1
- data/lib/bindata/struct.rb +17 -6
- data/lib/bindata/trace.rb +52 -0
- data/lib/bindata/wrapper.rb +28 -6
- data/manual.haml +56 -10
- data/manual.md +318 -113
- data/spec/alignment_spec.rb +61 -0
- data/spec/array_spec.rb +139 -178
- data/spec/base_primitive_spec.rb +86 -111
- data/spec/base_spec.rb +200 -172
- data/spec/bits_spec.rb +45 -53
- data/spec/choice_spec.rb +91 -87
- data/spec/deprecated_spec.rb +36 -14
- data/spec/float_spec.rb +16 -68
- data/spec/int_spec.rb +26 -27
- data/spec/io_spec.rb +105 -105
- data/spec/lazy_spec.rb +50 -50
- data/spec/primitive_spec.rb +36 -36
- data/spec/record_spec.rb +134 -134
- data/spec/registry_spec.rb +34 -38
- data/spec/rest_spec.rb +8 -11
- data/spec/skip_spec.rb +9 -17
- data/spec/spec_common.rb +4 -0
- data/spec/string_spec.rb +92 -115
- data/spec/stringz_spec.rb +41 -74
- data/spec/struct_spec.rb +132 -153
- data/spec/system_spec.rb +115 -60
- data/spec/wrapper_spec.rb +63 -31
- data/tasks/pkg.rake +1 -1
- metadata +15 -7
data/lib/bindata/choice.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'bindata/base'
|
2
|
-
require 'bindata/trace'
|
3
2
|
|
4
3
|
module BinData
|
5
4
|
# A Choice is a collection of data objects of which only one is active
|
@@ -13,26 +12,29 @@ module BinData
|
|
13
12
|
#
|
14
13
|
# choices = {5 => type1, 17 => type2}
|
15
14
|
# a = BinData::Choice.new(:choices => choices, :selection => 5)
|
16
|
-
# a
|
15
|
+
# a # => "Type1"
|
17
16
|
#
|
18
17
|
# choices = [ type1, type2 ]
|
19
18
|
# a = BinData::Choice.new(:choices => choices, :selection => 1)
|
20
|
-
# a
|
19
|
+
# a # => "Type2"
|
21
20
|
#
|
22
21
|
# choices = [ nil, nil, nil, type1, nil, type2 ]
|
23
22
|
# a = BinData::Choice.new(:choices => choices, :selection => 3)
|
24
|
-
# a
|
23
|
+
# a # => "Type1"
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# Chooser = Struct.new(:choice)
|
27
|
+
# mychoice = Chooser.new
|
28
|
+
# mychoice.choice = 'big'
|
25
29
|
#
|
26
|
-
# mychoice = 'big'
|
27
30
|
# choices = {'big' => :uint16be, 'little' => :uint16le}
|
28
|
-
# a = BinData::Choice.new(:choices => choices,
|
29
|
-
# :selection => lambda { mychoice })
|
30
|
-
# a.
|
31
|
+
# a = BinData::Choice.new(:choices => choices, :copy_on_change => true,
|
32
|
+
# :selection => lambda { mychoice.choice })
|
33
|
+
# a.assign(256)
|
31
34
|
# a.to_binary_s #=> "\001\000"
|
32
|
-
# mychoice.replace 'little'
|
33
|
-
# a.selection #=> 'little'
|
34
|
-
# a.to_binary_s #=> "\000\001"
|
35
35
|
#
|
36
|
+
# mychoice.choice = 'little'
|
37
|
+
# a.to_binary_s #=> "\000\001"
|
36
38
|
#
|
37
39
|
# == Parameters
|
38
40
|
#
|
@@ -98,9 +100,7 @@ module BinData
|
|
98
100
|
end
|
99
101
|
end
|
100
102
|
|
101
|
-
def
|
102
|
-
super
|
103
|
-
|
103
|
+
def initialize_instance
|
104
104
|
@choices = {}
|
105
105
|
@last_selection = nil
|
106
106
|
end
|
@@ -162,7 +162,7 @@ module BinData
|
|
162
162
|
end
|
163
163
|
|
164
164
|
def do_read(io) #:nodoc:
|
165
|
-
|
165
|
+
hook_before_do_read
|
166
166
|
current_choice.do_read(io)
|
167
167
|
end
|
168
168
|
|
@@ -177,12 +177,7 @@ module BinData
|
|
177
177
|
#---------------
|
178
178
|
private
|
179
179
|
|
180
|
-
def
|
181
|
-
BinData::trace_message do |tracer|
|
182
|
-
selection_string = eval_parameter(:selection).inspect
|
183
|
-
tracer.trace_obj("#{debug_name}-selection-", selection_string)
|
184
|
-
end
|
185
|
-
end
|
180
|
+
def hook_before_do_read; end
|
186
181
|
|
187
182
|
def current_choice
|
188
183
|
selection = eval_parameter(:selection)
|
@@ -197,12 +192,7 @@ module BinData
|
|
197
192
|
end
|
198
193
|
|
199
194
|
def get_or_instantiate_choice(selection)
|
200
|
-
|
201
|
-
if obj.nil?
|
202
|
-
obj = instantiate_choice(selection)
|
203
|
-
@choices[selection] = obj
|
204
|
-
end
|
205
|
-
obj
|
195
|
+
@choices[selection] ||= instantiate_choice(selection)
|
206
196
|
end
|
207
197
|
|
208
198
|
def instantiate_choice(selection)
|
@@ -210,7 +200,7 @@ module BinData
|
|
210
200
|
if prototype.nil?
|
211
201
|
raise IndexError, "selection '#{selection}' does not exist in :choices for #{debug_name}"
|
212
202
|
end
|
213
|
-
prototype.instantiate(self)
|
203
|
+
prototype.instantiate(nil, self)
|
214
204
|
end
|
215
205
|
|
216
206
|
def copy_previous_value_if_required(selection, obj)
|
data/lib/bindata/deprecated.rb
CHANGED
@@ -17,6 +17,23 @@ end
|
|
17
17
|
|
18
18
|
module BinData
|
19
19
|
class Base
|
20
|
+
|
21
|
+
alias_method :initialize_without_deprecation, :initialize
|
22
|
+
def initialize_with_deprecation(*args)
|
23
|
+
owner = method(:initialize).owner
|
24
|
+
if owner != BinData::Base
|
25
|
+
fail "implementing #initialize on #{owner} is not allowed.\nEither downgrade to BinData 1.2.2, or rename #initialize to #initialize_instance."
|
26
|
+
end
|
27
|
+
initialize_without_deprecation(*args)
|
28
|
+
end
|
29
|
+
alias_method :initialize, :initialize_with_deprecation
|
30
|
+
|
31
|
+
def initialize_instance(*args)
|
32
|
+
unless args.empty?
|
33
|
+
warn "#{caller[0]} remove the call to super in #initialize_instance"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
20
37
|
class << self
|
21
38
|
def register(name, class_to_register)
|
22
39
|
if class_to_register == self
|
data/lib/bindata/dsl.rb
CHANGED
@@ -13,6 +13,7 @@ module BinData
|
|
13
13
|
# [<tt>:sanitize_fields</tt>] Fields are to be sanitized.
|
14
14
|
# [<tt>:mandatory_fieldnames</tt>] Fieldnames are mandatory.
|
15
15
|
# [<tt>:optional_fieldnames</tt>] Fieldnames are optional.
|
16
|
+
# [<tt>:fieldnames_for_choices</tt>] Fieldnames are choice keys.
|
16
17
|
# [<tt>:no_fieldnames</tt>] Fieldnames are prohibited.
|
17
18
|
# [<tt>:all_or_none_fieldnames</tt>] All fields must have names, or
|
18
19
|
# none may have names.
|
@@ -108,7 +109,13 @@ module BinData
|
|
108
109
|
|
109
110
|
def hide(*args)
|
110
111
|
if option?(:hidden_fields)
|
111
|
-
|
112
|
+
hidden = args.collect do |name|
|
113
|
+
unless Symbol === name
|
114
|
+
warn "Hidden field '#{name}' should be provided as a symbol. Using strings is deprecated"
|
115
|
+
end
|
116
|
+
name.to_sym
|
117
|
+
end
|
118
|
+
@hide.concat(hidden.compact)
|
112
119
|
@hide
|
113
120
|
end
|
114
121
|
end
|
@@ -187,9 +194,10 @@ module BinData
|
|
187
194
|
|
188
195
|
def name_from_field_declaration(args)
|
189
196
|
name, params = args
|
190
|
-
name =
|
197
|
+
name = "" if name.nil? or name.is_a?(Hash)
|
198
|
+
name = name.to_s if name.is_a?(Symbol)
|
191
199
|
|
192
|
-
name
|
200
|
+
name
|
193
201
|
end
|
194
202
|
|
195
203
|
def params_from_field_declaration(type, args, &block)
|
@@ -234,20 +242,22 @@ module BinData
|
|
234
242
|
dsl_raise SyntaxError, "field must have a name"
|
235
243
|
end
|
236
244
|
|
237
|
-
|
238
|
-
|
239
|
-
|
245
|
+
unless option?(:fieldnames_for_choices)
|
246
|
+
if malformed_name?(name)
|
247
|
+
dsl_raise NameError.new("", name), "field '#{name}' is an illegal fieldname"
|
248
|
+
end
|
240
249
|
|
241
|
-
|
242
|
-
|
243
|
-
|
250
|
+
if duplicate_name?(name)
|
251
|
+
dsl_raise SyntaxError, "duplicate field '#{name}'"
|
252
|
+
end
|
244
253
|
|
245
|
-
|
246
|
-
|
247
|
-
|
254
|
+
if name_shadows_method?(name)
|
255
|
+
dsl_raise NameError.new("", name), "field '#{name}' shadows an existing method"
|
256
|
+
end
|
248
257
|
|
249
|
-
|
250
|
-
|
258
|
+
if name_is_reserved?(name)
|
259
|
+
dsl_raise NameError.new("", name), "field '#{name}' is a reserved name"
|
260
|
+
end
|
251
261
|
end
|
252
262
|
end
|
253
263
|
|
@@ -317,7 +327,7 @@ module BinData
|
|
317
327
|
|
318
328
|
class ChoiceBlockParser
|
319
329
|
def self.extract_params(endian, &block)
|
320
|
-
parser = DSLParser.new(BinData::Choice, :multiple_fields, :all_or_none_fieldnames)
|
330
|
+
parser = DSLParser.new(BinData::Choice, :multiple_fields, :all_or_none_fieldnames, :fieldnames_for_choices)
|
321
331
|
parser.endian endian
|
322
332
|
parser.instance_eval(&block)
|
323
333
|
|
data/lib/bindata/int.rb
CHANGED
@@ -56,6 +56,7 @@ module BinData
|
|
56
56
|
def read_and_return_value(io)
|
57
57
|
val = #{create_read_code(nbits, endian)}
|
58
58
|
#{create_uint2int_code(nbits) if signed == :signed}
|
59
|
+
val
|
59
60
|
end
|
60
61
|
END
|
61
62
|
end
|
@@ -82,8 +83,7 @@ module BinData
|
|
82
83
|
def create_uint2int_code(nbits)
|
83
84
|
mask = (1 << (nbits - 1)) - 1
|
84
85
|
|
85
|
-
"val = ((val & #{1 << (nbits - 1)})
|
86
|
-
"val & #{mask} : -(((~val) & #{mask}) + 1)"
|
86
|
+
"val = -(((~val) & #{mask}) + 1) if (val >= #{1 << (nbits - 1)})"
|
87
87
|
end
|
88
88
|
|
89
89
|
def create_read_code(nbits, endian)
|
data/lib/bindata/io.rb
CHANGED
@@ -111,6 +111,14 @@ module BinData
|
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
|
+
# Discards any read bits so the stream becomes aligned at the
|
115
|
+
# next byte boundary.
|
116
|
+
def reset_read_bits
|
117
|
+
raise "Internal state error nbits = #{@rnbits}" if @rnbits >= 8
|
118
|
+
@rnbits = 0
|
119
|
+
@rval = 0
|
120
|
+
end
|
121
|
+
|
114
122
|
# Writes the given string of bytes to the io stream.
|
115
123
|
def writebytes(str)
|
116
124
|
flushbits
|
@@ -160,12 +168,6 @@ module BinData
|
|
160
168
|
@positioning_supported
|
161
169
|
end
|
162
170
|
|
163
|
-
def reset_read_bits
|
164
|
-
raise "Internal state error nbits = #{@rnbits}" if @rnbits >= 8
|
165
|
-
@rnbits = 0
|
166
|
-
@rval = 0
|
167
|
-
end
|
168
|
-
|
169
171
|
def skipbytes(n)
|
170
172
|
# skip over data in 8k blocks
|
171
173
|
while n > 0
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module BinData
|
2
|
+
# == Parameters
|
3
|
+
#
|
4
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
5
|
+
# an object. These parameters are:
|
6
|
+
#
|
7
|
+
# [<tt>:check_offset</tt>] Raise an error if the current IO offset doesn't
|
8
|
+
# meet this criteria. A boolean return indicates
|
9
|
+
# success or failure. Any other return is compared
|
10
|
+
# to the current offset. The variable +offset+
|
11
|
+
# is made available to any lambda assigned to
|
12
|
+
# this parameter. This parameter is only checked
|
13
|
+
# before reading.
|
14
|
+
# [<tt>:adjust_offset</tt>] Ensures that the current IO offset is at this
|
15
|
+
# position before reading. This is like
|
16
|
+
# <tt>:check_offset</tt>, except that it will
|
17
|
+
# adjust the IO offset instead of raising an error.
|
18
|
+
module CheckOrAdjustOffsetMixin
|
19
|
+
|
20
|
+
def self.included(base) #:nodoc:
|
21
|
+
base.optional_parameters :check_offset, :adjust_offset
|
22
|
+
base.mutually_exclusive_parameters :check_offset, :adjust_offset
|
23
|
+
end
|
24
|
+
|
25
|
+
# Ideally these two methods should be protected,
|
26
|
+
# but Ruby 1.9.2 requires them to be public.
|
27
|
+
# see http://redmine.ruby-lang.org/issues/show/2375
|
28
|
+
|
29
|
+
def do_read_with_check_offset(io) #:nodoc:
|
30
|
+
check_offset(io)
|
31
|
+
do_read_without_check_offset(io)
|
32
|
+
end
|
33
|
+
|
34
|
+
def do_read_with_adjust_offset(io) #:nodoc:
|
35
|
+
adjust_offset(io)
|
36
|
+
do_read_without_adjust_offset(io)
|
37
|
+
end
|
38
|
+
|
39
|
+
#---------------
|
40
|
+
private
|
41
|
+
|
42
|
+
# To be called from BinData::Base#initialize.
|
43
|
+
#
|
44
|
+
# Monkey patches #do_read to check or adjust the stream offset
|
45
|
+
# as appropriate.
|
46
|
+
def add_methods_for_check_or_adjust_offset
|
47
|
+
if has_parameter?(:check_offset)
|
48
|
+
class << self
|
49
|
+
alias_method :do_read_without_check_offset, :do_read
|
50
|
+
alias_method :do_read, :do_read_with_check_offset
|
51
|
+
end
|
52
|
+
end
|
53
|
+
if has_parameter?(:adjust_offset)
|
54
|
+
class << self
|
55
|
+
alias_method :do_read_without_adjust_offset, :do_read
|
56
|
+
alias_method :do_read, :do_read_with_adjust_offset
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def check_offset(io)
|
62
|
+
actual_offset = io.offset
|
63
|
+
expected = eval_parameter(:check_offset, :offset => actual_offset)
|
64
|
+
|
65
|
+
if not expected
|
66
|
+
raise ValidityError, "offset not as expected for #{debug_name}"
|
67
|
+
elsif actual_offset != expected and expected != true
|
68
|
+
raise ValidityError,
|
69
|
+
"offset is '#{actual_offset}' but " +
|
70
|
+
"expected '#{expected}' for #{debug_name}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def adjust_offset(io)
|
75
|
+
actual_offset = io.offset
|
76
|
+
expected = eval_parameter(:adjust_offset)
|
77
|
+
if actual_offset != expected
|
78
|
+
begin
|
79
|
+
seek = expected - actual_offset
|
80
|
+
io.seekbytes(seek)
|
81
|
+
warn "adjusting stream position by #{seek} bytes" if $VERBOSE
|
82
|
+
rescue
|
83
|
+
raise ValidityError,
|
84
|
+
"offset is '#{actual_offset}' but couldn't seek to " +
|
85
|
+
"expected '#{expected}' for #{debug_name}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
data/lib/bindata/primitive.rb
CHANGED
@@ -29,7 +29,7 @@ module BinData
|
|
29
29
|
# ps = PascalString.new(:initial_value => "hello")
|
30
30
|
# ps.to_binary_s #=> "\005hello"
|
31
31
|
# ps.read("\003abcde")
|
32
|
-
# ps
|
32
|
+
# ps #=> "abc"
|
33
33
|
#
|
34
34
|
# # Unsigned 24 bit big endian integer
|
35
35
|
# class Uint24be < BinData::Primitive
|
@@ -53,7 +53,7 @@ module BinData
|
|
53
53
|
#
|
54
54
|
# u24 = Uint24be.new
|
55
55
|
# u24.read("\x12\x34\x56")
|
56
|
-
# "0x%x" % u24
|
56
|
+
# "0x%x" % u24 #=> 0x123456
|
57
57
|
#
|
58
58
|
# == Parameters
|
59
59
|
#
|
@@ -73,20 +73,36 @@ module BinData
|
|
73
73
|
|
74
74
|
mandatory_parameter :struct_params
|
75
75
|
|
76
|
-
def
|
77
|
-
super
|
78
|
-
|
76
|
+
def initialize_instance
|
79
77
|
@struct = BinData::Struct.new(get_parameter(:struct_params), self)
|
80
78
|
end
|
81
79
|
|
80
|
+
def respond_to?(symbol, include_private = false) #:nodoc:
|
81
|
+
@struct.respond_to?(symbol, include_private) || super
|
82
|
+
end
|
83
|
+
|
82
84
|
def method_missing(symbol, *args, &block) #:nodoc:
|
83
|
-
@struct.
|
85
|
+
if @struct.respond_to?(symbol)
|
86
|
+
@struct.__send__(symbol, *args, &block)
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
84
90
|
end
|
85
91
|
|
86
92
|
def debug_name_of(child) #:nodoc:
|
87
93
|
debug_name + "-internal-"
|
88
94
|
end
|
89
95
|
|
96
|
+
def do_write(io)
|
97
|
+
set(_value)
|
98
|
+
@struct.do_write(io)
|
99
|
+
end
|
100
|
+
|
101
|
+
def do_num_bytes
|
102
|
+
set(_value)
|
103
|
+
@struct.do_num_bytes
|
104
|
+
end
|
105
|
+
|
90
106
|
#---------------
|
91
107
|
private
|
92
108
|
|
@@ -99,11 +115,6 @@ module BinData
|
|
99
115
|
get
|
100
116
|
end
|
101
117
|
|
102
|
-
def value_to_binary_string(val)
|
103
|
-
set(val)
|
104
|
-
@struct.to_binary_s
|
105
|
-
end
|
106
|
-
|
107
118
|
###########################################################################
|
108
119
|
# To be implemented by subclasses
|
109
120
|
|
data/lib/bindata/record.rb
CHANGED
@@ -3,6 +3,28 @@ require 'bindata/sanitize'
|
|
3
3
|
require 'bindata/struct'
|
4
4
|
|
5
5
|
module BinData
|
6
|
+
class RecordArgExtractor
|
7
|
+
def self.extract(the_class, the_args)
|
8
|
+
value, parameters, parent = BaseArgExtractor.extract(the_class, the_args)
|
9
|
+
|
10
|
+
if value.nil? and parameters.length > 0
|
11
|
+
if field_names_in_parameters(the_class, parameters).length > 0
|
12
|
+
value = parameters
|
13
|
+
parameters = nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
[value, parameters, parent]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.field_names_in_parameters(the_class, parameters)
|
21
|
+
field_names = the_class.fields.field_names.collect { |k| k.to_s }
|
22
|
+
param_keys = parameters.keys.collect { |k| k.to_s }
|
23
|
+
|
24
|
+
(field_names & param_keys)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
6
28
|
# A Record is a declarative wrapper around Struct.
|
7
29
|
#
|
8
30
|
# require 'bindata'
|
@@ -49,6 +71,11 @@ module BinData
|
|
49
71
|
dsl_parser :multiple_fields, :optional_fieldnames, :sanitize_fields, :hidden_fields
|
50
72
|
|
51
73
|
class << self
|
74
|
+
|
75
|
+
def arg_extractor
|
76
|
+
RecordArgExtractor
|
77
|
+
end
|
78
|
+
|
52
79
|
def sanitize_parameters!(params, sanitizer) #:nodoc:
|
53
80
|
params[:fields] = fields
|
54
81
|
params[:endian] = endian unless endian.nil?
|
@@ -65,22 +92,25 @@ module BinData
|
|
65
92
|
def define_field_accessors(fields) #:nodoc:
|
66
93
|
unless method_defined?(:bindata_defined_accessors_for_fields?)
|
67
94
|
fields.each_with_index do |field, i|
|
68
|
-
|
69
|
-
|
70
|
-
define_method(name.to_sym) do
|
71
|
-
instantiate_obj_at(i) unless @field_objs[i]
|
72
|
-
@field_objs[i]
|
73
|
-
end
|
74
|
-
define_method((name + "=").to_sym) do |*vals|
|
75
|
-
instantiate_obj_at(i) unless @field_objs[i]
|
76
|
-
@field_objs[i].assign(*vals)
|
77
|
-
end
|
95
|
+
if field.name
|
96
|
+
define_field_accessors_for(field.name, i)
|
78
97
|
end
|
79
98
|
end
|
80
99
|
|
81
100
|
define_method(:bindata_defined_accessors_for_fields?) { true }
|
82
101
|
end
|
83
102
|
end
|
103
|
+
|
104
|
+
def define_field_accessors_for(name, index)
|
105
|
+
define_method(name.to_sym) do
|
106
|
+
instantiate_obj_at(index) unless @field_objs[index]
|
107
|
+
@field_objs[index]
|
108
|
+
end
|
109
|
+
define_method((name + "=").to_sym) do |*vals|
|
110
|
+
instantiate_obj_at(index) unless @field_objs[index]
|
111
|
+
@field_objs[index].assign(*vals)
|
112
|
+
end
|
113
|
+
end
|
84
114
|
end
|
85
115
|
end
|
86
116
|
end
|