iso8583 0.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.
@@ -0,0 +1,5 @@
1
+
2
+ class ISO8583Exception < Exception
3
+ end
4
+ class ISO8583ParseException < ISO8583Exception
5
+ end
@@ -0,0 +1,178 @@
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
+ #
@@ -0,0 +1,130 @@
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
@@ -0,0 +1,9 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require "field"
5
+ require "codec"
6
+ require "fields"
7
+ require "exception"
8
+ require "bitmap"
9
+ require "message"
@@ -0,0 +1,416 @@
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
+