diff --git a/codec/dagcbor/nongreedy_test.go b/codec/dagcbor/nongreedy_test.go new file mode 100644 index 00000000..21b28f30 --- /dev/null +++ b/codec/dagcbor/nongreedy_test.go @@ -0,0 +1,43 @@ +package dagcbor + +import ( + "bytes" + "encoding/hex" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/datamodel" + "github.com/ipld/go-ipld-prime/fluent/qp" + "github.com/ipld/go-ipld-prime/node/basicnode" +) + +func TestNonGreedy(t *testing.T) { + // same as JSON version of this test: {"a": 1}{"b": 2} + buf, err := hex.DecodeString("a1616101a1616202") + qt.Assert(t, err, qt.IsNil) + r := bytes.NewReader(buf) + opts := DecodeOptions{ + DontParseBeyondEnd: true, + } + + // first object + nb1 := basicnode.Prototype.Map.NewBuilder() + err = opts.Decode(nb1, r) + qt.Assert(t, err, qt.IsNil) + expected, err := qp.BuildMap(basicnode.Prototype.Any, 1, func(ma datamodel.MapAssembler) { + qp.MapEntry(ma, "a", qp.Int(1)) + }) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, ipld.DeepEqual(nb1.Build(), expected), qt.IsTrue) + + // second object + nb2 := basicnode.Prototype.Map.NewBuilder() + err = opts.Decode(nb2, r) + qt.Assert(t, err, qt.IsNil) + expected, err = qp.BuildMap(basicnode.Prototype.Any, 1, func(ma datamodel.MapAssembler) { + qp.MapEntry(ma, "b", qp.Int(2)) + }) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, ipld.DeepEqual(nb2.Build(), expected), qt.IsTrue) +} diff --git a/codec/dagcbor/unmarshal.go b/codec/dagcbor/unmarshal.go index 47c35f0a..92c3323e 100644 --- a/codec/dagcbor/unmarshal.go +++ b/codec/dagcbor/unmarshal.go @@ -54,6 +54,14 @@ type DecodeOptions struct { // // Note that this option is experimental as it only implements partial strictness. ExperimentalDeterminism bool + + // If true, the decoder stops reading from the stream at the end of a full, + // valid CBOR object. This may be useful for parsing a stream of undelimited + // CBOR objects. + // As per standard IPLD behavior, in the default mode the parser considers the + // entire block to be part of the CBOR object and will error if there is + // extraneous data after the end of the object. + DontParseBeyondEnd bool } // Decode deserializes data from the given io.Reader and feeds it into the given datamodel.NodeAssembler. @@ -77,6 +85,10 @@ func (cfg DecodeOptions) Decode(na datamodel.NodeAssembler, r io.Reader) error { return err } + if cfg.DontParseBeyondEnd { + return nil + } + var buf [1]byte _, err = io.ReadFull(r, buf[:]) switch err {