iso8583 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/exception.rb DELETED
@@ -1,5 +0,0 @@
1
-
2
- class ISO8583Exception < Exception
3
- end
4
- class ISO8583ParseException < ISO8583Exception
5
- end
data/lib/field.rb DELETED
@@ -1,178 +0,0 @@
1
- class Field
2
- # may either be some other Field in which the length is encoded or a Fixnum for
3
- # fixed length fields.
4
- attr_accessor :length
5
- attr_accessor :codec
6
- attr_accessor :padding
7
- attr_accessor :max
8
-
9
- attr_writer :name
10
- attr_accessor :bmp
11
-
12
- def name
13
- "BMP #{bmp}: #{@name}(#{self.class.to_s})"
14
- end
15
-
16
- def parse raw
17
- len, raw = case length
18
- when Fixnum
19
- [length, raw]
20
- when Field
21
- length.parse(raw)
22
- else
23
- raise ISO8583Exception.new("How did you manage to fuck up configuration this bad? <-")
24
- end
25
-
26
- raw_value = raw[0,len]
27
-
28
- # make sure we have enough data ...
29
- if raw_value.length != len
30
- mes = "Field has incorrect length! field: #{raw_value} len/expected: #{raw_value.length}/#{len}"
31
- raise ISO8583ParseException.new(mes)
32
- end
33
-
34
- rest = raw[len, raw.length]
35
- real_value = codec.decode(raw_value)
36
- return real_value, rest
37
- end
38
-
39
-
40
- # Encoding needs to consider length representation, the actual encoding (such as charset or BCD)
41
- # and padding.
42
- # The order may be important! This impl calls codec.encode and then pads, in case you need the other
43
- # special treatment, you may need to override this method alltogether.
44
- def encode value
45
- encoded_value = codec.encode(value)
46
-
47
-
48
- if padding
49
- if padding.arity == 1
50
- encoded_value = padding.call(encoded_value)
51
- elsif padding.arity == 2
52
- encoded_value = padding.call(encoded_value, length)
53
- end
54
- end
55
-
56
- len_str = case length
57
- when Fixnum
58
- raise ISO8583Exception.new("Too long: #{value}!") if encoded_value.length > length
59
- raise ISO8583Exception.new("Too short: #{value}!") if encoded_value.length < length
60
- ""
61
- when Field
62
- raise ISO8583Exception.new("Max lenth exceeded: #{value}, max: #{max}") if max && encoded_value.length > max
63
- length.encode(encoded_value.length)
64
- else
65
- raise ISO8583Exception.new("How did you manage to fuck up configuration this bad? ->")
66
- end
67
- return len_str + encoded_value
68
-
69
- end
70
- end
71
-
72
- #
73
- #class Field
74
- # attr_accessor :value
75
- #
76
- # def initialize value=nil
77
- # @value = value
78
- # end
79
- #
80
- # def name
81
- # self.class._name
82
- # end
83
- #
84
- # def to_b
85
- # self.class.to_b @value
86
- # end
87
- #
88
- # def to_s
89
- # "#{name} : #{value}"
90
- # end
91
- #
92
- # class << self
93
- # def name name
94
- # @name = name
95
- # end
96
- # def _name
97
- # @name
98
- # end
99
- # def length len
100
- # @len = len
101
- # end
102
- #
103
- # def _len
104
- # if (@len == nil) && (superclass.respond_to?(:_len))
105
- # return superclass.send(:_len)
106
- # end
107
- # @len
108
- # end
109
- #
110
- # def min min
111
- # @min = min
112
- # end
113
- #
114
- # def max max
115
- # @max = max
116
- # end
117
- # #def padding pad
118
- # # @pad = pad
119
- # #end
120
- # def codec codec
121
- # codec ||= Codec
122
- # @codec = codec
123
- # @codec = codec.new if codec.is_a? Class
124
- # end
125
- #
126
- # # tries to parse the
127
- # def parse raw
128
- # l, rest = _get_len raw
129
- # #puts "len #{l}"
130
- # #puts "rest: #{rest}"
131
- # value = rest[0,l]
132
- #
133
- # unless value.length == l
134
- # raise ISO8583ParseException.new "Wrong length for: #{value} (#{value.length}), expected #{l}"
135
- # end
136
- #
137
- # check(value)
138
- #
139
- #
140
- # rest = rest[l,rest.length]
141
- # real_value = @codec.decode value
142
- # field = self.new real_value
143
- # return field, rest
144
- # end
145
- #
146
- # def to_b value
147
- # enc_value = @codec.encode(value)
148
- # enc_len =
149
- # case _len
150
- # when Fixnum
151
- # enc_value
152
- # when Field
153
- # _len.to_b(enc_value.length)
154
- #
155
- # end
156
- #
157
- # def check value
158
- # if @min && value.length < @min
159
- # raise ISO8583ParseException.new "Wrong length for: #{value} (#{value.length}), min length #{@min}"
160
- # end
161
- #
162
- # if @max && value.length > @max
163
- # raise ISO8583ParseException.new "Wrong length for: #{value} (#{value.length}), max length #{@max}"
164
- # end
165
- # end
166
- #
167
- # def _get_len raw
168
- # return _len, raw if _len.is_a? Fixnum
169
- # puts self.ancestors
170
- # puts _len
171
- # len_field, rest = _len.parse(raw)
172
- # return len_field.value, rest
173
- # end
174
- #
175
- #
176
- # end
177
- #end
178
- #
data/lib/fields.rb DELETED
@@ -1,130 +0,0 @@
1
- #--
2
- # Copyright 2009 by Tim Becker (tim.becker@kuriostaet.de)
3
- # MIT License, for details, see the LICENSE file accompaning
4
- # this distribution
5
- #++
6
- # This file contains a number of preinstantiated Field definitions. You
7
- # will probably need to create own fields in your implementation, please
8
- # see Field and Codec for further discussion on how to do this.
9
- # The fields currently available are those necessary to implement the
10
- # Berlin Groups Authorization Spec.
11
- #
12
- # The following fields are available:
13
- #
14
- # [+LL+] special form to de/encode variable length indicators, two bytes ASCII numerals
15
- # [+LLL+] special form to de/encode variable length indicators, two bytes ASCII numerals
16
- # [+LLVAR_N+] two byte variable length ASCII numeral, payload ASCII numerals
17
- # [+LLLVAR_N+] three byte variable length ASCII numeral, payload ASCII numerals
18
- # [+LLVAR_Z+] two byte variable length ASCII numeral, payload Track2 data
19
- # [+LLVAR_ANS+] two byte variable length ASCII numeral, payload ASCII+special
20
- # [+LLLVAR_ANS+] three byte variable length ASCII numeral, payload ASCII+special
21
- # [+LLVAR_B+] Two byte variable length binary payload
22
- # [+LLLVAR_B+] Three byte variable length binary payload
23
- # [+N+] fixed lengh numerals, repesented in ASCII, padding right justified using zeros
24
- # [+AN+] fixed lengh ASCII [A-Za-z0-9], padding left justified using spaces.
25
- # [+ANP+] fixed lengh ASCII [A-Za-z0-9] and space, padding left, spaces
26
- # [+ANS+] fixed length ASCII [\x20-\x7E], padding left, spaces
27
- # [+B+] binary data, padding left using nulls (0x00)
28
- # [+MMDDhhmmss+] Date, formatted as described in ASCII numerals
29
- # [+YYMMDDhhmmss+] Date, formatted as named in ASCII numerals
30
- # [+YYMM+] Expiration Date, formatted as named in ASCII numerals
31
-
32
-
33
- # Special form to de/encode variable length indicators, two bytes ASCII numerals
34
- LL = Field.new
35
- LL.length = 2
36
- LL.codec = ASCII_Number
37
- LL.padding = lambda { |value|
38
- sprintf("%02d", value)
39
- }
40
- # Special form to de/encode variable length indicators, three bytes ASCII numerals
41
- LLL = Field.new
42
- LLL.length = 3
43
- LLL.codec = ASCII_Number
44
- LLL.padding = lambda { |value|
45
- sprintf("%03d", value)
46
- }
47
-
48
- # Two byte variable length ASCII numeral, payload ASCII numerals
49
- LLVAR_N = Field.new
50
- LLVAR_N.length = LL
51
- LLVAR_N.codec = ASCII_Number
52
-
53
- # Three byte variable length ASCII numeral, payload ASCII numerals
54
- LLLVAR_N = Field.new
55
- LLLVAR_N.length = LLL
56
- LLLVAR_N.codec = ASCII_Number
57
-
58
- # Two byte variable length ASCII numeral, payload Track2 data
59
- LLVAR_Z = Field.new
60
- LLVAR_Z.length = LL
61
- LLVAR_Z.codec = Track2
62
-
63
- # Two byte variable length ASCII numeral, payload ASCII+special
64
- LLVAR_ANS = Field.new
65
- LLVAR_ANS.length= LL
66
- LLVAR_ANS.codec = ANS_Codec
67
-
68
- # Three byte variable length ASCII numeral, payload ASCII+special
69
- LLLVAR_ANS = Field.new
70
- LLLVAR_ANS.length= LLL
71
- LLLVAR_ANS.codec = ANS_Codec
72
-
73
- # Two byte variable length binary payload
74
- LLVAR_B = Field.new
75
- LLVAR_B.length= LL
76
- LLVAR_B.codec = Null_Codec
77
-
78
-
79
- # Three byte variable length binary payload
80
- LLLVAR_B = Field.new
81
- LLLVAR_B.length= LLL
82
- LLLVAR_B.codec = Null_Codec
83
-
84
- # Fixed lengh numerals, repesented in ASCII, padding right justified using zeros
85
- N = Field.new
86
- N.codec = ASCII_Number
87
- N.padding = lambda {|val, len|
88
- sprintf("%0#{len}d", val)
89
- }
90
-
91
- PADDING_LEFT_JUSTIFIED_SPACES = lambda {|val, len|
92
- sprintf "%-#{len}s", val
93
- }
94
-
95
- # Fixed lengh ASCII [A-Za-z0-9], padding left justified using spaces.
96
- AN = Field.new
97
- AN.codec = AN_Codec
98
- AN.padding = PADDING_LEFT_JUSTIFIED_SPACES
99
-
100
- # Fixed lengh ASCII [A-Za-z0-9] and space, padding left, spaces
101
- ANP = Field.new
102
- ANP.codec = ANP_Codec
103
- ANP.padding = PADDING_LEFT_JUSTIFIED_SPACES
104
-
105
- # Fixed length ASCII [\x20-\x7E], padding left, spaces
106
- ANS = Field.new
107
- ANS.codec = ANS_Codec
108
- ANS.padding = PADDING_LEFT_JUSTIFIED_SPACES
109
-
110
- # Binary data, padding left using nulls (0x00)
111
- B = Field.new
112
- B.codec = Null_Codec
113
- B.padding = lambda {|val, len|
114
- while val.length < len
115
- val = val + "\000"
116
- end
117
- val
118
- }
119
-
120
- # Date, formatted as described in ASCII numerals
121
- MMDDhhmmss = Field.new
122
- MMDDhhmmss.codec = MMDDhhmmssCodec
123
-
124
- #Date, formatted as described in ASCII numerals
125
- YYMMDDhhmmss = Field.new
126
- YYMMDDhhmmss.codec = YYMMDDhhmmssCodec
127
-
128
- #Date, formatted as described in ASCII numerals
129
- YYMM = Field.new
130
- YYMM.codec = YYMMCodec
data/lib/message.rb DELETED
@@ -1,416 +0,0 @@
1
- # Copyright 2009 by Tim Becker (tim.becker@kuriostaet.de)
2
- # MIT License, for details, see the LICENSE file accompaning
3
- # this distribution
4
-
5
- # The class `Message` defines functionality to describe classes
6
- # representing different type of messages, or message families.
7
- # A message family consists of a number of possible message types that
8
- # are allowed, and a way of naming and encoding the bitmaps allowed in
9
- # the messages.
10
- #
11
- # To create your own message, start by subclassing Message:
12
- #
13
- # class MyMessage < Message
14
- # (...)
15
- # end
16
- #
17
- # the subtyped message should be told how the MTI is encoded:
18
- #
19
- # class MyMessage < Message
20
- # mti_format N, :length => 4
21
- # (...)
22
- # end
23
- #
24
- # `N` above is an instance of Field which encodes numbers into their
25
- # ASCII representations in a fixed length field. The option `length=>4`
26
- # indicates the length of the fixed field.
27
- #
28
- # Next, the allowed message types are specified:
29
- #
30
- # class MyMessage < Message
31
- # (...)
32
- # mti 1100, "Authorization Request Acquirer Gateway"
33
- # mti 1110, "Authorization Request Response Issuer Gateway"
34
- # (...)
35
- # end
36
- #
37
- # This basically defines to message types, 1100 and 1110 which may
38
- # be accessed later either via their name or value:
39
- #
40
- # mes = MyMessage.new 1100
41
- #
42
- # or
43
- # mes = MyMessage.new "Authorization Request Acquirer Gateway"
44
- #
45
- # or
46
- # mes = MyMessage.new
47
- # mes.mti = 1110 # or Auth. Req. Acq. Gateway ...
48
- #
49
- # Finally the allowed bitmaps, their names and the encoding rules are
50
- # specified:
51
- #
52
- # class MyMessage < Message
53
- # (...)
54
- # bmp 2, "Primary Account Number (PAN)", LLVAR_N, :max => 19
55
- # bmp 3, "Processing Code", N, :length => 6
56
- # bmp 4, "Amount (Transaction)", N, :length => 12
57
- # bmp 6, "Amount, Cardholder Billing" , N, :length => 12
58
- # (...)
59
- # end
60
- #
61
- # The example above defines four bitmaps (2,3,4 and 6), and provides
62
- # their bitmap number and description. The PAN field is variable length
63
- # encoded (LL length indicator, ASCII, contents numeric, ASCII) and the
64
- # maximum length of the field is limited to 19 using options.
65
- #
66
- # The other fields are fixed length numeric ASCII fields (the length of the fields is
67
- # indicated by the `:length` options.)
68
- #
69
- # This message may be used as follows in order to interpret a received message.:
70
- #
71
- # mes = MyMessage.parse inputData
72
- # puts mes[2] # prints the PAN from the message.
73
- #
74
- # Constructing own messages works as follows:
75
- #
76
- # mes = MyMessage.new 1100
77
- # mes[2]= 474747474747
78
- # # Alternatively
79
- # mes["Primary Account Number (PAN)"]= 4747474747
80
- # mes[3] = 1234 # padding is added by the Field en/decoder
81
- # mes["Amount (Transaction)"] = 100
82
- # mes[6] = 200
83
- #
84
- # the convenience method bmp_alias may be used in defining the class in
85
- # order to provide direct access to fields using methods:
86
- #
87
- # class MyMessage < Message
88
- # (...)
89
- # bmp 2, "Primary Account Number (PAN)", LLVAR_N, :max => 19
90
- # (...)
91
- # bmp_alias 2, :pan
92
- # end
93
- #
94
- # this allows accessing fields in the following manner:
95
- #
96
- # mes = MyMessage.new 1100
97
- # mes.pan = 474747474747
98
- # puts mes.pan
99
- # # Identical functionality to:
100
- # mes[2]= 474747474747
101
- # # or:
102
- # mes["Primary Account Number (PAN)"]= 4747474747
103
- #
104
- # Most of the work in implementing a new set of message type lays in
105
- # figuring out the correct fields to use defining the Message class via
106
- # bmp.
107
- #
108
-
109
-
110
- class Message
111
-
112
- # The value of the MTI (Message Type Indicator) of this message.
113
- attr_reader :mti
114
-
115
- # Instantiate a new instance of this type of Message
116
- # optionally specifying an mti.
117
- def initialize mti=nil
118
- # values is an internal field used to collect all the
119
- # bmp number | bmp name | field en/decoders | values
120
- # which are set in this message.
121
- @values = {}
122
- self.mti= mti if mti
123
- end
124
-
125
- # Set the mti of the Message using either the actual value
126
- # or the name of the message type that was defined using
127
- # Message.mti
128
- #
129
- # === Example
130
- # class MyMessage < Message
131
- # (...)
132
- # mti 1100, "Authorization Request Acquirer Gateway"
133
- # end
134
- #
135
- # mes = MyMessage.new
136
- # mes.mti = 1100 # or mes.mti = "Authorization Request Acquirer Gateway"
137
-
138
- def mti= value
139
- num, name = _get_mti_definition(value)
140
- @mti = num
141
- end
142
-
143
- # Set a field in this message, `key` is either the
144
- # bmp number or it's name.
145
- # ===Example
146
- #
147
- # mes = BlaBlaMessage.new
148
- # mes[2]=47474747 # bmp 2 is generally the PAN
149
- # mes["Primary Account Number"]=47474747 # if thats what you called the field in Message.bmp.
150
- def []= key, value
151
- bmp_def = _get_definition key
152
- bmp_def.value = value
153
- @values[bmp_def.bmp] = bmp_def
154
- end
155
-
156
- # Retrieve the decoded value of the contents of a bitmap
157
- # described either by the bitmap number or name.
158
- #
159
- # ===Example
160
- #
161
- # mes = BlaBlaMessage.parse someMessageBytes
162
- # mes[2] # bmp 2 is generally the PAN
163
- # mes["Primary Account Number"] # if thats what you called the field in Message.bmp.
164
- def [] key
165
- bmp_def = _get_definition key
166
- bmp = @values[bmp_def.bmp]
167
- bmp ? bmp.value : nil
168
- end
169
-
170
- # Retrieve the byte representation of the bitmap.
171
- def to_b
172
- raise ISO8583Exception.new "no MTI set!" unless mti
173
- mti_enc = self.class._mti_format.encode(mti)
174
- bitmap = Bitmap.new
175
- message = ""
176
- @values.keys.sort.each{|bmp_num|
177
- bitmap.set(bmp_num)
178
- enc_value = @values[bmp_num].encode
179
- message << enc_value
180
- }
181
- mti_enc+bitmap.to_bytes + message
182
- end
183
-
184
- # Returns a nicely formatted representation of this
185
- # message.
186
- def to_s
187
- _mti_name = _get_mti_definition(mti)[1]
188
- str = "MTI:#{mti} (#{_mti_name})\n\n"
189
- _max = @values.values.max {|a,b|
190
- a.name.length <=> b.name.length
191
- }
192
- _max_name = _max.name.length
193
-
194
- @values.keys.sort.each{|bmp_num|
195
- _bmp = @values[bmp_num]
196
- str += ("%03d %#{_max_name}s : %s\n" % [bmp_num, _bmp.name, _bmp.value])
197
- }
198
- str
199
- end
200
-
201
- def _get_definition key #:nodoc:
202
- b = self.class._definitions[key]
203
- unless b
204
- raise ISO8583Exception.new "no definition for field: #{key}"
205
- end
206
- b
207
- end
208
-
209
- # return [mti_num, mti_value] for key being either
210
- # mti_num or mti_value
211
- def _get_mti_definition key #:nodoc:
212
- num_hash,name_hash = self.class._mti_definitions
213
- if num_hash[key]
214
- [key, num_hash[key]]
215
- elsif name_hash[key]
216
- [name_hash[key], key]
217
- else
218
- raise ISO8583Exception.new("MTI: #{key} not allowed!")
219
- end
220
-
221
- end
222
-
223
- class << self
224
-
225
- # Defines how the message type indicator is encoded into bytes.
226
- # ===Params:
227
- # * field : the decoder/encoder for the MTI
228
- # * opts : the options to pass to this field
229
- #
230
- # === Example
231
- # class MyMessage < Message
232
- # mti_format N, :length =>4
233
- # (...)
234
- # end
235
- #
236
- # encodes the mti of this message using the `N` field (fixed
237
- # length, plain ASCII) and sets the fixed lengh to 4 bytes.
238
- #
239
- # See also: mti
240
- def mti_format field, opts
241
- f = field.dup
242
- _handle_opts(f, opts)
243
- @mti_format = f
244
- end
245
-
246
- # Defines the message types allowed for this type of message and
247
- # gives them names
248
- #
249
- # === Example
250
- # class MyMessage < Message
251
- # (...)
252
- # mti 1100, "Authorization Request Acquirer Gateway"
253
- # end
254
- #
255
- # mes = MyMessage.new
256
- # mes.mti = 1100 # or mes.mti = "Authorization Request Acquirer Gateway"
257
- #
258
- # See Also: mti_format
259
-
260
- def mti value, name
261
- @mtis_v ||= {}
262
- @mtis_n ||= {}
263
- @mtis_v[value] = name
264
- @mtis_n[name] = value
265
- end
266
-
267
- #
268
- # Define a bitmap in the message
269
- # ===Params:
270
- # * bmp : bitmap number
271
- # * name : human readable form
272
- # * field : field for encoding/decoding
273
- # * opts : options to pass to the field, e.g. length for fxed len fields.
274
- #
275
- # ===Example
276
- #
277
- # class MyMessage < Message
278
- # bmp 2, "PAN", LLVAR_N, :max =>19
279
- # (...)
280
- # end
281
- #
282
- # creates a class MyMessage that allows for a bitmap 2 which
283
- # is named "PAN" and encoded by an LLVAR_N Field. The maximum
284
- # length of the value is 19. This class may be used as follows:
285
- #
286
- # mes = MyMessage.new
287
- # mes[2] = 474747474747 # or mes["PAN"] = 4747474747
288
- #
289
- #
290
- def bmp bmp, name, field, opts=nil
291
- @defs ||= {}
292
-
293
- field = field.dup
294
- field.name = name
295
- field.bmp = bmp
296
- _handle_opts(field, opts) if opts
297
-
298
- bmp_def = BMP.new bmp, name, field
299
-
300
- @defs[bmp] = bmp_def
301
- @defs[name] = bmp_def
302
- end
303
-
304
- # Create an alias to access bitmaps directly using a method.
305
- # Example:
306
- # class MyMessage < Message
307
- # (...)
308
- # bmp 2, "PAN", LLVAR_N
309
- # (...)
310
- # bmp_alias 2, :pan
311
- # end #class
312
- #
313
- # would allow you to access the PAN like this:
314
- #
315
- # mes.pan = 1234
316
- # puts mes.pan
317
- #
318
- # instead of:
319
- #
320
- # mes[2] = 1234
321
- #
322
- def bmp_alias bmp, aliaz
323
- define_method (aliaz) {
324
- bmp_ = @values[bmp]
325
- bmp_ ? bmp_.value : nil
326
- }
327
-
328
- define_method ("#{aliaz}=") { |value|
329
- self[bmp]=value
330
- #bmp_def = _get_definition(bmp)
331
- #bmp_def.value= value
332
- #@values[bmp] = bmp_def
333
- }
334
- end
335
-
336
- # Parse the bytes `str` returning a message of the defined type.
337
- def parse str
338
- message = self.new
339
- message.mti, rest = _mti_format.parse str
340
- bmp,rest = Bitmap.parse(rest)
341
- bmp.each {|bit|
342
- bmp_def = _definitions[bit]
343
- value, rest = bmp_def.field.parse(rest)
344
- message[bit] = value
345
- }
346
- return message
347
- end
348
-
349
-
350
- # METHODS starting with an underscore are meant for
351
- # internal use only ...
352
-
353
- #
354
- # Access the field definitions of this class, this is a
355
- # hash containing [bmp_number, BMP] and [bitmap_name, BMP]
356
- # pairs.
357
- #
358
- def _definitions #:nodoc:
359
- @defs
360
- end
361
-
362
- #
363
- # access the mti definitions applicable to the Message
364
- #
365
- # returns a pair of hashes containing:
366
- #
367
- # mti_value => mti_name
368
- #
369
- # mti_name => mti_value
370
- #
371
- def _mti_definitions #:nodoc:
372
- [@mtis_v, @mtis_n]
373
- end
374
-
375
-
376
- # Returns the field definition to format the mti.
377
- def _mti_format #:nodoc:
378
- @mti_format
379
- end
380
-
381
- #
382
- # Modifies the field definitions of the fields passed
383
- # in through the `bmp` and `mti_format` class methods.
384
- #
385
- def _handle_opts field, opts #:nodoc:
386
- opts.each_pair {|key, value|
387
- key = (key.to_s+"=").to_sym
388
- if field.respond_to? key
389
- field.send(key, value)
390
- else
391
- warn "unknown option #{key} for #{field.name}"
392
- end
393
- }
394
- end
395
- end
396
- end
397
-
398
- # Internal class used to tie together name, bitmap number, field en/decoder
399
- # and the value of the corresponding field
400
- class BMP #:nodoc:
401
-
402
- attr_accessor :bmp
403
- attr_accessor :name
404
- attr_accessor :field
405
- attr_accessor :value
406
-
407
- def initialize bmp, name, field
408
- @bmp = bmp
409
- @name = name
410
- @field = field
411
- end
412
- def encode
413
- field.encode(value)
414
- end
415
- end
416
-