baikal 1.1.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.
- data/GPL-3 +674 -0
- data/History.txt +10 -0
- data/Makefile +7 -0
- data/Manifest.txt +11 -0
- data/README.txt +34 -0
- data/baikal.gemspec +20 -0
- data/lib/baikal.rb +459 -0
- data/lib/baikal/cursor.rb +297 -0
- data/lib/baikal/hexdump.rb +257 -0
- data/lib/baikal/tweak.rb +103 -0
- data/test/test_baikal.rb +61 -0
- metadata +65 -0
@@ -0,0 +1,297 @@
|
|
1
|
+
# $Id$
|
2
|
+
|
3
|
+
require_relative '../baikal'
|
4
|
+
|
5
|
+
module Baikal
|
6
|
+
#
|
7
|
+
# Represents an offset into a byte pool, permitting traversing the byte
|
8
|
+
# pool in a linear manner.
|
9
|
+
#
|
10
|
+
class Cursor
|
11
|
+
#
|
12
|
+
# The cursor's current offset into the underlying byte pool.
|
13
|
+
#
|
14
|
+
attr_accessor :offset
|
15
|
+
|
16
|
+
#
|
17
|
+
# Creates a cursor of the given +pool+, initialising it to point at
|
18
|
+
# its given +offset+. If +offset+ is not given, defaults to zero, that
|
19
|
+
# is to say, the byte pool's beginning.
|
20
|
+
#
|
21
|
+
def initialize pool, offset = 0
|
22
|
+
raise 'Type mismatch' unless pool.is_a? Baikal::Pool
|
23
|
+
raise 'Type mismatch' unless offset.is_a? Integer
|
24
|
+
super()
|
25
|
+
@pool = pool
|
26
|
+
@offset = offset
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Retrieves an unsigned byte from the underlying pool's current
|
32
|
+
# position and advances the cursor by one byte.
|
33
|
+
#
|
34
|
+
# Error if the byte referred to by this cursor lies outside the
|
35
|
+
# boundaries of the underlying pool.
|
36
|
+
#
|
37
|
+
def parse_unsigned_byte
|
38
|
+
value = @pool.get_unsigned_byte(@offset)
|
39
|
+
@offset += 1
|
40
|
+
return value
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Retrieves an unsigned wyde from the underlying pool's current
|
45
|
+
# position using the pool's currently selected byte order and advances
|
46
|
+
# the cursor by two bytes.
|
47
|
+
#
|
48
|
+
# Error if the wyde referred to by this cursor lies outside the
|
49
|
+
# boundaries of the underlying pool, even partially.
|
50
|
+
#
|
51
|
+
def parse_unsigned_wyde
|
52
|
+
value = @pool.get_unsigned_wyde(@offset)
|
53
|
+
@offset += 2
|
54
|
+
return value
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Retrieves an unsigned tetrabyte from the underlying pool's current
|
59
|
+
# position using the pool's currently selected byte order and advances
|
60
|
+
# the cursor by four bytes.
|
61
|
+
#
|
62
|
+
# Error if the tetra referred to by this cursor lies outside the
|
63
|
+
# boundaries of the underlying pool, even partially.
|
64
|
+
#
|
65
|
+
def parse_unsigned_tetra
|
66
|
+
value = @pool.get_unsigned_tetra(@offset)
|
67
|
+
@offset += 4
|
68
|
+
return value
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Retrieves an unsigned octabyte from the underlying pool's current
|
73
|
+
# position using the pool's currently selected byte order and advances
|
74
|
+
# the cursor by eight bytes.
|
75
|
+
#
|
76
|
+
# Error if the octa referred to by this cursor lies outside the
|
77
|
+
# boundaries of the underlying pool, even partially.
|
78
|
+
#
|
79
|
+
def parse_unsigned_octa
|
80
|
+
value = @pool.get_unsigned_octa(@offset)
|
81
|
+
@offset += 8
|
82
|
+
return value
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Retrieves a signed byte from the underlying pool's current position
|
87
|
+
# and advances the cursor by one byte.
|
88
|
+
#
|
89
|
+
# Error if the byte referred to by this cursor lies outside the
|
90
|
+
# boundaries of the underlying pool.
|
91
|
+
#
|
92
|
+
def parse_signed_byte
|
93
|
+
value = @pool.get_signed_byte(@offset)
|
94
|
+
@offset += 1
|
95
|
+
return value
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Retrieves a signed wyde from the underlying pool's current position
|
100
|
+
# using the pool's currently selected byte order and advances the
|
101
|
+
# cursor by two bytes.
|
102
|
+
#
|
103
|
+
# Error if the wyde referred to by this cursor lies outside the
|
104
|
+
# boundaries of the underlying pool, even partially.
|
105
|
+
#
|
106
|
+
def parse_signed_wyde
|
107
|
+
value = @pool.get_signed_wyde(@offset)
|
108
|
+
@offset += 2
|
109
|
+
return value
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Retrieves a signed tetrabyte from the underlying pool's current
|
114
|
+
# position using the pool's currently selected byte order and advances
|
115
|
+
# the cursor by four bytes.
|
116
|
+
#
|
117
|
+
# Error if the tetra referred to by this cursor lies outside the
|
118
|
+
# boundaries of the underlying pool, even partially.
|
119
|
+
#
|
120
|
+
def parse_signed_tetra
|
121
|
+
value = @pool.get_signed_tetra(@offset)
|
122
|
+
@offset += 4
|
123
|
+
return value
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# Retrieves a signed octabyte from the underlying pool's current
|
128
|
+
# position using the pool's currently selected byte order and advances
|
129
|
+
# the cursor by eight bytes.
|
130
|
+
#
|
131
|
+
# Error if the octa referred to by this cursor lies outside the
|
132
|
+
# boundaries of the underlying pool, even partially.
|
133
|
+
#
|
134
|
+
def parse_signed_octa
|
135
|
+
value = @pool.get_signed_octa(@offset)
|
136
|
+
@offset += 8
|
137
|
+
return value
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Retrieves an unsigned byte from the underlying pool's current
|
142
|
+
# position. Does not affect the position.
|
143
|
+
#
|
144
|
+
# Error if the byte referred to by this cursor lies outside the
|
145
|
+
# boundaries of the underlying pool.
|
146
|
+
#
|
147
|
+
def peek_unsigned_byte
|
148
|
+
return @pool.get_unsigned_byte(@offset)
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# Retrieves an unsigned wyde from the underlying pool's current
|
153
|
+
# position using the pool's currently selected byte order. Does not
|
154
|
+
# affect the position.
|
155
|
+
#
|
156
|
+
# Error if the wyde referred to by this cursor lies outside the
|
157
|
+
# boundaries of the underlying pool, even partially.
|
158
|
+
#
|
159
|
+
def peek_unsigned_wyde
|
160
|
+
return @pool.get_unsigned_wyde(@offset)
|
161
|
+
end
|
162
|
+
|
163
|
+
#
|
164
|
+
# Retrieves an unsigned tetrabyte from the underlying pool's current
|
165
|
+
# position using the pool's currently selected byte order. Does not
|
166
|
+
# affect the position.
|
167
|
+
#
|
168
|
+
# Error if the tetra referred to by this cursor lies outside the
|
169
|
+
# boundaries of the underlying pool, even partially.
|
170
|
+
#
|
171
|
+
def peek_unsigned_tetra
|
172
|
+
return @pool.get_unsigned_tetra(@offset)
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# Retrieves an unsigned octabyte from the underlying pool's current
|
177
|
+
# position using the pool's currently selected byte order. Does not
|
178
|
+
# affect the position.
|
179
|
+
#
|
180
|
+
# Error if the octa referred to by this cursor lies outside the
|
181
|
+
# boundaries of the underlying pool, even partially.
|
182
|
+
#
|
183
|
+
def peek_unsigned_octa
|
184
|
+
return @pool.get_unsigned_octa(@offset)
|
185
|
+
end
|
186
|
+
|
187
|
+
#
|
188
|
+
# Retrieves a signed byte from the underlying pool's current position.
|
189
|
+
# Does not affect the position.
|
190
|
+
#
|
191
|
+
# Error if the byte referred to by this cursor lies outside the
|
192
|
+
# boundaries of the underlying pool.
|
193
|
+
#
|
194
|
+
def peek_signed_byte
|
195
|
+
return @pool.get_signed_byte(@offset)
|
196
|
+
end
|
197
|
+
|
198
|
+
#
|
199
|
+
# Retrieves a signed wyde from the underlying pool's current position
|
200
|
+
# using the pool's currently selected byte order. Does not affect the
|
201
|
+
# position.
|
202
|
+
#
|
203
|
+
# Error if the wyde referred to by this cursor lies outside the
|
204
|
+
# boundaries of the underlying pool, even partially.
|
205
|
+
#
|
206
|
+
def peek_signed_wyde
|
207
|
+
return @pool.get_signed_wyde(@offset)
|
208
|
+
end
|
209
|
+
|
210
|
+
#
|
211
|
+
# Retrieves a signed tetrabyte from the underlying pool's current
|
212
|
+
# position using the pool's currently selected byte order. Does not
|
213
|
+
# affect the position.
|
214
|
+
#
|
215
|
+
# Error if the tetra referred to by this cursor lies outside the
|
216
|
+
# boundaries of the underlying pool, even partially.
|
217
|
+
#
|
218
|
+
def peek_signed_tetra
|
219
|
+
return @pool.get_signed_tetra(@offset)
|
220
|
+
end
|
221
|
+
|
222
|
+
#
|
223
|
+
# Retrieves a signed octabyte from the underlying pool's current
|
224
|
+
# position using the pool's currently selected byte order. Does not
|
225
|
+
# affect the position.
|
226
|
+
#
|
227
|
+
# Error if the octa referred to by this cursor lies outside the
|
228
|
+
# boundaries of the underlying pool, even partially.
|
229
|
+
#
|
230
|
+
def peek_signed_octa
|
231
|
+
return @pool.get_signed_octa(@offset)
|
232
|
+
end
|
233
|
+
|
234
|
+
#
|
235
|
+
# Retrieves an unsigned integer of the given +size+ (which must be +1+,
|
236
|
+
# +2+, +4+ or +8+) from the underlying pool's current position using the
|
237
|
+
# pool's currently selected byte order and advances the cursor by the
|
238
|
+
# integer's size.
|
239
|
+
#
|
240
|
+
# Error if the integer of this size referred to by this cursor lies
|
241
|
+
# outside the boundaries of the underlying pool, even partially.
|
242
|
+
#
|
243
|
+
def parse_unsigned_integer size
|
244
|
+
value = @pool.get_unsigned_integer(size, @offset)
|
245
|
+
@offset += size
|
246
|
+
return value
|
247
|
+
end
|
248
|
+
|
249
|
+
#
|
250
|
+
# Retrieves a specified number of bytes from the underlying pool's
|
251
|
+
# current position and advances the cursor by the same number.
|
252
|
+
#
|
253
|
+
# Error if the blob referred to by this cursor and this byte count lies
|
254
|
+
# outside the boundaries of the underlying pool, even partially.
|
255
|
+
#
|
256
|
+
def parse_blob size
|
257
|
+
raise 'Type mismatch' unless size.is_a? Integer
|
258
|
+
raise 'Invalid blob size' unless size >= 0
|
259
|
+
blob = @pool.get_blob(@offset, size)
|
260
|
+
@offset += size
|
261
|
+
return blob
|
262
|
+
end
|
263
|
+
|
264
|
+
#
|
265
|
+
# Moves the cursor forwards by +delta+ bytes, passing a part of the
|
266
|
+
# byte sequence without parsing.
|
267
|
+
#
|
268
|
+
# Error if +delta+ is negative.
|
269
|
+
#
|
270
|
+
def skip delta
|
271
|
+
raise 'Type mismatch' unless delta.is_a? Integer
|
272
|
+
raise 'Invalid byte count' if delta < 0
|
273
|
+
@offset += delta
|
274
|
+
return
|
275
|
+
end
|
276
|
+
|
277
|
+
#
|
278
|
+
# Moves the cursor backwards by +delta+ bytes.
|
279
|
+
#
|
280
|
+
# Error if +delta+ is negative.
|
281
|
+
#
|
282
|
+
def unskip delta
|
283
|
+
raise 'Type mismatch' unless delta.is_a? Integer
|
284
|
+
raise 'Invalid byte count' if delta < 0
|
285
|
+
@offset -= delta
|
286
|
+
return
|
287
|
+
end
|
288
|
+
|
289
|
+
#
|
290
|
+
# Returns +true+ if the cursor has passed the last byte currently in
|
291
|
+
# the underlying pool or +false+ otherwise.
|
292
|
+
#
|
293
|
+
def eof?
|
294
|
+
return @offset >= @pool.size
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
# $Id: hexdump.rb 1275 2009-10-03 10:28:54Z digwuren $
|
2
|
+
|
3
|
+
module Baikal
|
4
|
+
#
|
5
|
+
# Hexdumps bytes from +data_source+ into the given +port+ (by default,
|
6
|
+
# +$stdout+) using the given format (by default, +DEFAULT_HEXDUMP_FORMAT+).
|
7
|
+
# Uses the +length+ method of +data_source+ to determine byte count and the
|
8
|
+
# +[]+ method with integer range arguments to extract row-sized slices.
|
9
|
+
# +data_source+ being a +String+ instance behaves as expected.
|
10
|
+
#
|
11
|
+
def Baikal::hexdump data_source, port = $stdout, format = Hexdump::DEFAULT_HEXDUMP_FORMAT
|
12
|
+
row = Hexdump::Row.new
|
13
|
+
row.expected_size = format.bytes_per_row
|
14
|
+
# iterate over rows
|
15
|
+
row.offset = 0
|
16
|
+
block_row_counter = 0
|
17
|
+
while row.offset < data_source.bytesize do
|
18
|
+
if format.rows_per_block != 0 then
|
19
|
+
block_row_counter += 1
|
20
|
+
if block_row_counter == format.rows_per_block then
|
21
|
+
port.puts # block separator
|
22
|
+
block_row_counter = 0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
row.data = data_source.unpack "@#{row.offset} C#{format.bytes_per_row}"
|
26
|
+
format.format_row row, port
|
27
|
+
row.offset += format.bytes_per_row
|
28
|
+
end
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
module Hexdump
|
33
|
+
#
|
34
|
+
# Represents the row currently being processed by +hexdump+.
|
35
|
+
#
|
36
|
+
class Row
|
37
|
+
#
|
38
|
+
# Offset of the first byte on this row.
|
39
|
+
#
|
40
|
+
attr_accessor :offset
|
41
|
+
|
42
|
+
#
|
43
|
+
# The row's expected size, as per +Hexdump::Format#bytes_per_row+.
|
44
|
+
#
|
45
|
+
attr_accessor :expected_size
|
46
|
+
|
47
|
+
#
|
48
|
+
# Bytes of this row in an +Array+ instance. For the last row, some of
|
49
|
+
# the trailing elements may be +nil+.
|
50
|
+
#
|
51
|
+
attr_accessor :data
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Represents a particular field of a hexdump format. Abstract class; see
|
56
|
+
# +Field::Offset+, +Field::Decoration+, and +Field::Data+ as practical
|
57
|
+
# hexdump fields.
|
58
|
+
#
|
59
|
+
class Field
|
60
|
+
#
|
61
|
+
# Returns a string representing this field's contribution to dumping
|
62
|
+
# +row+, an instance of +Hexdump::Row+
|
63
|
+
#
|
64
|
+
def format row
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# The +Offset+ field outputs the offset of a hexdump row, formatted
|
69
|
+
# via a printf template.
|
70
|
+
#
|
71
|
+
class Offset < Field
|
72
|
+
def initialize template
|
73
|
+
super()
|
74
|
+
@template = template
|
75
|
+
return
|
76
|
+
end
|
77
|
+
|
78
|
+
def format row
|
79
|
+
return sprintf(@template, row.offset)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# The +Decoration+ field outputs a literal string.
|
85
|
+
#
|
86
|
+
class Decoration < Field
|
87
|
+
def initialize content
|
88
|
+
super()
|
89
|
+
@content = content
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
def format row
|
94
|
+
return @content
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# The +Data+ field outputs data on a hexdump row, formatted using a
|
100
|
+
# supplied +Proc+ instance. +grouping_rules+ are specified as
|
101
|
+
# pairs of group size and separator. All group sizes are processed
|
102
|
+
# in parallel. In group boundaries where multiple grouping rules
|
103
|
+
# would match, only the leftmost one is used.
|
104
|
+
#
|
105
|
+
class Data < Field
|
106
|
+
def initialize formatter, *grouping_rules
|
107
|
+
super()
|
108
|
+
@formatter = formatter
|
109
|
+
@grouping_rules = grouping_rules
|
110
|
+
return
|
111
|
+
end
|
112
|
+
|
113
|
+
def format row
|
114
|
+
output = ""
|
115
|
+
(0 ... row.expected_size).each do |column|
|
116
|
+
if column != 0 then
|
117
|
+
rule = @grouping_rules.detect{|divisor, separator| column % divisor == 0}
|
118
|
+
output << rule[1] if rule
|
119
|
+
end
|
120
|
+
output << @formatter.call(row.data[column])
|
121
|
+
end
|
122
|
+
return output
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Formats the byte as a zero-padded two-digit lowercase hexadecimal number.
|
127
|
+
#
|
128
|
+
# :stopdoc: Unfortunately, RDoc gets confused by thunk constants.
|
129
|
+
LOWERCASE_HEX = proc do |value|
|
130
|
+
if value then
|
131
|
+
raise 'Type mismatch' unless value.is_a? Integer
|
132
|
+
sprintf("%02x", value)
|
133
|
+
else
|
134
|
+
" "
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Formats the byte as a zero-padded two-digit uppercase hexadecimal number.
|
140
|
+
#
|
141
|
+
UPPERCASE_HEX = proc do |value|
|
142
|
+
if value then
|
143
|
+
raise 'Type mismatch' unless value.is_a? Integer
|
144
|
+
sprintf("%02X", value)
|
145
|
+
else
|
146
|
+
" "
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Formats the byte as a zero-padded three-digit octal number.
|
152
|
+
#
|
153
|
+
OCTAL = proc do |value|
|
154
|
+
if value then
|
155
|
+
raise 'Type mismatch' unless value.is_a? Integer
|
156
|
+
sprintf("%03o", value)
|
157
|
+
else
|
158
|
+
" "
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Formats the byte as a space-padded three-digit decimal number.
|
164
|
+
#
|
165
|
+
DECIMAL = proc do |value|
|
166
|
+
if value then
|
167
|
+
raise 'Type mismatch' unless value.is_a? Integer
|
168
|
+
sprintf("%3i", value)
|
169
|
+
else
|
170
|
+
" "
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# Formats the byte as an ASCII character. Nonprintable characters are
|
176
|
+
# replaced by a period.
|
177
|
+
#
|
178
|
+
ASCII = proc do |value|
|
179
|
+
if value then
|
180
|
+
raise 'Type mismatch' unless value.is_a? Integer
|
181
|
+
value >= 0x20 && value <= 0x7E ? value.chr : "."
|
182
|
+
else
|
183
|
+
" "
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
#
|
188
|
+
# Decodes the byte as a Latin-1 character and formats it in UTF-8.
|
189
|
+
# Nonprintable characters are replaced by a period, as in
|
190
|
+
# +ASCII+.
|
191
|
+
#
|
192
|
+
LATIN1 = proc do |value|
|
193
|
+
if value then
|
194
|
+
raise 'Type mismatch' unless value.is_a? Integer
|
195
|
+
if (0x20 .. 0x7E).include? value or (0xA0 .. 0xFF).include? value then
|
196
|
+
[value].pack('U')
|
197
|
+
else
|
198
|
+
"."
|
199
|
+
end
|
200
|
+
else
|
201
|
+
" "
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# :startdoc:
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
#
|
210
|
+
# Describes a textual hexdump format.
|
211
|
+
#
|
212
|
+
class Format
|
213
|
+
attr_reader :rows_per_block # zero indicates no block separation
|
214
|
+
attr_reader :bytes_per_row # zero indicates no group separation
|
215
|
+
attr_reader :fields
|
216
|
+
|
217
|
+
#
|
218
|
+
# Creates a new +Hexdump::Format+ instance with the specified structure.
|
219
|
+
# +bytes_per_row+ specifies the number of bytes to be listed on every
|
220
|
+
# row; +fields+ (a list of +Hexdump::Field+ instances) contains the
|
221
|
+
# formatting rules. +rows_per_block+, if given and nonzero, will cause
|
222
|
+
# an empty line to be printed after every block of that many rows.
|
223
|
+
#
|
224
|
+
def initialize bytes_per_row, fields, rows_per_block = 0
|
225
|
+
super()
|
226
|
+
@bytes_per_row = bytes_per_row
|
227
|
+
@fields = fields
|
228
|
+
@rows_per_block = rows_per_block
|
229
|
+
return
|
230
|
+
end
|
231
|
+
|
232
|
+
#
|
233
|
+
# Formats a given +row+ (an instance of +Hexdump::Row+) according to
|
234
|
+
# formatting rules embodied in this +Hexdump_Format+ instance and
|
235
|
+
# outputs the result into the given +port+
|
236
|
+
#
|
237
|
+
def format_row row, port
|
238
|
+
raise 'Type mismatch' unless row.is_a? Hexdump::Row
|
239
|
+
port.puts @fields.map{|field| field.format(row)}.join('')
|
240
|
+
return
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
#
|
245
|
+
# The default hexdump format uses five-digit offsets (fits up to a
|
246
|
+
# mebibyte of data, which should be enough for everybody) and lists the
|
247
|
+
# content on sixteen bytes per row, both in hexadecimal (grouped by
|
248
|
+
# four bytes) and ASCII-or-dot form.
|
249
|
+
#
|
250
|
+
DEFAULT_HEXDUMP_FORMAT = Format.new(16, [
|
251
|
+
Field::Offset.new("%05X: "),
|
252
|
+
Field::Data.new(Field::Data::UPPERCASE_HEX, [4, ' '], [1, ' ']),
|
253
|
+
Field::Decoration.new(" "),
|
254
|
+
Field::Data.new(Field::Data::ASCII),
|
255
|
+
])
|
256
|
+
end
|
257
|
+
end
|