Skip to content

Commit

Permalink
use length of the field value for prefixer, not length of encoded data (
Browse files Browse the repository at this point in the history
#227)

* use length of the field value for prefixer, not length of encoded data

* add test for composite with BCD fields

* update composite test and hex field

* add tests for Hex field

* fix typo

* update description of the Hex field
  • Loading branch information
alovak authored May 26, 2023
1 parent c327538 commit fb3b4df
Show file tree
Hide file tree
Showing 13 changed files with 506 additions and 40 deletions.
7 changes: 6 additions & 1 deletion encoding/encoder.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package encoding

type Encoder interface {
// Encode encodes source data (ASCII, characters, digits, etc.) into
// destination bytes. It returns encoded bytes and any error
Encode([]byte) ([]byte, error)
// Returns data decoded into ASCII (or bytes), how many bytes were read, error

// Decode decodes data into into bytes (ASCII, characters, digits,
// etc.). It returns the bytes representing the decoded data, the
// number of bytes read from the input, and any error
Decode([]byte, int) (data []byte, read int, err error)
}
2 changes: 1 addition & 1 deletion field/binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (f *Binary) Pack() ([]byte, error) {
return nil, fmt.Errorf("failed to encode content: %w", err)
}

packedLength, err := f.spec.Pref.EncodeLength(f.spec.Length, len(packed))
packedLength, err := f.spec.Pref.EncodeLength(f.spec.Length, len(data))
if err != nil {
return nil, fmt.Errorf("failed to encode length: %w", err)
}
Expand Down
3 changes: 3 additions & 0 deletions field/composite.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ var _ json.Unmarshaler = (*Composite)(nil)
// documentation and error messages. These subfields are defined using the
// 'Subfields' field on the field.Spec struct.
//
// Because composite subfields may be encoded with different encodings, the
// Length field on the field.Spec struct is in bytes.
//
// Composite handles aggregate fields of the following format:
// - Length (if variable)
// - []Subfield
Expand Down
120 changes: 89 additions & 31 deletions field/composite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,14 @@ var (
Sort: sort.StringsByHex,
},
Subfields: map[string]Field{
"9A": NewString(&Spec{
"9A": NewHex(&Spec{
Description: "Transaction Date",
Enc: encoding.ASCIIHexToBytes,
Enc: encoding.Binary,
Pref: prefix.BerTLV,
}),
"9F02": NewString(&Spec{
"9F02": NewHex(&Spec{
Description: "Amount, Authorized (Numeric)",
Enc: encoding.ASCIIHexToBytes,
Enc: encoding.Binary,
Pref: prefix.BerTLV,
}),
},
Expand All @@ -255,14 +255,14 @@ var (
Sort: sort.StringsByHex,
},
Subfields: map[string]Field{
"82": NewString(&Spec{
"82": NewHex(&Spec{
Description: "Application Interchange Profile",
Enc: encoding.ASCIIHexToBytes,
Enc: encoding.Binary,
Pref: prefix.BerTLV,
}),
"9F36": NewString(&Spec{
"9F36": NewHex(&Spec{
Description: "Currency Code, Application Reference",
Enc: encoding.ASCIIHexToBytes,
Enc: encoding.Binary,
Pref: prefix.BerTLV,
}),
"9F3B": NewComposite(&Spec{
Expand All @@ -273,9 +273,9 @@ var (
Sort: sort.StringsByHex,
},
Subfields: map[string]Field{
"9F45": NewString(&Spec{
"9F45": NewHex(&Spec{
Description: "Data Authentication Code",
Enc: encoding.ASCIIHexToBytes,
Enc: encoding.Binary,
Pref: prefix.BerTLV,
}),
},
Expand Down Expand Up @@ -306,18 +306,18 @@ type CompositeTestDataWithoutTagPaddingWithIndexTag struct {
}

type TLVTestData struct {
F9A *String
F9F02 *String
F9A *Hex
F9F02 *Hex
}

type ConstructedTLVTestData struct {
F82 *String
F9F36 *String
F82 *Hex
F9F36 *Hex
F9F3B *SubConstructedTLVTestData
}

type SubConstructedTLVTestData struct {
F9F45 *String
F9F45 *Hex
}

func TestComposite_SetData(t *testing.T) {
Expand All @@ -334,8 +334,8 @@ func TestCompositeFieldUnmarshal(t *testing.T) {
// we will do it by packing the field
composite := NewComposite(tlvTestSpec)
err := composite.SetData(&TLVTestData{
F9A: NewStringValue("210720"),
F9F02: NewStringValue("000000000501"),
F9A: NewHexValue("210720"),
F9F02: NewHexValue("000000000501"),
})
require.NoError(t, err)

Expand All @@ -353,10 +353,10 @@ func TestCompositeFieldUnmarshal(t *testing.T) {
t.Run("Unmarshal gets data for composite field (constructed)", func(t *testing.T) {
composite := NewComposite(constructedBERTLVTestSpec)
err := composite.SetData(&ConstructedTLVTestData{
F82: NewStringValue("017F"),
F9F36: NewStringValue("027F"),
F82: NewHexValue("017F"),
F9F36: NewHexValue("027F"),
F9F3B: &SubConstructedTLVTestData{
F9F45: NewStringValue("047F"),
F9F45: NewHexValue("047F"),
},
})
require.NoError(t, err)
Expand All @@ -375,15 +375,15 @@ func TestCompositeFieldUnmarshal(t *testing.T) {

t.Run("Unmarshal gets data for composite field using field tag `index`", func(t *testing.T) {
type tlvTestData struct {
Date *String `index:"9A"`
TransactionID *String `index:"9F02"`
Date *Hex `index:"9A"`
TransactionID *Hex `index:"9F02"`
}
// first, we need to populate fields of composite field
// we will do it by packing the field
composite := NewComposite(tlvTestSpec)
err := composite.SetData(&TLVTestData{
F9A: NewStringValue("210720"),
F9F02: NewStringValue("000000000501"),
F9A: NewHexValue("210720"),
F9F02: NewHexValue("000000000501"),
})
require.NoError(t, err)

Expand All @@ -402,8 +402,8 @@ func TestCompositeFieldUnmarshal(t *testing.T) {
func TestTLVPacking(t *testing.T) {
t.Run("Pack correctly serializes data to bytes (general tlv)", func(t *testing.T) {
data := &TLVTestData{
F9A: NewStringValue("210720"),
F9F02: NewStringValue("000000000501"),
F9A: NewHexValue("210720"),
F9F02: NewHexValue("000000000501"),
}

composite := NewComposite(tlvTestSpec)
Expand Down Expand Up @@ -465,10 +465,10 @@ func TestTLVPacking(t *testing.T) {

t.Run("Pack correctly serializes data to bytes (constructed ber-tlv)", func(t *testing.T) {
data := &ConstructedTLVTestData{
F82: NewStringValue("017f"),
F9F36: NewStringValue("027f"),
F82: NewHexValue("017f"),
F9F36: NewHexValue("027f"),
F9F3B: &SubConstructedTLVTestData{
F9F45: NewStringValue("047f"),
F9F45: NewHexValue("047f"),
},
}

Expand Down Expand Up @@ -629,6 +629,64 @@ func TestCompositePacking(t *testing.T) {
require.Equal(t, "ABCD12", string(packed))
})

t.Run("Pack and unpack data with BCD encoding", func(t *testing.T) {
var compositeSpecWithBCD = &Spec{
Length: 2, // always in bytes
Description: "Point of Service Entry Mode",
Pref: prefix.BCD.Fixed,
Tag: &TagSpec{
Sort: sort.StringsByInt,
},
Subfields: map[string]Field{
"1": NewString(&Spec{
Length: 2,
Description: "PAN/Date Entry Mode",
Enc: encoding.BCD,
Pref: prefix.BCD.Fixed,
}),
"2": NewString(&Spec{
Length: 2,
Description: "PIN Entry Capability",
Enc: encoding.BCD,
Pref: prefix.BCD.Fixed,
}),
},
}

type data struct {
PANEntryMode *String `index:"1"`
PINEntryMode *String `index:"2"`
}

f := NewComposite(compositeSpecWithBCD)

d := &data{
PANEntryMode: NewStringValue("01"),
PINEntryMode: NewStringValue("02"),
}

err := f.Marshal(d)
require.NoError(t, err)

packed, err := f.Pack()
require.NoError(t, err)
require.Equal(t, []byte{0x01, 0x02}, packed)

// unpacking

f = NewComposite(compositeSpecWithBCD)
read, err := f.Unpack(packed)
require.NoError(t, err)
require.Equal(t, 2, read) // two bytes read

d = &data{}
err = f.Unmarshal(d)
require.NoError(t, err)

require.Equal(t, "01", d.PANEntryMode.Value())
require.Equal(t, "02", d.PINEntryMode.Value())
})

t.Run("Unpack returns an error on mismatch of subfield types", func(t *testing.T) {
type TestDataIncorrectType struct {
F1 *Numeric
Expand Down Expand Up @@ -1671,8 +1729,8 @@ func TestTLVJSONConversion(t *testing.T) {

t.Run("MarshalJSON TLV Data Ok", func(t *testing.T) {
data := &TLVTestData{
F9A: NewStringValue("210720"),
F9F02: NewStringValue("000000000501"),
F9A: NewHexValue("210720"),
F9F02: NewHexValue("000000000501"),
}

composite := NewComposite(tlvTestSpec)
Expand Down
Loading

0 comments on commit fb3b4df

Please sign in to comment.