diff --git a/encoding/hex.go b/encoding/hex.go index abc30769..ecce8426 100644 --- a/encoding/hex.go +++ b/encoding/hex.go @@ -11,8 +11,13 @@ import ( // HEX to ASCII encoder var ( - _ Encoder = (*hexToASCIIEncoder)(nil) - BytesToASCIIHex = &hexToASCIIEncoder{} + _ Encoder = (*hexToASCIIEncoder)(nil) + // BytesToASCIIHex is an encoder that converts bytes into their ASCII + // representation. On success, the ASCII representation bytes are returned + // Don't use this encoder with String, Numeric or Binary fields as packing and + // unpacking in these fields uses length of value/bytes, so only Pack will be + // able to write the value correctly. + BytesToASCIIHex = &hexToASCIIEncoder{} ) type hexToASCIIEncoder struct{} @@ -56,8 +61,14 @@ func (e hexToASCIIEncoder) Decode(data []byte, length int) ([]byte, int, error) // ASCII To HEX encoder var ( - _ Encoder = (*asciiToHexEncoder)(nil) - ASCIIHexToBytes = &asciiToHexEncoder{} + _ Encoder = (*asciiToHexEncoder)(nil) + // ASCIIHexToBytes is an encoder that converts ASCII Hex-digits into a byte slice + // This encoder is used in TagSpec, BerTLVTag and similar. + // It shouldn't be used with String, Numeric or Binary fields as packing and unpacking + // in these fields uses length of value/bytes, so only Unpack will be able to read + // the value correctly. + // If you are looking for a way to work with HEX strings, use Hex field instead. + ASCIIHexToBytes = &asciiToHexEncoder{} ) type asciiToHexEncoder struct{} diff --git a/examples/icc_test.go b/examples/icc_test.go deleted file mode 100644 index 12fd1980..00000000 --- a/examples/icc_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package examples - -import ( - "fmt" - "testing" - - "github.com/moov-io/iso8583/encoding" - "github.com/moov-io/iso8583/field" - "github.com/moov-io/iso8583/prefix" - "github.com/moov-io/iso8583/sort" - "github.com/stretchr/testify/require" -) - -// The sample is for VSDC chip data usage - -// This field 55 VSDC chip data usage contains three subfields after the length subfield. -// Positions: -// 1 2 3 4 ... 255 -// Fields -// - Subfield 1: length Byte, a one-byte binary subfield that contains the number of bytes in this field after the length subfield -// - Subfield 2: dataset ID, a one-byte binary identifier -// - Subfield 3: dataset length, 2-byte binary subfield that contains the total length of all TLV elements that follow. -// - Subfield 4: -// Chip Card TLV data elements -// Tag Length Value Tag Length Value -func TestICCField55(t *testing.T) { - field55Spec := &field.Spec{ - Length: 255, - Description: "Integrated Circuit Card (ICC) Data", - Pref: prefix.Binary.L, - Tag: &field.TagSpec{ - // We have 1 byte length tag, that in the spec is seen as a Hex string - // but will be encoded as a binary byte (ASCIIHexToBytes) - // We sort the TLV tags by their hex values, but it's not important - // Finally, if we have unknown tag, in order to skip it, we need to know - // how long its value is. For this, we need to read the length tag. - // To read the length tag, we need to know its length. - // Setting PrefUnknownTLV to prefix.Binary.LL will read the length prefix - // as a 2-byte binary value. - Length: 1, - Enc: encoding.ASCIIHexToBytes, - Sort: sort.StringsByHex, - SkipUnknownTLVTags: true, - // if we have unknown TLV tags, - PrefUnknownTLV: prefix.Binary.LL, - }, - Subfields: map[string]field.Field{ - "01": field.NewComposite(&field.Spec{ - Length: 252, - Description: "VSDC Data", - Pref: prefix.Binary.LL, - Tag: &field.TagSpec{ - Enc: encoding.BerTLVTag, - Sort: sort.StringsByHex, - SkipUnknownTLVTags: true, - }, - Subfields: map[string]field.Field{ - "9A": field.NewString(&field.Spec{ - Description: "Transaction Date", - Enc: encoding.Binary, - Pref: prefix.BerTLV, - }), - "9F02": field.NewString(&field.Spec{ - Description: "Amount, Authorized (Numeric)", - Enc: encoding.Binary, - Pref: prefix.BerTLV, - }), - }, - }), - }, - } - - type VSDCData struct { - TransactionDate string `iso8583:"9A"` - Amount string `iso8583:"9F02"` - } - - type ICCData struct { - VSDCData *VSDCData `iso8583:"01"` - } - - filed55 := field.NewComposite(field55Spec) - err := filed55.Marshal(&ICCData{ - VSDCData: &VSDCData{ - TransactionDate: "210720", - Amount: "000000000501", - }, - }) - require.NoError(t, err) - - packed, err := filed55.Pack() - require.NoError(t, err) - - require.Equal(t, "1A0100179A063231303732309F020C303030303030303030353031", fmt.Sprintf("%X", packed)) -} diff --git a/field/hex.go b/field/hex.go index cff570ac..840ff42e 100644 --- a/field/hex.go +++ b/field/hex.go @@ -20,7 +20,7 @@ var ( // field. It's convenient to use when you need to work with hex strings, but // don't want to deal with converting them to bytes manually. // If provided value is not a valid hex string, it will return an error during -// packing. +// packing. For the Hex field, the Binary encoding shoud be used in the Spec. type Hex struct { value string spec *Spec @@ -85,43 +85,24 @@ func (f *Hex) Pack() ([]byte, error) { return nil, utils.NewSafeErrorf(err, "converting hex field into bytes") } - if f.spec.Pad != nil { - data = f.spec.Pad.Pad(data, f.spec.Length) - } - - packed, err := f.spec.Enc.Encode(data) - if err != nil { - return nil, fmt.Errorf("failed to encode content: %w", err) - } + packer := f.spec.getPacker() - packedLength, err := f.spec.Pref.EncodeLength(f.spec.Length, len(data)) - if err != nil { - return nil, fmt.Errorf("failed to encode length: %w", err) - } - - return append(packedLength, packed...), nil + return packer.Pack(data, f.spec) } func (f *Hex) Unpack(data []byte) (int, error) { - dataLen, prefBytes, err := f.spec.Pref.DecodeLength(f.spec.Length, data) - if err != nil { - return 0, fmt.Errorf("failed to decode length: %w", err) - } + unpacker := f.spec.getUnpacker() - raw, read, err := f.spec.Enc.Decode(data[prefBytes:], dataLen) + raw, bytesRead, err := unpacker.Unpack(data, f.spec) if err != nil { - return 0, fmt.Errorf("failed to decode content: %w", err) - } - - if f.spec.Pad != nil { - raw = f.spec.Pad.Unpad(raw) + return 0, err } if err := f.SetBytes(raw); err != nil { return 0, fmt.Errorf("failed to set bytes: %w", err) } - return read + prefBytes, nil + return bytesRead, nil } // Deprecated. Use Marshal instead diff --git a/field/hex_test.go b/field/hex_test.go index 3b0edcbd..5ae3b6b7 100644 --- a/field/hex_test.go +++ b/field/hex_test.go @@ -38,6 +38,31 @@ func TestHexField(t *testing.T) { require.Equal(t, "AABBCCDDEE", f.Value()) }) + t.Run("packing and unpacking with variable length", func(t *testing.T) { + spec := &Spec{ + Length: 5, // 5 bytes, 10 hex chars + Description: "Field", + Enc: encoding.Binary, + Pref: prefix.Binary.LL, + } + + f := NewHexValue("AABBCCDDEE") + f.SetSpec(spec) + + packed, err := f.Pack() + + require.NoError(t, err) + require.Equal(t, []byte{0x00, 0x05, 0xaa, 0xbb, 0xcc, 0xdd, 0xee}, packed) + + f = NewHex(spec) + read, err := f.Unpack(packed) + + require.NoError(t, err) + require.Equal(t, 7, read) + require.Equal(t, "AABBCCDDEE", f.Value()) + + }) + t.Run("marshaling", func(t *testing.T) { f := NewHexValue("AABBCCDDEE") f2 := &Hex{} diff --git a/field/packer_unpacker.go b/field/packer_unpacker.go index b9cd3bce..5bd48857 100644 --- a/field/packer_unpacker.go +++ b/field/packer_unpacker.go @@ -1,6 +1,8 @@ package field -import "fmt" +import ( + "fmt" +) type defaultPacker struct{}