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.
- data/AUTHORS +1 -0
- data/CHANGELOG +0 -0
- data/LICENSE +21 -0
- data/README +44 -0
- data/Rakefile +131 -0
- data/TODO +10 -0
- data/lib/berlin.rb +79 -0
- data/lib/bitmap.rb +117 -0
- data/lib/codec.rb +173 -0
- data/lib/exception.rb +5 -0
- data/lib/field.rb +178 -0
- data/lib/fields.rb +130 -0
- data/lib/iso8583.rb +9 -0
- data/lib/message.rb +416 -0
- data/test/BitmapTests.rb +68 -0
- data/test/message_test.rb +124 -0
- data/test/test_codec.rb +90 -0
- data/test/test_fields.rb +164 -0
- metadata +70 -0
data/lib/exception.rb
ADDED
data/lib/field.rb
ADDED
@@ -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
|
+
#
|
data/lib/fields.rb
ADDED
@@ -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
|
data/lib/iso8583.rb
ADDED
data/lib/message.rb
ADDED
@@ -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
|
+
|