Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting custom field packer and unpacker #310

Merged
merged 5 commits into from
Jun 11, 2024

Conversation

alovak
Copy link
Contributor

@alovak alovak commented Jun 7, 2024

Currently, all Pack and Unpack methods of the fields (except Composite) do the same steps. These steps were extracted into the DefaultPacker.

We also introduced Packer and Unpacker interfaces, so you can create custom implementation to pack/unpack any field you have.

Here is an example of when such a change can be helpful.

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:

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)

require.Equal(t, []byte{0x03, 0x01, 0x23}, packed)

Here, the length is expected to be 2 bytes since 123 encoded in BCD is 0x01, 0x23. By default, the length prefix will contain 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.

fc := field.NewNumeric(&field.Spec{
    Length:      5, // Indicates the max length of the encoded field value 9/2+1 = 5
    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(data []byte, spec *field.Spec) ([]byte, error) {
        if spec.Pad != nil {
            data = spec.Pad.Pad(data, spec.Length)
        }

        packed, err := spec.Enc.Encode(data)
        if err != nil {
            return nil, fmt.Errorf("failed to encode content: %w", err)
        }

        // Encode the length of the packed data, not the value length
        packedLength, err := spec.Pref.EncodeLength(spec.Length, len(packed))
        if err != nil {
            return nil, fmt.Errorf("failed to encode length: %w", err)
        }

        return append(packedLength, packed...), nil
    }),
    // Define a custom unpacker to decode the length of the packed data
    Unpacker: field.UnpackerFunc(func(data []byte, spec *field.Spec) ([]byte, int, error) {
        dataLen, prefBytes, err := spec.Pref.DecodeLength(spec.Length, data)
        if err != nil {
            return nil, 0, fmt.Errorf("failed to decode length: %w", err)
        }

        // Decode the packed data length
        raw, read, err := spec.Enc.Decode(data[prefBytes:], dataLen*2)
        if err != nil {
            return nil, 0, fmt.Errorf("failed to decode content: %w", err)
        }

        if spec.Pad != nil {
            raw = spec.Pad.Unpad(raw)
        }

        return raw, read + prefBytes, nil
    }),
})

fc.SetValue(123)

packed, err = fc.Pack()
require.NoError(t, err)

require.Equal(t, []byte{0x02, 0x01, 0x23}, packed)

In this case, the length is expected to be 2 bytes since 123 encoded in BCD is 0x01, 0x23. Thus, the length prefix is 0x02, indicating the length of the packed data is 2 bytes.

@alovak alovak requested a review from adamdecaf June 7, 2024 21:50
@alovak alovak changed the title Allow to have custom packer unpacker Allow setting custom field packer and unpacker Jun 10, 2024
@alovak alovak merged commit e9c44b8 into master Jun 11, 2024
9 checks passed
@alovak alovak deleted the allow-to-have-custom-packer-unpacker branch June 11, 2024 08:34
@alovak alovak mentioned this pull request Jun 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants