From e9c44b8e49ad0a4940bf8079996bd77398931cba Mon Sep 17 00:00:00 2001 From: Pavel Gabriel Date: Tue, 11 Jun 2024 10:34:12 +0200 Subject: [PATCH] Allow setting custom field packer and unpacker (#310) * add Packer and Unpacker to the field spec * add PackerFunc and UnpackerFunc to use them in the spec definition --- docs/howtos.md | 105 ++++++++++++++++++++++++++++++++ field/binary.go | 39 ++++-------- field/composite.go | 8 ++- field/hex.go | 8 ++- field/numeric.go | 39 ++++-------- field/packer_unpacker.go | 51 ++++++++++++++++ field/packer_unpacker_test.go | 111 ++++++++++++++++++++++++++++++++++ field/spec.go | 66 +++++++++++++++++--- field/string.go | 39 ++++-------- field/track1.go | 35 +++-------- field/track2.go | 36 +++-------- field/track3.go | 35 +++-------- 12 files changed, 391 insertions(+), 181 deletions(-) create mode 100644 docs/howtos.md create mode 100644 field/packer_unpacker.go create mode 100644 field/packer_unpacker_test.go diff --git a/docs/howtos.md b/docs/howtos.md new file mode 100644 index 00000000..21677737 --- /dev/null +++ b/docs/howtos.md @@ -0,0 +1,105 @@ +# Howtos + +## Howto create custom packer and unpacker for a field + +### Problem + +The default behavior of the field packer and unpacker may not meet your requirements. For instance, you might need the length prefix to represent the length of the encoded data, not the field value. This is often necessary when using BCD or HEX encoding, where the field value's length differs from the encoded field value's length. + +**Example Requirement:** + +- Maximum length of the field: 9 +- Field value encoding: BCD +- Length prefix: L (1 byte) representing the length of the encoded data +- Field value: "123" + +### Default Behavior + +Let's explore the default behavior of a Numeric field: + +```go +f := field.NewNumeric(&field.Spec{ + Length: 9, // The max length of the field is 9 digits + Description: "Amount", + Enc: encoding.BCD, + Pref: prefix.Binary.L, +}) + +f.SetValue(123) + +packed, err := f.Pack() +require.NoError(t, err) + +require.Equal(t, []byte{0x03, 0x01, 0x23}, packed) +``` + +By default, the length prefix contains the field value's length, which is 3 digits, resulting in a length prefix of 0x03. + +### Custom Packer and Unpacker + +Let's create a custom packer and unpacker for the Numeric field to pack the field value as BCD and set the length prefix to the length of the encoded field value. + +```go +f := field.NewNumeric(&field.Spec{ + Length: 9, // max length of the field value (9 digits) + Description: "Amount", + Enc: encoding.BCD, + Pref: prefix.Binary.L, + // Define a custom packer to encode the length of the packed data + Packer: field.PackerFunc(func(value []byte, spec *field.Spec) ([]byte, error) { + if spec.Pad != nil { + value = spec.Pad.Pad(value, spec.Length) + } + + encodedValue, err := spec.Enc.Encode(value) + if err != nil { + return nil, fmt.Errorf("failed to encode content: %w", err) + } + + // Encode the length of the packed data, not the length of the value + maxLength := spec.Length/2 + 1 + + // Encode the length of the encoded value + lengthPrefix, err := spec.Pref.EncodeLength(maxLength, len(encodedValue)) + if err != nil { + return nil, fmt.Errorf("failed to encode length: %w", err) + } + + return append(lengthPrefix, encodedValue...), nil + }), + + // Define a custom unpacker to decode the length of the packed data + Unpacker: field.UnpackerFunc(func(packedFieldValue []byte, spec *field.Spec) ([]byte, int, error) { + maxEncodedValueLength := spec.Length/2 + 1 + + encodedValueLength, prefBytes, err := spec.Pref.DecodeLength(maxEncodedValueLength, packedFieldValue) + if err != nil { + return nil, 0, fmt.Errorf("failed to decode length: %w", err) + } + + // for BCD encoding, the length of the packed data is twice the length of the encoded value + valueLength := encodedValueLength * 2 + + // Decode the packed data length + value, read, err := spec.Enc.Decode(packedFieldValue[prefBytes:], valueLength) + if err != nil { + return nil, 0, fmt.Errorf("failed to decode content: %w", err) + } + + if spec.Pad != nil { + value = spec.Pad.Unpad(value) + } + + return value, read + prefBytes, nil + }), +}) + +f.SetValue(123) + +packed, err = f.Pack() +require.NoError(t, err) + +require.Equal(t, []byte{0x02, 0x01, 0x23}, packed) +``` + +Since 123 encoded in BCD is 0x01, 0x23, the length prefix is 0x02, indicating the length of the packed data is 2 bytes, not the field value's length which is 3 digits. diff --git a/field/binary.go b/field/binary.go index 07a48708..6ae6df49 100644 --- a/field/binary.go +++ b/field/binary.go @@ -10,9 +10,11 @@ import ( "github.com/moov-io/iso8583/utils" ) -var _ Field = (*Binary)(nil) -var _ json.Marshaler = (*Binary)(nil) -var _ json.Unmarshaler = (*Binary)(nil) +var ( + _ Field = (*Binary)(nil) + _ json.Marshaler = (*Binary)(nil) + _ json.Unmarshaler = (*Binary)(nil) +) type Binary struct { value []byte @@ -72,43 +74,24 @@ func (f *Binary) SetValue(v []byte) { func (f *Binary) Pack() ([]byte, error) { data := f.value - 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) - } - - packedLength, err := f.spec.Pref.EncodeLength(f.spec.Length, len(data)) - if err != nil { - return nil, fmt.Errorf("failed to encode length: %w", err) - } + packer := f.spec.getPacker() - return append(packedLength, packed...), nil + return packer.Pack(data, f.spec) } func (f *Binary) 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/composite.go b/field/composite.go index 0db1399f..b46252b8 100644 --- a/field/composite.go +++ b/field/composite.go @@ -15,9 +15,11 @@ import ( "github.com/moov-io/iso8583/utils" ) -var _ Field = (*Composite)(nil) -var _ json.Marshaler = (*Composite)(nil) -var _ json.Unmarshaler = (*Composite)(nil) +var ( + _ Field = (*Composite)(nil) + _ json.Marshaler = (*Composite)(nil) + _ json.Unmarshaler = (*Composite)(nil) +) // Composite is a wrapper object designed to hold ISO8583 TLVs, subfields and // subelements. Because Composite handles both of these usecases generically, diff --git a/field/hex.go b/field/hex.go index e9b15fbf..cff570ac 100644 --- a/field/hex.go +++ b/field/hex.go @@ -10,9 +10,11 @@ import ( "github.com/moov-io/iso8583/utils" ) -var _ Field = (*Hex)(nil) -var _ json.Marshaler = (*Hex)(nil) -var _ json.Unmarshaler = (*Hex)(nil) +var ( + _ Field = (*Hex)(nil) + _ json.Marshaler = (*Hex)(nil) + _ json.Unmarshaler = (*Hex)(nil) +) // Hex field allows working with hex strings but under the hood it's a binary // field. It's convenient to use when you need to work with hex strings, but diff --git a/field/numeric.go b/field/numeric.go index e4ebc8eb..3f30ebd7 100644 --- a/field/numeric.go +++ b/field/numeric.go @@ -9,9 +9,11 @@ import ( "github.com/moov-io/iso8583/utils" ) -var _ Field = (*Numeric)(nil) -var _ json.Marshaler = (*Numeric)(nil) -var _ json.Unmarshaler = (*Numeric)(nil) +var ( + _ Field = (*Numeric)(nil) + _ json.Marshaler = (*Numeric)(nil) + _ json.Unmarshaler = (*Numeric)(nil) +) type Numeric struct { value int64 @@ -84,44 +86,25 @@ func (f *Numeric) SetValue(v int64) { func (f *Numeric) Pack() ([]byte, error) { data := []byte(strconv.FormatInt(f.value, 10)) - 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) - } - - packedLength, err := f.spec.Pref.EncodeLength(f.spec.Length, len(data)) - if err != nil { - return nil, fmt.Errorf("failed to encode length: %w", err) - } + packer := f.spec.getPacker() - return append(packedLength, packed...), nil + return packer.Pack(data, f.spec) } // returns number of bytes was read func (f *Numeric) 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/packer_unpacker.go b/field/packer_unpacker.go new file mode 100644 index 00000000..b9cd3bce --- /dev/null +++ b/field/packer_unpacker.go @@ -0,0 +1,51 @@ +package field + +import "fmt" + +type defaultPacker struct{} + +// Pack packs the data according to the spec +func (p defaultPacker) Pack(value []byte, spec *Spec) ([]byte, error) { + // pad the value if needed + if spec.Pad != nil { + value = spec.Pad.Pad(value, spec.Length) + } + + // encode the value + encodedValue, err := spec.Enc.Encode(value) + if err != nil { + return nil, fmt.Errorf("failed to encode content: %w", err) + } + + // encode the length + lengthPrefix, err := spec.Pref.EncodeLength(spec.Length, len(value)) + if err != nil { + return nil, fmt.Errorf("failed to encode length: %w", err) + } + + return append(lengthPrefix, encodedValue...), nil +} + +type defaultUnpacker struct{} + +// Unpack unpacks the data according to the spec +func (u defaultUnpacker) Unpack(packedFieldValue []byte, spec *Spec) ([]byte, int, error) { + // decode the length + valueLength, prefBytes, err := spec.Pref.DecodeLength(spec.Length, packedFieldValue) + if err != nil { + return nil, 0, fmt.Errorf("failed to decode length: %w", err) + } + + // decode the value + value, read, err := spec.Enc.Decode(packedFieldValue[prefBytes:], valueLength) + if err != nil { + return nil, 0, fmt.Errorf("failed to decode content: %w", err) + } + + // unpad the value if needed + if spec.Pad != nil { + value = spec.Pad.Unpad(value) + } + + return value, read + prefBytes, nil +} diff --git a/field/packer_unpacker_test.go b/field/packer_unpacker_test.go new file mode 100644 index 00000000..47eab12e --- /dev/null +++ b/field/packer_unpacker_test.go @@ -0,0 +1,111 @@ +package field_test + +import ( + "fmt" + "testing" + + "github.com/moov-io/iso8583/encoding" + "github.com/moov-io/iso8583/field" + "github.com/moov-io/iso8583/prefix" + "github.com/stretchr/testify/require" +) + +func TestCustomPackerAndUnpacker(t *testing.T) { + // let's say we have a requirements that the lentgh prefix should + // contain the length of the data we pass, not the length of the field + // value. This is the case when you use BCD or HEX encoding, where the + // length of the field value is not the same as the length of the + // encoded field value. + + // Here is an example of such requirement: + // - the max length of the field is 9 + // - the field value should be encoded using BCD encoding + // - the lenth prefix is L (1 byte) and should contain the length of the + // the data in the field we pass. + // - the field value is "123" + + // let's see the default behavior of the Numeric field + fd := field.NewNumeric(&field.Spec{ + Length: 9, // the max length of the field is 9 digits + Description: "Amount", + Enc: encoding.BCD, + Pref: prefix.Binary.L, + }) + + fd.SetValue(123) + + packed, err := fd.Pack() + require.NoError(t, err) + + // we expect the length to be 2 bytes, as 123 encoded in BCD is 0x01, 0x23 + // by the default behavior, the length prefix will contain the length of the + // field value, which is 3 digits, so the length prefix as you can see is 0x03 + require.Equal(t, []byte{0x03, 0x01, 0x23}, packed) + + // now let's create a custom packer and unpacker for the Numeric field + // that will pack the field value as BCD and the length prefix as the length + // of the encoded field value. + fc := field.NewNumeric(&field.Spec{ + Length: 9, // max length of the field value (9 digits) + Description: "Amount", + Enc: encoding.BCD, + Pref: prefix.Binary.L, + // Define a custom packer to encode the length of the packed data + Packer: field.PackerFunc(func(value []byte, spec *field.Spec) ([]byte, error) { + if spec.Pad != nil { + value = spec.Pad.Pad(value, spec.Length) + } + + encodedValue, err := spec.Enc.Encode(value) + if err != nil { + return nil, fmt.Errorf("failed to encode content: %w", err) + } + + // Encode the length of the packed data, not the length of the value + maxLength := spec.Length/2 + 1 + + // Encode the length of the encoded value + lengthPrefix, err := spec.Pref.EncodeLength(maxLength, len(encodedValue)) + if err != nil { + return nil, fmt.Errorf("failed to encode length: %w", err) + } + + return append(lengthPrefix, encodedValue...), nil + }), + + // Define a custom unpacker to decode the length of the packed data + Unpacker: field.UnpackerFunc(func(packedFieldValue []byte, spec *field.Spec) ([]byte, int, error) { + maxEncodedValueLength := spec.Length/2 + 1 + + encodedValueLength, prefBytes, err := spec.Pref.DecodeLength(maxEncodedValueLength, packedFieldValue) + if err != nil { + return nil, 0, fmt.Errorf("failed to decode length: %w", err) + } + + // for BCD encoding, the length of the packed data is twice the length of the encoded value + valueLength := encodedValueLength * 2 + + // Decode the packed data length + value, read, err := spec.Enc.Decode(packedFieldValue[prefBytes:], valueLength) + if err != nil { + return nil, 0, fmt.Errorf("failed to decode content: %w", err) + } + + if spec.Pad != nil { + value = spec.Pad.Unpad(value) + } + + return value, read + prefBytes, nil + }), + }) + + fc.SetValue(123) + + packed, err = fc.Pack() + require.NoError(t, err) + + // we expect the length to be 2 bytes, as 123 encoded in BCD is 0x01, 0x23 + // so, you can see that the length prefix is 0x02, as the length of the packed + // data is 2 bytes. + require.Equal(t, []byte{0x02, 0x01, 0x23}, packed) +} diff --git a/field/spec.go b/field/spec.go index 6fd14d5c..a0eb7a21 100644 --- a/field/spec.go +++ b/field/spec.go @@ -44,12 +44,12 @@ type TagSpec struct { // Spec defines the structure of a field. type Spec struct { - // Length defines the maximum length of field (bytes, characters, - // digits or hex digits), for both fixed and variable lengths. - // You should use appropriate field types corresponding to the + // Length defines the maximum length of the field value (bytes, + // characters, digits or hex digits), for both fixed and variable + // lengths. You should use appropriate field types corresponding to the // length of the field you're defining, e.g. Numeric, String, Binary - // etc. For Hex fields, the length is defined in terms of the number - // of bytes, while the value of the field is hex string. + // etc. For Hex fields, the length is defined in terms of the number of + // bytes, while the value of the field is hex string. Length int // Tag sets the tag specification. Only applicable to composite field // types. @@ -77,9 +77,47 @@ type Spec struct { // will be disregarded, and the size of the bitmap will not change when // the first bit is set. DisableAutoExpand bool - // Bitmap defines a bitmap field that is used only by a composite field type. - // It defines the way that the composite will determine its subflieds existence. + // Bitmap defines a bitmap field that is used only by a composite field + // type. It defines the way that the composite will determine its + // subflieds existence. Bitmap *Bitmap + // Packer packes the field value according to its spec. Default is + // defaultPacker. + Packer Packer + // Unpacker unpackes the field value according to its spec. Default is + // defaultUnpacker. + Unpacker Unpacker +} + +// Packer is the interface that wraps the Pack method. +type Packer interface { + // Pack packs the data (field value) according to the spec and returns + // the packed data. + Pack(data []byte, spec *Spec) ([]byte, error) +} + +// Unpacker is the interface that wraps the Unpack method. +type Unpacker interface { + // Unpack unpacks the packed data according to the spec and returns the + // unpacked data (field value) and the number of bytes read. + Unpack(data []byte, spec *Spec) ([]byte, int, error) +} + +// PackerFunc is a function type that implements the Packer interface. +type PackerFunc func(data []byte, spec *Spec) ([]byte, error) + +// Pack packs the data (field value) according to the spec. +func (f PackerFunc) Pack(data []byte, spec *Spec) ([]byte, error) { + return f(data, spec) +} + +// UnpackerFunc is a function type that implements the Unpacker interface. +type UnpackerFunc func(data []byte, spec *Spec) ([]byte, int, error) + +// Unpack unpacks the packed data according to the spec and returns the +// unpacked data (field value) and the number of bytes read. +func (f UnpackerFunc) Unpack(data []byte, spec *Spec) ([]byte, int, error) { + return f(data, spec) } func NewSpec(length int, desc string, enc encoding.Encoder, pref prefix.Prefixer) *Spec { @@ -91,6 +129,20 @@ func NewSpec(length int, desc string, enc encoding.Encoder, pref prefix.Prefixer } } +func (spec *Spec) getPacker() Packer { + if spec.Packer == nil { + return defaultPacker{} + } + return spec.Packer +} + +func (spec *Spec) getUnpacker() Unpacker { + if spec.Unpacker == nil { + return defaultUnpacker{} + } + return spec.Unpacker +} + // Validate validates the spec. func (s *Spec) Validate() error { if s.Enc != nil { diff --git a/field/string.go b/field/string.go index c0ed4fb7..5d977a5d 100644 --- a/field/string.go +++ b/field/string.go @@ -10,9 +10,11 @@ import ( "github.com/moov-io/iso8583/utils" ) -var _ Field = (*String)(nil) -var _ json.Marshaler = (*String)(nil) -var _ json.Unmarshaler = (*String)(nil) +var ( + _ Field = (*String)(nil) + _ json.Marshaler = (*String)(nil) + _ json.Unmarshaler = (*String)(nil) +) type String struct { value string @@ -72,43 +74,24 @@ func (f *String) SetValue(v string) { func (f *String) Pack() ([]byte, error) { data := []byte(f.value) - 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) - } - - packedLength, err := f.spec.Pref.EncodeLength(f.spec.Length, len(data)) - if err != nil { - return nil, fmt.Errorf("failed to encode length: %w", err) - } + packer := f.spec.getPacker() - return append(packedLength, packed...), nil + return packer.Pack(data, f.spec) } func (f *String) 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/track1.go b/field/track1.go index afc421d9..52280780 100644 --- a/field/track1.go +++ b/field/track1.go @@ -28,9 +28,7 @@ const ( track1Format = `%s%s^%s^%s%s%s` ) -var ( - track1Regex = regexp.MustCompile(`^([A-Z]{1})([0-9]{1,19})\^([^\^]{2,26})\^([0-9]{4}|\^)([0-9]{3}|\^)([^\?]+)$`) -) +var track1Regex = regexp.MustCompile(`^([A-Z]{1})([0-9]{1,19})\^([^\^]{2,26})\^([0-9]{4}|\^)([0-9]{3}|\^)([^\?]+)$`) func NewTrack1(spec *Spec) *Track1 { return &Track1{ @@ -68,37 +66,18 @@ func (f *Track1) Pack() ([]byte, error) { return nil, err } - 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) - } - - packedLength, err := f.spec.Pref.EncodeLength(f.spec.Length, len(data)) - if err != nil { - return nil, fmt.Errorf("failed to encode length: %w", err) - } + packer := f.spec.getPacker() - return append(packedLength, packed...), nil + return packer.Pack(data, f.spec) } // returns number of bytes was read func (f *Track1) 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 len(raw) > 0 { @@ -108,7 +87,7 @@ func (f *Track1) Unpack(data []byte) (int, error) { } } - return read + prefBytes, nil + return bytesRead, nil } // Deprecated. Use Marshal instead diff --git a/field/track2.go b/field/track2.go index ec4aea9f..973bb924 100644 --- a/field/track2.go +++ b/field/track2.go @@ -27,9 +27,7 @@ const ( defaultSeparator = "=" ) -var ( - track2Regex = regexp.MustCompile(`^([0-9]{1,19})(=|D)([0-9]{4})([0-9]{3})([^?]+)$`) -) +var track2Regex = regexp.MustCompile(`^([0-9]{1,19})(=|D)([0-9]{4})([0-9]{3})([^?]+)$`) func NewTrack2(spec *Spec) *Track2 { return &Track2{ @@ -67,37 +65,18 @@ func (f *Track2) Pack() ([]byte, error) { return nil, err } - if f.spec.Pad != nil { - data = f.spec.Pad.Pad(data, f.spec.Length) - } + packer := f.spec.getPacker() - packed, err := f.spec.Enc.Encode(data) - if err != nil { - return nil, fmt.Errorf("failed to encode content: %w", err) - } - - 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) } // returns number of bytes was read func (f *Track2) 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 len(raw) > 0 { @@ -106,7 +85,8 @@ func (f *Track2) Unpack(data []byte) (int, error) { return 0, err } } - return read + prefBytes, nil + + return bytesRead, nil } // Deprecated. Use Marshal instead diff --git a/field/track3.go b/field/track3.go index 97390504..c4e889c3 100644 --- a/field/track3.go +++ b/field/track3.go @@ -22,9 +22,7 @@ const ( track3Format = `%s%s=%s` ) -var ( - track3Regex = regexp.MustCompile(`^([0-9]{2})([0-9]{1,19})\=([^\?]+)$`) -) +var track3Regex = regexp.MustCompile(`^([0-9]{2})([0-9]{1,19})\=([^\?]+)$`) func NewTrack3(spec *Spec) *Track3 { return &Track3{ @@ -65,37 +63,18 @@ func (f *Track3) Pack() ([]byte, error) { return nil, err } - 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) - } - - packedLength, err := f.spec.Pref.EncodeLength(f.spec.Length, len(data)) - if err != nil { - return nil, fmt.Errorf("failed to encode length: %w", err) - } + packer := f.spec.getPacker() - return append(packedLength, packed...), nil + return packer.Pack(data, f.spec) } // returns number of bytes was read func (f *Track3) 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 len(raw) > 0 { @@ -105,7 +84,7 @@ func (f *Track3) Unpack(data []byte) (int, error) { } } - return read + prefBytes, nil + return bytesRead, nil } // Deprecated. Use Marshal instead