Skip to content

Commit 60f5399

Browse files
authored
Enhance Track2 Data Implementation (#336)
* fix some comment errors * implement track2 packer and unpacker. add some convenience methods to track1 and track2 to make interacting with them cleaner and closer to what we have implemented for other fields. Add tests for all new code added, except for constructors * format code should be in options not in constructor * [PR] remove the options and just use params in the constructor. Update test to use constructor. Cleanup unpacker and make it simple
1 parent 1f2f3af commit 60f5399

File tree

7 files changed

+235
-57
lines changed

7 files changed

+235
-57
lines changed

field/bitmap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func (f *Bitmap) Marshal(v interface{}) error {
148148
return nil
149149
}
150150

151-
// Reset resets the bitmap to its initial state because of how message works,
151+
// Reset the bitmap to its initial state because of how message works,
152152
// Message need a way to initialize bitmap. That's why we set parameters to
153153
// their default values here like we do in constructor.
154154
func (f *Bitmap) Reset() {

field/composite.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,17 +138,17 @@ func (f *Composite) getSubfields() map[string]Field {
138138
// SetSpec validates the spec and creates new instances of Subfields defined
139139
// in the specification.
140140
// NOTE: Composite does not support padding on the base spec. Therefore, users
141-
// should only pass None or nil values for ths type. Passing any other value
141+
// should only pass None or nil values for this type. Passing any other value
142142
// will result in a panic.
143143
func (f *Composite) SetSpec(spec *Spec) {
144144
if err := spec.Validate(); err != nil {
145-
panic(err) //nolint:forbidigo,nolintlint // as specs moslty static, we panic on spec validation errors
145+
panic(err) //nolint:forbidigo,nolintlint // as specs mostly static, we panic on spec validation errors
146146
}
147147
f.spec = spec
148148

149149
var sortFn sort.StringSlice
150150

151-
// When bitmap is defined, always order tags by int.
151+
// When bitmap is not defined, always order tags by int.
152152
if spec.Bitmap != nil {
153153
sortFn = sort.StringsByInt
154154
} else {

field/packer_unpacker.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,61 @@ func (u defaultUnpacker) Unpack(packedFieldValue []byte, spec *Spec) ([]byte, in
5151

5252
return value, read + prefBytes, nil
5353
}
54+
55+
type Track2Packer struct{}
56+
57+
// This is a custom packer for Track2 Data. Some specifications require the length
58+
// to be equal to the length of the pre-padded value.
59+
func (p Track2Packer) Pack(value []byte, spec *Spec) ([]byte, error) {
60+
data := value
61+
62+
// Only pad if the length is odd. If so, just add
63+
// one pad character, so tell the Pad function that
64+
// the length we want is +1 to what the value is
65+
if spec.Pad != nil && len(value)%2 != 0 {
66+
data = spec.Pad.Pad(data, len(value)+1)
67+
}
68+
69+
packed, err := spec.Enc.Encode(data)
70+
if err != nil {
71+
return nil, fmt.Errorf("failed to encode content: %w", err)
72+
}
73+
74+
// Encode the length to that of the original string, not the potentially
75+
// padded length
76+
packedLength, err := spec.Pref.EncodeLength(spec.Length, len(value))
77+
if err != nil {
78+
return nil, fmt.Errorf("failed to encode length: %w", err)
79+
}
80+
81+
return append(packedLength, packed...), nil
82+
}
83+
84+
type Track2Unpacker struct{}
85+
86+
func (p Track2Unpacker) Unpack(packedFieldValue []byte, spec *Spec) ([]byte, int, error) {
87+
// decode the length
88+
valueLength, prefBytes, err := spec.Pref.DecodeLength(spec.Length, packedFieldValue)
89+
if err != nil {
90+
return nil, 0, fmt.Errorf("failed to decode length: %w", err)
91+
}
92+
93+
// if valueLength is odd we need to make it even to adjust for
94+
// the padding in our Packer
95+
if valueLength%2 != 0 {
96+
valueLength++
97+
}
98+
99+
// decode the value
100+
value, read, err := spec.Enc.Decode(packedFieldValue[prefBytes:], valueLength)
101+
if err != nil {
102+
return nil, 0, fmt.Errorf("failed to decode content: %w", err)
103+
}
104+
105+
// unpad the value if needed
106+
if spec.Pad != nil {
107+
value = spec.Pad.Unpad(value)
108+
}
109+
110+
return value, read + prefBytes, nil
111+
}

field/packer_unpacker_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package field_test
33
import (
44
"fmt"
55
"testing"
6+
"time"
67

78
"github.com/moov-io/iso8583/encoding"
89
"github.com/moov-io/iso8583/field"
10+
"github.com/moov-io/iso8583/padding"
911
"github.com/moov-io/iso8583/prefix"
1012
"github.com/stretchr/testify/require"
1113
)
@@ -109,3 +111,76 @@ func TestCustomPackerAndUnpacker(t *testing.T) {
109111
// data is 2 bytes.
110112
require.Equal(t, []byte{0x02, 0x01, 0x23}, packed)
111113
}
114+
115+
func TestTrack2Packer(t *testing.T) {
116+
type testCase struct {
117+
name, primaryAccountNumber, serviceCode, discretionaryData, separator string
118+
expirationDate time.Time
119+
expectedPack []byte
120+
}
121+
122+
s := &field.Spec{
123+
Length: 37,
124+
Description: "Track 2 Data",
125+
Enc: encoding.ASCII,
126+
Pref: prefix.ASCII.LL,
127+
Pad: padding.Left('0'),
128+
Packer: field.Track2Packer{},
129+
Unpacker: field.Track2Unpacker{},
130+
}
131+
132+
expirationDate, err := time.Parse("0601", "3112")
133+
require.NoError(t, err)
134+
135+
testCases := []testCase{
136+
{
137+
name: "even length",
138+
primaryAccountNumber: "4444444444444444",
139+
serviceCode: "201",
140+
discretionaryData: "1474900373",
141+
separator: "D",
142+
expirationDate: expirationDate,
143+
// Two bytes for length then: 44444444444444D31122011474900373
144+
expectedPack: []byte{0x33, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x44, 0x33, 0x31, 0x31, 0x32, 0x32, 0x30, 0x31, 0x31, 0x34, 0x37, 0x34, 0x39, 0x30, 0x30, 0x33, 0x37, 0x33},
145+
},
146+
{
147+
name: "odd length",
148+
primaryAccountNumber: "4444444444444444",
149+
serviceCode: "201",
150+
discretionaryData: "147",
151+
separator: "D",
152+
expirationDate: expirationDate,
153+
// Two bytes for length then: 04444444444444444D3112201147
154+
expectedPack: []byte{0x32, 0x37, 0x30, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x44, 0x33, 0x31, 0x31, 0x32, 0x32, 0x30, 0x31, 0x31, 0x34, 0x37},
155+
},
156+
}
157+
158+
for _, tc := range testCases {
159+
t.Run(tc.name, func(t *testing.T) {
160+
fd := field.NewTrack2Value(
161+
tc.primaryAccountNumber,
162+
&tc.expirationDate,
163+
tc.serviceCode,
164+
tc.discretionaryData,
165+
tc.separator,
166+
)
167+
fd.SetSpec(s)
168+
169+
packed, err := fd.Pack()
170+
require.NoError(t, err)
171+
172+
require.Equal(t, tc.expectedPack, packed)
173+
174+
// unpack and verify that it is the same
175+
unpackedFd := field.NewTrack2(s)
176+
_, err = unpackedFd.Unpack(packed)
177+
require.NoError(t, err)
178+
179+
require.Equal(t, fd.PrimaryAccountNumber, unpackedFd.PrimaryAccountNumber)
180+
require.Equal(t, fd.ExpirationDate, unpackedFd.ExpirationDate)
181+
require.Equal(t, fd.ServiceCode, unpackedFd.ServiceCode)
182+
require.Equal(t, fd.DiscretionaryData, unpackedFd.DiscretionaryData)
183+
require.Equal(t, fd.Separator, unpackedFd.Separator)
184+
})
185+
}
186+
}

field/track1.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,28 @@ func NewTrack1(spec *Spec) *Track1 {
3636
}
3737
}
3838

39+
func NewTrack1Value(
40+
primaryAccountNumber,
41+
name string,
42+
expirationDate *time.Time,
43+
serviceCode,
44+
discretionaryData,
45+
formatCode string,
46+
fixedLength bool,
47+
) *Track1 {
48+
t := &Track1{
49+
PrimaryAccountNumber: primaryAccountNumber,
50+
Name: name,
51+
ExpirationDate: expirationDate,
52+
ServiceCode: serviceCode,
53+
DiscretionaryData: discretionaryData,
54+
FormatCode: formatCode,
55+
FixedLength: fixedLength,
56+
}
57+
58+
return t
59+
}
60+
3961
func (f *Track1) Spec() *Spec {
4062
return f.spec
4163
}

field/track2.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,24 @@ func NewTrack2(spec *Spec) *Track2 {
3535
}
3636
}
3737

38+
func NewTrack2Value(
39+
primaryAccountNumber string,
40+
expirationDate *time.Time,
41+
serviceCode,
42+
discretionaryData,
43+
separator string,
44+
) *Track2 {
45+
t := &Track2{
46+
PrimaryAccountNumber: primaryAccountNumber,
47+
Separator: separator,
48+
ExpirationDate: expirationDate,
49+
ServiceCode: serviceCode,
50+
DiscretionaryData: discretionaryData,
51+
}
52+
53+
return t
54+
}
55+
3856
func (f *Track2) Spec() *Spec {
3957
return f.spec
4058
}
@@ -180,6 +198,7 @@ func (f *Track2) pack() ([]byte, error) {
180198
if len(f.ServiceCode) > 0 {
181199
code = f.ServiceCode
182200
}
201+
183202
separator := defaultSeparator
184203
if f.Separator != "" {
185204
separator = f.Separator

0 commit comments

Comments
 (0)