Repository: fxamacker/cbor
Pull request #412 (2023-06-25 23:14:33)

Add `Decoder.Buffered` to return buffered data
Buffered returns an `io.Reader` for data remaining in Decoder's buffer. The returned reader is valid until the next call to `Decode` or `Skip`.

This basically matches `Decoder.Buffered` in Go's encoding/json.

Thanks @immesys for discussions and insights at PR #397.  


Patch:
diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml
index 4fc3584..77b7a90 100644
--- a/.github/workflows/govulncheck.yml
+++ b/.github/workflows/govulncheck.yml
@@ -45,7 +45,7 @@ jobs:
         go-version: 1.20.x
         check-latest: true
     - name: Install latest from golang.org
-      run: go install golang.org/x/vuln/cmd/govulncheck@b43f5afc876383b2adc0ec0d3ff1998fe58eeda0 # v0.1.0
+      run: go install golang.org/x/vuln/cmd/govulncheck@latest
     - name: Run govulncheck
       # Use -v flag to print a full call stack for each vulnerability found.
       run: govulncheck -v ./...
diff --git a/stream.go b/stream.go
index f60f129..02fea43 100644
--- a/stream.go
+++ b/stream.go
@@ -4,6 +4,7 @@
 package cbor
 
 import (
+	"bytes"
 	"errors"
 	"io"
 	"reflect"
@@ -65,6 +66,12 @@ func (dec *Decoder) NumBytesRead() int {
 	return dec.bytesRead
 }
 
+// Buffered returns a reader for data remaining in Decoder's buffer.
+// Returned reader is valid until the next call to Decode or Skip.
+func (dec *Decoder) Buffered() io.Reader {
+	return bytes.NewReader(dec.buf[dec.off:])
+}
+
 // readNext() reads next CBOR data item from Reader to buffer.
 // It returns the size of next CBOR data item.
 // It also returns validation error or read error if any.


Test patch:
diff --git a/stream_test.go b/stream_test.go
index bbd74ac..095effd 100644
--- a/stream_test.go
+++ b/stream_test.go
@@ -577,6 +577,92 @@ func TestDecoderStructTag(t *testing.T) {
 	}
 }
 
+func TestDecoderBuffered(t *testing.T) {
+	testCases := []struct {
+		name      string
+		cborData  []byte
+		buffered  []byte
+		decodeErr error
+	}{
+		{
+			name:      "empty",
+			cborData:  []byte{},
+			buffered:  []byte{},
+			decodeErr: io.EOF,
+		},
+		{
+			name:      "malformed CBOR data item",
+			cborData:  []byte{0xc0},
+			buffered:  []byte{0xc0},
+			decodeErr: io.ErrUnexpectedEOF,
+		},
+		{
+			name:     "1 CBOR data item",
+			cborData: []byte{0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+			buffered: []byte{},
+		},
+		{
+			name: "2 CBOR data items",
+			cborData: []byte{
+				// First CBOR data item
+				0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+				// Second CBOR data item
+				0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			},
+			buffered: []byte{
+				// Second CBOR data item
+				0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			},
+		},
+		{
+			name: "1 CBOR data item followed by non-CBOR data",
+			cborData: []byte{
+				// CBOR data item
+				0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+				// Extraneous non-CBOR data ("abc")
+				0x61, 0x62, 0x63,
+			},
+			buffered: []byte{
+				// non-CBOR data ("abc")
+				0x61, 0x62, 0x63,
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			r := bytes.NewReader(tc.cborData)
+
+			decoder := NewDecoder(r)
+
+			// Decoder's buffer has no data yet.
+			br := decoder.Buffered()
+			buffered, err := io.ReadAll(br)
+			if err != nil {
+				t.Errorf("failed to read from reader returned by Buffered(): %v", err)
+			}
+			if len(buffered) > 0 {
+				t.Errorf("Buffered() = 0x%x (%d bytes), want 0 bytes", buffered, len(buffered))
+			}
+
+			var v interface{}
+			err = decoder.Decode(&v)
+			if err != tc.decodeErr {
+				t.Errorf("Decode() returned error %v, want %v", err, tc.decodeErr)
+			}
+
+			br = decoder.Buffered()
+			buffered, err = io.ReadAll(br)
+			if err != nil {
+				t.Errorf("failed to read from reader returned by Buffered(): %v", err)
+			}
+			if !bytes.Equal(tc.buffered, buffered) {
+				t.Errorf("Buffered() = 0x%x (%d bytes), want 0x%x (%d bytes)", buffered, len(buffered), tc.buffered, len(tc.buffered))
+			}
+		})
+	}
+}
+
 func TestEncoder(t *testing.T) {
 	var want bytes.Buffer
 	var w bytes.Buffer