From df3dc8a5eb05d11192d81b6e9b782bdf88610c26 Mon Sep 17 00:00:00 2001 From: Kufa Date: Thu, 26 Jan 2023 12:00:35 -0300 Subject: [PATCH 1/2] Add BitmapOptions and use it in Bitmap --- field/bitmap.go | 52 +++++++++++++++++++++++++++++++++++-------------- field/spec.go | 3 +++ 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/field/bitmap.go b/field/bitmap.go index dfb38c91..9a26f5cf 100644 --- a/field/bitmap.go +++ b/field/bitmap.go @@ -6,23 +6,30 @@ import ( "github.com/moov-io/iso8583/utils" ) -const minBitmapLength = 8 // 64 bit, 8 bytes, or 16 hex digits -const maxBitmaps = 3 +const ( + defaultBitmapSize = 8 + defaultMaxBitmaps = 3 +) var _ Field = (*Bitmap)(nil) // NOTE: Bitmap does not support JSON encoding or decoding. type Bitmap struct { - spec *Spec - bitmap *utils.Bitmap - data *Bitmap + spec *Spec + bitmap *utils.Bitmap + data *Bitmap + options *BitmapOptions +} + +type BitmapOptions struct { + MaxBitmaps int + BitmapSize int } func NewBitmap(spec *Spec) *Bitmap { - return &Bitmap{ - spec: spec, - bitmap: utils.NewBitmap(64 * maxBitmaps), - } + bm := &Bitmap{} + bm.SetSpec(spec) + return bm } func (f *Bitmap) Spec() *Spec { @@ -30,7 +37,22 @@ func (f *Bitmap) Spec() *Spec { } func (f *Bitmap) SetSpec(spec *Spec) { + bmo := (*BitmapOptions)(nil) + if spec != nil { + bmo = spec.BitmapOptions + } + if bmo == nil { + bmo = &BitmapOptions{} + } + if bmo.MaxBitmaps == 0 { + bmo.MaxBitmaps = defaultMaxBitmaps + } + if bmo.BitmapSize == 0 { + bmo.BitmapSize = defaultBitmapSize + } + f.options = bmo f.spec = spec + f.Reset() } func (f *Bitmap) SetBytes(b []byte) error { @@ -66,7 +88,7 @@ func (f *Bitmap) Pack() ([]byte, error) { return nil, fmt.Errorf("failed to retrieve bytes: %w", err) } - data = data[0 : 8*count] + data = data[0 : f.options.BitmapSize*count] packed, err := f.spec.Enc.Encode(data) if err != nil { @@ -81,7 +103,7 @@ func (f *Bitmap) Pack() ([]byte, error) { // if secondary bitmap presents (bit 1 is set) we return 16 bytes (or 32 for hex encoding) // and so on for maxBitmaps func (f *Bitmap) Unpack(data []byte) (int, error) { - minLen, _, err := f.spec.Pref.DecodeLength(minBitmapLength, data) + minLen, _, err := f.spec.Pref.DecodeLength(f.options.BitmapSize, data) if err != nil { return 0, fmt.Errorf("failed to decode length: %w", err) } @@ -90,7 +112,7 @@ func (f *Bitmap) Unpack(data []byte) (int, error) { read := 0 // read max - for i := 0; i < maxBitmaps; i++ { + for i := 0; i < f.options.MaxBitmaps; i++ { decoded, readDecoded, err := f.spec.Enc.Decode(data[read:], minLen) if err != nil { return 0, fmt.Errorf("failed to decode content for %d bitmap: %w", i+1, err) @@ -150,7 +172,7 @@ func (f *Bitmap) Marshal(data interface{}) error { } func (f *Bitmap) Reset() { - f.bitmap = utils.NewBitmap(64 * maxBitmaps) + f.bitmap = utils.NewBitmap(f.options.BitmapSize * 8 * f.options.MaxBitmaps) } func (f *Bitmap) Set(i int) { @@ -167,7 +189,7 @@ func (f *Bitmap) Len() int { func (f *Bitmap) bitmapsCount() int { count := 1 - for i := 0; i < maxBitmaps; i++ { + for i := 0; i < f.options.MaxBitmaps-1; i++ { if f.IsSet(i*64 + 1) { count += 1 } @@ -184,7 +206,7 @@ func (f *Bitmap) setBitmapFields() bool { // bitmap bit 65 // start from the 2nd bitmap as for the 1st bitmap we don't need to set any bits - for bitmapIndex := 2; bitmapIndex <= maxBitmaps; bitmapIndex++ { + for bitmapIndex := 2; bitmapIndex <= f.options.MaxBitmaps; bitmapIndex++ { // are there fields for this (bitmapIndex) bitmap? bitmapStart := (bitmapIndex-1)*64 + 2 // we skip firt bit as it's for the next bitmap diff --git a/field/spec.go b/field/spec.go index 67d8b626..5ba4d070 100644 --- a/field/spec.go +++ b/field/spec.go @@ -55,6 +55,9 @@ type Spec struct { // Subfields defines the subfields held within the field. Only // applicable to composite field types. Subfields map[string]Field + // BitmapOptions could be used on a bitmap field, and is optional. + // Contains options that modifies the behaviour of the bitmap. + BitmapOptions *BitmapOptions } func NewSpec(length int, desc string, enc encoding.Encoder, pref prefix.Prefixer) *Spec { From e53af044be066b27cacefb2058079e301463f153 Mon Sep 17 00:00:00 2001 From: Kufa Date: Thu, 26 Jan 2023 15:04:24 -0300 Subject: [PATCH 2/2] Add composite capability to handle bitmaps --- field/composite.go | 148 +++++++++++++++++++++++++++++++++++++--- field/composite_test.go | 8 +-- 2 files changed, 143 insertions(+), 13 deletions(-) diff --git a/field/composite.go b/field/composite.go index d9a05e9b..5485c19e 100644 --- a/field/composite.go +++ b/field/composite.go @@ -6,12 +6,15 @@ import ( "fmt" "reflect" "regexp" + "strconv" "github.com/moov-io/iso8583/padding" "github.com/moov-io/iso8583/sort" "github.com/moov-io/iso8583/utils" ) +const compositeBitmapKey = "0" + var _ Field = (*Composite)(nil) var _ json.Marshaler = (*Composite)(nil) var _ json.Unmarshaler = (*Composite)(nil) @@ -58,6 +61,8 @@ var _ json.Unmarshaler = (*Composite)(nil) type Composite struct { spec *Spec + bitmap *Bitmap + orderedSpecFieldTags []string // stores all fields according to the spec @@ -115,7 +120,10 @@ func (f *Composite) SetSpec(spec *Spec) { panic(err) } f.spec = spec - f.orderedSpecFieldTags = orderedKeys(spec.Subfields, spec.Tag.Sort) + + if spec.Tag != nil && spec.Tag.Sort != nil { + f.orderedSpecFieldTags = orderedKeys(spec.Subfields, spec.Tag.Sort) + } } func (f *Composite) Unmarshal(v interface{}) error { @@ -290,6 +298,18 @@ func (f *Composite) Bytes() ([]byte, error) { return f.pack() } +// Bitmap returns an instantiation of Bitmap using the BitmapSpec from the spec attribute. +func (f *Composite) Bitmap() *Bitmap { + if f.bitmap == nil { + if field, ok := f.subfields[compositeBitmapKey]; ok { + f.bitmap = field.(*Bitmap) + } + + } + + return f.bitmap +} + // String iterates over the receiver's subfields, packs them and converts the // result to a string. The result does not incorporate the encoded aggregate // length of the subfields in the prefix. @@ -342,6 +362,53 @@ func (f *Composite) UnmarshalJSON(b []byte) error { } func (f *Composite) pack() ([]byte, error) { + if f.Bitmap() != nil { + return f.packSubfieldsByBitmap() + } + + return f.packSubfieldsByTag() +} + +func (f *Composite) packSubfieldsByBitmap() ([]byte, error) { + packed := []byte{} + f.Bitmap().Reset() + + ids, err := f.packableFieldIDs() + if err != nil { + return nil, fmt.Errorf("failed to pack message: %w", err) + } + + for _, id := range ids { + // index 0 is for bitmap + // regular field number startd from index 1 + idInt, err := strconv.Atoi(id) + if err != nil { + return nil, fmt.Errorf("failed to pack message: %w", err) + } + + if idInt < 1 { + continue + } + f.Bitmap().Set(idInt) + } + + // pack fields + for _, i := range ids { + field, ok := f.subfields[i] + if !ok { + return nil, fmt.Errorf("failed to pack field %v: no specification found", i) + } + packedField, err := field.Pack() + if err != nil { + return nil, fmt.Errorf("failed to pack field %v (%s): %w", i, field.Spec().Description, err) + } + packed = append(packed, packedField...) + } + + return packed, nil +} + +func (f *Composite) packSubfieldsByTag() ([]byte, error) { packed := []byte{} for _, tag := range f.orderedSpecFieldTags { field, ok := f.subfields[tag] @@ -377,6 +444,9 @@ func (f *Composite) pack() ([]byte, error) { } func (f *Composite) unpack(data []byte, isVariableLength bool) (int, error) { + if f.Bitmap() != nil { + return f.unpackSubfieldsByBitmap(data) + } if f.spec.Tag.Enc != nil { return f.unpackSubfieldsByTag(data) } @@ -442,21 +512,63 @@ func (f *Composite) unpackSubfieldsByTag(data []byte) (int, error) { return offset, nil } +func (f *Composite) unpackSubfieldsByBitmap(data []byte) (int, error) { + var off int + + // reset fields that were set + f.setSubfields = map[string]struct{}{} + + // unpack Bitmap + read, err := f.Bitmap().Unpack(data[off:]) + if err != nil { + return 0, fmt.Errorf("failed to unpack bitmap: %w", err) + } + + off += read + + for i := 1; i <= f.Bitmap().Len(); i++ { + if f.Bitmap().IsSet(i) { + iStr := fmt.Sprintf("%v", i) + + fl, ok := f.subfields[iStr] + if !ok { + return 0, fmt.Errorf("failed to unpack field %v: no specification found", iStr) + } + + read, err = fl.Unpack(data[off:]) + if err != nil { + return 0, fmt.Errorf("failed to unpack field %v (%s): %w", iStr, fl.Spec().Description, err) + } + + f.setSubfields[iStr] = struct{}{} + + off += read + } + } + + return off, nil +} + func validateCompositeSpec(spec *Spec) error { - if spec.Tag == nil || spec.Tag.Sort == nil { - return fmt.Errorf("Composite spec requires a Tag.Sort function to be defined") + var bm *Bitmap + if value, ok := spec.Subfields[compositeBitmapKey]; ok { + bm = value.(*Bitmap) } - if spec.Pad != nil && spec.Pad != padding.None { - return fmt.Errorf("Composite spec only supports nil or None spec padding values") + + if (spec.Tag == nil || spec.Tag.Sort == nil) && bm == nil { + return fmt.Errorf("Composite spec requires a Tag.Sort function or a BitmapSpec to be defined") } - if spec.Enc != nil { - return fmt.Errorf("Composite spec only supports a nil Enc value") + if spec.Tag != nil && spec.Tag.Sort != nil && bm != nil { + return fmt.Errorf("Composite spec doesn't support Tag.Sort function when a BitmapSpec is defined") } if spec.Tag != nil && spec.Tag.Enc == nil && spec.Tag.Length > 0 { return fmt.Errorf("Composite spec requires a Tag.Enc to be defined if Tag.Length > 0") } - if spec.Tag.Sort == nil { - return fmt.Errorf("Composite spec requires a Tag.Sort function to be defined") + if spec.Pad != nil && spec.Pad != padding.None { + return fmt.Errorf("Composite spec only supports nil or None spec padding values") + } + if spec.Enc != nil { + return fmt.Errorf("Composite spec only supports a nil Enc value") } return nil } @@ -488,3 +600,21 @@ func getFieldIndexOrTag(field reflect.StructField) (string, error) { return "", nil } + +func (f *Composite) packableFieldIDs() ([]string, error) { + // Index 0 represent bitmap which is always populated. + populatedFieldIDs := []string{compositeBitmapKey} + + for id := range f.setSubfields { + // represents the bitmap + if id == compositeBitmapKey { + continue + } + + populatedFieldIDs = append(populatedFieldIDs, id) + } + + sort.StringsByInt(populatedFieldIDs) + + return populatedFieldIDs, nil +} diff --git a/field/composite_test.go b/field/composite_test.go index d2c9cba8..44e7076c 100644 --- a/field/composite_test.go +++ b/field/composite_test.go @@ -979,8 +979,8 @@ func TestCompositePanicsOnSpecValidationFailures(t *testing.T) { spec *Spec }{ { - desc: "panics on nil Tag being defined in spec", - err: "Composite spec requires a Tag.Sort function to be defined", + desc: "panics on nil Tag and no Bitmap being defined in spec", + err: "Composite spec requires a Tag.Sort function or a BitmapSpec to be defined", spec: &Spec{ Length: 6, Pref: prefix.ASCII.Fixed, @@ -989,8 +989,8 @@ func TestCompositePanicsOnSpecValidationFailures(t *testing.T) { }, }, { - desc: "panics on nil Tag.Sort being defined in spec", - err: "Composite spec requires a Tag.Sort function to be defined", + desc: "panics on nil Tag and no Bitmap being defined in spec", + err: "Composite spec requires a Tag.Sort function or a BitmapSpec to be defined", spec: &Spec{ Length: 6, Pref: prefix.ASCII.Fixed,