diff --git a/mli.go b/mli.go index daadb8b..594af0d 100644 --- a/mli.go +++ b/mli.go @@ -30,6 +30,7 @@ import ( "encoding/hex" "errors" "fmt" + "io" "strconv" ) @@ -357,3 +358,53 @@ func validateLength(length int, minLength, maxLength int64) error { } return nil } + +// EncodeWriter encodes the length into the MLI selected by key and writes it to w. +// length must exclude the MLI itself. Returns [ErrLength] when length is negative +// or outside the selected format's range. +func EncodeWriter(w io.Writer, key string, length int) error { + b, err := Encode(key, length) + if err != nil { + return err + } + _, err = w.Write(b) + return err +} + +// DecodeReader reads the MLI bytes from r for the given key and returns the decoded message length. +// Returns [ErrByteSize] if the reader doesn't provide enough bytes. +// Returns [ErrLength] if the decoded value is invalid for the MLI type. +func DecodeReader(r io.Reader, key string) (int, error) { + size := mliSize(key) + if size == 0 { + return 0, fmt.Errorf("Invalid MLI type provided") + } + + b := make([]byte, size) + if _, err := io.ReadFull(r, b); err != nil { + return 0, ErrByteSize + } + + return Decode(key, &b) +} + +func mliSize(key string) int { + switch key { + case MLI2I: + return Size2I + case MLI2E: + return Size2E + case MLI4I: + return Size4I + case MLI4E: + return Size4E + case MLI2EE: + return Size2EE + case MLI2BCD2: + return Size2BCD2 + case MLIA4E: + return SizeA4E + default: + return 0 + } +} diff --git a/mli_test.go b/mli_test.go index de6d23f..477cdf1 100644 --- a/mli_test.go +++ b/mli_test.go @@ -14,6 +14,7 @@ package simplemli import ( + "bytes" "encoding/hex" "errors" "testing" @@ -361,3 +362,117 @@ func FuzzDecode(f *testing.F) { } }) } + +func TestEncodeWriter(t *testing.T) { + tests := []struct { + name string + key string + length int + }{ + {name: "2I", key: MLI2I, length: 43}, + {name: "2E", key: MLI2E, length: 43}, + {name: "4I", key: MLI4I, length: 49}, + {name: "4E", key: MLI4E, length: 34}, + {name: "2EE", key: MLI2EE, length: 56}, + {name: "2BCD2", key: MLI2BCD2, length: 284}, + {name: "A4E", key: MLIA4E, length: 43}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + err := EncodeWriter(&buf, tt.key, tt.length) + if err != nil { + t.Fatalf("EncodeWriter(%q, %d) unexpected error: %v", tt.key, tt.length, err) + } + + expected, err := Encode(tt.key, tt.length) + if err != nil { + t.Fatalf("Encode(%q, %d) unexpected error: %v", tt.key, tt.length, err) + } + + if !bytes.Equal(buf.Bytes(), expected) { + t.Errorf("EncodeWriter output mismatch: got %x, want %x", buf.Bytes(), expected) + } + }) + } +} + +func TestEncodeWriterErrors(t *testing.T) { + t.Run("Invalid key", func(t *testing.T) { + var buf bytes.Buffer + err := EncodeWriter(&buf, "Invalid", 10) + if err == nil { + t.Error("Expected error for invalid key") + } + }) + + t.Run("Negative length", func(t *testing.T) { + var buf bytes.Buffer + err := EncodeWriter(&buf, MLI2I, -1) + if !errors.Is(err, ErrLength) { + t.Errorf("Expected ErrLength, got %v", err) + } + }) +} + +func TestDecodeReader(t *testing.T) { + tests := []struct { + name string + key string + length int + }{ + {name: "2I", key: MLI2I, length: 43}, + {name: "2E", key: MLI2E, length: 43}, + {name: "4I", key: MLI4I, length: 49}, + {name: "4E", key: MLI4E, length: 34}, + {name: "2EE", key: MLI2EE, length: 56}, + {name: "2BCD2", key: MLI2BCD2, length: 284}, + {name: "A4E", key: MLIA4E, length: 43}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + encoded, err := Encode(tt.key, tt.length) + if err != nil { + t.Fatalf("Encode(%q, %d) unexpected error: %v", tt.key, tt.length, err) + } + + reader := bytes.NewReader(encoded) + got, err := DecodeReader(reader, tt.key) + if err != nil { + t.Fatalf("DecodeReader(%q) unexpected error: %v", tt.key, err) + } + + if got != tt.length { + t.Errorf("DecodeReader(%q) = %d, want %d", tt.key, got, tt.length) + } + }) + } +} + +func TestDecodeReaderErrors(t *testing.T) { + t.Run("Invalid key", func(t *testing.T) { + reader := bytes.NewReader([]byte{0x00, 0x00}) + _, err := DecodeReader(reader, "Invalid") + if err == nil { + t.Error("Expected error for invalid key") + } + }) + + t.Run("Insufficient bytes", func(t *testing.T) { + reader := bytes.NewReader([]byte{0x00}) // Only 1 byte for 2I which needs 2 + _, err := DecodeReader(reader, MLI2I) + if !errors.Is(err, ErrByteSize) { + t.Errorf("Expected ErrByteSize, got %v", err) + } + }) + + t.Run("EOF", func(t *testing.T) { + reader := bytes.NewReader([]byte{}) + _, err := DecodeReader(reader, MLI2I) + if !errors.Is(err, ErrByteSize) { + t.Errorf("Expected ErrByteSize, got %v", err) + } + }) +}