-
Notifications
You must be signed in to change notification settings - Fork 115
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
12 changed files
with
391 additions
and
181 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
Oops, something went wrong.