iso8583 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS CHANGED
@@ -1 +1,2 @@
1
1
  Tim Becker (tim@kuriositaet.de)
2
+ Slava Kravchenko (cordawyn@gmail.com)
data/CHANGELOG CHANGED
@@ -0,0 +1,10 @@
1
+ 0.1.3
2
+ ==========
3
+ bugfix, field definitions not duplicated
4
+ THANKS: Toni Lyytikäinen
5
+ 0.1.2
6
+ ==========
7
+ branched from original 0.1.1;
8
+ cleaned-up files;
9
+ put all stuff into ISO8583 namespace (module)
10
+ rearrange files under `lib` dir
data/README CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- = ISO 8583 Financial Messaging for Ruby
2
+ = ISO 8583 Financial Messaging for Ruby
3
3
 
4
4
  This package currently contains code for coding an decoding ISO 8583
5
5
  Financial Message.
@@ -12,38 +12,16 @@ class you need to use ...
12
12
 
13
13
  == Installing
14
14
 
15
- You can install the +hello+ package by executing:
15
+ You can install the +iso8583+ package by executing:
16
16
 
17
- gem install iso7816 -r
18
-
19
- alternatively, you can download +.tar.gz+ or +.zip+ archives from
20
- Rubyforge[http://rubyforge.org/frs/?group_id=1791].
17
+ gem install iso8583
21
18
 
22
19
  == Source
23
20
 
24
- The source is most readily available on github[http://github.com/a2800276/8583/],
25
- and there should usually be a more or less current version available
26
- in the Rubyforge SVN repository.
27
-
21
+ The source is most readily available on github[http://github.com/cordawyn/8583].
28
22
 
29
23
  == Mailing List
30
24
 
31
25
  In case you discover bugs, spelling errors, offer suggestions for
32
26
  improvements or would like to help out with the project, you can contact
33
- me directly (tim@kuriositaet.de).
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
- =
27
+ me directly (tim@kuriositaet.de).
data/Rakefile CHANGED
@@ -7,21 +7,21 @@ require "rubygems"
7
7
  # Some definitions that you'll need to edit in case you reuse this
8
8
  # Rakefile for your own project.
9
9
 
10
- SHORTNAME ='iso8583' # this should be the rubyforge project name
11
- DESC ="Ruby implementation of ISO 8583 financial messaging"
12
- PKG_VERSION ='0.1.1'
10
+ SHORTNAME = "iso8583" # this should be the rubyforge project name
11
+ DESC = "Ruby implementation of ISO 8583 financial messaging"
12
+ PKG_VERSION = "0.1.3"
13
13
  LONG_DESC = <<END_DESC
14
- Ruby implementation of ISO 8583 financial messaging
14
+ Ruby implementation of ISO 8583 financial messaging
15
15
  END_DESC
16
- RUBYFORGE_USER ='a2800276'
16
+ RUBYFORGE_USER = "a2800276"
17
17
 
18
18
  # Specifies the default task to execute. This is often the "test" task
19
19
  # and we'll change things around as soon as we have some tests.
20
20
 
21
- task :default => [:rdoc]
21
+ task :default => [:test]
22
22
 
23
23
  # The directory to generate +rdoc+ in.
24
- RDOC_DIR="doc/html"
24
+ RDOC_DIR = "doc/html"
25
25
 
26
26
  # This global variable contains files that will be erased by the `clean` task.
27
27
  # The `clean` task itself is automatically generated by requiring `rake/clean`.
@@ -34,32 +34,32 @@ CLEAN << RDOC_DIR << "pkg"
34
34
  # task called `rdoc`.
35
35
 
36
36
  Rake::RDocTask.new do |rd|
37
- # Options for documenation generation are specified inside of
38
- # this block. For example the following line specifies that the
39
- # content of the README file should be the main page of the
40
- # documenation.
41
- rd.main = "README"
42
-
43
- # The following line specifies all the files to extract
44
- # documenation from.
45
- rd.rdoc_files.include( "README", "AUTHORS", "LICENSE", "TODO",
46
- "CHANGELOG", "bin/**/*", "lib/**/*.rb",
47
- "examples/**/*rb", "doc/*.rdoc")
48
- # This one specifies the output directory ...
49
- rd.rdoc_dir = "doc/html"
50
-
51
- # Or the HTML title of the generated documentation set.
52
- rd.title = "#{SHORTNAME}: #{DESC}"
53
-
54
- # These are options specifiying how source code inlined in the
55
- # documentation should be formatted.
56
-
57
- rd.options = ["--line-numbers", "--inline-source"]
58
-
59
- # Check:
60
- # `rdoc --help` for more rdoc options
61
- # the {rdoc documenation home}[http://www.ruby-doc.org/stdlib/libdoc/rdoc/rdoc/index.html]
62
- # or the documentation for the +Rake::RDocTask+ task[http://rake.rubyforge.org/classes/Rake/RDocTask.html]
37
+ # Options for documenation generation are specified inside of
38
+ # this block. For example the following line specifies that the
39
+ # content of the README file should be the main page of the
40
+ # documenation.
41
+ rd.main = "README"
42
+
43
+ # The following line specifies all the files to extract
44
+ # documenation from.
45
+ rd.rdoc_files.include("README", "AUTHORS", "LICENSE", "TODO",
46
+ "CHANGELOG", "bin/**/*", "lib/**/*.rb",
47
+ "examples/**/*rb", "doc/*.rdoc")
48
+ # This one specifies the output directory ...
49
+ rd.rdoc_dir = "doc/html"
50
+
51
+ # Or the HTML title of the generated documentation set.
52
+ rd.title = "#{SHORTNAME}: #{DESC}"
53
+
54
+ # These are options specifiying how source code inlined in the
55
+ # documentation should be formatted.
56
+
57
+ rd.options = ["--line-numbers", "--inline-source"]
58
+
59
+ # Check:
60
+ # `rdoc --help` for more rdoc options
61
+ # the {rdoc documenation home}[http://www.ruby-doc.org/stdlib/libdoc/rdoc/rdoc/index.html]
62
+ # or the documentation for the +Rake::RDocTask+ task[http://rake.rubyforge.org/classes/Rake/RDocTask.html]
63
63
  end
64
64
 
65
65
  # The GemPackageTask facilitates getting all your files collected
@@ -68,30 +68,29 @@ end
68
68
 
69
69
  # First you'll need to assemble a gemspec
70
70
 
71
- PKG_FILES = FileList['lib/**/*.rb', 'bin/**/*', 'examples/**/*', '[A-Z]*', 'test/**/*'].to_a
71
+ PKG_FILES = FileList["lib/**/*.rb", "bin/**/*", "examples/**/*", "[A-Z]*", "test/**/*"].to_a
72
72
 
73
73
  spec = Gem::Specification.new do |s|
74
- s.platform = Gem::Platform::RUBY
75
- s.summary = "#{SHORTNAME}: #{DESC}"
76
- s.name = SHORTNAME
77
- s.rubyforge_project = SHORTNAME
78
- s.version = PKG_VERSION
79
- s.files = PKG_FILES
80
- s.requirements << "none"
81
- s.require_path = 'lib'
82
- s.description = LONG_DESC
83
- s.has_rdoc=true
84
- s.author="Tim Becker"
85
- s.email="tim.becker@kuriositaet.de"
86
- s.homepage="http://iso8583.rubyforge.org/"
87
-
74
+ s.platform = Gem::Platform::RUBY
75
+ s.summary = "#{SHORTNAME}: #{DESC}"
76
+ s.name = SHORTNAME
77
+ s.rubyforge_project = SHORTNAME
78
+ s.version = PKG_VERSION
79
+ s.files = PKG_FILES
80
+ s.requirements << "none"
81
+ s.require_path = "lib"
82
+ s.description = LONG_DESC
83
+ s.has_rdoc = true
84
+ s.authors = ["Tim Becker", "Slava Kravchenko"]
85
+ s.email = ["tim.becker@kuriositaet.de","cordawyn@gmail.com"]
86
+ s.homepage = "http://github.com/a2800276/8583/"
88
87
  end
89
88
 
90
89
  # Adding a new GemPackageTask adds a task named `package`, which generates
91
90
  # packages as gems, tarball and zip archives.
92
91
  Rake::GemPackageTask.new(spec) do |pkg|
93
- pkg.need_zip = true
94
- pkg.need_tar_gz = true
92
+ pkg.need_zip = true
93
+ pkg.need_tar_gz = true
95
94
  end
96
95
 
97
96
 
@@ -105,7 +104,7 @@ end
105
104
  # Windows. I'm currently not aware of any pure ruby way to do scp
106
105
  # transfers.
107
106
 
108
- RubyForgeProject=SHORTNAME
107
+ RubyForgeProject = SHORTNAME
109
108
 
110
109
  desc "Upload the web pages to the web."
111
110
  task :upload_pages => ["rdoc"] do
@@ -119,13 +118,10 @@ end
119
118
  # This task will run the unit tests provided in files called
120
119
  # `test/test*.rb`. The task itself can be run with a call to `rake test`
121
120
 
122
- Rake::TestTask.new do |t|
123
- t.libs << "test"
124
- t.libs << "lib"
125
- t.ruby_opts=["-rubygems"]
126
- t.test_files = FileList['test/*.rb']
127
- t.verbose = true
121
+ Rake::TestTask.new do |t|
122
+ t.libs << "test"
123
+ t.libs << "lib"
124
+ t.ruby_opts = ["-rubygems"]
125
+ t.test_files = FileList["test/*.rb"]
126
+ t.verbose = true
128
127
  end
129
-
130
-
131
-
data/lib/8583.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 "8583/field"
6
+ require "8583/codec"
7
+ require "8583/fields"
8
+ require "8583/exception"
9
+ require "8583/bitmap"
10
+ require "8583/message"
11
+ require "8583/util"
12
+ end
@@ -0,0 +1,82 @@
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
+ require 'lib/iso8583'
6
+
7
+
8
+ # Example of a protocol specification based on:
9
+ # http://www.berlin-group.org/documents/BG_Authorisation_3.0.pdf
10
+ # The Berlin Groups Authorisation Interface specification.
11
+ # No gurantees are made that this is an accurate implemenation.
12
+ # It currently serves as an example only.
13
+
14
+ module ISO8583
15
+
16
+ class BerlinMessage < Message
17
+ mti_format N, :length => 4
18
+ mti 1100, "Authorization Request Acquirer Gateway"
19
+ mti 1110, "Authorization Request Response Issuer Gateway"
20
+ mti 1420, "Reversal Advice Acquirer Gateway"
21
+ mti 1421, "Reversal Advice Repeat Acquirer Gateway"
22
+ mti 1430, "Reversal Advice Response Issuer Gateway"
23
+ mti 1804, "Network Management Request Acquirer Gateway or Issuer Gateway"
24
+ mti 1814, "Network Management Request Response Issuer Gateway or Acquirer Gateway"
25
+
26
+ bmp 2, "Primary Account Number (PAN)", LLVAR_N, :max => 19
27
+ bmp 3, "Processing Code", N, :length => 6
28
+ bmp 4, "Amount (Transaction)", N, :length => 12
29
+ bmp 6, "Amount, Cardholder Billing" , N, :length => 12
30
+ bmp 7, "Date and Time, Transmission" , MMDDhhmmss
31
+ bmp 10, "Conversion Rate, Cardholder Billing", N, :length => 8
32
+ bmp 11, "System Trace Audit Number (STAN)", N, :length => 6
33
+ bmp 12, "Date and Time, Local Transaction", YYMMDDhhmmss
34
+ bmp 14, "Date, Expiration", YYMM
35
+ bmp 22, "POS Data Code", AN, :length => 12
36
+ bmp 23, "Card Sequence Number", N, :length => 3
37
+ bmp 24, "Function Code", N, :length => 3
38
+ bmp 25, "Message Reason Code", N, :length => 4
39
+ bmp 26, "Card Acceptor Business Code", N, :length => 4
40
+ bmp 30, "Amounts, Original", N, :length => 24
41
+ bmp 32, "Acquiring Institution Identification Code", LLVAR_N, :max => 11
42
+ bmp 35, "Track 2 Data", LLVAR_Z, :max => 37
43
+ bmp 37, "Retrieval Reference Number", ANP, :length => 12
44
+ bmp 38, "Approval Code", ANP, :length => 6
45
+ bmp 39, "Action Code", N, :length => 3
46
+ bmp 41, "Card Acceptor Terminal Identification", ANS, :length => 8
47
+ bmp 42, "Card Acceptor Identification Code", ANS, :length => 15
48
+ bmp 43, "Card Acceptor Name/Location", LLVAR_ANS, :max => 56
49
+ bmp 49, "Currency Code, Transaction", N, :length => 3
50
+ bmp 51, "Currency Code, Cardholder Billing", N, :length => 3
51
+ bmp 52, "Personal Identification Number (PIN) Data", B, :length => 8
52
+ bmp 53, "Security Related Control Information", LLVAR_B, :max => 48
53
+ bmp 54, "Amounts, Additional", LLLVAR_ANS,:max => 40
54
+
55
+ bmp 55, "Integrated Circuit Card (ICC) System Related Data", LLLVAR_B, :max => 255
56
+ bmp 56, "Original Data Elements", LLVAR_N, :max => 35
57
+ bmp 58, "Authorizing Agent Institution Identification Code", LLVAR_N, :max => 11
58
+ bmp 59, "Additional Data - Private", LLLVAR_ANS, :max => 67
59
+ bmp 64, "Message Authentication Code (MAC) Field", B, :length => 8
60
+
61
+ bmp_alias 2, :pan
62
+ bmp_alias 3, :proc_code
63
+ bmp_alias 4, :amount
64
+ bmp_alias 12, :exp_date
65
+ end
66
+
67
+ end
68
+
69
+ if __FILE__==$0
70
+ mes = ISO8583::BerlinMessage.new
71
+ mes.mti = 1110
72
+ mes[2] = 474747474747
73
+ mes["Processing Code"] = "123456"
74
+
75
+ pan = mes["Primary Account Number (PAN)"]
76
+ #mes.pan = 47474747474747
77
+
78
+ #puts mes.pan
79
+ puts mes.to_b
80
+ puts mes.to_s
81
+ #mes2 = BerlinMessage.parse input
82
+ end
@@ -0,0 +1,117 @@
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
+ # This class constructs an object for handling bitmaps
8
+ # with which ISO8583 messages typically begin.
9
+ # Bitmaps are either 8 or 16 bytes long, an extended length
10
+ # bitmap is indicated by the first bit being set.
11
+ # In all likelyhood, you won't be using this class much, it's used
12
+ # transparently by the Message class.
13
+ class Bitmap
14
+
15
+ # create a new Bitmap object. In case an iso message
16
+ # is passed in, that messages bitmap will be parsed. If
17
+ # not, this initializes and empty bitmap.
18
+ def initialize(message = nil)
19
+ @bmp = Array.new(128, false)
20
+ if !message
21
+
22
+ else
23
+ initialize_from_message message
24
+ end
25
+ end
26
+
27
+ # yield once with the number of each set field.
28
+ def each #:yields: each bit set in the bitmap.
29
+ @bmp.each_with_index {|set, i| yield i+1 if set}
30
+ end
31
+
32
+ # Returns whether the bit is set or not.
33
+ def [](i)
34
+ @bmp[i-1]
35
+ end
36
+
37
+ # Set the bit to the indicated value. Only `true` sets the
38
+ # bit, any other value unsets it.
39
+ def []=(i, value)
40
+ if i > 128
41
+ raise ISO8583Exception.new("Bits > 128 are not permitted.")
42
+ elsif i < 2
43
+ raise ISO8583Exception.new("Bits < 2 are not permitted (continutation bit is set automatically)")
44
+ end
45
+ @bmp[i-1] = (value == true)
46
+ end
47
+
48
+ # Sets bit #i
49
+ def set(i)
50
+ self[i] = true
51
+ end
52
+
53
+ # Unsets bit #i
54
+ def unset(i)
55
+ self[i] = false
56
+ end
57
+
58
+ # Generate the bytes representing this bitmap.
59
+ def to_bytes
60
+ arr = [self.to_s]
61
+ # tricky and ugly, setting bit[1] only when generating to_s...
62
+ count = self[1] ? 128 : 64
63
+ arr.pack("B#{count}")
64
+ end
65
+ alias_method :to_b, :to_bytes
66
+
67
+ # Generate a String representation of this bitmap in the form:
68
+ # 01001100110000011010110110010100100110011000001101011011001010
69
+ def to_s
70
+ #check whether any `high` bits are set
71
+ @bmp[0] = false
72
+ 65.upto(128) {|i|
73
+ if self[i]
74
+ # if so, set continuation bit
75
+ @bmp[0] = true
76
+ break
77
+ end
78
+ }
79
+ str = ""
80
+ 1.upto(self[1] ? 128 : 64) {|i|
81
+ str << (self[i] ? "1" : "0")
82
+ }
83
+ str
84
+ end
85
+
86
+
87
+ private
88
+
89
+ def initialize_from_message(message)
90
+ bmp = message.unpack("B64")[0]
91
+ if bmp[0,1] == "1"
92
+ bmp = message.unpack("B128")[0]
93
+ end
94
+
95
+ 0.upto(bmp.length-1) do |i|
96
+ @bmp[i] = (bmp[i,1] == "1")
97
+ end
98
+ end
99
+
100
+ class << self
101
+ # Parse the bytes in string and return the Bitmap and bytes remaining in `str`
102
+ # after the bitmap is taken away.
103
+ def parse(str)
104
+ bmp = Bitmap.new(str)
105
+ rest = bmp[1] ? str[16, str.length] : str[8, str.length]
106
+ [ bmp, rest ]
107
+ end
108
+ end
109
+
110
+ end
111
+ end
112
+
113
+ if __FILE__==$0
114
+ mp = ISO8583::Bitmap.new
115
+ 20.step(128,7) {|i| mp.set(i)}
116
+ print mp.to_bytes
117
+ end
data/lib/8583/codec.rb ADDED
@@ -0,0 +1,189 @@
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
+ require 'date'
6
+
7
+ module ISO8583
8
+
9
+ # Codec provides functionality to encode and decode values, codecs are
10
+ # used internally by Field instances in order to do character conversions
11
+ # and checking for proper values.
12
+ # Although they are used internally, you will probably need to write
13
+ # your own Codec sooner or later. The codecs used by Field instances are
14
+ # typically instances of Codec, it may or may not be usefull to subclass
15
+ # Codec.
16
+ #
17
+ # Say, for example, a text field needs to be encoded in EBCDIC in the
18
+ # message, this is how a corresponding codec would be constructed:
19
+ #
20
+ # EBCDIC_Codec = Codec.new
21
+ # EBCDIC_Codec.encoder = lambda {|ascii_str|
22
+ # raise ISO8583Exception.new("String (#{ascii_str})not valid!") unless =~ /someregexp/
23
+ # ascii2ebcdic ascii_str # implementing ascii_str is left as an excercise
24
+ # }
25
+ # EBCDIC_Codec.decode = lambda {|ebcdic_str|
26
+ # # you may or may not want to raise exceptions at this point ....
27
+ # # strip removes any padding...
28
+ # ebcdic2ascii(ebcdic_str).strip
29
+ # }
30
+ #
31
+ # This instance of Codec would then be used be the corresponding Field
32
+ # encoder/decoder, which may look similar to this:
33
+ #
34
+ # EBCDIC = Field.new
35
+ # EBCDIC.codec = EBCDIC_Codec
36
+ # EBCDIC.padding = PADDING_LEFT_JUSTIFIED_SPACES
37
+ #
38
+ # Notice there is a bit of inconsistancy: the padding is added by the
39
+ # field, but removed by the codec. I would like to find a better
40
+ # solution to this...
41
+ #
42
+ # See also: Field, link:files/lib/fields_rb.html
43
+ #
44
+ # The following codecs are already implemented:
45
+ # [+ASCII_Number+] encodes either a Number or String representation of
46
+ # a number to the ASCII represenation of the number,
47
+ # decodes ASCII numerals to a number
48
+ # [+AN_Codec+] passes through ASCII string checking they conform to [A-Za-z0-9]
49
+ # during encoding, no validity check during decoding.
50
+ # [+ANP_Codec+] passes through ASCII string checking they conform to [A-Za-z0-9 ]
51
+ # during encoding, no validity check during decoding.
52
+ # [+ANS_Codec+] passes through ASCII string checking they conform to [\x20-\x7E]
53
+ # during encoding, no validity check during decoding.
54
+ # [+Null_Codec+] passes anything along untouched.
55
+ # [<tt>Track2</tt>] rudimentary check that string conforms to Track2
56
+ # [+MMDDhhmmssCodec+] encodes Time, Datetime or String to the described date format, checking
57
+ # that it is a valid date. Decodes to a DateTime instance, decoding and
58
+ # encoding perform validity checks!
59
+ # [+YYMMDDhhmmssCodec+] encodes Time, Datetime or String to the described date format, checking
60
+ # that it is a valid date. Decodes to a DateTime instance, decoding and
61
+ # encoding perform validity checks!
62
+ # [+YYMMCodec+] encodes Time, Datetime or String to the described date format (exp date),
63
+ # checking that it is a valid date. Decodes to a DateTime instance, decoding
64
+ # and encoding perform validity checks!
65
+ #
66
+ class Codec
67
+ attr_accessor :encoder
68
+ attr_accessor :decoder
69
+
70
+ def decode(raw)
71
+ decoder.call(raw)
72
+ end
73
+
74
+ # length is either a fixnum or a lenth encoder.
75
+ def encode(value)
76
+ encoder.call(value)
77
+ end
78
+ end
79
+
80
+ # ASCII_Number
81
+ ASCII_Number = Codec.new
82
+ ASCII_Number.encoder= lambda{|num|
83
+ enc = num.to_s
84
+ raise ISO8583Exception.new("Invalid value: #{enc} must be numeric!") unless enc =~ /^[0-9]*$/
85
+ enc
86
+ }
87
+
88
+ ASCII_Number.decoder = lambda{|raw|
89
+ raw.to_i
90
+ }
91
+
92
+ PASS_THROUGH_DECODER = lambda{|str|
93
+ str.strip # remove padding
94
+ }
95
+
96
+ # Takes a number or str representation of a number and BCD encodes it, e.g.
97
+ # "1234" => "\x12\x34"
98
+ # 3456 => "\x34\x56"
99
+ #
100
+ # right justified with null ... (correct to do this? almost certainly not...)
101
+ Packed_Number = Codec.new
102
+ Packed_Number.encoder = lambda { |val|
103
+ val = val.to_s
104
+ val = val.length % 2 == 0 ? val : "0"+val
105
+ raise ISO8583Exception.new("Invalid value: #{val} must be numeric!") unless val =~ /^[0-9]*$/
106
+ [val].pack("H*")
107
+ }
108
+ Packed_Number.decoder = lambda{|encoded|
109
+ d = encoded.unpack("H*")[0].to_i
110
+ }
111
+
112
+ AN_Codec = Codec.new
113
+ AN_Codec.encoder = lambda{|str|
114
+ raise ISO8583Exception.new("Invalid value: #{str} must be [A-Za-y0-9]") unless str =~ /^[A-Za-z0-9]*$/
115
+ str
116
+ }
117
+ AN_Codec.decoder = PASS_THROUGH_DECODER
118
+
119
+ ANP_Codec = Codec.new
120
+ ANP_Codec.encoder = lambda{|str|
121
+ raise ISO8583Exception.new("Invalid value: #{str} must be [A-Za-y0-9 ]") unless str =~ /^[A-Za-z0-9 ]*$/
122
+ str
123
+ }
124
+ ANP_Codec.decoder = PASS_THROUGH_DECODER
125
+
126
+ ANS_Codec = Codec.new
127
+ ANS_Codec.encoder = lambda{|str|
128
+ raise ISO8583Exception.new("Invalid value: #{str} must be [\x20-\x7E]") unless str =~ /^[\x20-\x7E]*$/
129
+ str
130
+ }
131
+ ANS_Codec.decoder = PASS_THROUGH_DECODER
132
+
133
+ Null_Codec = Codec.new
134
+ Null_Codec.encoder = lambda {|str|
135
+ str
136
+ }
137
+ Null_Codec.decoder = lambda {|str|
138
+ str.gsub(/\000*$/, '')
139
+ }
140
+
141
+ Track2 = Codec.new
142
+ Track2.encoder = lambda{|track2|
143
+ #SS | PAN | FS | Expiration Date | Service Code | Discretionary Data | ES | LRC
144
+ # SS = ;
145
+ # PAN = up to 19 digits (at least 9?)
146
+ # FS = '='
147
+ # Exp Date = YYMM
148
+ # SC: 3 digits or =
149
+ # ES = ?
150
+ # lrc : 1byte
151
+ raise ISO8583Exception.new("Invalid Track2 data: #{track2}") unless track2 =~ /^;*(\d{9,19})=(.*)\?.$/
152
+ track2
153
+ }
154
+ Track2.decoder = PASS_THROUGH_DECODER
155
+
156
+ def self._date_codec(fmt)
157
+ c = Codec.new
158
+ c.encoder = lambda {|date|
159
+ enc = case date
160
+ when DateTime, Date, Time
161
+ date.strftime(fmt)
162
+ when String
163
+ begin
164
+ dt = DateTime.strptime(date, fmt)
165
+ dt.strftime(fmt)
166
+ rescue
167
+ raise ISO8583Exception.new("Invalid format encoding: #{date}, must be #{fmt}.")
168
+ end
169
+ else
170
+ raise ISO8583Exception.new("Don't know how to encode: #{date.class} to a time.")
171
+ end
172
+ return enc
173
+ }
174
+ c.decoder = lambda {|str|
175
+ begin
176
+ DateTime.strptime(str, fmt)
177
+ rescue
178
+ raise ISO8583Exception.new("Invalid format decoding: #{str}, must be #{fmt}.")
179
+ end
180
+ }
181
+
182
+ c
183
+ end
184
+
185
+ MMDDhhmmssCodec = _date_codec("%m%d%H%M%S")
186
+ YYMMDDhhmmssCodec = _date_codec("%y%m%d%H%M%S")
187
+ YYMMCodec = _date_codec("%y%m")
188
+
189
+ end