iso8583 0.1.1 → 0.1.3

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