-
Notifications
You must be signed in to change notification settings - Fork 112
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
odd value parsing issue in ISOMessage #327
Comments
Could you please share your (Go) spec here? |
I've had exactly this problem and solved it by creating a custom Hex field and a custom BCD prefix: 35: custom.NewTrack2(&field.Spec{
Length: 37,
Description: "Track 2 Data",
Enc: encoding.Binary,
Pref: custom.BCD.LL,
}), I could'nt manage to use moov/iso8583 built in types (perhaps it is possible to do so). I can share you this implementation here if you want and moderators allow. |
The spec is as follows.
|
if they allows, I wanna see it. It will give idea at least. |
Hey! @anilgorgec , I'm not sure what this means: "Since ISO 8583 messages are often processed in hexadecimal or BCD format, they need to be byte-aligned." Do you mean some fields of the message are in the BCD format? Is your whole message encoded in HEX? Because BCD is used only for decimal numbers, I'm not sure how it can be used to encode whole message. In provided message:
I see that fields are in Also, please, check the length of the fields 22 and 24. In expected output you show 4 digits, but in the field spec, you set the length to 3. My guess is that you might use BCD encoding for all numeric fields, with BCD prefix and Binary for the rest. You have to check the ISO 8583 specification of your provider. |
@carlosmgc2003 sure, please share your implementation. I want to understand the case and to find out why it can't be done with existing encoders. |
Sure! For example: 4379960000125625D26012217630000000000 package custom
import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/moov-io/iso8583/utils"
"reflect"
"strings"
"github.com/moov-io/iso8583/field"
)
var _ field.Field = (*Track2)(nil)
var _ json.Marshaler = (*Track2)(nil)
var _ json.Unmarshaler = (*Track2)(nil)
// Track2 is a customization of github.com/moov-io/iso8583 Hex Field.
// this 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
// 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.
type Track2 struct {
value string
spec *field.Spec
}
func NewTrack2(spec *field.Spec) *Track2 {
return &Track2{
spec: spec,
}
}
// NewHexValue creates a new Track2 field with the given value The value is
// converted from hex to bytes before packing, so we don't validate that val is
// a valid hex string here.
func NewHexValue(val string) *Track2 {
return &Track2{
value: val,
}
}
func (f *Track2) Spec() *field.Spec {
return f.spec
}
func (f *Track2) SetSpec(spec *field.Spec) {
f.spec = spec
}
func (f *Track2) SetBytes(b []byte) error {
aux := strings.ToUpper(hex.EncodeToString(b))
if len(aux) > f.spec.Length {
aux = aux[:f.spec.Length]
}
f.value = aux
return nil
}
func (f *Track2) Bytes() ([]byte, error) {
if f == nil {
return nil, nil
}
return hex.DecodeString(f.value)
}
func (f *Track2) String() (string, error) {
if f == nil {
return "", nil
}
return f.value, nil
}
func (f *Track2) Value() string {
if f == nil {
return ""
}
return f.value
}
func (f *Track2) SetValue(v string) {
f.value = v
}
func (f *Track2) Pack() ([]byte, error) {
var data []byte
var err error
// Customizacion: si el string entrante tiene longitud impar concateno un 0 para volverla par.
if len(f.value)%2 != 0 {
data, err = hex.DecodeString(f.value + "0")
} else {
data, err = f.Bytes()
}
if err != nil {
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)
}
// Customizacion: pasamos el valor de longitud del String en lugar del Array de Nibbles (que es la mitad)
packedLength, err := f.spec.Pref.EncodeLength(f.spec.Length, len(f.value))
if err != nil {
return nil, fmt.Errorf("failed to encode length: %w", err)
}
return append(packedLength, packed...), nil
}
func (f *Track2) Unpack(data []byte) (int, error) {
// Requiere usar la implementacion de bcd prefix custom de ini para funcionar (bcd.go)
dataLen, prefBytes, err := f.spec.Pref.DecodeLength(f.spec.Length, data)
if err != nil {
return 0, fmt.Errorf("failed to decode length: %w", err)
}
raw, read, err := f.spec.Enc.Decode(data[prefBytes:], dataLen)
if err != nil {
return 0, fmt.Errorf("failed to decode content: %w", err)
}
if f.spec.Pad != nil {
raw = f.spec.Pad.Unpad(raw)
}
if err := f.SetBytes(raw); err != nil {
return 0, fmt.Errorf("failed to set bytes: %w", err)
}
return read + prefBytes, nil
}
// Deprecated. Use Marshal instead
func (f *Track2) SetData(data interface{}) error {
return f.Marshal(data)
}
func (f *Track2) Unmarshal(v interface{}) error {
switch val := v.(type) {
case reflect.Value:
if !val.CanSet() {
return fmt.Errorf("cannot set reflect.Value of type %s", val.Kind())
}
switch val.Kind() { //nolint:exhaustive
case reflect.String:
str, _ := f.String()
val.SetString(str)
case reflect.Slice:
buf, _ := f.Bytes()
val.SetBytes(buf)
default:
return fmt.Errorf("unsupported reflect.Value type: %s", val.Kind())
}
case *string:
*val, _ = f.String()
case *[]byte:
*val, _ = f.Bytes()
case *Track2:
val.value = f.value
default:
return fmt.Errorf("unsupported type: expected *Track2, *string, *[]byte, or reflect.Value, got %T", v)
}
return nil
}
func (f *Track2) Marshal(v interface{}) error {
if v == nil || reflect.ValueOf(v).IsZero() {
f.value = ""
return nil
}
switch v := v.(type) {
case *Track2:
f.value = v.value
case string:
f.value = v
case *string:
f.value = *v
case []byte:
f.SetBytes(v)
case *[]byte:
f.SetBytes(*v)
default:
return fmt.Errorf("data does not match required *Track2 or (string, *string, []byte, *[]byte) type")
}
return nil
}
func (f *Track2) MarshalJSON() ([]byte, error) {
data, err := f.String()
if err != nil {
return nil, utils.NewSafeError(err, "convert hex field into bytes")
}
bytes, err := json.Marshal(data)
if err != nil {
return nil, utils.NewSafeError(err, "failed to JSON marshal string to bytes")
}
return bytes, nil
}
func (f *Track2) UnmarshalJSON(b []byte) error {
var v string
err := json.Unmarshal(b, &v)
if err != nil {
return utils.NewSafeError(err, "failed to JSON unmarshal bytes to string")
}
f.value = v
return nil
} package custom
import (
"fmt"
"strconv"
"strings"
"github.com/moov-io/iso8583/encoding"
"github.com/moov-io/iso8583/prefix"
"github.com/yerden/go-util/bcd"
)
type bcdVarPrefixer struct {
Digits int
}
// BCD is a customized implementation for Prisma of the BCD prefix of github.com/moov-io/iso8583
// it is modified to support nibble hex data arrays. If the length is not even, it decodes to a valid
// array length (dataLen + 1) / 2
var BCD = prefix.Prefixers{
Fixed: &bcdFixedPrefixer{},
L: &bcdVarPrefixer{1},
LL: &bcdVarPrefixer{2},
LLL: &bcdVarPrefixer{3},
LLLL: &bcdVarPrefixer{4},
LLLLL: &bcdVarPrefixer{5},
LLLLLL: &bcdVarPrefixer{6},
}
func (p *bcdVarPrefixer) EncodeLength(maxLen, dataLen int) ([]byte, error) {
if dataLen > maxLen {
return nil, fmt.Errorf("field length: %d is larger than maximum: %d", dataLen, maxLen)
}
if len(strconv.Itoa(dataLen)) > p.Digits {
return nil, fmt.Errorf("number of digits in length: %d exceeds: %d", dataLen, p.Digits)
}
strLen := fmt.Sprintf("%0*d", p.Digits, dataLen)
res, err := encoding.BCD.Encode([]byte(strLen))
if err != nil {
return nil, err
}
return res, nil
}
func (p *bcdVarPrefixer) DecodeLength(maxLen int, data []byte) (int, int, error) {
length := bcd.EncodedLen(p.Digits)
if len(data) < length {
return 0, 0, fmt.Errorf("length mismatch: want to read %d bytes, get only %d", length, len(data))
}
bDigits, _, err := encoding.BCD.Decode(data[:length], p.Digits)
if err != nil {
return 0, 0, err
}
dataLen, err := strconv.Atoi(string(bDigits))
if err != nil {
return 0, 0, err
}
if dataLen > maxLen {
return 0, 0, fmt.Errorf("data length %d is larger than maximum %d", dataLen, maxLen)
}
// Customizacion: Dividimos la longitud por dos para pasar de BCD (nibbles) a BYTES
if dataLen%2 != 0 {
dataLen += 1
}
// Luego retornamos la longitud del array necesario para decodificar el campo (dataLen / 2)
return dataLen / 2, length, nil
}
func (p *bcdVarPrefixer) Inspect() string {
return fmt.Sprintf("BCD.%s", strings.Repeat("L", p.Digits))
}
type bcdFixedPrefixer struct {
}
func (p *bcdFixedPrefixer) EncodeLength(fixLen, dataLen int) ([]byte, error) {
if dataLen > fixLen {
return nil, fmt.Errorf("field length: %d should be fixed: %d", dataLen, fixLen)
}
return []byte{}, nil
}
// Returns number of characters that should be decoded
func (p *bcdFixedPrefixer) DecodeLength(fixLen int, data []byte) (int, int, error) {
return fixLen, 0, nil
}
func (p *bcdFixedPrefixer) Inspect() string {
return "BCD.Fixed"
} |
@alovak , 22 and 24 field are presented in the document as 3 digit. it looks 4 digit because of one byte can't be presented 1 digit. |
@anilgorgec you might try to leverage what I have implemented in this PR: #336 to see if it fixes your issue. Specifically the packer/unpacker implementation that handles even/odd length track 2 data. @carlosmgc2003 you might try a spec something like this:
to see if it fixes your issue |
Hello,
I have a question regarding odd numbers. for example the Track 2 Data field has a length of 37 characters, which is an odd number. Since ISO 8583 messages are often processed in hexadecimal or BCD format, they need to be byte-aligned.
I tried to parse the iso message in the example below.but I could not get the output as expected output.
Can you help me with this issue?
ISO Message:
020030200580208000000000000000000015001079890071002100375111111211111111d11110000000000000000f5445535430303031
output
expected output
The text was updated successfully, but these errors were encountered: