From 11ed5970817dbdb74cdf9c2fd12c53d80c76403a Mon Sep 17 00:00:00 2001 From: Daniel Akinola Date: Mon, 1 Aug 2022 14:19:09 +0100 Subject: [PATCH] Add EBCDIC 1047 encoder & prefixer (#190) * Add EBCDIC 1047 encoder & prefixer * add length check when decoding Co-authored-by: dakinola --- encoding/ebcdic1047.go | 43 ++++ encoding/ebcdic1047_test.go | 281 +++++++++++++++++++++++ go.mod | 1 + go.sum | 3 + prefix/ebcdic1047.go | 82 +++++++ prefix/ebcdic1047_test.go | 428 ++++++++++++++++++++++++++++++++++++ 6 files changed, 838 insertions(+) create mode 100644 encoding/ebcdic1047.go create mode 100644 encoding/ebcdic1047_test.go create mode 100644 prefix/ebcdic1047.go create mode 100644 prefix/ebcdic1047_test.go diff --git a/encoding/ebcdic1047.go b/encoding/ebcdic1047.go new file mode 100644 index 00000000..2792bc1b --- /dev/null +++ b/encoding/ebcdic1047.go @@ -0,0 +1,43 @@ +package encoding + +import ( + "fmt" + + "github.com/moov-io/iso8583/utils" + xencoding "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" +) + +// EBCDIC1047 is an encoder for EBCDIC characters using IBM Code Page 1047. +var EBCDIC1047 Encoder = &ebcdic1047Encoder{ + encoder: charmap.CodePage1047.NewEncoder(), + decoder: charmap.CodePage1047.NewDecoder(), +} + +type ebcdic1047Encoder struct { + encoder *xencoding.Encoder + decoder *xencoding.Decoder +} + +func (e ebcdic1047Encoder) Encode(data []byte) ([]byte, error) { + bytes, err := e.encoder.Bytes(data) + if err != nil { + return nil, utils.NewSafeError(err, "failed to encode EBCDIC") + } + return bytes, nil +} + +func (e ebcdic1047Encoder) Decode(data []byte, length int) ([]byte, int, error) { + if len(data) < length { + return nil, 0, fmt.Errorf( + "not enough data to decode. expected len %d, got %d", length, len(data), + ) + } + + data = data[:length] + out, err := e.decoder.Bytes(data) + if err != nil { + return nil, 0, utils.NewSafeError(err, "failed to decode EBCDIC") + } + return out, length, nil +} diff --git a/encoding/ebcdic1047_test.go b/encoding/ebcdic1047_test.go new file mode 100644 index 00000000..b65ea300 --- /dev/null +++ b/encoding/ebcdic1047_test.go @@ -0,0 +1,281 @@ +package encoding + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// EBCDIC Code Page 1047 Encodings, taken from https://www.ibm.com/docs/en/personal-communications/5.9?topic=pages-1047103-latin-open-systems +var ebcdic1047CharacterEncodings = map[string]byte{ + "â": 0x42, + "ä": 0x43, + "à": 0x44, + "á": 0x45, + "ã": 0x46, + "å": 0x47, + "ç": 0x48, + "ñ": 0x49, + "¢": 0x4A, + ".": 0x4B, + "<": 0x4C, + "(": 0x4D, + "+": 0x4E, + "|": 0x4F, + "&": 0x50, + "é": 0x51, + "ê": 0x52, + "ë": 0x53, + "è": 0x54, + "í": 0x55, + "î": 0x56, + "ï": 0x57, + "ì": 0x58, + "ß": 0x59, + "!": 0x5A, + "$": 0x5B, + "*": 0x5C, + ")": 0x5D, + ";": 0x5E, + "^": 0x5F, + "-": 0x60, + "/": 0x61, + "Â": 0x62, + "Ä": 0x63, + "À": 0x64, + "Á": 0x65, + "Ã": 0x66, + "Å": 0x67, + "Ç": 0x68, + "Ñ": 0x69, + "¦": 0x6A, + ",": 0x6B, + "%": 0x6C, + "_": 0x6D, + ">": 0x6E, + "?": 0x6F, + "ø": 0x70, + "É": 0x71, + "Ê": 0x72, + "Ë": 0x73, + "È": 0x74, + "Í": 0x75, + "Î": 0x76, + "Ï": 0x77, + "Ì": 0x78, + "`": 0x79, + ":": 0x7A, + "#": 0x7B, + "@": 0x7C, + "'": 0x7D, + "=": 0x7E, + "\"": 0x7F, + "Ø": 0x80, + "a": 0x81, + "b": 0x82, + "c": 0x83, + "d": 0x84, + "e": 0x85, + "f": 0x86, + "g": 0x87, + "h": 0x88, + "i": 0x89, + "«": 0x8A, + "»": 0x8B, + "ð": 0x8C, + "ý": 0x8D, + "þ": 0x8E, + "±": 0x8F, + "°": 0x90, + "j": 0x91, + "k": 0x92, + "l": 0x93, + "m": 0x94, + "n": 0x95, + "o": 0x96, + "p": 0x97, + "q": 0x98, + "r": 0x99, + "ª": 0x9A, + "º": 0x9B, + "æ": 0x9C, + "¸": 0x9D, + "Æ": 0x9E, + "¤": 0x9F, + "µ": 0xA0, + "~": 0xA1, + "s": 0xA2, + "t": 0xA3, + "u": 0xA4, + "v": 0xA5, + "w": 0xA6, + "x": 0xA7, + "y": 0xA8, + "z": 0xA9, + "¡": 0xAA, + "¿": 0xAB, + "Ð": 0xAC, + "[": 0xAD, + "Þ": 0xAE, + "®": 0xAF, + "¬": 0xB0, + "£": 0xB1, + "¥": 0xB2, + "·": 0xB3, + "©": 0xB4, + "§": 0xB5, + "¶": 0xB6, + "¼": 0xB7, + "½": 0xB8, + "¾": 0xB9, + "Ý": 0xBA, + "¨": 0xBB, + "¯": 0xBC, + "]": 0xBD, + "´": 0xBE, + "×": 0xBF, + "{": 0xC0, + "A": 0xC1, + "B": 0xC2, + "C": 0xC3, + "D": 0xC4, + "E": 0xC5, + "F": 0xC6, + "G": 0xC7, + "H": 0xC8, + "I": 0xC9, + "ô": 0xCB, + "ö": 0xCC, + "ò": 0xCD, + "ó": 0xCE, + "õ": 0xCF, + "}": 0xD0, + "J": 0xD1, + "K": 0xD2, + "L": 0xD3, + "M": 0xD4, + "N": 0xD5, + "O": 0xD6, + "P": 0xD7, + "Q": 0xD8, + "R": 0xD9, + "¹": 0xDA, + "û": 0xDB, + "ü": 0xDC, + "ù": 0xDD, + "ú": 0xDE, + "ÿ": 0xDF, + "\\": 0xE0, + "÷": 0xE1, + "S": 0xE2, + "T": 0xE3, + "U": 0xE4, + "V": 0xE5, + "W": 0xE6, + "X": 0xE7, + "Y": 0xE8, + "Z": 0xE9, + "²": 0xEA, + "Ô": 0xEB, + "Ö": 0xEC, + "Ò": 0xED, + "Ó": 0xEE, + "Õ": 0xEF, + "0": 0xF0, + "1": 0xF1, + "2": 0xF2, + "3": 0xF3, + "4": 0xF4, + "5": 0xF5, + "6": 0xF6, + "7": 0xF7, + "8": 0xF8, + "9": 0xF9, + "³": 0xFA, + "Û": 0xFB, + "Ü": 0xFC, + "Ù": 0xFD, + "Ú": 0xFE, +} + +// some randomly-chosen phrases with some interesting characters +var knownEncodings = []struct { + Phrase string + Encoding []byte +}{ + { + Phrase: "hello, world!", + Encoding: []byte{0x88, 0x85, 0x93, 0x93, 0x96, 0x6B, 0x40, 0xA6, 0x96, 0x99, 0x93, 0x84, 0x5A}, + }, + { + Phrase: "¿Cómo estás?", + Encoding: []byte{0xAB, 0xC3, 0xCE, 0x94, 0x96, 0x40, 0x85, 0xA2, 0xA3, 0x45, 0xA2, 0x6F}, + }, + { + Phrase: "Ágætis byrjun", + Encoding: []byte{0x65, 0x87, 0x9C, 0xA3, 0x89, 0xA2, 0x40, 0x82, 0xA8, 0x99, 0x91, 0xA4, 0x95}, + }, +} + +func TestEBCDIC1047SingleCharacterEncode(t *testing.T) { + t.Parallel() + for character, expectedEncoding := range ebcdic1047CharacterEncodings { + encoding, err := EBCDIC1047.Encode([]byte(character)) + require.NoError(t, err) + require.Len(t, encoding, 1) + require.Equal(t, expectedEncoding, encoding[0]) + } +} + +func TestEBCDIC1047SingleCharacterDecode(t *testing.T) { + t.Parallel() + for expectedCharacter, byteChar := range ebcdic1047CharacterEncodings { + decoding, length, err := EBCDIC1047.Decode([]byte{byteChar}, 1) + require.NoError(t, err) + require.Equal(t, 1, length) + require.Equal(t, expectedCharacter, string(decoding)) + } +} + +func TestEBCDIC1047Encode(t *testing.T) { + t.Parallel() + for _, testCase := range knownEncodings { + encoding, err := EBCDIC1047.Encode([]byte(testCase.Phrase)) + require.NoError(t, err) + require.Equal(t, testCase.Encoding, encoding) + } +} + +func TestEBCDIC1047Decode(t *testing.T) { + t.Parallel() + + t.Run("errors on invalid data length", func(t *testing.T) { + t.Parallel() + + decoding, length, err := EBCDIC1047.Decode([]byte("test"), 5) + require.Nil(t, decoding) + require.Zero(t, length) + require.EqualError(t, err, "not enough data to decode. expected len 5, got 4") + }) + + t.Run("decode whole string", func(t *testing.T) { + t.Parallel() + for _, testCase := range knownEncodings { + decoding, length, err := EBCDIC1047.Decode(testCase.Encoding, len(testCase.Encoding)) + require.NoError(t, err) + require.Equal(t, len(testCase.Encoding), length) + require.Equal(t, testCase.Phrase, string(decoding)) + } + }) + + t.Run("decode partial string", func(t *testing.T) { + t.Parallel() + messageToDecode := []byte{0xF6, 0x40, 0xBF, 0x40, 0xF9, 0x40, 0x7E, 0x40, 0xF4, 0xF2} // 6 × 9 = 42 + lengthToDecode := 7 + expectedPartialMessage := "6 × 9 =" // first 7 characters + decoding, length, err := EBCDIC1047.Decode(messageToDecode, lengthToDecode) + require.NoError(t, err) + require.Equal(t, lengthToDecode, length) + require.Equal(t, expectedPartialMessage, string(decoding)) + }) +} diff --git a/go.mod b/go.mod index ccd2fdcb..40970380 100644 --- a/go.mod +++ b/go.mod @@ -7,5 +7,6 @@ require ( github.com/kr/pretty v0.1.0 // indirect github.com/stretchr/testify v1.8.0 github.com/yerden/go-util v1.1.4 + golang.org/x/text v0.3.7 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index 31f03ce3..ddc12b43 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,9 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/yerden/go-util v1.1.4 h1:jd8JyjLHzpEs1ZZQzDkfRgosDtXp/BtIAV1kpNjVTtw= github.com/yerden/go-util v1.1.4/go.mod h1:3HeLrvtkEeAv67ARostM9Yn0DcAVqgJ3uAiCuywEEXk= golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/prefix/ebcdic1047.go b/prefix/ebcdic1047.go new file mode 100644 index 00000000..05530235 --- /dev/null +++ b/prefix/ebcdic1047.go @@ -0,0 +1,82 @@ +package prefix + +import ( + "fmt" + "strconv" + "strings" + + "github.com/moov-io/iso8583/encoding" +) + +var EBCDIC1047 = Prefixers{ + Fixed: &ebcdic1047FixedPrefixer{}, + L: &ebcdic1047Prefixer{1}, + LL: &ebcdic1047Prefixer{2}, + LLL: &ebcdic1047Prefixer{3}, + LLLL: &ebcdic1047Prefixer{4}, +} + +type ebcdic1047Prefixer struct { + digits int +} + +func (p *ebcdic1047Prefixer) 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 data [%d] exceeds its maximum indicator [%d]", dataLen, p.digits) + } + + strLen := fmt.Sprintf("%0*d", p.digits, dataLen) + res, err := encoding.EBCDIC1047.Encode([]byte(strLen)) + if err != nil { + return nil, err + } + return res, nil +} + +func (p *ebcdic1047Prefixer) DecodeLength(maxLen int, data []byte) (int, int, error) { + if len(data) < p.digits { + return 0, 0, fmt.Errorf("not enough data length [%d] to read [%d] byte digits", len(data), p.digits) + } + + decodedData, _, err := encoding.EBCDIC1047.Decode(data[:p.digits], p.digits) + if err != nil { + return 0, 0, err + } + + dataLen, err := strconv.Atoi(string(decodedData)) + if err != nil { + return 0, 0, fmt.Errorf("length [%s] is not a valid integer length field", string(decodedData)) + } + + if dataLen > maxLen { + return 0, 0, fmt.Errorf("data length [%d] is larger than maximum [%d]", dataLen, maxLen) + } + + return dataLen, p.digits, nil +} + +func (p *ebcdic1047Prefixer) Inspect() string { + return fmt.Sprintf("EBCDIC.%s", strings.Repeat("L", p.digits)) +} + +type ebcdic1047FixedPrefixer struct{} + +func (p *ebcdic1047FixedPrefixer) 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 +} + +func (p *ebcdic1047FixedPrefixer) DecodeLength(fixLen int, data []byte) (int, int, error) { + return fixLen, 0, nil +} + +func (p *ebcdic1047FixedPrefixer) Inspect() string { + return "EBCDIC.Fixed" +} diff --git a/prefix/ebcdic1047_test.go b/prefix/ebcdic1047_test.go new file mode 100644 index 00000000..405bf81b --- /dev/null +++ b/prefix/ebcdic1047_test.go @@ -0,0 +1,428 @@ +package prefix + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// Byte representations of EBCDIC (Code Page 1047) codes for digits +const ( + ebcdic1047_0 = 0xF0 + ebcdic1047_1 = 0xF1 + ebcdic1047_2 = 0xF2 + ebcdic1047_3 = 0xF3 + ebcdic1047_4 = 0xF4 + ebcdic1047_5 = 0xF5 + ebcdic1047_6 = 0xF6 + ebcdic1047_7 = 0xF7 + ebcdic1047_8 = 0xF8 + ebcdic1047_9 = 0xF9 +) + +// Some non-digit EBCDIC-encoded (Code Page 1047) characters +const ( + ebcdic1047l = 0x93 // "l" + ebcdic1047Plus = 0x4E // "+" + ebcdic1047Dot = 0x4B // "." +) + +func TestEBCDIC1047PrefixersEncode(t *testing.T) { + t.Parallel() + for _, testCase := range []struct { + prefixer Prefixer + maxLen int + dataLen int + expectedOutput []byte + }{ + { + prefixer: EBCDIC1047.Fixed, + maxLen: 64, + dataLen: 64, + expectedOutput: []byte{}, + }, + { + prefixer: EBCDIC1047.L, + maxLen: 8, + dataLen: 8, + expectedOutput: []byte{ebcdic1047_8}, + }, + { + prefixer: EBCDIC1047.LL, + maxLen: 14, + dataLen: 9, + expectedOutput: []byte{ebcdic1047_0, ebcdic1047_9}, + }, + { + prefixer: EBCDIC1047.LL, + maxLen: 32, + dataLen: 16, + expectedOutput: []byte{ebcdic1047_1, ebcdic1047_6}, + }, + { + prefixer: EBCDIC1047.LLL, + maxLen: 128, + dataLen: 1, + expectedOutput: []byte{ebcdic1047_0, ebcdic1047_0, ebcdic1047_1}, + }, + { + prefixer: EBCDIC1047.LLL, + maxLen: 256, + dataLen: 81, + expectedOutput: []byte{ebcdic1047_0, ebcdic1047_8, ebcdic1047_1}, + }, + { + prefixer: EBCDIC1047.LLL, + maxLen: 512, + dataLen: 512, + expectedOutput: []byte{ebcdic1047_5, ebcdic1047_1, ebcdic1047_2}, + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 1024, + dataLen: 5, + expectedOutput: []byte{ebcdic1047_0, ebcdic1047_0, ebcdic1047_0, ebcdic1047_5}, + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 2048, + dataLen: 66, + expectedOutput: []byte{ebcdic1047_0, ebcdic1047_0, ebcdic1047_6, ebcdic1047_6}, + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 4096, + dataLen: 124, + expectedOutput: []byte{ebcdic1047_0, ebcdic1047_1, ebcdic1047_2, ebcdic1047_4}, + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 8192, + dataLen: 5432, + expectedOutput: []byte{ebcdic1047_5, ebcdic1047_4, ebcdic1047_3, ebcdic1047_2}, + }, + } { + encoded, err := testCase.prefixer.EncodeLength(testCase.maxLen, testCase.dataLen) + require.NoError(t, err) + require.Equal(t, testCase.expectedOutput, encoded) + } +} + +func TestEBCDIC1047PrefixersEncodeErrors(t *testing.T) { + t.Parallel() + + t.Run("data longer than maximum allowed length", func(t *testing.T) { + t.Parallel() + for _, testCase := range []struct { + prefixer Prefixer + maxLen int + dataLen int + expectedError string + }{ + { + prefixer: EBCDIC1047.L, + maxLen: 8, + dataLen: 9, + expectedError: "field length [9] is larger than maximum [8]", + }, + { + prefixer: EBCDIC1047.LL, + maxLen: 52, + dataLen: 73, + expectedError: "field length [73] is larger than maximum [52]", + }, + { + prefixer: EBCDIC1047.LLL, + maxLen: 512, + dataLen: 999, + expectedError: "field length [999] is larger than maximum [512]", + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 1024, + dataLen: 2048, + expectedError: "field length [2048] is larger than maximum [1024]", + }, + } { + encoded, err := testCase.prefixer.EncodeLength(testCase.maxLen, testCase.dataLen) + require.Nil(t, encoded) + require.EqualError(t, err, testCase.expectedError) + } + }) + + t.Run("length has too many digits", func(t *testing.T) { + // N.B. this error case should never be reached, as the maxLen of any field should be + // within the bounds set on the number of digits required to represent this number. This is + // only possible in incorrectly defined schemes. + t.Parallel() + for _, testCase := range []struct { + prefixer Prefixer + maxLen int + dataLen int + expectedError string + }{ + { + prefixer: EBCDIC1047.L, + maxLen: 52, + dataLen: 10, + expectedError: "number of digits in data [10] exceeds its maximum indicator [1]", + }, + { + prefixer: EBCDIC1047.LL, + maxLen: 101, + dataLen: 100, + expectedError: "number of digits in data [100] exceeds its maximum indicator [2]", + }, + { + prefixer: EBCDIC1047.LLL, + maxLen: 1333, + dataLen: 1001, + expectedError: "number of digits in data [1001] exceeds its maximum indicator [3]", + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 11111, + dataLen: 10908, + expectedError: "number of digits in data [10908] exceeds its maximum indicator [4]", + }, + } { + encoded, err := testCase.prefixer.EncodeLength(testCase.maxLen, testCase.dataLen) + require.Nil(t, encoded) + require.EqualError(t, err, testCase.expectedError) + } + }) + + t.Run("fixed length error", func(t *testing.T) { + t.Parallel() + encoded, err := EBCDIC1047.Fixed.EncodeLength(128, 127) + require.Nil(t, encoded) + require.EqualError(t, err, "field length [127] should be fixed [128]") + }) +} + +func TestEBCDIC1047PrefixersDecode(t *testing.T) { + t.Parallel() + for _, testCase := range []struct { + prefixer Prefixer + maxLen int + data []byte + expectedOutput int + expectedRead int + }{ + { + prefixer: EBCDIC1047.Fixed, + maxLen: 64, + data: []byte{}, + expectedOutput: 64, + expectedRead: 0, + }, + { + prefixer: EBCDIC1047.L, + maxLen: 8, + data: []byte{ebcdic1047_7}, + expectedOutput: 7, + expectedRead: 1, + }, + { + prefixer: EBCDIC1047.LL, + maxLen: 14, + data: []byte{ebcdic1047_0, ebcdic1047_1}, + expectedOutput: 1, + expectedRead: 2, + }, + { + prefixer: EBCDIC1047.LL, + maxLen: 32, + data: []byte{ebcdic1047_2, ebcdic1047_8}, + expectedOutput: 28, + expectedRead: 2, + }, + { + prefixer: EBCDIC1047.LLL, + maxLen: 128, + data: []byte{ebcdic1047_0, ebcdic1047_0, ebcdic1047_7}, + expectedOutput: 7, + expectedRead: 3, + }, + { + prefixer: EBCDIC1047.LLL, + maxLen: 128, + data: []byte{ebcdic1047_0, ebcdic1047_6, ebcdic1047_2}, + expectedOutput: 62, + expectedRead: 3, + }, + { + prefixer: EBCDIC1047.LLL, + maxLen: 512, + data: []byte{ebcdic1047_2, ebcdic1047_0, ebcdic1047_2}, + expectedOutput: 202, + expectedRead: 3, + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 1024, + data: []byte{ebcdic1047_0, ebcdic1047_0, ebcdic1047_0, ebcdic1047_1}, + expectedOutput: 1, + expectedRead: 4, + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 2048, + data: []byte{ebcdic1047_0, ebcdic1047_0, ebcdic1047_9, ebcdic1047_1}, + expectedOutput: 91, + expectedRead: 4, + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 4096, + data: []byte{ebcdic1047_0, ebcdic1047_7, ebcdic1047_7, ebcdic1047_7}, + expectedOutput: 777, + expectedRead: 4, + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 8192, + data: []byte{ebcdic1047_1, ebcdic1047_9, ebcdic1047_9, ebcdic1047_3}, + expectedOutput: 1993, + expectedRead: 4, + }, + } { + length, read, err := testCase.prefixer.DecodeLength(testCase.maxLen, testCase.data) + require.NoError(t, err) + require.Equal(t, testCase.expectedOutput, length) + require.Equal(t, testCase.expectedRead, read) + } +} + +func TestEBCDIC1047PrefixersDecodeErrors(t *testing.T) { + t.Parallel() + + t.Run("insufficient data to read length", func(t *testing.T) { + t.Parallel() + for _, testCase := range []struct { + prefixer Prefixer + maxLen int + data []byte + expectedError string + }{ + { + prefixer: EBCDIC1047.L, + maxLen: 8, + data: []byte{}, + expectedError: "not enough data length [0] to read [1] byte digits", + }, + { + prefixer: EBCDIC1047.LL, + maxLen: 16, + data: []byte{ebcdic1047_8}, + expectedError: "not enough data length [1] to read [2] byte digits", + }, + { + prefixer: EBCDIC1047.LLL, + maxLen: 128, + data: []byte{ebcdic1047_0, ebcdic1047_0}, + expectedError: "not enough data length [2] to read [3] byte digits", + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 8, + data: []byte{ebcdic1047_0, ebcdic1047_0, ebcdic1047_9}, + expectedError: "not enough data length [3] to read [4] byte digits", + }, + } { + length, read, err := testCase.prefixer.DecodeLength(testCase.maxLen, testCase.data) + require.Zero(t, length) + require.Zero(t, read) + require.EqualError(t, err, testCase.expectedError) + } + }) + + t.Run("data length to large", func(t *testing.T) { + t.Parallel() + for _, testCase := range []struct { + prefixer Prefixer + maxLen int + data []byte + expectedError string + }{ + { + prefixer: EBCDIC1047.L, + maxLen: 8, + data: []byte{ebcdic1047_9}, + expectedError: "data length [9] is larger than maximum [8]", + }, + { + prefixer: EBCDIC1047.LL, + maxLen: 16, + data: []byte{ebcdic1047_2, ebcdic1047_0}, + expectedError: "data length [20] is larger than maximum [16]", + }, + { + prefixer: EBCDIC1047.LLL, + maxLen: 128, + data: []byte{ebcdic1047_1, ebcdic1047_9, ebcdic1047_4}, + expectedError: "data length [194] is larger than maximum [128]", + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 8000, + data: []byte{ebcdic1047_8, ebcdic1047_0, ebcdic1047_9, ebcdic1047_2}, + expectedError: "data length [8092] is larger than maximum [8000]", + }, + } { + length, read, err := testCase.prefixer.DecodeLength(testCase.maxLen, testCase.data) + require.Zero(t, length) + require.Zero(t, read) + require.EqualError(t, err, testCase.expectedError) + } + }) + + t.Run("non-numeric length", func(t *testing.T) { + t.Parallel() + for _, testCase := range []struct { + prefixer Prefixer + maxLen int + data []byte + expectedError string + }{ + { + prefixer: EBCDIC1047.L, + maxLen: 9, + data: []byte{ebcdic1047Plus}, + expectedError: "length [+] is not a valid integer length field", + }, + { + prefixer: EBCDIC1047.LL, + maxLen: 13, + data: []byte{ebcdic1047l, ebcdic1047_3}, + expectedError: "length [l3] is not a valid integer length field", + }, + { + prefixer: EBCDIC1047.LLL, + maxLen: 128, + data: []byte{ebcdic1047_1, ebcdic1047Dot, ebcdic1047_0}, + expectedError: "length [1.0] is not a valid integer length field", + }, + { + prefixer: EBCDIC1047.LLLL, + maxLen: 9999, + data: []byte{ebcdic1047l, ebcdic1047l, ebcdic1047l, ebcdic1047l}, + expectedError: "length [llll] is not a valid integer length field", + }, + } { + length, read, err := testCase.prefixer.DecodeLength(testCase.maxLen, testCase.data) + require.Zero(t, length) + require.Zero(t, read) + require.EqualError(t, err, testCase.expectedError) + } + }) +} + +func TestEBCDIC1047PrefixersInspect(t *testing.T) { + t.Parallel() + require.Equal(t, "EBCDIC.Fixed", EBCDIC1047.Fixed.Inspect()) + require.Equal(t, "EBCDIC.L", EBCDIC1047.L.Inspect()) + require.Equal(t, "EBCDIC.LL", EBCDIC1047.LL.Inspect()) + require.Equal(t, "EBCDIC.LLL", EBCDIC1047.LLL.Inspect()) + require.Equal(t, "EBCDIC.LLLL", EBCDIC1047.LLLL.Inspect()) +}