cordawyn-iso8583 0.1.2
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/AUTHORS +2 -0
- data/CHANGELOG +5 -0
- data/LICENSE +21 -0
- data/README +27 -0
- data/Rakefile +127 -0
- data/TODO +10 -0
- data/lib/berlin.rb +82 -0
- data/lib/bitmap.rb +117 -0
- data/lib/codec.rb +189 -0
- data/lib/exception.rb +4 -0
- data/lib/field.rb +90 -0
- data/lib/fields.rb +148 -0
- data/lib/iso8583.rb +12 -0
- data/lib/message.rb +417 -0
- data/lib/util.rb +94 -0
- data/test/BitmapTests.rb +80 -0
- data/test/message_test.rb +163 -0
- data/test/test_codec.rb +97 -0
- data/test/test_fields.rb +188 -0
- data/test/test_util.rb +32 -0
- metadata +91 -0
data/lib/exception.rb
ADDED
data/lib/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
|
data/lib/fields.rb
ADDED
@@ -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
|
data/lib/iso8583.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
module ISO8583
|
5
|
+
require "field"
|
6
|
+
require "codec"
|
7
|
+
require "fields"
|
8
|
+
require "exception"
|
9
|
+
require "bitmap"
|
10
|
+
require "message"
|
11
|
+
require "util"
|
12
|
+
end
|
data/lib/message.rb
ADDED
@@ -0,0 +1,417 @@
|
|
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
|
+
private
|
195
|
+
|
196
|
+
def body
|
197
|
+
bitmap = Bitmap.new
|
198
|
+
message = ""
|
199
|
+
@values.keys.sort.each do |bmp_num|
|
200
|
+
bitmap.set(bmp_num)
|
201
|
+
enc_value = @values[bmp_num].encode
|
202
|
+
message << enc_value
|
203
|
+
end
|
204
|
+
[ bitmap.to_bytes, message ]
|
205
|
+
end
|
206
|
+
|
207
|
+
def _get_definition(key) #:nodoc:
|
208
|
+
b = self.class._definitions[key]
|
209
|
+
unless b
|
210
|
+
raise ISO8583Exception.new "no definition for field: #{key}"
|
211
|
+
end
|
212
|
+
b
|
213
|
+
end
|
214
|
+
|
215
|
+
# return [mti_num, mti_value] for key being either
|
216
|
+
# mti_num or mti_value
|
217
|
+
def _get_mti_definition(key)
|
218
|
+
num_hash,name_hash = self.class._mti_definitions
|
219
|
+
if num_hash[key]
|
220
|
+
[key, num_hash[key]]
|
221
|
+
elsif name_hash[key]
|
222
|
+
[name_hash[key], key]
|
223
|
+
else
|
224
|
+
raise ISO8583Exception.new("MTI: #{key} not allowed!")
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
class << self
|
229
|
+
|
230
|
+
# Defines how the message type indicator is encoded into bytes.
|
231
|
+
# ===Params:
|
232
|
+
# * field : the decoder/encoder for the MTI
|
233
|
+
# * opts : the options to pass to this field
|
234
|
+
#
|
235
|
+
# === Example
|
236
|
+
# class MyMessage < Message
|
237
|
+
# mti_format N, :length =>4
|
238
|
+
# (...)
|
239
|
+
# end
|
240
|
+
#
|
241
|
+
# encodes the mti of this message using the `N` field (fixed
|
242
|
+
# length, plain ASCII) and sets the fixed lengh to 4 bytes.
|
243
|
+
#
|
244
|
+
# See also: mti
|
245
|
+
def mti_format(field, opts)
|
246
|
+
f = field.dup
|
247
|
+
_handle_opts(f, opts)
|
248
|
+
@mti_format = f
|
249
|
+
end
|
250
|
+
|
251
|
+
# Defines the message types allowed for this type of message and
|
252
|
+
# gives them names
|
253
|
+
#
|
254
|
+
# === Example
|
255
|
+
# class MyMessage < Message
|
256
|
+
# (...)
|
257
|
+
# mti 1100, "Authorization Request Acquirer Gateway"
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# mes = MyMessage.new
|
261
|
+
# mes.mti = 1100 # or mes.mti = "Authorization Request Acquirer Gateway"
|
262
|
+
#
|
263
|
+
# See Also: mti_format
|
264
|
+
def mti(value, name)
|
265
|
+
@mtis_v ||= {}
|
266
|
+
@mtis_n ||= {}
|
267
|
+
@mtis_v[value] = name
|
268
|
+
@mtis_n[name] = value
|
269
|
+
end
|
270
|
+
|
271
|
+
# Define a bitmap in the message
|
272
|
+
# ===Params:
|
273
|
+
# * bmp : bitmap number
|
274
|
+
# * name : human readable form
|
275
|
+
# * field : field for encoding/decoding
|
276
|
+
# * opts : options to pass to the field, e.g. length for fxed len fields.
|
277
|
+
#
|
278
|
+
# ===Example
|
279
|
+
#
|
280
|
+
# class MyMessage < Message
|
281
|
+
# bmp 2, "PAN", LLVAR_N, :max =>19
|
282
|
+
# (...)
|
283
|
+
# end
|
284
|
+
#
|
285
|
+
# creates a class MyMessage that allows for a bitmap 2 which
|
286
|
+
# is named "PAN" and encoded by an LLVAR_N Field. The maximum
|
287
|
+
# length of the value is 19. This class may be used as follows:
|
288
|
+
#
|
289
|
+
# mes = MyMessage.new
|
290
|
+
# mes[2] = 474747474747 # or mes["PAN"] = 4747474747
|
291
|
+
#
|
292
|
+
def bmp(bmp, name, field, opts = nil)
|
293
|
+
@defs ||= {}
|
294
|
+
|
295
|
+
field = field.dup
|
296
|
+
field.name = name
|
297
|
+
field.bmp = bmp
|
298
|
+
_handle_opts(field, opts) if opts
|
299
|
+
|
300
|
+
bmp_def = BMP.new bmp, name, field
|
301
|
+
|
302
|
+
@defs[bmp] = bmp_def
|
303
|
+
@defs[name] = bmp_def
|
304
|
+
end
|
305
|
+
|
306
|
+
# Create an alias to access bitmaps directly using a method.
|
307
|
+
# Example:
|
308
|
+
# class MyMessage < Message
|
309
|
+
# (...)
|
310
|
+
# bmp 2, "PAN", LLVAR_N
|
311
|
+
# (...)
|
312
|
+
# bmp_alias 2, :pan
|
313
|
+
# end #class
|
314
|
+
#
|
315
|
+
# would allow you to access the PAN like this:
|
316
|
+
#
|
317
|
+
# mes.pan = 1234
|
318
|
+
# puts mes.pan
|
319
|
+
#
|
320
|
+
# instead of:
|
321
|
+
#
|
322
|
+
# mes[2] = 1234
|
323
|
+
#
|
324
|
+
def bmp_alias(bmp, aliaz)
|
325
|
+
define_method (aliaz) {
|
326
|
+
bmp_ = @values[bmp]
|
327
|
+
bmp_ ? bmp_.value : nil
|
328
|
+
}
|
329
|
+
|
330
|
+
define_method ("#{aliaz}=") {|value|
|
331
|
+
self[bmp] = value
|
332
|
+
# bmp_def = _get_definition(bmp)
|
333
|
+
# bmp_def.value= value
|
334
|
+
# @values[bmp] = bmp_def
|
335
|
+
}
|
336
|
+
end
|
337
|
+
|
338
|
+
# Parse the bytes `str` returning a message of the defined type.
|
339
|
+
def parse(str)
|
340
|
+
message = self.new
|
341
|
+
message.mti, rest = _mti_format.parse(str)
|
342
|
+
bmp,rest = Bitmap.parse(rest)
|
343
|
+
bmp.each {|bit|
|
344
|
+
bmp_def = _definitions[bit]
|
345
|
+
value, rest = bmp_def.field.parse(rest)
|
346
|
+
message[bit] = value
|
347
|
+
}
|
348
|
+
message
|
349
|
+
end
|
350
|
+
|
351
|
+
# access the mti definitions applicable to the Message
|
352
|
+
#
|
353
|
+
# returns a pair of hashes containing:
|
354
|
+
#
|
355
|
+
# mti_value => mti_name
|
356
|
+
#
|
357
|
+
# mti_name => mti_value
|
358
|
+
#
|
359
|
+
def _mti_definitions
|
360
|
+
[@mtis_v, @mtis_n]
|
361
|
+
end
|
362
|
+
|
363
|
+
# Access the field definitions of this class, this is a
|
364
|
+
# hash containing [bmp_number, BMP] and [bitmap_name, BMP]
|
365
|
+
# pairs.
|
366
|
+
#
|
367
|
+
def _definitions
|
368
|
+
@defs
|
369
|
+
end
|
370
|
+
|
371
|
+
# Returns the field definition to format the mti.
|
372
|
+
def _mti_format
|
373
|
+
@mti_format
|
374
|
+
end
|
375
|
+
|
376
|
+
|
377
|
+
private
|
378
|
+
|
379
|
+
# METHODS starting with an underscore are meant for
|
380
|
+
# internal use only ...
|
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)
|
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
|
401
|
+
attr_accessor :bmp
|
402
|
+
attr_accessor :name
|
403
|
+
attr_accessor :field
|
404
|
+
attr_accessor :value
|
405
|
+
|
406
|
+
def initialize(bmp, name, field)
|
407
|
+
@bmp = bmp
|
408
|
+
@name = name
|
409
|
+
@field = field
|
410
|
+
end
|
411
|
+
|
412
|
+
def encode
|
413
|
+
field.encode(value)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
end
|