diff --git a/bson/buffered_byte_src.go b/bson/buffered_byte_src.go new file mode 100644 index 0000000000..eb19e3cb3f --- /dev/null +++ b/bson/buffered_byte_src.go @@ -0,0 +1,128 @@ +// Copyright (C) MongoDB, Inc. 2025-present. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + +package bson + +import ( + "bytes" + "io" +) + +// bufferedByteSrc implements the low-level byteSrc interface by reading +// directly from an in-memory byte slice. It provides efficient, zero-copy +// access for parsing BSON when the entire document is buffered in memory. +type bufferedByteSrc struct { + buf []byte // entire BSON document + offset int64 // Current read index into buf +} + +var _ byteSrc = (*bufferedByteSrc)(nil) + +// Read reads up to len(p) bytes from the in-memory buffer, advancing the offset +// by the number of bytes read. +func (b *bufferedByteSrc) readExact(p []byte) (int, error) { + if b.offset >= int64(len(b.buf)) { + return 0, io.EOF + } + n := copy(p, b.buf[b.offset:]) + b.offset += int64(n) + return n, nil +} + +// ReadByte returns the single byte at buf[offset] and advances offset by 1. +func (b *bufferedByteSrc) ReadByte() (byte, error) { + if b.offset >= int64(len(b.buf)) { + return 0, io.EOF + } + b.offset++ + return b.buf[b.offset-1], nil +} + +// peek returns buf[offset:offset+n] without advancing offset. +func (b *bufferedByteSrc) peek(n int) ([]byte, error) { + // Ensure we don't read past the end of the buffer. + if int64(n)+b.offset > int64(len(b.buf)) { + return b.buf[b.offset:], io.EOF + } + + // Return the next n bytes without advancing the offset + return b.buf[b.offset : b.offset+int64(n)], nil +} + +// discard advances offset by n bytes, returning the number of bytes discarded. +func (b *bufferedByteSrc) discard(n int) (int, error) { + // Ensure we don't read past the end of the buffer. + if int64(n)+b.offset > int64(len(b.buf)) { + // If we have exceeded the buffer length, discard only up to the end. + left := len(b.buf) - int(b.offset) + b.offset = int64(len(b.buf)) + + return left, io.EOF + } + + // Advance the read position + b.offset += int64(n) + return n, nil +} + +// readSlice scans buf[offset:] for the first occurrence of delim, returns +// buf[offset:idx+1], and advances offset past it; errors if delim not found. +func (b *bufferedByteSrc) readSlice(delim byte) ([]byte, error) { + // Ensure we don't read past the end of the buffer. + if b.offset >= int64(len(b.buf)) { + return nil, io.EOF + } + + // Look for the delimiter in the remaining bytes + rem := b.buf[b.offset:] + idx := bytes.IndexByte(rem, delim) + if idx < 0 { + return nil, io.EOF + } + + // Build the result slice up through the delimiter. + result := rem[:idx+1] + + // Advance the offset past the delimiter. + b.offset += int64(idx + 1) + + return result, nil +} + +// pos returns the current read position in the buffer. +func (b *bufferedByteSrc) pos() int64 { + return b.offset +} + +// regexLength will return the total byte length of a BSON regex value. +func (b *bufferedByteSrc) regexLength() (int32, error) { + rem := b.buf[b.offset:] + + // Find end of the first C-string (pattern). + i := bytes.IndexByte(rem, 0x00) + if i < 0 { + return 0, io.EOF + } + + // Find end of second C-string (options). + j := bytes.IndexByte(rem[i+1:], 0x00) + if j < 0 { + return 0, io.EOF + } + + // Total length = first C-string length (pattern) + second C-string length + // (options) + 2 null terminators + return int32(i + j + 2), nil +} + +func (*bufferedByteSrc) streamable() bool { + return false +} + +func (b *bufferedByteSrc) reset() { + b.buf = nil + b.offset = 0 +} diff --git a/bson/buffered_byte_src_test.go b/bson/buffered_byte_src_test.go new file mode 100644 index 0000000000..fc7fe76bfd --- /dev/null +++ b/bson/buffered_byte_src_test.go @@ -0,0 +1,153 @@ +// Copyright (C) MongoDB, Inc. 2025-present. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + +package bson + +import ( + "bytes" + "io" + "testing" + + "go.mongodb.org/mongo-driver/v2/internal/assert" + "go.mongodb.org/mongo-driver/v2/internal/require" +) + +func TestBufferedvalueReader_discard(t *testing.T) { + tests := []struct { + name string + buf []byte + n int + want int + wantOffset int64 + wantErr error + }{ + { + name: "nothing", + buf: bytes.Repeat([]byte("a"), 1024), + n: 0, + want: 0, + wantOffset: 0, + wantErr: nil, + }, + { + name: "amount less than buffer size", + buf: bytes.Repeat([]byte("a"), 1024), + n: 100, + want: 100, + wantOffset: 100, + wantErr: nil, + }, + { + name: "amount greater than buffer size", + buf: bytes.Repeat([]byte("a"), 1024), + n: 10000, + want: 1024, + wantOffset: 1024, + wantErr: io.EOF, + }, + { + name: "exact buffer size", + buf: bytes.Repeat([]byte("a"), 1024), + n: 1024, + want: 1024, + wantOffset: 1024, + wantErr: nil, + }, + { + name: "from empty buffer", + buf: []byte{}, + n: 10, + want: 0, + wantOffset: 0, + wantErr: io.EOF, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := &bufferedByteSrc{buf: tt.buf, offset: 0} + n, err := reader.discard(tt.n) + if tt.wantErr != nil { + assert.ErrorIs(t, err, tt.wantErr, "Expected error %v, got %v", tt.wantErr, err) + } else { + require.NoError(t, err, "Expected no error when discarding %d bytes", tt.n) + } + + assert.Equal(t, tt.want, n, "Expected to discard %d bytes, got %d", tt.want, n) + assert.Equal(t, tt.wantOffset, reader.offset, "Expected offset to be %d, got %d", tt.wantOffset, reader.offset) + }) + } +} + +func TestBufferedvalueReader_peek(t *testing.T) { + tests := []struct { + name string + buf []byte + n int + offset int64 + want []byte + wantErr error + }{ + { + name: "nothing", + buf: bytes.Repeat([]byte("a"), 1024), + n: 0, + want: []byte{}, + wantErr: nil, + }, + { + name: "amount less than buffer size", + buf: bytes.Repeat([]byte("a"), 1024), + n: 100, + want: bytes.Repeat([]byte("a"), 100), + wantErr: nil, + }, + { + name: "amount greater than buffer size", + buf: bytes.Repeat([]byte("a"), 1024), + n: 10000, + want: bytes.Repeat([]byte("a"), 1024), + wantErr: io.EOF, + }, + { + name: "exact buffer size", + buf: bytes.Repeat([]byte("a"), 1024), + n: 1024, + want: bytes.Repeat([]byte("a"), 1024), + wantErr: nil, + }, + { + name: "from empty buffer", + buf: []byte{}, + n: 10, + want: []byte{}, + wantErr: io.EOF, + }, + { + name: "peek with offset", + buf: append(bytes.Repeat([]byte("a"), 100), bytes.Repeat([]byte("b"), 100)...), + offset: 100, + n: 100, + want: bytes.Repeat([]byte("b"), 100), + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := &bufferedByteSrc{buf: tt.buf, offset: tt.offset} + n, err := reader.peek(tt.n) + if tt.wantErr != nil { + assert.ErrorIs(t, err, tt.wantErr, "Expected error %v, got %v", tt.wantErr, err) + } else { + require.NoError(t, err, "Expected no error when peeking %d bytes", tt.n) + } + + assert.Equal(t, tt.want, n, "Expected to peek %d bytes, got %d", len(tt.want), len(n)) + assert.Equal(t, tt.offset, reader.offset, "Expected offset to be %d, got %d", tt.offset, reader.offset) + }) + } +} diff --git a/bson/copier.go b/bson/copier.go index 6279f07230..c9a37c756b 100644 --- a/bson/copier.go +++ b/bson/copier.go @@ -7,7 +7,6 @@ package bson import ( - "bytes" "errors" "fmt" "io" @@ -180,8 +179,11 @@ func copyValueFromBytes(dst ValueWriter, t Type, src []byte) error { return wvb.writeValueBytes(t, src) } - vr := newDocumentReader(bytes.NewReader(src)) - vr.pushElement(t) + vr := newBufferedDocumentReader(src) + vr.advanceFrame() + + vr.stack[vr.frame].mode = mElement + vr.stack[vr.frame].vType = t return copyValue(dst, vr) } diff --git a/bson/copier_test.go b/bson/copier_test.go index 890b084349..32273f511a 100644 --- a/bson/copier_test.go +++ b/bson/copier_test.go @@ -40,7 +40,7 @@ func TestCopier(t *testing.T) { doc = bsoncore.AppendStringElement(doc, "Hello", "world") doc, err := bsoncore.AppendDocumentEnd(doc, idx) noerr(t, err) - src := newDocumentReader(bytes.NewReader(doc)) + src := newBufferedDocumentReader(doc) dst := newValueWriterFromSlice(make([]byte, 0)) want := doc err = copyDocument(dst, src) @@ -77,7 +77,7 @@ func TestCopier(t *testing.T) { noerr(t, err) doc, err = bsoncore.AppendDocumentEnd(doc, idx) noerr(t, err) - src := newDocumentReader(bytes.NewReader(doc)) + src := newBufferedDocumentReader(doc) _, err = src.ReadDocument() noerr(t, err) @@ -450,7 +450,7 @@ func TestCopier(t *testing.T) { idx, ) noerr(t, err) - vr := newDocumentReader(bytes.NewReader(b)) + vr := newBufferedDocumentReader(b) _, err = vr.ReadDocument() noerr(t, err) _, _, err = vr.ReadElement() @@ -489,7 +489,7 @@ func TestCopier(t *testing.T) { idx, ) noerr(t, err) - vr := newDocumentReader(bytes.NewReader(b)) + vr := newBufferedDocumentReader(b) _, err = vr.ReadDocument() noerr(t, err) _, _, err = vr.ReadElement() diff --git a/bson/mgoregistry_test.go b/bson/mgoregistry_test.go index fa0051ea14..d9dacbbce8 100644 --- a/bson/mgoregistry_test.go +++ b/bson/mgoregistry_test.go @@ -485,7 +485,7 @@ func (t *prefixPtr) SetBSON(raw RawValue) error { if err != nil { return err } - vr := newValueReader(raw.Type, bytes.NewReader(raw.Value)) + vr := newBufferedValueReader(raw.Type, raw.Value) err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval) if err != nil { return err @@ -512,7 +512,7 @@ func (t *prefixVal) SetBSON(raw RawValue) error { if err != nil { return err } - vr := newValueReader(raw.Type, bytes.NewReader(raw.Value)) + vr := newBufferedValueReader(raw.Type, raw.Value) err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval) if err != nil { return err @@ -936,7 +936,7 @@ func (o *setterType) SetBSON(raw RawValue) error { if raw.Type == 0x00 { raw.Type = TypeEmbeddedDocument } - vr := newValueReader(raw.Type, bytes.NewReader(raw.Value)) + vr := newBufferedValueReader(raw.Type, raw.Value) err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval) if err != nil { return err @@ -1289,7 +1289,7 @@ func (s *getterSetterD) SetBSON(raw RawValue) error { if raw.Type == 0x00 { raw.Type = TypeEmbeddedDocument } - vr := newValueReader(raw.Type, bytes.NewReader(raw.Value)) + vr := newBufferedValueReader(raw.Type, raw.Value) err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval) if err != nil { return err @@ -1315,7 +1315,7 @@ func (i *getterSetterInt) SetBSON(raw RawValue) error { if raw.Type == 0x00 { raw.Type = TypeEmbeddedDocument } - vr := newValueReader(raw.Type, bytes.NewReader(raw.Value)) + vr := newBufferedValueReader(raw.Type, raw.Value) err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval) if err != nil { return err @@ -1337,7 +1337,7 @@ func (s *ifaceSlice) SetBSON(raw RawValue) error { if err != nil { return err } - vr := newValueReader(raw.Type, bytes.NewReader(raw.Value)) + vr := newBufferedValueReader(raw.Type, raw.Value) err = decoder.DecodeValue(DecodeContext{Registry: NewMgoRegistry()}, vr, rval) if err != nil { return err diff --git a/bson/raw_value.go b/bson/raw_value.go index 53f053f03e..84b737128e 100644 --- a/bson/raw_value.go +++ b/bson/raw_value.go @@ -71,7 +71,7 @@ func (rv RawValue) UnmarshalWithRegistry(r *Registry, val interface{}) error { return ErrNilRegistry } - vr := newValueReader(rv.Type, bytes.NewReader(rv.Value)) + vr := newBufferedValueReader(rv.Type, rv.Value) rval := reflect.ValueOf(val) if rval.Kind() != reflect.Ptr { return fmt.Errorf("argument to Unmarshal* must be a pointer to a type, but got %v", rval) @@ -91,7 +91,7 @@ func (rv RawValue) UnmarshalWithContext(dc *DecodeContext, val interface{}) erro return ErrNilContext } - vr := newValueReader(rv.Type, bytes.NewReader(rv.Value)) + vr := newBufferedValueReader(rv.Type, rv.Value) rval := reflect.ValueOf(val) if rval.Kind() != reflect.Ptr { return fmt.Errorf("argument to Unmarshal* must be a pointer to a type, but got %v", rval) diff --git a/bson/streaming_byte_src.go b/bson/streaming_byte_src.go new file mode 100644 index 0000000000..c9366e9f0d --- /dev/null +++ b/bson/streaming_byte_src.go @@ -0,0 +1,104 @@ +// Copyright (C) MongoDB, Inc. 2025-present. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + +package bson + +import ( + "bufio" + "io" +) + +// streamingByteSrc reads from an ioReader wrapped in a bufio.Reader. It +// first reads the BSON length header, then ensures it only ever reads exactly +// that many bytes. +// +// Note: this approach trades memory usage for extra buffering and reader calls, +// so it is less performanted than the in-memory bufferedValueReader. +type streamingByteSrc struct { + br *bufio.Reader + offset int64 // offset is the current read position in the buffer +} + +var _ byteSrc = (*streamingByteSrc)(nil) + +// Read reads up to len(p) bytes from the underlying bufio.Reader, advancing +// the offset by the number of bytes read. +func (s *streamingByteSrc) readExact(p []byte) (int, error) { + n, err := io.ReadFull(s.br, p) + if err == nil { + s.offset += int64(n) + } + + return n, err +} + +// ReadByte returns the single byte at buf[offset] and advances offset by 1. +func (s *streamingByteSrc) ReadByte() (byte, error) { + c, err := s.br.ReadByte() + if err == nil { + s.offset++ + } + return c, err +} + +// peek returns buf[offset:offset+n] without advancing offset. +func (s *streamingByteSrc) peek(n int) ([]byte, error) { + return s.br.Peek(n) +} + +// discard advances offset by n bytes, returning the number of bytes discarded. +func (s *streamingByteSrc) discard(n int) (int, error) { + m, err := s.br.Discard(n) + s.offset += int64(m) + return m, err +} + +// readSlice scans buf[offset:] for the first occurrence of delim, returns +// buf[offset:idx+1], and advances offset past it; errors if delim not found. +func (s *streamingByteSrc) readSlice(delim byte) ([]byte, error) { + data, err := s.br.ReadSlice(delim) + if err != nil { + return nil, err + } + s.offset += int64(len(data)) + return data, nil +} + +// pos returns the current read position in the buffer. +func (s *streamingByteSrc) pos() int64 { + return s.offset +} + +// regexLength will return the total byte length of a BSON regex value. +func (s *streamingByteSrc) regexLength() (int32, error) { + var ( + count int32 + nulCount int + ) + + for nulCount < 2 { + buf, err := s.br.Peek(int(count) + 1) + if err != nil { + return 0, err + } + + b := buf[count] + count++ + if b == 0x00 { + nulCount++ + } + } + + return count, nil +} + +func (*streamingByteSrc) streamable() bool { + return true +} + +func (s *streamingByteSrc) reset() { + s.offset = 0 +} diff --git a/bson/unmarshal.go b/bson/unmarshal.go index 52bd94fed7..5597bc7a92 100644 --- a/bson/unmarshal.go +++ b/bson/unmarshal.go @@ -42,8 +42,8 @@ type ValueUnmarshaler interface { // When unmarshaling BSON, if the BSON value is null and the Go value is a // pointer, the pointer is set to nil without calling UnmarshalBSONValue. func Unmarshal(data []byte, val interface{}) error { - vr := getDocumentReader(bytes.NewReader(data)) - defer putDocumentReader(vr) + vr := getBufferedDocumentReader(data) + defer putBufferedDocumentReader(vr) if l, err := vr.peekLength(); err != nil { return err @@ -57,7 +57,7 @@ func Unmarshal(data []byte, val interface{}) error { // stores the result in the value pointed to by val. If val is nil or not a pointer, // UnmarshalValue returns an error. func UnmarshalValue(t Type, data []byte, val interface{}) error { - vr := newValueReader(t, bytes.NewReader(data)) + vr := newBufferedValueReader(t, data) return unmarshalFromReader(DecodeContext{Registry: defaultRegistry}, vr, val) } diff --git a/bson/unmarshal_test.go b/bson/unmarshal_test.go index 06dfe4ff22..702abcd152 100644 --- a/bson/unmarshal_test.go +++ b/bson/unmarshal_test.go @@ -71,7 +71,7 @@ func TestUnmarshalWithRegistry(t *testing.T) { // Assert that unmarshaling the input data results in the expected value. gotValue := reflect.New(reflect.TypeOf(tc.val)) - dec := NewDecoder(newValueReader(tc.bsontype, bytes.NewReader(tc.bytes))) + dec := NewDecoder(newBufferedValueReader(tc.bsontype, tc.bytes)) dec.SetRegistry(reg) err := dec.Decode(gotValue.Interface()) noerr(t, err) diff --git a/bson/unmarshal_value_test.go b/bson/unmarshal_value_test.go index b9132221d9..a10474f601 100644 --- a/bson/unmarshal_value_test.go +++ b/bson/unmarshal_value_test.go @@ -7,7 +7,6 @@ package bson import ( - "bytes" "reflect" "strings" "testing" @@ -80,7 +79,7 @@ func BenchmarkSliceCodecUnmarshal(b *testing.B) { dec := NewDecoder(nil) dec.SetRegistry(reg) for pb.Next() { - dec.Reset(newValueReader(bm.bsontype, bytes.NewReader(bm.bytes))) + dec.Reset(newBufferedValueReader(bm.bsontype, bm.bytes)) err := dec.Decode(&[]byte{}) if err != nil { b.Fatal(err) diff --git a/bson/unmarshaling_cases_test.go b/bson/unmarshaling_cases_test.go index 6e84f80e28..61dbe9dc2a 100644 --- a/bson/unmarshaling_cases_test.go +++ b/bson/unmarshaling_cases_test.go @@ -7,7 +7,6 @@ package bson import ( - "bytes" "reflect" ) @@ -269,7 +268,7 @@ func (mi *myInt64) UnmarshalBSONValue(t byte, b []byte) error { } if Type(t) == TypeInt64 { - i, err := newValueReader(TypeInt64, bytes.NewReader(b)).ReadInt64() + i, err := newBufferedValueReader(TypeInt64, b).ReadInt64() if err != nil { return err } @@ -284,7 +283,7 @@ func (mi *myInt64) UnmarshalBSON(b []byte) error { if len(b) == 0 { return nil } - i, err := newValueReader(TypeInt64, bytes.NewReader(b)).ReadInt64() + i, err := newBufferedValueReader(TypeInt64, b).ReadInt64() if err != nil { return err } @@ -310,7 +309,7 @@ func (mb *myBytes) UnmarshalBSON(b []byte) error { if len(b) == 0 { return nil } - b, _, err := newValueReader(TypeBinary, bytes.NewReader(b)).ReadBinary() + b, _, err := newBufferedValueReader(TypeBinary, b).ReadBinary() if err != nil { return err } @@ -324,7 +323,7 @@ func (ms *myString) UnmarshalBSON(b []byte) error { if len(b) == 0 { return nil } - s, err := newValueReader(TypeString, bytes.NewReader(b)).ReadString() + s, err := newBufferedValueReader(TypeString, b).ReadString() if err != nil { return err } diff --git a/bson/value_reader.go b/bson/value_reader.go index d713e669a9..62d9d0f772 100644 --- a/bson/value_reader.go +++ b/bson/value_reader.go @@ -16,6 +16,38 @@ import ( "sync" ) +type byteSrc interface { + io.ByteReader + + readExact(p []byte) (int, error) + + // Peek returns the next n bytes without advancing the cursor. It must return + // exactly n bytes or n error if fewer are available. + peek(n int) ([]byte, error) + + // discard advanced the cursor by n bytes, returning the actual number + // discarded or an error if fewer were available. + discard(n int) (int, error) + + // readSlice reads until (and including) the first occurrence of delim, + // returning the entire slice [start...delimiter] and advancing the cursor. + // past it. Returns an error if delim is not found. + readSlice(delim byte) ([]byte, error) + + // pos returns the number of bytes consumed so far. + pos() int64 + + // regexLength returns the total byte length of a BSON regex value (two + // C-strings including their terminating NULs) in buffered mode. + regexLength() (int32, error) + + // streamable returns true if this source can be used in a streaming context. + streamable() bool + + // reset resets the source to its initial state. + reset() +} + var _ ValueReader = &valueReader{} // ErrEOA is the error returned when the end of a BSON array has been reached. @@ -30,12 +62,6 @@ type vrState struct { end int64 } -var bufioReaderPool = sync.Pool{ - New: func() interface{} { - return bufio.NewReader(nil) - }, -} - var vrPool = sync.Pool{ New: func() interface{} { return &valueReader{ @@ -46,36 +72,28 @@ var vrPool = sync.Pool{ // valueReader is for reading BSON values. type valueReader struct { - r *bufio.Reader + src byteSrc offset int64 stack []vrState frame int64 } -func getDocumentReader(r io.Reader) *valueReader { - vr := vrPool.Get().(*valueReader) - - vr.offset = 0 - vr.frame = 0 - - vr.stack = vr.stack[:1] - vr.stack[0] = vrState{mode: mTopLevel} - - br := bufioReaderPool.Get().(*bufio.Reader) - br.Reset(r) - vr.r = br - - return vr +func getBufferedDocumentReader(b []byte) *valueReader { + return newBufferedDocumentReader(b) } -func putDocumentReader(vr *valueReader) { +func putBufferedDocumentReader(vr *valueReader) { if vr == nil { return } - bufioReaderPool.Put(vr.r) - vr.r = nil + vr.src.reset() + + // Reset src and stack to avoid holding onto memory. + vr.src = nil + vr.frame = 0 + vr.stack = vr.stack[:0] vrPool.Put(vr) } @@ -83,32 +101,53 @@ func putDocumentReader(vr *valueReader) { // NewDocumentReader returns a ValueReader using b for the underlying BSON // representation. func NewDocumentReader(r io.Reader) ValueReader { - return newDocumentReader(r) -} - -// newValueReader returns a ValueReader that starts in the Value mode instead of in top -// level document mode. This enables the creation of a ValueReader for a single BSON value. -func newValueReader(t Type, r io.Reader) ValueReader { - vr := newDocumentReader(r) - if len(vr.stack) == 0 { - vr.stack = make([]vrState, 1, 5) - } - vr.stack[0].mode = mValue - vr.stack[0].vType = t - return vr -} - -func newDocumentReader(r io.Reader) *valueReader { stack := make([]vrState, 1, 5) stack[0] = vrState{ mode: mTopLevel, } + return &valueReader{ - r: bufio.NewReader(r), + src: &streamingByteSrc{br: bufio.NewReader(r), offset: 0}, stack: stack, } } +// newBufferedValueReader returns a ValueReader that starts in the Value mode +// instead of in top level document mode. This enables the creation of a +// ValueReader for a single BSON value. +func newBufferedValueReader(t Type, b []byte) ValueReader { + bVR := newBufferedDocumentReader(b) + + bVR.stack[0].vType = t + bVR.stack[0].mode = mValue + + return bVR +} + +func newBufferedDocumentReader(b []byte) *valueReader { + vr := vrPool.Get().(*valueReader) + + vr.src = &bufferedByteSrc{ + buf: b, + offset: 0, + } + + // Reset parse state. + vr.frame = 0 + if cap(vr.stack) < 1 { + vr.stack = make([]vrState, 1, 5) + } else { + vr.stack = vr.stack[:1] + } + + vr.stack[0] = vrState{ + mode: mTopLevel, + end: int64(len(b)), + } + + return vr +} + func (vr *valueReader) advanceFrame() { if vr.frame+1 >= int64(len(vr.stack)) { // We need to grow the stack length := len(vr.stack) @@ -128,62 +167,6 @@ func (vr *valueReader) advanceFrame() { vr.stack[vr.frame].end = 0 } -func (vr *valueReader) pushDocument() error { - vr.advanceFrame() - - vr.stack[vr.frame].mode = mDocument - - length, err := vr.readLength() - if err != nil { - return err - } - vr.stack[vr.frame].end = int64(length) + vr.offset - 4 - - return nil -} - -func (vr *valueReader) pushArray() error { - vr.advanceFrame() - - vr.stack[vr.frame].mode = mArray - - length, err := vr.readLength() - if err != nil { - return err - } - vr.stack[vr.frame].end = int64(length) + vr.offset - 4 - - return nil -} - -func (vr *valueReader) pushElement(t Type) { - vr.advanceFrame() - - vr.stack[vr.frame].mode = mElement - vr.stack[vr.frame].vType = t -} - -func (vr *valueReader) pushValue(t Type) { - vr.advanceFrame() - - vr.stack[vr.frame].mode = mValue - vr.stack[vr.frame].vType = t -} - -func (vr *valueReader) pushCodeWithScope() (int64, error) { - vr.advanceFrame() - - vr.stack[vr.frame].mode = mCodeWithScope - - length, err := vr.readLength() - if err != nil { - return 0, err - } - vr.stack[vr.frame].end = int64(length) + vr.offset - 4 - - return int64(length), nil -} - func (vr *valueReader) pop() error { var cnt int switch vr.stack[vr.frame].mode { @@ -193,22 +176,27 @@ func (vr *valueReader) pop() error { cnt = 2 // we pop twice to jump over the vrElement: vrDocument -> vrElement -> vrDocument/TopLevel/etc... } for i := 0; i < cnt && vr.frame > 0; i++ { - if vr.offset < vr.stack[vr.frame].end { - _, err := vr.r.Discard(int(vr.stack[vr.frame].end - vr.offset)) + if vr.src.pos() < vr.stack[vr.frame].end { + _, err := vr.src.discard(int(vr.stack[vr.frame].end - vr.src.pos())) if err != nil { return err } } vr.frame-- } - if vr.frame == 0 { - if vr.stack[0].end > vr.offset { - vr.stack[0].end -= vr.offset - } else { - vr.stack[0].end = 0 + + if vr.src.streamable() { + if vr.frame == 0 { + if vr.stack[0].end > vr.src.pos() { + vr.stack[0].end -= vr.src.pos() + } else { + vr.stack[0].end = 0 + } + + vr.src.reset() } - vr.offset = 0 } + return nil } @@ -251,7 +239,9 @@ func (vr *valueReader) Type() Type { return vr.stack[vr.frame].vType } -func (vr *valueReader) appendNextElement(dst []byte) ([]byte, error) { +// peekNextValueSize returns the length of the next value in the stream without +// offsetting the reader position. +func peekNextValueSize(vr *valueReader) (int32, error) { var length int32 var err error switch vr.stack[vr.frame].vType { @@ -279,72 +269,73 @@ func (vr *valueReader) appendNextElement(dst []byte) ([]byte, error) { case TypeObjectID: length = 12 case TypeRegex: - for n := 0; n < 2; n++ { // Read two C strings. - str, err := vr.r.ReadBytes(0x00) - if err != nil { - return nil, err - } - dst = append(dst, str...) - vr.offset += int64(len(str)) - } - return dst, nil + length, err = vr.src.regexLength() default: - return nil, fmt.Errorf("attempted to read bytes of unknown BSON type %v", vr.stack[vr.frame].vType) - } - if err != nil { - return nil, err + return 0, fmt.Errorf("attempted to read bytes of unknown BSON type %v", vr.stack[vr.frame].vType) } - buf, err := vr.r.Peek(int(length)) - if err != nil { - if err == bufio.ErrBufferFull { - temp := make([]byte, length) - if _, err = io.ReadFull(vr.r, temp); err != nil { - return nil, err - } - dst = append(dst, temp...) - vr.offset += int64(len(temp)) - return dst, nil + return length, err +} + +// readBytes tries to grab the next n bytes zero-allocation using peek+discard. +// If peek fails (e.g. bufio buffer full), it falls back to io.ReadFull. +func readBytes(src byteSrc, n int) ([]byte, error) { + if src.streamable() { + data := make([]byte, n) + if _, err := src.readExact(data); err != nil { + return nil, err } - return nil, err + return data, nil } - dst = append(dst, buf...) - if _, err = vr.r.Discard(int(length)); err != nil { + // Zero-allocation path. + buf, err := src.peek(n) + if err != nil { return nil, err } - vr.offset += int64(length) - return dst, nil + _, _ = src.discard(n) // Discard the bytes from the source. + return buf, nil } +// readBytesValueReader returns a subslice [offset, offset+length) or EOF. +func (vr *valueReader) readBytes(n int32) ([]byte, error) { + if n < 0 { + return nil, fmt.Errorf("invalid length: %d", n) + } + + return readBytes(vr.src, int(n)) +} + +//nolint:unparam func (vr *valueReader) readValueBytes(dst []byte) (Type, []byte, error) { switch vr.stack[vr.frame].mode { case mTopLevel: length, err := vr.peekLength() if err != nil { - return Type(0), nil, err - } - dst, err = vr.appendBytes(dst, length) - if err != nil { - return Type(0), nil, err + return 0, nil, err } - return Type(0), dst, nil + b, err := vr.readBytes(length) + return Type(0), append(dst, b...), err case mElement, mValue: - dst, err := vr.appendNextElement(dst) + t := vr.stack[vr.frame].vType + + length, err := peekNextValueSize(vr) if err != nil { - return Type(0), dst, err + return t, dst, err } - t := vr.stack[vr.frame].vType - err = vr.pop() - if err != nil { + b, err := vr.readBytes(length) + + if err := vr.pop(); err != nil { return Type(0), nil, err } - return t, dst, nil + + return t, append(dst, b...), err + default: - return Type(0), nil, vr.invalidTransitionErr(0, "ReadValueBytes", []mode{mElement, mValue}) + return Type(0), nil, vr.invalidTransitionErr(0, "readValueBytes", []mode{mElement, mValue}) } } @@ -355,7 +346,12 @@ func (vr *valueReader) Skip() error { return vr.invalidTransitionErr(0, "Skip", []mode{mElement, mValue}) } - _, err := vr.appendNextElement(nil) + length, err := peekNextValueSize(vr) + if err != nil { + return err + } + + _, err = vr.src.discard(int(length)) if err != nil { return err } @@ -363,20 +359,33 @@ func (vr *valueReader) Skip() error { return vr.pop() } +// ReadArray returns an ArrayReader for the next BSON array in the valueReader +// source, advancing the reader position to the end of the array. func (vr *valueReader) ReadArray() (ArrayReader, error) { if err := vr.ensureElementValue(TypeArray, mArray, "ReadArray"); err != nil { return nil, err } - err := vr.pushArray() + // Push a new frame for the array. + vr.advanceFrame() + + // Read the 4-byte length. + size, err := vr.readLength() if err != nil { return nil, err } + // Compute the end position: current position + total size - length. + vr.stack[vr.frame].mode = mArray + vr.stack[vr.frame].end = vr.src.pos() + int64(size) - 4 + return vr, nil } -func (vr *valueReader) ReadBinary() (b []byte, btype byte, err error) { +// ReadBinary reads a BSON binary value, returning the byte slice and the +// type of the binary data (0x02 for old binary, 0x00 for new binary, etc.), +// advancing the reader position to the end of the binary value. +func (vr *valueReader) ReadBinary() ([]byte, byte, error) { if err := vr.ensureElementValue(TypeBinary, 0, "ReadBinary"); err != nil { return nil, 0, err } @@ -386,7 +395,7 @@ func (vr *valueReader) ReadBinary() (b []byte, btype byte, err error) { return nil, 0, err } - btype, err = vr.readByte() + btype, err := vr.readByte() if err != nil { return nil, 0, err } @@ -399,18 +408,24 @@ func (vr *valueReader) ReadBinary() (b []byte, btype byte, err error) { } } - b = make([]byte, length) - err = vr.read(b) + b, err := vr.readBytes(length) if err != nil { return nil, 0, err } + // copy so user doesn’t share underlying buffer + cp := make([]byte, len(b)) + copy(cp, b) + if err := vr.pop(); err != nil { return nil, 0, err } - return b, btype, nil + + return cp, btype, nil } +// ReadBoolean reads a BSON boolean value, returning true or false, advancing +// the reader position to the end of the boolean value. func (vr *valueReader) ReadBoolean() (bool, error) { if err := vr.ensureElementValue(TypeBoolean, 0, "ReadBoolean"); err != nil { return false, err @@ -431,6 +446,8 @@ func (vr *valueReader) ReadBoolean() (bool, error) { return b == 1, nil } +// ReadDocument reads a BSON embedded document, returning a DocumentReader, +// advancing the reader position to the end of the document. func (vr *valueReader) ReadDocument() (DocumentReader, error) { switch vr.stack[vr.frame].mode { case mTopLevel: @@ -442,7 +459,7 @@ func (vr *valueReader) ReadDocument() (DocumentReader, error) { return nil, fmt.Errorf("invalid string length: %d", length) } - vr.stack[vr.frame].end = int64(length) + vr.offset - 4 + vr.stack[vr.frame].end = int64(length) + vr.src.pos() - 4 return vr, nil case mElement, mValue: if vr.stack[vr.frame].vType != TypeEmbeddedDocument { @@ -452,15 +469,22 @@ func (vr *valueReader) ReadDocument() (DocumentReader, error) { return nil, vr.invalidTransitionErr(mDocument, "ReadDocument", []mode{mTopLevel, mElement, mValue}) } - err := vr.pushDocument() + vr.advanceFrame() + + size, err := vr.readLength() if err != nil { return nil, err } + vr.stack[vr.frame].mode = mDocument + vr.stack[vr.frame].end = int64(size) + vr.src.pos() - 4 + return vr, nil } -func (vr *valueReader) ReadCodeWithScope() (code string, dr DocumentReader, err error) { +// ReadCodeWithScope reads a BSON CodeWithScope value, returning the code as a +// string, advancing the reader position to the end of the CodeWithScope value. +func (vr *valueReader) ReadCodeWithScope() (string, DocumentReader, error) { if err := vr.ensureElementValue(TypeCodeWithScope, 0, "ReadCodeWithScope"); err != nil { return "", nil, err } @@ -476,21 +500,26 @@ func (vr *valueReader) ReadCodeWithScope() (code string, dr DocumentReader, err if strLength <= 0 { return "", nil, fmt.Errorf("invalid string length: %d", strLength) } - strBytes := make([]byte, strLength) - err = vr.read(strBytes) + buf, err := vr.readBytes(strLength) if err != nil { return "", nil, err } - code = string(strBytes[:len(strBytes)-1]) - size, err := vr.pushCodeWithScope() + code := string(buf[:len(buf)-1]) + vr.advanceFrame() + + // Use readLength to ensure that we are not out of bounds. + size, err := vr.readLength() if err != nil { return "", nil, err } + vr.stack[vr.frame].mode = mCodeWithScope + vr.stack[vr.frame].end = vr.src.pos() + int64(size) - 4 + // The total length should equal: // 4 (total length) + strLength + 4 (the length of str itself) + (document length) - componentsLength := int64(4+strLength+4) + size + componentsLength := int64(4+strLength+4) + int64(size) if int64(totalLength) != componentsLength { return "", nil, fmt.Errorf( "length of CodeWithScope does not match lengths of components; total: %d; components: %d", @@ -500,27 +529,34 @@ func (vr *valueReader) ReadCodeWithScope() (code string, dr DocumentReader, err return code, vr, nil } -func (vr *valueReader) ReadDBPointer() (ns string, oid ObjectID, err error) { +// ReadDBPointer reads a BSON DBPointer value, returning the namespace, the +// object ID, and an error if any, advancing the reader position to the end of +// the DBPointer value. +func (vr *valueReader) ReadDBPointer() (string, ObjectID, error) { if err := vr.ensureElementValue(TypeDBPointer, 0, "ReadDBPointer"); err != nil { - return "", oid, err + return "", ObjectID{}, err } - - ns, err = vr.readString() + ns, err := vr.readString() if err != nil { - return "", oid, err + return "", ObjectID{}, err } - err = vr.read(oid[:]) + oidBytes, err := vr.readBytes(12) if err != nil { return "", ObjectID{}, err } + var oid ObjectID + copy(oid[:], oidBytes) + if err := vr.pop(); err != nil { return "", ObjectID{}, err } return ns, oid, nil } +// ReadDateTime reads a BSON DateTime value, advancing the reader position to +// the end of the DateTime value. func (vr *valueReader) ReadDateTime() (int64, error) { if err := vr.ensureElementValue(TypeDateTime, 0, "ReadDateTime"); err != nil { return 0, err @@ -537,17 +573,16 @@ func (vr *valueReader) ReadDateTime() (int64, error) { return i, nil } +// ReadDecimal128 reads a BSON Decimal128 value, advancing the reader +// to the end of the Decimal128 value. func (vr *valueReader) ReadDecimal128() (Decimal128, error) { if err := vr.ensureElementValue(TypeDecimal128, 0, "ReadDecimal128"); err != nil { return Decimal128{}, err } - - var b [16]byte - err := vr.read(b[:]) + b, err := vr.readBytes(16) if err != nil { return Decimal128{}, err } - l := binary.LittleEndian.Uint64(b[0:8]) h := binary.LittleEndian.Uint64(b[8:16]) @@ -557,6 +592,8 @@ func (vr *valueReader) ReadDecimal128() (Decimal128, error) { return NewDecimal128(h, l), nil } +// ReadDouble reads a BSON double value, advancing the reader position to +// to the end of the double value. func (vr *valueReader) ReadDouble() (float64, error) { if err := vr.ensureElementValue(TypeDouble, 0, "ReadDouble"); err != nil { return 0, err @@ -573,39 +610,59 @@ func (vr *valueReader) ReadDouble() (float64, error) { return math.Float64frombits(u), nil } +// ReadInt32 reads a BSON int32 value, advancing the reader position to the end +// of the int32 value. func (vr *valueReader) ReadInt32() (int32, error) { if err := vr.ensureElementValue(TypeInt32, 0, "ReadInt32"); err != nil { return 0, err } + i, err := vr.readi32() + if err != nil { + return 0, err + } if err := vr.pop(); err != nil { return 0, err } - return vr.readi32() + return i, nil } +// ReadInt64 reads a BSON int64 value, advancing the reader position to the end +// of the int64 value. func (vr *valueReader) ReadInt64() (int64, error) { if err := vr.ensureElementValue(TypeInt64, 0, "ReadInt64"); err != nil { return 0, err } + i, err := vr.readi64() + if err != nil { + return 0, err + } if err := vr.pop(); err != nil { return 0, err } - return vr.readi64() + return i, nil } +// ReadJavascript reads a BSON JavaScript value, advancing the reader +// to the end of the JavaScript value. func (vr *valueReader) ReadJavascript() (string, error) { if err := vr.ensureElementValue(TypeJavaScript, 0, "ReadJavascript"); err != nil { return "", err } + s, err := vr.readString() + if err != nil { + return "", err + } if err := vr.pop(); err != nil { return "", err } - return vr.readString() + return s, nil } +// ReadMaxKey reads a BSON MaxKey value, advancing the reader position to the +// end of the MaxKey value. func (vr *valueReader) ReadMaxKey() error { if err := vr.ensureElementValue(TypeMaxKey, 0, "ReadMaxKey"); err != nil { return err @@ -614,6 +671,8 @@ func (vr *valueReader) ReadMaxKey() error { return vr.pop() } +// ReadMinKey reads a BSON MinKey value, advancing the reader position to the +// end of the MinKey value. func (vr *valueReader) ReadMinKey() error { if err := vr.ensureElementValue(TypeMinKey, 0, "ReadMinKey"); err != nil { return err @@ -622,6 +681,8 @@ func (vr *valueReader) ReadMinKey() error { return vr.pop() } +// REadNull reads a BSON Null value, advancing the reader position to the +// end of the Null value. func (vr *valueReader) ReadNull() error { if err := vr.ensureElementValue(TypeNull, 0, "ReadNull"); err != nil { return err @@ -630,23 +691,29 @@ func (vr *valueReader) ReadNull() error { return vr.pop() } +// ReadObjectID reads a BSON ObjectID value, advancing the reader to the end of +// the ObjectID value. func (vr *valueReader) ReadObjectID() (ObjectID, error) { if err := vr.ensureElementValue(TypeObjectID, 0, "ReadObjectID"); err != nil { return ObjectID{}, err } - var oid ObjectID - err := vr.read(oid[:]) + oidBytes, err := vr.readBytes(12) if err != nil { return ObjectID{}, err } + var oid ObjectID + copy(oid[:], oidBytes) + if err := vr.pop(); err != nil { return ObjectID{}, err } return oid, nil } +// ReadRegex reads a BSON Regex value, advancing the reader position to the +// regex value. func (vr *valueReader) ReadRegex() (string, string, error) { if err := vr.ensureElementValue(TypeRegex, 0, "ReadRegex"); err != nil { return "", "", err @@ -668,39 +735,52 @@ func (vr *valueReader) ReadRegex() (string, string, error) { return pattern, options, nil } +// ReadString reads a BSON String value, advancing the reader position to the +// end of the String value. func (vr *valueReader) ReadString() (string, error) { if err := vr.ensureElementValue(TypeString, 0, "ReadString"); err != nil { return "", err } + s, err := vr.readString() + if err != nil { + return "", err + } if err := vr.pop(); err != nil { return "", err } - return vr.readString() + return s, nil } +// ReadSymbol reads a BSON Symbol value, advancing the reader position to the +// end of the Symbol value. func (vr *valueReader) ReadSymbol() (string, error) { if err := vr.ensureElementValue(TypeSymbol, 0, "ReadSymbol"); err != nil { return "", err } - + s, err := vr.readString() + if err != nil { + return "", err + } if err := vr.pop(); err != nil { return "", err } - return vr.readString() + return s, nil } -func (vr *valueReader) ReadTimestamp() (t uint32, i uint32, err error) { +// ReadTimestamp reads a BSON Timestamp value, advancing the reader to the end +// of the Timestamp value. +func (vr *valueReader) ReadTimestamp() (uint32, uint32, error) { if err := vr.ensureElementValue(TypeTimestamp, 0, "ReadTimestamp"); err != nil { return 0, 0, err } - i, err = vr.readu32() + i, err := vr.readu32() if err != nil { return 0, 0, err } - t, err = vr.readu32() + t, err := vr.readu32() if err != nil { return 0, 0, err } @@ -711,6 +791,8 @@ func (vr *valueReader) ReadTimestamp() (t uint32, i uint32, err error) { return t, i, nil } +// ReadUndefined reads a BSON Undefined value, advancing the reader position +// to the end of the Undefined value. func (vr *valueReader) ReadUndefined() error { if err := vr.ensureElementValue(TypeUndefined, 0, "ReadUndefined"); err != nil { return err @@ -719,6 +801,8 @@ func (vr *valueReader) ReadUndefined() error { return vr.pop() } +// ReadElement reads the next element in the BSON document, advancing the +// reader position to the end of the element. func (vr *valueReader) ReadElement() (string, ValueReader, error) { switch vr.stack[vr.frame].mode { case mTopLevel, mDocument, mCodeWithScope: @@ -732,7 +816,7 @@ func (vr *valueReader) ReadElement() (string, ValueReader, error) { } if t == 0 { - if vr.offset != vr.stack[vr.frame].end { + if vr.src.pos() != vr.stack[vr.frame].end { return "", nil, vr.invalidDocumentLengthError() } @@ -745,10 +829,15 @@ func (vr *valueReader) ReadElement() (string, ValueReader, error) { return "", nil, err } - vr.pushElement(Type(t)) + vr.advanceFrame() + + vr.stack[vr.frame].mode = mElement + vr.stack[vr.frame].vType = Type(t) return name, vr, nil } +// ReadValue reads the next value in the BSON array, advancing the to the end of +// the value. func (vr *valueReader) ReadValue() (ValueReader, error) { switch vr.stack[vr.frame].mode { case mArray: @@ -762,7 +851,7 @@ func (vr *valueReader) ReadValue() (ValueReader, error) { } if t == 0 { - if vr.offset != vr.stack[vr.frame].end { + if vr.src.pos() != vr.stack[vr.frame].end { return nil, vr.invalidDocumentLengthError() } @@ -770,49 +859,32 @@ func (vr *valueReader) ReadValue() (ValueReader, error) { return nil, ErrEOA } - if _, err := vr.readCString(); err != nil { + _, err = vr.src.readSlice(0x00) + if err != nil { return nil, err } - vr.pushValue(Type(t)) - return vr, nil -} - -func (vr *valueReader) read(p []byte) error { - n, err := io.ReadFull(vr.r, p) - if err != nil { - return err - } - vr.offset += int64(n) - return nil -} + vr.advanceFrame() -func (vr *valueReader) appendBytes(dst []byte, length int32) ([]byte, error) { - buf := make([]byte, length) - err := vr.read(buf) - if err != nil { - return nil, err - } - return append(dst, buf...), nil + vr.stack[vr.frame].mode = mValue + vr.stack[vr.frame].vType = Type(t) + return vr, nil } func (vr *valueReader) readByte() (byte, error) { - b, err := vr.r.ReadByte() + b, err := vr.src.ReadByte() if err != nil { return 0x0, err } - vr.offset++ return b, nil } func (vr *valueReader) readCString() (string, error) { - str, err := vr.r.ReadString(0x00) + data, err := vr.src.readSlice(0x00) if err != nil { return "", err } - l := len(str) - vr.offset += int64(l) - return str[:l-1], nil + return string(data[:len(data)-1]), nil } func (vr *valueReader) readString() (string, error) { @@ -820,79 +892,69 @@ func (vr *valueReader) readString() (string, error) { if err != nil { return "", err } + if length <= 0 { return "", fmt.Errorf("invalid string length: %d", length) } - buf := make([]byte, length) - err = vr.read(buf) + raw, err := readBytes(vr.src, int(length)) if err != nil { return "", err } - if buf[length-1] != 0x00 { - return "", fmt.Errorf("string does not end with null byte, but with %v", buf[length-1]) + // Check that the last byte is the NUL terminator. + if raw[len(raw)-1] != 0x00 { + return "", fmt.Errorf("string does not end with null byte, but with %v", raw[len(raw)-1]) } - return string(buf[:length-1]), nil + // Convert and strip the trailing NUL. + return string(raw[:len(raw)-1]), nil } func (vr *valueReader) peekLength() (int32, error) { - buf, err := vr.r.Peek(4) + buf, err := vr.src.peek(4) if err != nil { return 0, err } - return int32(binary.LittleEndian.Uint32(buf)), nil } func (vr *valueReader) readLength() (int32, error) { - l, err := vr.readi32() - if err != nil { - return 0, err - } - if l < 0 { - return 0, fmt.Errorf("invalid negative length: %d", l) - } - return l, nil + return vr.readi32() } func (vr *valueReader) readi32() (int32, error) { - var buf [4]byte - err := vr.read(buf[:]) + raw, err := readBytes(vr.src, 4) if err != nil { return 0, err } - return int32(binary.LittleEndian.Uint32(buf[:])), nil + return int32(binary.LittleEndian.Uint32(raw)), nil } func (vr *valueReader) readu32() (uint32, error) { - var buf [4]byte - err := vr.read(buf[:]) + raw, err := readBytes(vr.src, 4) if err != nil { return 0, err } - return binary.LittleEndian.Uint32(buf[:]), nil + return binary.LittleEndian.Uint32(raw), nil } func (vr *valueReader) readi64() (int64, error) { - var buf [8]byte - err := vr.read(buf[:]) + raw, err := readBytes(vr.src, 8) if err != nil { return 0, err } - return int64(binary.LittleEndian.Uint64(buf[:])), nil + return int64(binary.LittleEndian.Uint64(raw)), nil } func (vr *valueReader) readu64() (uint64, error) { - var buf [8]byte - err := vr.read(buf[:]) + raw, err := readBytes(vr.src, 8) if err != nil { return 0, err } - return binary.LittleEndian.Uint64(buf[:]), nil + return binary.LittleEndian.Uint64(raw), nil } diff --git a/bson/value_reader_test.go b/bson/value_reader_test.go index 852a291dd4..e259e16b07 100644 --- a/bson/value_reader_test.go +++ b/bson/value_reader_test.go @@ -26,62 +26,62 @@ var lorem []byte var testcstring = append(lorem, []byte{0x00}...) -func TestValueReader(t *testing.T) { - t.Run("ReadBinary", func(t *testing.T) { - testCases := []struct { - name string - data []byte - btype byte - b []byte - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - 0, - nil, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeBinary), - TypeEmbeddedDocument, - }, - { - "length too short", - []byte{}, - 0, - nil, - io.EOF, - TypeBinary, - }, - { - "no byte available", - []byte{0x00, 0x00, 0x00, 0x00}, - 0, - nil, - io.EOF, - TypeBinary, - }, - { - "not enough bytes for binary", - []byte{0x05, 0x00, 0x00, 0x00, 0x00}, - 0, - nil, - io.EOF, - TypeBinary, - }, - { - "success", - []byte{0x03, 0x00, 0x00, 0x00, 0xEA, 0x01, 0x02, 0x03}, - 0xEA, - []byte{0x01, 0x02, 0x03}, - nil, - TypeBinary, - }, - } +func TestValueReader_ReadBinary(t *testing.T) { + testCases := []struct { + name string + data []byte + btype byte + b []byte + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + btype: 0, + b: nil, + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeBinary), + vType: TypeEmbeddedDocument, + }, + { + name: "length too short", + data: []byte{}, + btype: 0, + b: nil, + err: io.EOF, + vType: TypeBinary, + }, + { + name: "no byte available", + data: []byte{0x00, 0x00, 0x00, 0x00}, + btype: 0, + b: nil, + err: io.EOF, + vType: TypeBinary, + }, + { + name: "not enough bytes for binary", + data: []byte{0x05, 0x00, 0x00, 0x00, 0x00}, + btype: 0, + b: nil, + err: io.EOF, + vType: TypeBinary, + }, + { + name: "success", + data: []byte{0x03, 0x00, 0x00, 0x00, 0xEA, 0x01, 0x02, 0x03}, + btype: 0xEA, + b: []byte{0x01, 0x02, 0x03}, + err: nil, + vType: TypeBinary, + }, + } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { @@ -103,50 +103,100 @@ func TestValueReader(t *testing.T) { t.Errorf("Binary data does not match. got %v; want %v", b, tc.b) } }) - } - }) - t.Run("ReadBoolean", func(t *testing.T) { - testCases := []struct { - name string - data []byte - boolean bool - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - false, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeBoolean), - TypeEmbeddedDocument, - }, - { - "no byte available", - []byte{}, - false, - io.EOF, - TypeBoolean, - }, - { - "invalid byte for boolean", - []byte{0x03}, - false, - fmt.Errorf("invalid byte for boolean, %b", 0x03), - TypeBoolean, - }, - { - "success", - []byte{0x01}, - true, - nil, - TypeBoolean, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { + t.Run("streaming", func(t *testing.T) { + vr := &valueReader{ + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + + b, btype, err := vr.ReadBinary() + if !errequal(t, err, tc.err) { + t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) + } + if btype != tc.btype { + t.Errorf("Incorrect binary type returned. got %v; want %v", btype, tc.btype) + } + if !bytes.Equal(b, tc.b) { + t.Errorf("Binary data does not match. got %v; want %v", b, tc.b) + } + }) + }) + } +} + +func TestValueReader_ReadBoolean(t *testing.T) { + testCases := []struct { + name string + data []byte + boolean bool + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + boolean: false, + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeBoolean), + vType: TypeEmbeddedDocument, + }, + { + name: "no byte available", + data: []byte{}, + boolean: false, + err: io.EOF, + vType: TypeBoolean, + }, + { + name: "invalid byte for boolean", + data: []byte{0x03}, + boolean: false, + err: fmt.Errorf("invalid byte for boolean, %b", 0x03), + vType: TypeBoolean, + }, + { + name: "success", + data: []byte{0x01}, + boolean: true, + err: nil, + vType: TypeBoolean, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: tc.data}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + + boolean, err := vr.ReadBoolean() + if !errequal(t, err, tc.err) { + t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) + } + if boolean != tc.boolean { + t.Errorf("Incorrect boolean returned. got %v; want %v", boolean, tc.boolean) + } + }) + + t.Run("streaming", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, stack: []vrState{ {mode: mTopLevel}, { @@ -165,154 +215,253 @@ func TestValueReader(t *testing.T) { t.Errorf("Incorrect boolean returned. got %v; want %v", boolean, tc.boolean) } }) + }) + } +} + +func TestValueReader_ReadDocument_TopLevel_InvalidLength(t *testing.T) { + t.Run("buffered", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: []byte{0x00, 0x00}}, + stack: []vrState{{mode: mTopLevel}}, + frame: 0, + } + + _, err := vr.ReadDocument() + if !errors.Is(err, io.EOF) { + t.Errorf("Expected io.ErrUnexpectedEOF with document length too small. got %v; want %v", err, io.EOF) + } + if vr.src.pos() != 0 { + t.Errorf("Expected 0 offset. got %d", vr.src.pos()) } }) - t.Run("ReadDocument", func(t *testing.T) { - t.Run("TopLevel", func(t *testing.T) { - doc := []byte{0x05, 0x00, 0x00, 0x00, 0x00} - vr := &valueReader{ - stack: []vrState{{mode: mTopLevel}}, - frame: 0, - } - // invalid length - vr.r = bufio.NewReader(bytes.NewReader([]byte{0x00, 0x00})) - _, err := vr.ReadDocument() - if !errors.Is(err, io.ErrUnexpectedEOF) { - t.Errorf("Expected io.ErrUnexpectedEOF with document length too small. got %v; want %v", err, io.EOF) - } - if vr.offset != 0 { - t.Errorf("Expected 0 offset. got %d", vr.offset) - } + t.Run("streaming", func(t *testing.T) { + vr := &valueReader{ + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader([]byte{0x00, 0x00}))}, + stack: []vrState{{mode: mTopLevel}}, + frame: 0, + } - vr.r = bufio.NewReader(bytes.NewReader(doc)) - _, err = vr.ReadDocument() - noerr(t, err) - if vr.stack[vr.frame].end != 5 { - t.Errorf("Incorrect end for document. got %d; want %d", vr.stack[vr.frame].end, 5) - } - }) - t.Run("EmbeddedDocument", func(t *testing.T) { - vr := &valueReader{ - stack: []vrState{ - {mode: mTopLevel}, - {mode: mElement, vType: TypeBoolean}, - }, - frame: 1, - } + _, err := vr.ReadDocument() + if !errors.Is(err, io.ErrUnexpectedEOF) { + t.Errorf("Expected io.ErrUnexpectedEOF with document length too small. got %v; want %v", err, io.EOF) + } + if vr.src.pos() != 0 { + t.Errorf("Expected 0 offset. got %d", vr.src.pos()) + } + }) +} - var wanterr = (&valueReader{stack: []vrState{{mode: mElement, vType: TypeBoolean}}}).typeError(TypeEmbeddedDocument) - _, err := vr.ReadDocument() - if err == nil || err.Error() != wanterr.Error() { - t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr) - } +func TestValueReader_ReadDocument_TopLevel_ValidDocumentWithIncorrectEnd(t *testing.T) { + t.Run("buffered", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: []byte{0x05, 0x00, 0x00, 0x00, 0x00}}, + stack: []vrState{{mode: mTopLevel}}, + frame: 0, + } - vr.stack[1].mode = mArray - wanterr = vr.invalidTransitionErr(mDocument, "ReadDocument", []mode{mTopLevel, mElement, mValue}) - _, err = vr.ReadDocument() - if err == nil || err.Error() != wanterr.Error() { - t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr) - } + _, err := vr.ReadDocument() + noerr(t, err) + if vr.stack[vr.frame].end != 5 { + t.Errorf("Incorrect end for document. got %d; want %d", vr.stack[vr.frame].end, 5) + } + }) - vr.stack[1].mode, vr.stack[1].vType = mElement, TypeEmbeddedDocument - vr.offset = 4 - vr.r = bufio.NewReader(bytes.NewReader([]byte{0x05, 0x00, 0x00, 0x00, 0x00, 0x00})) - _, err = vr.ReadDocument() - noerr(t, err) - if len(vr.stack) != 3 { - t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3) - } - if vr.stack[2].mode != mDocument { - t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument) - } - if vr.stack[2].end != 9 { - t.Errorf("End of embedded document is not correct. got %d; want %d", vr.stack[2].end, 9) - } - if vr.offset != 8 { - t.Errorf("Offset not incremented correctly. got %d; want %d", vr.offset, 8) - } + t.Run("streaming", func(t *testing.T) { - vr.frame-- - _, err = vr.ReadDocument() - if !errors.Is(err, io.ErrUnexpectedEOF) { - t.Errorf("Should return error when attempting to read length with not enough bytes. got %v; want %v", err, io.EOF) - } - }) + vr := &valueReader{ + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader([]byte{0x05, 0x00, 0x00, 0x00, 0x00}))}, + stack: []vrState{{mode: mTopLevel}}, + frame: 0, + } + + _, err := vr.ReadDocument() + noerr(t, err) + if vr.stack[vr.frame].end != 5 { + t.Errorf("Incorrect end for document. got %d; want %d", vr.stack[vr.frame].end, 5) + } }) - t.Run("ReadCodeWithScope", func(t *testing.T) { - codeWithScope := []byte{ - 0x11, 0x00, 0x00, 0x00, // total length - 0x4, 0x00, 0x00, 0x00, // string length - 'f', 'o', 'o', 0x00, // string - 0x05, 0x00, 0x00, 0x00, 0x00, // document +} + +func TestValueReader_ReadDocument_EmbeddedDocument(t *testing.T) { + t.Run("buffered", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: []byte{0x0a, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}, + stack: []vrState{ + {mode: mTopLevel}, + {mode: mElement, vType: TypeBoolean}, + }, + frame: 1, } - mismatchCodeWithScope := []byte{ - 0x11, 0x00, 0x00, 0x00, // total length - 0x4, 0x00, 0x00, 0x00, // string length - 'f', 'o', 'o', 0x00, // string - 0x07, 0x00, 0x00, 0x00, // document - 0x0A, 0x00, // null element, empty key - 0x00, // document end + + var wanterr = (&valueReader{stack: []vrState{{mode: mElement, vType: TypeBoolean}}}).typeError(TypeEmbeddedDocument) + _, err := vr.ReadDocument() + if err == nil || err.Error() != wanterr.Error() { + t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr) } - invalidCodeWithScope := []byte{ - 0x7, 0x00, 0x00, 0x00, // total length - 0x0, 0x00, 0x00, 0x00, // string length = 0 - 0x05, 0x00, 0x00, 0x00, 0x00, // document + + vr.stack[1].mode = mArray + wanterr = vr.invalidTransitionErr(mDocument, "ReadDocument", []mode{mTopLevel, mElement, mValue}) + _, err = vr.ReadDocument() + if err == nil || err.Error() != wanterr.Error() { + t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr) } - testCases := []struct { - name string - data []byte - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeCodeWithScope), - TypeEmbeddedDocument, - }, - { - "total length not enough bytes", - []byte{}, - io.EOF, - TypeCodeWithScope, - }, - { - "string length not enough bytes", - codeWithScope[:4], - io.EOF, - TypeCodeWithScope, - }, - { - "not enough string bytes", - codeWithScope[:8], - io.EOF, - TypeCodeWithScope, - }, - { - "document length not enough bytes", - codeWithScope[:12], - io.EOF, - TypeCodeWithScope, - }, - { - "length mismatch", - mismatchCodeWithScope, - fmt.Errorf("length of CodeWithScope does not match lengths of components; total: %d; components: %d", 17, 19), - TypeCodeWithScope, + + vr.stack[1].mode, vr.stack[1].vType = mElement, TypeEmbeddedDocument + _, _ = vr.src.discard(4) + + _, err = vr.ReadDocument() + noerr(t, err) + if len(vr.stack) != 3 { + t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3) + } + if vr.stack[2].mode != mDocument { + t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument) + } + if vr.stack[2].end != 9 { + t.Errorf("End of embedded document is not correct. got %d; want %d", vr.stack[2].end, 9) + } + if vr.src.pos() != 8 { + t.Errorf("Offset not incremented correctly. got %d; want %d", vr.src.pos(), 8) + } + + vr.frame-- + _, err = vr.ReadDocument() + + if !errors.Is(err, io.EOF) { + t.Errorf("Should return error when attempting to read length with not enough bytes. got %v; want %v", err, io.EOF) + } + }) + + t.Run("streaming", func(t *testing.T) { + vr := &valueReader{ + src: &streamingByteSrc{ + br: bufio.NewReader(bytes.NewReader([]byte{0x0a, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00})), }, - { - "invalid strLength", - invalidCodeWithScope, - fmt.Errorf("invalid string length: %d", 0), - TypeCodeWithScope, + stack: []vrState{ + {mode: mTopLevel}, + {mode: mElement, vType: TypeBoolean}, }, + frame: 1, } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { + var wanterr = (&valueReader{stack: []vrState{{mode: mElement, vType: TypeBoolean}}}).typeError(TypeEmbeddedDocument) + _, err := vr.ReadDocument() + if err == nil || err.Error() != wanterr.Error() { + t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr) + } + + vr.stack[1].mode = mArray + wanterr = vr.invalidTransitionErr(mDocument, "ReadDocument", []mode{mTopLevel, mElement, mValue}) + _, err = vr.ReadDocument() + if err == nil || err.Error() != wanterr.Error() { + t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr) + } + + vr.stack[1].mode, vr.stack[1].vType = mElement, TypeEmbeddedDocument + _, _ = vr.src.discard(4) + + _, err = vr.ReadDocument() + noerr(t, err) + if len(vr.stack) != 3 { + t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3) + } + if vr.stack[2].mode != mDocument { + t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument) + } + if vr.stack[2].end != 9 { + t.Errorf("End of embedded document is not correct. got %d; want %d", vr.stack[2].end, 9) + } + if vr.src.pos() != 8 { + t.Errorf("Offset not incremented correctly. got %d; want %d", vr.src.pos(), 8) + } + + vr.frame-- + _, err = vr.ReadDocument() + + if !errors.Is(err, io.ErrUnexpectedEOF) { + t.Errorf("Should return error when attempting to read length with not enough bytes. got %v; want %v", err, io.EOF) + } + }) +} + +func TestValueReader_ReadCodeWithScope(t *testing.T) { + codeWithScope := []byte{ + 0x11, 0x00, 0x00, 0x00, // total length + 0x4, 0x00, 0x00, 0x00, // string length + 'f', 'o', 'o', 0x00, // string + 0x05, 0x00, 0x00, 0x00, 0x00, // document + } + mismatchCodeWithScope := []byte{ + 0x11, 0x00, 0x00, 0x00, // total length + 0x4, 0x00, 0x00, 0x00, // string length + 'f', 'o', 'o', 0x00, // string + 0x07, 0x00, 0x00, 0x00, // document + 0x0A, 0x00, // null element, empty key + 0x00, // document end + } + invalidCodeWithScope := []byte{ + 0x7, 0x00, 0x00, 0x00, // total length + 0x0, 0x00, 0x00, 0x00, // string length = 0 + 0x05, 0x00, 0x00, 0x00, 0x00, // document + } + + testCases := []struct { + name string + data []byte + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeCodeWithScope), + vType: TypeEmbeddedDocument, + }, + { + name: "total length not enough bytes", + data: []byte{}, + err: io.EOF, + vType: TypeCodeWithScope, + }, + { + name: "string length not enough bytes", + data: codeWithScope[:4], + err: io.EOF, + vType: TypeCodeWithScope, + }, + { + name: "not enough string bytes", + data: codeWithScope[:8], + err: io.EOF, + vType: TypeCodeWithScope, + }, + { + name: "document length not enough bytes", + data: codeWithScope[:12], + err: io.EOF, + vType: TypeCodeWithScope, + }, + { + name: "length mismatch", + data: mismatchCodeWithScope, + err: fmt.Errorf("length of CodeWithScope does not match lengths of components; total: %d; components: %d", 17, 19), + vType: TypeCodeWithScope, + }, + { + name: "invalid strLength", + data: invalidCodeWithScope, + err: fmt.Errorf("invalid string length: %d", 0), + vType: TypeCodeWithScope, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { @@ -328,96 +477,154 @@ func TestValueReader(t *testing.T) { t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) } }) - } + }) - t.Run("success", func(t *testing.T) { + t.Run("streaming", func(t *testing.T) { vr := &valueReader{ - offset: 4, - r: bufio.NewReader(bytes.NewReader(codeWithScope)), + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, stack: []vrState{ {mode: mTopLevel}, - {mode: mElement, vType: TypeCodeWithScope}, + { + mode: mElement, + vType: tc.vType, + }, }, frame: 1, } - code, _, err := vr.ReadCodeWithScope() - noerr(t, err) - if code != "foo" { - t.Errorf("Code does not match. got %s; want %s", code, "foo") - } - if len(vr.stack) != 3 { - t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3) - } - if vr.stack[2].mode != mCodeWithScope { - t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument) - } - if vr.stack[2].end != 21 { - t.Errorf("End of scope is not correct. got %d; want %d", vr.stack[2].end, 21) - } - if vr.offset != 20 { - t.Errorf("Offset not incremented correctly. got %d; want %d", vr.offset, 20) + _, _, err := vr.ReadCodeWithScope() + if !errequal(t, err, tc.err) { + t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) } }) + } + + t.Run("buffered success", func(t *testing.T) { + doc := []byte{0x00, 0x00, 0x00, 0x00} + doc = append(doc, codeWithScope...) + doc = append(doc, 0x00) + vr := &valueReader{ + src: &bufferedByteSrc{buf: doc}, + stack: []vrState{ + {mode: mTopLevel}, + {mode: mElement, vType: TypeCodeWithScope}, + }, + frame: 1, + } + _, _ = vr.src.discard(4) // discard the document length + + code, _, err := vr.ReadCodeWithScope() + noerr(t, err) + if code != "foo" { + t.Errorf("Code does not match. got %s; want %s", code, "foo") + } + if len(vr.stack) != 3 { + t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3) + } + if vr.stack[2].mode != mCodeWithScope { + t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument) + } + if vr.stack[2].end != 21 { + t.Errorf("End of scope is not correct. got %d; want %d", vr.stack[2].end, 21) + } + if vr.src.pos() != 20 { + t.Errorf("Offset not incremented correctly. got %d; want %d", vr.src.pos(), 20) + } }) - t.Run("ReadDBPointer", func(t *testing.T) { - testCases := []struct { - name string - data []byte - ns string - oid ObjectID - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - "", - ObjectID{}, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDBPointer), - TypeEmbeddedDocument, - }, - { - "length too short", - []byte{}, - "", - ObjectID{}, - io.EOF, - TypeDBPointer, - }, - { - "not enough bytes for namespace", - []byte{0x04, 0x00, 0x00, 0x00}, - "", - ObjectID{}, - io.EOF, - TypeDBPointer, - }, - { - "not enough bytes for objectID", - []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}, - "", - ObjectID{}, - io.EOF, - TypeDBPointer, - }, - { - "success", - []byte{ - 0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, - }, - "foo", - ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, - nil, - TypeDBPointer, - }, + + t.Run("streaming success", func(t *testing.T) { + doc := []byte{0x00, 0x00, 0x00, 0x00} + doc = append(doc, codeWithScope...) + doc = append(doc, 0x00) + vr := &valueReader{ + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(doc))}, + stack: []vrState{ + {mode: mTopLevel}, + {mode: mElement, vType: TypeCodeWithScope}, + }, + frame: 1, } + _, _ = vr.src.discard(4) // discard the document length - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { + code, _, err := vr.ReadCodeWithScope() + noerr(t, err) + if code != "foo" { + t.Errorf("Code does not match. got %s; want %s", code, "foo") + } + if len(vr.stack) != 3 { + t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3) + } + if vr.stack[2].mode != mCodeWithScope { + t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument) + } + if vr.stack[2].end != 21 { + t.Errorf("End of scope is not correct. got %d; want %d", vr.stack[2].end, 21) + } + if vr.src.pos() != 20 { + t.Errorf("Offset not incremented correctly. got %d; want %d", vr.src.pos(), 20) + } + }) +} + +func TestValueReader_ReadDBPointer(t *testing.T) { + testCases := []struct { + name string + data []byte + ns string + oid ObjectID + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + ns: "", + oid: ObjectID{}, + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDBPointer), + vType: TypeEmbeddedDocument, + }, + { + name: "length too short", + data: []byte{}, + ns: "", + oid: ObjectID{}, + err: io.EOF, + vType: TypeDBPointer, + }, + { + name: "not enough bytes for namespace", + data: []byte{0x04, 0x00, 0x00, 0x00}, + ns: "", + oid: ObjectID{}, + err: io.EOF, + vType: TypeDBPointer, + }, + { + name: "not enough bytes for objectID", + data: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}, + ns: "", + oid: ObjectID{}, + err: io.EOF, + vType: TypeDBPointer, + }, + { + name: "success", + data: []byte{ + 0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + }, + ns: "foo", + oid: ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, + err: nil, + vType: TypeDBPointer, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { @@ -439,43 +646,10 @@ func TestValueReader(t *testing.T) { t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid) } }) - } - }) - t.Run("ReadDateTime", func(t *testing.T) { - testCases := []struct { - name string - data []byte - dt int64 - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - 0, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDateTime), - TypeEmbeddedDocument, - }, - { - "length too short", - []byte{}, - 0, - io.EOF, - TypeDateTime, - }, - { - "success", - []byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - 255, - nil, - TypeDateTime, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { + t.Run("streaming", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, stack: []vrState{ {mode: mTopLevel}, { @@ -486,54 +660,57 @@ func TestValueReader(t *testing.T) { frame: 1, } - dt, err := vr.ReadDateTime() + ns, oid, err := vr.ReadDBPointer() if !errequal(t, err, tc.err) { t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) } - if dt != tc.dt { - t.Errorf("Incorrect datetime returned. got %d; want %d", dt, tc.dt) + if ns != tc.ns { + t.Errorf("Incorrect namespace returned. got %v; want %v", ns, tc.ns) } - }) - } - }) - t.Run("ReadDecimal128", func(t *testing.T) { - testCases := []struct { - name string - data []byte - dc128 Decimal128 - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - Decimal128{}, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDecimal128), - TypeEmbeddedDocument, - }, - { - "length too short", - []byte{}, - Decimal128{}, - io.EOF, - TypeDecimal128, - }, - { - "success", - []byte{ - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Low - 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // High - }, - NewDecimal128(65280, 255), - nil, - TypeDecimal128, - }, - } + if oid != tc.oid { + t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid) + } + }) + }) + } +} - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { +func TestValueReader_ReadDateTime(t *testing.T) { + testCases := []struct { + name string + data []byte + dt int64 + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + dt: 0, + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDateTime), + vType: TypeEmbeddedDocument, + }, + { + name: "length too short", + data: []byte{}, + dt: 0, + err: io.EOF, + vType: TypeDateTime, + }, + { + name: "success", + data: []byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + dt: 255, + err: nil, + vType: TypeDateTime, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { @@ -544,56 +721,18 @@ func TestValueReader(t *testing.T) { frame: 1, } - dc128, err := vr.ReadDecimal128() + dt, err := vr.ReadDateTime() if !errequal(t, err, tc.err) { t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) } - gotHigh, gotLow := dc128.GetBytes() - wantHigh, wantLow := tc.dc128.GetBytes() - if gotHigh != wantHigh { - t.Errorf("Retuired high byte does not match. got %d; want %d", gotHigh, wantHigh) - } - if gotLow != wantLow { - t.Errorf("Returned low byte does not match. got %d; want %d", gotLow, wantLow) + if dt != tc.dt { + t.Errorf("Incorrect datetime returned. got %d; want %d", dt, tc.dt) } }) - } - }) - t.Run("ReadDouble", func(t *testing.T) { - testCases := []struct { - name string - data []byte - double float64 - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - 0, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDouble), - TypeEmbeddedDocument, - }, - { - "length too short", - []byte{}, - 0, - io.EOF, - TypeDouble, - }, - { - "success", - []byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - math.Float64frombits(255), - nil, - TypeDouble, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { + t.Run("streaming", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, stack: []vrState{ {mode: mTopLevel}, { @@ -604,51 +743,57 @@ func TestValueReader(t *testing.T) { frame: 1, } - double, err := vr.ReadDouble() + dt, err := vr.ReadDateTime() if !errequal(t, err, tc.err) { t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) } - if double != tc.double { - t.Errorf("Incorrect double returned. got %f; want %f", double, tc.double) + if dt != tc.dt { + t.Errorf("Incorrect datetime returned. got %d; want %d", dt, tc.dt) } }) - } - }) - t.Run("ReadInt32", func(t *testing.T) { - testCases := []struct { - name string - data []byte - i32 int32 - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - 0, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeInt32), - TypeEmbeddedDocument, - }, - { - "length too short", - []byte{}, - 0, - io.EOF, - TypeInt32, - }, - { - "success", - []byte{0xFF, 0x00, 0x00, 0x00}, - 255, - nil, - TypeInt32, - }, - } + }) + } +} - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { +func TestValueReader_ReadDecimal128(t *testing.T) { + testCases := []struct { + name string + data []byte + dc128 Decimal128 + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + dc128: Decimal128{}, + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDecimal128), + vType: TypeEmbeddedDocument, + }, + { + name: "length too short", + data: []byte{}, + dc128: Decimal128{}, + err: io.EOF, + vType: TypeDecimal128, + }, + { + name: "success", + data: []byte{ + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Low + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // High + }, + dc128: NewDecimal128(65280, 255), + err: nil, + vType: TypeDecimal128, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { @@ -659,51 +804,23 @@ func TestValueReader(t *testing.T) { frame: 1, } - i32, err := vr.ReadInt32() + dc128, err := vr.ReadDecimal128() if !errequal(t, err, tc.err) { t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) } - if i32 != tc.i32 { - t.Errorf("Incorrect int32 returned. got %d; want %d", i32, tc.i32) + gotHigh, gotLow := dc128.GetBytes() + wantHigh, wantLow := tc.dc128.GetBytes() + if gotHigh != wantHigh { + t.Errorf("Returned high byte does not match. got %d; want %d", gotHigh, wantHigh) + } + if gotLow != wantLow { + t.Errorf("Returned low byte does not match. got %d; want %d", gotLow, wantLow) } }) - } - }) - t.Run("ReadInt64", func(t *testing.T) { - testCases := []struct { - name string - data []byte - i64 int64 - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - 0, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeInt64), - TypeEmbeddedDocument, - }, - { - "length too short", - []byte{}, - 0, - io.EOF, - TypeInt64, - }, - { - "success", - []byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - 255, - nil, - TypeInt64, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { + t.Run("streaming", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, stack: []vrState{ {mode: mTopLevel}, { @@ -714,151 +831,59 @@ func TestValueReader(t *testing.T) { frame: 1, } - i64, err := vr.ReadInt64() + dc128, err := vr.ReadDecimal128() if !errequal(t, err, tc.err) { t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) } - if i64 != tc.i64 { - t.Errorf("Incorrect int64 returned. got %d; want %d", i64, tc.i64) + gotHigh, gotLow := dc128.GetBytes() + wantHigh, wantLow := tc.dc128.GetBytes() + if gotHigh != wantHigh { + t.Errorf("Returned high byte does not match. got %d; want %d", gotHigh, wantHigh) + } + if gotLow != wantLow { + t.Errorf("Returned low byte does not match. got %d; want %d", gotLow, wantLow) } }) - } - }) - t.Run("ReadJavascript/ReadString/ReadSymbol", func(t *testing.T) { - testCases := []struct { - name string - data []byte - fn func(*valueReader) (string, error) - css string // code, string, symbol :P - err error - vType Type - }{ - { - "ReadJavascript/incorrect type", - []byte{}, - (*valueReader).ReadJavascript, - "", - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeJavaScript), - TypeEmbeddedDocument, - }, - { - "ReadString/incorrect type", - []byte{}, - (*valueReader).ReadString, - "", - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeString), - TypeEmbeddedDocument, - }, - { - "ReadSymbol/incorrect type", - []byte{}, - (*valueReader).ReadSymbol, - "", - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeSymbol), - TypeEmbeddedDocument, - }, - { - "ReadJavascript/length too short", - []byte{}, - (*valueReader).ReadJavascript, - "", - io.EOF, - TypeJavaScript, - }, - { - "ReadString/length too short", - []byte{}, - (*valueReader).ReadString, - "", - io.EOF, - TypeString, - }, - { - "ReadString/long - length too short", - append([]byte{0x40, 0x27, 0x00, 0x00}, testcstring...), - (*valueReader).ReadString, - "", - io.ErrUnexpectedEOF, - TypeString, - }, - { - "ReadSymbol/length too short", - []byte{}, - (*valueReader).ReadSymbol, - "", - io.EOF, - TypeSymbol, - }, - { - "ReadJavascript/incorrect end byte", - []byte{0x01, 0x00, 0x00, 0x00, 0x05}, - (*valueReader).ReadJavascript, - "", - fmt.Errorf("string does not end with null byte, but with %v", 0x05), - TypeJavaScript, - }, - { - "ReadString/incorrect end byte", - []byte{0x01, 0x00, 0x00, 0x00, 0x05}, - (*valueReader).ReadString, - "", - fmt.Errorf("string does not end with null byte, but with %v", 0x05), - TypeString, - }, - { - "ReadString/long - incorrect end byte", - append([]byte{0x35, 0x27, 0x00, 0x00}, testcstring[:len(testcstring)-1]...), - (*valueReader).ReadString, - "", - fmt.Errorf("string does not end with null byte, but with %v", 0x20), - TypeString, - }, - { - "ReadSymbol/incorrect end byte", - []byte{0x01, 0x00, 0x00, 0x00, 0x05}, - (*valueReader).ReadSymbol, - "", - fmt.Errorf("string does not end with null byte, but with %v", 0x05), - TypeSymbol, - }, - { - "ReadJavascript/success", - []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}, - (*valueReader).ReadJavascript, - "foo", - nil, - TypeJavaScript, - }, - { - "ReadString/success", - []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}, - (*valueReader).ReadString, - "foo", - nil, - TypeString, - }, - { - "ReadString/long - success", - append([]byte{0x36, 0x27, 0x00, 0x00}, testcstring...), - (*valueReader).ReadString, - string(testcstring[:len(testcstring)-1]), - nil, - TypeString, - }, - { - "ReadSymbol/success", - []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}, - (*valueReader).ReadSymbol, - "foo", - nil, - TypeSymbol, - }, - } + }) + } +} - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { +func TestValueReader_ReadDouble(t *testing.T) { + testCases := []struct { + name string + data []byte + double float64 + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + double: 0, + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDouble), + vType: TypeEmbeddedDocument, + }, + { + name: "length too short", + data: []byte{}, + double: 0, + err: io.EOF, + vType: TypeDouble, + }, + { + name: "success", + data: []byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + double: math.Float64frombits(255), + err: nil, + vType: TypeDouble, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { @@ -869,76 +894,18 @@ func TestValueReader(t *testing.T) { frame: 1, } - css, err := tc.fn(vr) + double, err := vr.ReadDouble() if !errequal(t, err, tc.err) { t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) } - if css != tc.css { - t.Errorf("Incorrect (JavaScript,String,Symbol) returned. got %s; want %s", css, tc.css) + if double != tc.double { + t.Errorf("Incorrect double returned. got %f; want %f", double, tc.double) } }) - } - }) - t.Run("ReadMaxKey/ReadMinKey/ReadNull/ReadUndefined", func(t *testing.T) { - testCases := []struct { - name string - fn func(*valueReader) error - err error - vType Type - }{ - { - "ReadMaxKey/incorrect type", - (*valueReader).ReadMaxKey, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeMaxKey), - TypeEmbeddedDocument, - }, - { - "ReadMaxKey/success", - (*valueReader).ReadMaxKey, - nil, - TypeMaxKey, - }, - { - "ReadMinKey/incorrect type", - (*valueReader).ReadMinKey, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeMinKey), - TypeEmbeddedDocument, - }, - { - "ReadMinKey/success", - (*valueReader).ReadMinKey, - nil, - TypeMinKey, - }, - { - "ReadNull/incorrect type", - (*valueReader).ReadNull, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeNull), - TypeEmbeddedDocument, - }, - { - "ReadNull/success", - (*valueReader).ReadNull, - nil, - TypeNull, - }, - { - "ReadUndefined/incorrect type", - (*valueReader).ReadUndefined, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeUndefined), - TypeEmbeddedDocument, - }, - { - "ReadUndefined/success", - (*valueReader).ReadUndefined, - nil, - TypeUndefined, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { + t.Run("streaming", func(t *testing.T) { vr := &valueReader{ + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, stack: []vrState{ {mode: mTopLevel}, { @@ -949,48 +916,54 @@ func TestValueReader(t *testing.T) { frame: 1, } - err := tc.fn(vr) + double, err := vr.ReadDouble() if !errequal(t, err, tc.err) { t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) } + if double != tc.double { + t.Errorf("Incorrect double returned. got %f; want %f", double, tc.double) + } }) - } - }) - t.Run("ReadObjectID", func(t *testing.T) { - testCases := []struct { - name string - data []byte - oid ObjectID - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - ObjectID{}, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeObjectID), - TypeEmbeddedDocument, - }, - { - "not enough bytes for objectID", - []byte{}, - ObjectID{}, - io.EOF, - TypeObjectID, - }, - { - "success", - []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, - ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, - nil, - TypeObjectID, - }, - } + }) + } +} - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { +func TestValueReader_ReadInt32(t *testing.T) { + testCases := []struct { + name string + data []byte + i32 int32 + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + i32: 0, + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeInt32), + vType: TypeEmbeddedDocument, + }, + { + name: "length too short", + data: []byte{}, + i32: 0, + err: io.EOF, + vType: TypeInt32, + }, + { + name: "success", + data: []byte{0xFF, 0x00, 0x00, 0x00}, + i32: 255, + err: nil, + vType: TypeInt32, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { @@ -1001,63 +974,18 @@ func TestValueReader(t *testing.T) { frame: 1, } - oid, err := vr.ReadObjectID() + i32, err := vr.ReadInt32() if !errequal(t, err, tc.err) { t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) } - if oid != tc.oid { - t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid) + if i32 != tc.i32 { + t.Errorf("Incorrect int32 returned. got %d; want %d", i32, tc.i32) } }) - } - }) - t.Run("ReadRegex", func(t *testing.T) { - testCases := []struct { - name string - data []byte - pattern string - options string - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - "", - "", - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeRegex), - TypeEmbeddedDocument, - }, - { - "length too short", - []byte{}, - "", - "", - io.EOF, - TypeRegex, - }, - { - "not enough bytes for options", - []byte{'f', 'o', 'o', 0x00}, - "", - "", - io.EOF, - TypeRegex, - }, - { - "success", - []byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r', 0x00}, - "foo", - "bar", - nil, - TypeRegex, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { + t.Run("streaming", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, stack: []vrState{ {mode: mTopLevel}, { @@ -1068,66 +996,633 @@ func TestValueReader(t *testing.T) { frame: 1, } - pattern, options, err := vr.ReadRegex() + i32, err := vr.ReadInt32() if !errequal(t, err, tc.err) { t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) } - if pattern != tc.pattern { - t.Errorf("Incorrect pattern returned. got %s; want %s", pattern, tc.pattern) + if i32 != tc.i32 { + t.Errorf("Incorrect int32 returned. got %d; want %d", i32, tc.i32) } - if options != tc.options { + }) + }) + } +} + +func TestValueReader_ReadInt64(t *testing.T) { + testCases := []struct { + name string + data []byte + i64 int64 + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + i64: 0, + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeInt64), + vType: TypeEmbeddedDocument, + }, + { + name: "length too short", + data: []byte{}, + i64: 0, + err: io.EOF, + vType: TypeInt64, + }, + { + name: "success", + data: []byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + i64: 255, + err: nil, + vType: TypeInt64, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: tc.data}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + + i64, err := vr.ReadInt64() + if !errequal(t, err, tc.err) { + t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) + } + if i64 != tc.i64 { + t.Errorf("Incorrect int64 returned. got %d; want %d", i64, tc.i64) + } + }) + + t.Run("streaming", func(t *testing.T) { + vr := &valueReader{ + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + + i64, err := vr.ReadInt64() + if !errequal(t, err, tc.err) { + t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) + } + if i64 != tc.i64 { + t.Errorf("Incorrect int64 returned. got %d; want %d", i64, tc.i64) + } + }) + }) + } +} + +func TestValueReader_ReadJavascript_ReadString_ReadSymbol(t *testing.T) { + testCases := []struct { + name string + data []byte + fn func(*valueReader) (string, error) + css string // code, string, symbol + streamingErr error + bufferedError error + vType Type + }{ + { + name: "ReadJavascript/incorrect type", + data: []byte{}, + fn: (*valueReader).ReadJavascript, + css: "", + streamingErr: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeJavaScript), + bufferedError: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeJavaScript), + vType: TypeEmbeddedDocument, + }, + { + name: "ReadString/incorrect type", + data: []byte{}, + fn: (*valueReader).ReadString, + css: "", + streamingErr: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeString), + bufferedError: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeString), + vType: TypeEmbeddedDocument, + }, + { + name: "ReadSymbol/incorrect type", + data: []byte{}, + fn: (*valueReader).ReadSymbol, + css: "", + streamingErr: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeSymbol), + bufferedError: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeSymbol), + vType: TypeEmbeddedDocument, + }, + { + name: "ReadJavascript/length too short", + data: []byte{}, + fn: (*valueReader).ReadJavascript, + css: "", + streamingErr: io.EOF, + bufferedError: io.EOF, + vType: TypeJavaScript, + }, + { + name: "ReadString/length too short", + data: []byte{}, + fn: (*valueReader).ReadString, + css: "", + streamingErr: io.EOF, + bufferedError: io.EOF, + vType: TypeString, + }, + { + name: "ReadString/long - length too short", + data: append([]byte{0x40, 0x27, 0x00, 0x00}, testcstring...), + fn: (*valueReader).ReadString, + css: "", + streamingErr: io.ErrUnexpectedEOF, + bufferedError: io.EOF, + vType: TypeString, + }, + { + name: "ReadSymbol/length too short", + data: []byte{}, + fn: (*valueReader).ReadSymbol, + css: "", + streamingErr: io.EOF, + bufferedError: io.EOF, + vType: TypeSymbol, + }, + { + name: "ReadJavascript/incorrect end byte", + data: []byte{0x01, 0x00, 0x00, 0x00, 0x05}, + fn: (*valueReader).ReadJavascript, + css: "", + streamingErr: fmt.Errorf("string does not end with null byte, but with %v", 0x05), + bufferedError: fmt.Errorf("string does not end with null byte, but with %v", 0x05), + vType: TypeJavaScript, + }, + { + name: "ReadString/incorrect end byte", + data: []byte{0x01, 0x00, 0x00, 0x00, 0x05}, + fn: (*valueReader).ReadString, + css: "", + streamingErr: fmt.Errorf("string does not end with null byte, but with %v", 0x05), + bufferedError: fmt.Errorf("string does not end with null byte, but with %v", 0x05), + vType: TypeString, + }, + { + name: "ReadString/long - incorrect end byte", + data: append([]byte{0x35, 0x27, 0x00, 0x00}, testcstring[:len(testcstring)-1]...), + fn: (*valueReader).ReadString, + css: "", + streamingErr: fmt.Errorf("string does not end with null byte, but with %v", 0x20), + bufferedError: fmt.Errorf("string does not end with null byte, but with %v", 0x20), + vType: TypeString, + }, + { + name: "ReadSymbol/incorrect end byte", + data: []byte{0x01, 0x00, 0x00, 0x00, 0x05}, + fn: (*valueReader).ReadSymbol, + css: "", + streamingErr: fmt.Errorf("string does not end with null byte, but with %v", 0x05), + bufferedError: fmt.Errorf("string does not end with null byte, but with %v", 0x05), + vType: TypeSymbol, + }, + { + name: "ReadJavascript/success", + data: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}, + fn: (*valueReader).ReadJavascript, + css: "foo", + streamingErr: nil, + bufferedError: nil, + vType: TypeJavaScript, + }, + { + name: "ReadString/success", + data: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}, + fn: (*valueReader).ReadString, + css: "foo", + streamingErr: nil, + bufferedError: nil, + vType: TypeString, + }, + { + name: "ReadString/long - success", + data: append([]byte{0x36, 0x27, 0x00, 0x00}, testcstring...), + fn: (*valueReader).ReadString, + css: string(testcstring[:len(testcstring)-1]), + streamingErr: nil, + bufferedError: nil, + vType: TypeString, + }, + { + name: "ReadSymbol/success", + data: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}, + fn: (*valueReader).ReadSymbol, + css: "foo", + streamingErr: nil, + bufferedError: nil, + vType: TypeSymbol, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: tc.data}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + wantErr := tc.bufferedError + + css, err := tc.fn(vr) + if !errequal(t, err, wantErr) { + t.Errorf("Returned errors do not match. got %v; want %v", err, wantErr) + } + if css != tc.css { + t.Errorf("Incorrect (JavaScript,String,Symbol) returned. got %s; want %s", css, tc.css) + } + }) + + t.Run("streaming", func(t *testing.T) { + vr := &valueReader{ + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + wantErr := tc.streamingErr + + css, err := tc.fn(vr) + if !errequal(t, err, wantErr) { + t.Errorf("Returned errors do not match. got %v; want %v", err, wantErr) + } + if css != tc.css { + t.Errorf("Incorrect (JavaScript,String,Symbol) returned. got %s; want %s", css, tc.css) + } + }) + }) + } +} + +func TestValueReader_ReadMaxKey_ReadMinKey_ReadNull_ReadUndefined(t *testing.T) { + testCases := []struct { + name string + fn func(*valueReader) error + streamingErr error + bufferedErr error + vType Type + }{ + { + name: "ReadMaxKey/incorrect type", + fn: (*valueReader).ReadMaxKey, + streamingErr: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeMaxKey), + bufferedErr: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeMaxKey), + vType: TypeEmbeddedDocument, + }, + { + name: "ReadMaxKey/success", + fn: (*valueReader).ReadMaxKey, + streamingErr: nil, + bufferedErr: nil, + vType: TypeMaxKey, + }, + { + name: "ReadMinKey/incorrect type", + fn: (*valueReader).ReadMinKey, + streamingErr: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeMinKey), + bufferedErr: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeMinKey), + vType: TypeEmbeddedDocument, + }, + { + name: "ReadMinKey/success", + fn: (*valueReader).ReadMinKey, + streamingErr: nil, + bufferedErr: nil, + vType: TypeMinKey, + }, + { + name: "ReadNull/incorrect type", + fn: (*valueReader).ReadNull, + streamingErr: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeNull), + bufferedErr: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeNull), + vType: TypeEmbeddedDocument, + }, + { + name: "ReadNull/success", + fn: (*valueReader).ReadNull, + streamingErr: nil, + bufferedErr: nil, + vType: TypeNull, + }, + { + name: "ReadUndefined/incorrect type", + fn: (*valueReader).ReadUndefined, + streamingErr: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeUndefined), + bufferedErr: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeUndefined), + vType: TypeEmbeddedDocument, + }, + { + name: "ReadUndefined/success", + fn: (*valueReader).ReadUndefined, + streamingErr: nil, + bufferedErr: nil, + vType: TypeUndefined, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: []byte{}}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + wantErr := tc.bufferedErr + + err := tc.fn(vr) + if !errequal(t, err, wantErr) { + t.Errorf("Returned errors do not match. got %v; want %v", err, wantErr) + } + }) + + t.Run("streaming", func(t *testing.T) { + vr := &valueReader{ + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader([]byte{}))}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + wantErr := tc.streamingErr + + err := tc.fn(vr) + if !errequal(t, err, wantErr) { + t.Errorf("Returned errors do not match. got %v; want %v", err, wantErr) + } + }) + }) + } +} + +func TestValueReader_ReadObjectID(t *testing.T) { + testCases := []struct { + name string + data []byte + oid ObjectID + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + oid: ObjectID{}, + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeObjectID), + vType: TypeEmbeddedDocument, + }, + { + name: "not enough bytes for objectID", + data: []byte{}, + oid: ObjectID{}, + err: io.EOF, + vType: TypeObjectID, + }, + { + name: "success", + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, + oid: ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, + err: nil, + vType: TypeObjectID, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: tc.data}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + + oid, err := vr.ReadObjectID() + if !errequal(t, err, tc.err) { + t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) + } + if oid != tc.oid { + t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid) + } + }) + + t.Run("streaming", func(t *testing.T) { + vr := &valueReader{ + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + + oid, err := vr.ReadObjectID() + if !errequal(t, err, tc.err) { + t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) + } + if oid != tc.oid { + t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid) + } + }) + }) + } +} + +func TestValueReader_ReadRegex(t *testing.T) { + testCases := []struct { + name string + data []byte + pattern string + options string + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + pattern: "", + options: "", + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeRegex), + vType: TypeEmbeddedDocument, + }, + { + name: "length too short", + data: []byte{}, + pattern: "", + options: "", + err: io.EOF, + vType: TypeRegex, + }, + { + name: "not enough bytes for options", + data: []byte{'f', 'o', 'o', 0x00}, + pattern: "", + options: "", + err: io.EOF, + vType: TypeRegex, + }, + { + name: "success", + data: []byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r', 0x00}, + pattern: "foo", + options: "bar", + err: nil, + vType: TypeRegex, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: tc.data}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + + pattern, options, err := vr.ReadRegex() + if !errequal(t, err, tc.err) { + t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) + } + if pattern != tc.pattern { + t.Errorf("Incorrect pattern returned. got %s; want %s", pattern, tc.pattern) + } + if options != tc.options { t.Errorf("Incorrect options returned. got %s; want %s", options, tc.options) } }) - } - }) - t.Run("ReadTimestamp", func(t *testing.T) { - testCases := []struct { - name string - data []byte - ts uint32 - incr uint32 - err error - vType Type - }{ - { - "incorrect type", - []byte{}, - 0, - 0, - (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeTimestamp), - TypeEmbeddedDocument, - }, - { - "not enough bytes for increment", - []byte{}, - 0, - 0, - io.EOF, - TypeTimestamp, - }, - { - "not enough bytes for timestamp", - []byte{0x01, 0x02, 0x03, 0x04}, - 0, - 0, - io.EOF, - TypeTimestamp, - }, - { - "success", - []byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}, - 256, - 255, - nil, - TypeTimestamp, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { + t.Run("streaming", func(t *testing.T) { vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data)), + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + + pattern, options, err := vr.ReadRegex() + if !errequal(t, err, tc.err) { + t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) + } + if pattern != tc.pattern { + t.Errorf("Incorrect pattern returned. got %s; want %s", pattern, tc.pattern) + } + if options != tc.options { + t.Errorf("Incorrect options returned. got %s; want %s", options, tc.options) + } + }) + }) + } +} + +func TestValueReader_ReadTimestamp(t *testing.T) { + testCases := []struct { + name string + data []byte + ts uint32 + incr uint32 + err error + vType Type + }{ + { + name: "incorrect type", + data: []byte{}, + ts: 0, + incr: 0, + err: (&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeTimestamp), + vType: TypeEmbeddedDocument, + }, + { + name: "not enough bytes for increment", + data: []byte{}, + ts: 0, + incr: 0, + err: io.EOF, + vType: TypeTimestamp, + }, + { + name: "not enough bytes for timestamp", + data: []byte{0x01, 0x02, 0x03, 0x04}, + ts: 0, + incr: 0, + err: io.EOF, + vType: TypeTimestamp, + }, + { + name: "success", + data: []byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}, + ts: 256, + incr: 255, + err: nil, + vType: TypeTimestamp, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("buffered", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { @@ -1149,318 +1644,874 @@ func TestValueReader(t *testing.T) { t.Errorf("Incorrect increment returned. got %d; want %d", incr, tc.incr) } }) - } - }) - t.Run("ReadBytes & Skip", func(t *testing.T) { - index, docb := bsoncore.ReserveLength(nil) - docb = bsoncore.AppendNullElement(docb, "foobar") - docb = append(docb, 0x00) - docb = bsoncore.UpdateLength(docb, index, int32(len(docb))) - cwsbytes := bsoncore.AppendCodeWithScope(nil, "var hellow = world;", docb) - strbytes := []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00} + t.Run("streaming", func(t *testing.T) { + vr := &valueReader{ + src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(tc.data))}, + stack: []vrState{ + {mode: mTopLevel}, + { + mode: mElement, + vType: tc.vType, + }, + }, + frame: 1, + } + + ts, incr, err := vr.ReadTimestamp() + if !errequal(t, err, tc.err) { + t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err) + } + if ts != tc.ts { + t.Errorf("Incorrect timestamp returned. got %d; want %d", ts, tc.ts) + } + if incr != tc.incr { + t.Errorf("Incorrect increment returned. got %d; want %d", incr, tc.incr) + } + }) + }) + } +} + +func TestValueReader_ReadBytes_Skip_Buffered(t *testing.T) { + index, docb := bsoncore.ReserveLength(nil) + docb = bsoncore.AppendNullElement(docb, "foobar") + docb = append(docb, 0x00) + docb = bsoncore.UpdateLength(docb, index, int32(len(docb))) + cwsbytes := bsoncore.AppendCodeWithScope(nil, "var hellow = world;", docb) + strbytes := []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00} + + testCases := []struct { + name string + t Type + data []byte + err error + offset int64 + startingOffset int64 + }{ + { + name: "Array/invalid length", + t: TypeArray, + data: []byte{0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Array/not enough bytes", + t: TypeArray, + data: []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Array/success", + t: TypeArray, + data: []byte{0x08, 0x00, 0x00, 0x00, 0x0A, '1', 0x00, 0x00}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "EmbeddedDocument/invalid length", + t: TypeEmbeddedDocument, + data: []byte{0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "EmbeddedDocument/not enough bytes", + t: TypeEmbeddedDocument, + data: []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "EmbeddedDocument/success", + t: TypeEmbeddedDocument, + data: []byte{0x08, 0x00, 0x00, 0x00, 0x0A, 'A', 0x00, 0x00}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "CodeWithScope/invalid length", + t: TypeCodeWithScope, + data: []byte{0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "CodeWithScope/not enough bytes", + t: TypeCodeWithScope, + data: []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "CodeWithScope/success", + t: TypeCodeWithScope, + data: cwsbytes, + err: nil, offset: 41, startingOffset: 0, + }, + { + name: "Binary/invalid length", + t: TypeBinary, + data: []byte{0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Binary/not enough bytes", + t: TypeBinary, + data: []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Binary/success", + t: TypeBinary, + data: []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Boolean/invalid length", + t: TypeBoolean, + data: []byte{}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Boolean/success", + t: TypeBoolean, + data: []byte{0x01}, + err: nil, offset: 1, startingOffset: 0, + }, + { + name: "DBPointer/invalid length", + t: TypeDBPointer, + data: []byte{0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "DBPointer/not enough bytes", + t: TypeDBPointer, + data: []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "DBPointer/success", + t: TypeDBPointer, + data: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, + err: nil, offset: 17, startingOffset: 0, + }, + { + name: "DBPointer/not enough bytes", + t: TypeDateTime, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "DBPointer/success", + t: TypeDateTime, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Double/not enough bytes", + t: TypeDouble, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Double/success", + t: TypeDouble, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Int64/not enough bytes", + t: TypeInt64, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Int64/success", + t: TypeInt64, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Timestamp/not enough bytes", + t: TypeTimestamp, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Timestamp/success", + t: TypeTimestamp, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Decimal128/not enough bytes", + t: TypeDecimal128, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Decimal128/success", + t: TypeDecimal128, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}, + err: nil, offset: 16, startingOffset: 0, + }, + { + name: "Int32/not enough bytes", + t: TypeInt32, + data: []byte{0x01, 0x02}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Int32/success", + t: TypeInt32, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: nil, offset: 4, startingOffset: 0, + }, + { + name: "Javascript/invalid length", + t: TypeJavaScript, + data: strbytes[:2], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Javascript/not enough bytes", + t: TypeJavaScript, + data: strbytes[:5], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Javascript/success", + t: TypeJavaScript, + data: strbytes, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "String/invalid length", + t: TypeString, + data: strbytes[:2], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "String/not enough bytes", + t: TypeString, + data: strbytes[:5], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "String/success", + t: TypeString, + data: strbytes, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Symbol/invalid length", + t: TypeSymbol, + data: strbytes[:2], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Symbol/not enough bytes", + t: TypeSymbol, + data: strbytes[:5], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Symbol/success", + t: TypeSymbol, + data: strbytes, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "MaxKey/success", + t: TypeMaxKey, + data: []byte{}, + err: nil, offset: 0, startingOffset: 0, + }, + { + name: "MinKey/success", + t: TypeMinKey, + data: []byte{}, + err: nil, offset: 0, startingOffset: 0, + }, + { + name: "Null/success", + t: TypeNull, + data: []byte{}, + err: nil, offset: 0, startingOffset: 0, + }, + { + name: "Undefined/success", + t: TypeUndefined, + data: []byte{}, + err: nil, offset: 0, startingOffset: 0, + }, + { + name: "ObjectID/not enough bytes", + t: TypeObjectID, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "ObjectID/success", + t: TypeObjectID, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, + err: nil, offset: 12, startingOffset: 0, + }, + { + name: "Regex/not enough bytes (first string)", + t: TypeRegex, + data: []byte{'f', 'o', 'o'}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Regex/not enough bytes (second string)", + t: TypeRegex, + data: []byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r'}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Regex/success", + t: TypeRegex, + data: []byte{0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00, 'i', 0x00}, + err: nil, offset: 9, startingOffset: 3, + }, + { + name: "Unknown Type", + t: Type(0), + data: nil, + err: fmt.Errorf("attempted to read bytes of unknown BSON type %v", Type(0)), + offset: 0, startingOffset: 0, + }, + } + + for _, tc := range testCases { + tc := tc + const startingEnd = 64 + + t.Run(tc.name, func(t *testing.T) { + t.Run("Skip", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: tc.data, offset: tc.startingOffset}, + stack: []vrState{ + {mode: mTopLevel, end: startingEnd}, + {mode: mElement, vType: tc.t}, + }, + frame: 1, + } + + err := vr.Skip() + if !errequal(t, err, tc.err) { + t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err) + } + if tc.err == nil && vr.src.pos() != tc.offset { + t.Errorf("Offset not set at correct position; got %d; want %d", vr.src.pos(), tc.offset) + } + }) + t.Run("ReadBytes", func(t *testing.T) { + vr := &valueReader{ + src: &bufferedByteSrc{buf: tc.data, offset: tc.startingOffset}, + stack: []vrState{ + {mode: mTopLevel, end: startingEnd}, + {mode: mElement, vType: tc.t}, + }, + frame: 1, + } + + _, got, err := vr.readValueBytes(nil) + if !errequal(t, err, tc.err) { + t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err) + } + if tc.err == nil && vr.src.pos() != tc.offset { + t.Errorf("Offset not set at correct position; got %d; want %d", vr.src.pos(), tc.offset) + } + if tc.err == nil && !bytes.Equal(got, tc.data[tc.startingOffset:]) { + t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.data[tc.startingOffset:]) + } + }) + }) + } + + t.Run("ReadValueBytes/Top Level Doc", func(t *testing.T) { testCases := []struct { - name string - t Type - data []byte - err error - offset int64 - startingOffset int64 + name string + want []byte + wantType Type + wantErr error }{ { - "Array/invalid length", - TypeArray, - []byte{0x01, 0x02, 0x03}, - io.EOF, 0, 0, - }, - { - "Array/not enough bytes", - TypeArray, - []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, - io.EOF, 0, 0, - }, - { - "Array/success", - TypeArray, - []byte{0x08, 0x00, 0x00, 0x00, 0x0A, '1', 0x00, 0x00}, - nil, 8, 0, - }, - { - "EmbeddedDocument/invalid length", - TypeEmbeddedDocument, - []byte{0x01, 0x02, 0x03}, - io.EOF, 0, 0, - }, - { - "EmbeddedDocument/not enough bytes", - TypeEmbeddedDocument, - []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, - io.EOF, 0, 0, - }, - { - "EmbeddedDocument/success", - TypeEmbeddedDocument, - []byte{0x08, 0x00, 0x00, 0x00, 0x0A, 'A', 0x00, 0x00}, - nil, 8, 0, - }, - { - "CodeWithScope/invalid length", - TypeCodeWithScope, - []byte{0x01, 0x02, 0x03}, - io.EOF, 0, 0, - }, - { - "CodeWithScope/not enough bytes", - TypeCodeWithScope, - []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, - io.EOF, 0, 0, - }, - { - "CodeWithScope/success", - TypeCodeWithScope, - cwsbytes, - nil, 41, 0, - }, - { - "Binary/invalid length", - TypeBinary, - []byte{0x01, 0x02, 0x03}, - io.EOF, 0, 0, - }, - { - "Binary/not enough bytes", - TypeBinary, - []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, - io.EOF, 0, 0, - }, - { - "Binary/success", - TypeBinary, - []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, - nil, 8, 0, - }, - { - "Boolean/invalid length", - TypeBoolean, - []byte{}, - io.EOF, 0, 0, - }, - { - "Boolean/success", - TypeBoolean, - []byte{0x01}, - nil, 1, 0, + "success", + bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159)), + Type(0), + nil, }, { - "DBPointer/invalid length", - TypeDBPointer, + "wrong length", []byte{0x01, 0x02, 0x03}, - io.EOF, 0, 0, - }, - { - "DBPointer/not enough bytes", - TypeDBPointer, - []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, - io.EOF, 0, 0, - }, - { - "DBPointer/success", - TypeDBPointer, - []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, - nil, 17, 0, - }, - {"DBPointer/not enough bytes", TypeDateTime, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0}, - {"DBPointer/success", TypeDateTime, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0}, - {"Double/not enough bytes", TypeDouble, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0}, - {"Double/success", TypeDouble, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0}, - {"Int64/not enough bytes", TypeInt64, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0}, - {"Int64/success", TypeInt64, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0}, - {"Timestamp/not enough bytes", TypeTimestamp, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0}, - {"Timestamp/success", TypeTimestamp, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0}, - { - "Decimal128/not enough bytes", - TypeDecimal128, - []byte{0x01, 0x02, 0x03, 0x04}, - io.EOF, 0, 0, - }, - { - "Decimal128/success", - TypeDecimal128, - []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}, - nil, 16, 0, + Type(0), + io.EOF, }, - {"Int32/not enough bytes", TypeInt32, []byte{0x01, 0x02}, io.EOF, 0, 0}, - {"Int32/success", TypeInt32, []byte{0x01, 0x02, 0x03, 0x04}, nil, 4, 0}, - {"Javascript/invalid length", TypeJavaScript, strbytes[:2], io.EOF, 0, 0}, - {"Javascript/not enough bytes", TypeJavaScript, strbytes[:5], io.EOF, 0, 0}, - {"Javascript/success", TypeJavaScript, strbytes, nil, 8, 0}, - {"String/invalid length", TypeString, strbytes[:2], io.EOF, 0, 0}, - {"String/not enough bytes", TypeString, strbytes[:5], io.EOF, 0, 0}, - {"String/success", TypeString, strbytes, nil, 8, 0}, - {"Symbol/invalid length", TypeSymbol, strbytes[:2], io.EOF, 0, 0}, - {"Symbol/not enough bytes", TypeSymbol, strbytes[:5], io.EOF, 0, 0}, - {"Symbol/success", TypeSymbol, strbytes, nil, 8, 0}, - {"MaxKey/success", TypeMaxKey, []byte{}, nil, 0, 0}, - {"MinKey/success", TypeMinKey, []byte{}, nil, 0, 0}, - {"Null/success", TypeNull, []byte{}, nil, 0, 0}, - {"Undefined/success", TypeUndefined, []byte{}, nil, 0, 0}, { - "ObjectID/not enough bytes", - TypeObjectID, + "append bytes", []byte{0x01, 0x02, 0x03, 0x04}, - io.EOF, 0, 0, - }, - { - "ObjectID/success", - TypeObjectID, - []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, - nil, 12, 0, - }, - { - "Regex/not enough bytes (first string)", - TypeRegex, - []byte{'f', 'o', 'o'}, - io.EOF, 0, 0, - }, - { - "Regex/not enough bytes (second string)", - TypeRegex, - []byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r'}, - io.EOF, 0, 0, - }, - { - "Regex/success", - TypeRegex, - []byte{0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00, 'i', 0x00}, - nil, 9, 3, - }, - { - "Unknown Type", Type(0), - nil, - fmt.Errorf("attempted to read bytes of unknown BSON type %v", Type(0)), 0, 0, + io.EOF, }, } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { - const startingEnd = 64 - t.Run("Skip", func(t *testing.T) { - vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data[tc.startingOffset:tc.offset])), - stack: []vrState{ - {mode: mTopLevel, end: startingEnd}, - {mode: mElement, vType: tc.t}, - }, - frame: 1, + t.Parallel() + vr := &valueReader{ + src: &bufferedByteSrc{buf: tc.want}, + stack: []vrState{ + {mode: mTopLevel}, + }, + frame: 0, + } + gotType, got, gotErr := vr.readValueBytes(nil) + if !errors.Is(gotErr, tc.wantErr) { + t.Errorf("Did not receive expected error. got %v; want %v", gotErr, tc.wantErr) + } + if tc.wantErr == nil && gotType != tc.wantType { + t.Errorf("Did not receive expected type. got %v; want %v", gotType, tc.wantType) + } + if tc.wantErr == nil && !bytes.Equal(got, tc.want) { + t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.want) + } + }) + } + }) +} + +func TestValueReader_ReadBytes_Skip_Streaming(t *testing.T) { + index, docb := bsoncore.ReserveLength(nil) + docb = bsoncore.AppendNullElement(docb, "foobar") + docb = append(docb, 0x00) + docb = bsoncore.UpdateLength(docb, index, int32(len(docb))) + cwsbytes := bsoncore.AppendCodeWithScope(nil, "var hellow = world;", docb) + strbytes := []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00} + + testCases := []struct { + name string + t Type + data []byte + err error + offset int64 + startingOffset int64 + }{ + { + name: "Array/invalid length", + t: TypeArray, + data: []byte{0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Array/not enough bytes", + t: TypeArray, + data: []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Array/success", + t: TypeArray, + data: []byte{0x08, 0x00, 0x00, 0x00, 0x0A, '1', 0x00, 0x00}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "EmbeddedDocument/invalid length", + t: TypeEmbeddedDocument, + data: []byte{0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "EmbeddedDocument/not enough bytes", + t: TypeEmbeddedDocument, + data: []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "EmbeddedDocument/success", + t: TypeEmbeddedDocument, + data: []byte{0x08, 0x00, 0x00, 0x00, 0x0A, 'A', 0x00, 0x00}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "CodeWithScope/invalid length", + t: TypeCodeWithScope, + data: []byte{0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "CodeWithScope/not enough bytes", + t: TypeCodeWithScope, + data: []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "CodeWithScope/success", + t: TypeCodeWithScope, + data: cwsbytes, + err: nil, offset: 41, startingOffset: 0, + }, + { + name: "Binary/invalid length", + t: TypeBinary, + data: []byte{0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Binary/not enough bytes", + t: TypeBinary, + data: []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Binary/success", + t: TypeBinary, + data: []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Boolean/invalid length", + t: TypeBoolean, + data: []byte{}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Boolean/success", + t: TypeBoolean, + data: []byte{0x01}, + err: nil, offset: 1, startingOffset: 0, + }, + { + name: "DBPointer/invalid length", + t: TypeDBPointer, + data: []byte{0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "DBPointer/not enough bytes", + t: TypeDBPointer, + data: []byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "DBPointer/success", + t: TypeDBPointer, + data: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, + err: nil, offset: 17, startingOffset: 0, + }, + { + name: "DBPointer/not enough bytes", + t: TypeDateTime, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "DBPointer/success", + t: TypeDateTime, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Double/not enough bytes", + t: TypeDouble, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Double/success", + t: TypeDouble, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Int64/not enough bytes", + t: TypeInt64, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Int64/success", + t: TypeInt64, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Timestamp/not enough bytes", + t: TypeTimestamp, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Timestamp/success", + t: TypeTimestamp, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Decimal128/not enough bytes", + t: TypeDecimal128, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Decimal128/success", + t: TypeDecimal128, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}, + err: nil, offset: 16, startingOffset: 0, + }, + { + name: "Int32/not enough bytes", + t: TypeInt32, + data: []byte{0x01, 0x02}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Int32/success", + t: TypeInt32, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: nil, offset: 4, startingOffset: 0, + }, + { + name: "Javascript/invalid length", + t: TypeJavaScript, + data: strbytes[:2], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Javascript/not enough bytes", + t: TypeJavaScript, + data: strbytes[:5], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Javascript/success", + t: TypeJavaScript, + data: strbytes, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "String/invalid length", + t: TypeString, + data: strbytes[:2], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "String/not enough bytes", + t: TypeString, + data: strbytes[:5], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "String/success", + t: TypeString, + data: strbytes, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "Symbol/invalid length", + t: TypeSymbol, + data: strbytes[:2], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Symbol/not enough bytes", + t: TypeSymbol, + data: strbytes[:5], + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Symbol/success", + t: TypeSymbol, + data: strbytes, + err: nil, offset: 8, startingOffset: 0, + }, + { + name: "MaxKey/success", + t: TypeMaxKey, + data: []byte{}, + err: nil, offset: 0, startingOffset: 0, + }, + { + name: "MinKey/success", + t: TypeMinKey, + data: []byte{}, + err: nil, offset: 0, startingOffset: 0, + }, + { + name: "Null/success", + t: TypeNull, + data: []byte{}, + err: nil, offset: 0, startingOffset: 0, + }, + { + name: "Undefined/success", + t: TypeUndefined, + data: []byte{}, + err: nil, offset: 0, startingOffset: 0, + }, + { + name: "ObjectID/not enough bytes", + t: TypeObjectID, + data: []byte{0x01, 0x02, 0x03, 0x04}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "ObjectID/success", + t: TypeObjectID, + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, + err: nil, offset: 12, startingOffset: 0, + }, + { + name: "Regex/not enough bytes (first string)", + t: TypeRegex, + data: []byte{'f', 'o', 'o'}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Regex/not enough bytes (second string)", + t: TypeRegex, + data: []byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r'}, + err: io.EOF, offset: 0, startingOffset: 0, + }, + { + name: "Regex/success", + t: TypeRegex, + data: []byte{0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00, 'i', 0x00}, + err: nil, offset: 9, startingOffset: 3, + }, + { + name: "Unknown Type", + t: Type(0), + data: nil, + err: fmt.Errorf("attempted to read bytes of unknown BSON type %v", Type(0)), + offset: 0, startingOffset: 0, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + const startingEnd = 64 + t.Run("Skip", func(t *testing.T) { + vr := &valueReader{ + src: &streamingByteSrc{ + br: bufio.NewReader(bytes.NewReader(tc.data[tc.startingOffset:tc.offset])), offset: tc.startingOffset, - } + }, + stack: []vrState{ + {mode: mTopLevel, end: startingEnd}, + {mode: mElement, vType: tc.t}, + }, + frame: 1, + } - err := vr.Skip() - if !errequal(t, err, tc.err) { - t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err) - } - if tc.err == nil { - offset := startingEnd - vr.stack[0].end - if offset != tc.offset { - t.Errorf("Offset not set at correct position; got %d; want %d", offset, tc.offset) - } + err := vr.Skip() + if !errequal(t, err, tc.err) { + t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err) + } + if tc.err == nil { + offset := startingEnd - vr.stack[0].end + if offset != tc.offset { + t.Errorf("Offset not set at correct position; got %d; want %d", offset, tc.offset) } - }) - t.Run("ReadBytes", func(t *testing.T) { - vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.data[tc.startingOffset:tc.offset])), - stack: []vrState{ - {mode: mTopLevel, end: startingEnd}, - {mode: mElement, vType: tc.t}, - }, - frame: 1, + } + }) + t.Run("ReadBytes", func(t *testing.T) { + vr := &valueReader{ + src: &streamingByteSrc{ + br: bufio.NewReader(bytes.NewReader(tc.data[tc.startingOffset:tc.offset])), offset: tc.startingOffset, - } + }, + stack: []vrState{ + {mode: mTopLevel, end: startingEnd}, + {mode: mElement, vType: tc.t}, + }, + frame: 1, + } - _, got, err := vr.readValueBytes(nil) - if !errequal(t, err, tc.err) { - t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err) - } - if tc.err == nil { - offset := startingEnd - vr.stack[0].end - if offset != tc.offset { - t.Errorf("Offset not set at correct position; got %d; want %d", vr.offset, tc.offset) - } - } - if tc.err == nil && !bytes.Equal(got, tc.data[tc.startingOffset:]) { - t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.data[tc.startingOffset:]) + _, got, err := vr.readValueBytes(nil) + if !errequal(t, err, tc.err) { + t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err) + } + if tc.err == nil { + offset := startingEnd - vr.stack[0].end + if offset != tc.offset { + t.Errorf("Offset not set at correct position; got %d; want %d", vr.offset, tc.offset) } - }) + } + if tc.err == nil && !bytes.Equal(got, tc.data[tc.startingOffset:]) { + t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.data[tc.startingOffset:]) + } }) + }) + } + + t.Run("ReadValueBytes/Top Level Doc", func(t *testing.T) { + testCases := []struct { + name string + want []byte + wantType Type + wantErr error + }{ + { + "success", + bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159)), + Type(0), + nil, + }, + { + "wrong length", + []byte{0x01, 0x02, 0x03}, + Type(0), + io.EOF, + }, + { + "append bytes", + []byte{0x01, 0x02, 0x03, 0x04}, + Type(0), + io.ErrUnexpectedEOF, + }, } - t.Run("ReadValueBytes/Top Level Doc", func(t *testing.T) { - testCases := []struct { - name string - want []byte - wantType Type - wantErr error - }{ - { - "success", - bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159)), - Type(0), - nil, - }, - { - "wrong length", - []byte{0x01, 0x02, 0x03}, - Type(0), - io.EOF, - }, - { - "append bytes", - []byte{0x01, 0x02, 0x03, 0x04}, - Type(0), - io.ErrUnexpectedEOF, - }, - } - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - vr := &valueReader{ - r: bufio.NewReader(bytes.NewReader(tc.want)), - stack: []vrState{ - {mode: mTopLevel}, - }, - frame: 0, - } - gotType, got, gotErr := vr.readValueBytes(nil) - if !errors.Is(gotErr, tc.wantErr) { - t.Errorf("Did not receive expected error. got %v; want %v", gotErr, tc.wantErr) - } - if tc.wantErr == nil && gotType != tc.wantType { - t.Errorf("Did not receive expected type. got %v; want %v", gotType, tc.wantType) - } - if tc.wantErr == nil && !bytes.Equal(got, tc.want) { - t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.want) - } - }) - } - }) + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + vr := &valueReader{ + src: &streamingByteSrc{ + br: bufio.NewReader(bytes.NewReader(tc.want)), + }, + stack: []vrState{ + {mode: mTopLevel}, + }, + frame: 0, + } + gotType, got, gotErr := vr.readValueBytes(nil) + if !errors.Is(gotErr, tc.wantErr) { + t.Errorf("Did not receive expected error. got %v; want %v", gotErr, tc.wantErr) + } + if tc.wantErr == nil && gotType != tc.wantType { + t.Errorf("Did not receive expected type. got %v; want %v", gotType, tc.wantType) + } + if tc.wantErr == nil && !bytes.Equal(got, tc.want) { + t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.want) + } + }) + } }) +} - t.Run("invalid transition", func(t *testing.T) { - t.Run("Skip", func(t *testing.T) { - vr := &valueReader{stack: []vrState{{mode: mTopLevel}}} - wanterr := (&valueReader{stack: []vrState{{mode: mTopLevel}}}).invalidTransitionErr(0, "Skip", []mode{mElement, mValue}) - goterr := vr.Skip() - if !cmp.Equal(goterr, wanterr, cmp.Comparer(assert.CompareErrors)) { - t.Errorf("Expected correct invalid transition error. got %v; want %v", goterr, wanterr) - } - }) +func TestValueReader_InvalidTransition(t *testing.T) { + t.Run("Skip", func(t *testing.T) { + vr := &valueReader{stack: []vrState{{mode: mTopLevel}}} + wanterr := (&valueReader{stack: []vrState{{mode: mTopLevel}}}).invalidTransitionErr(0, "Skip", []mode{mElement, mValue}) + goterr := vr.Skip() + if !cmp.Equal(goterr, wanterr, cmp.Comparer(assert.CompareErrors)) { + t.Errorf("Expected correct invalid transition error. got %v; want %v", goterr, wanterr) + } }) + t.Run("ReadBytes", func(t *testing.T) { vr := &valueReader{stack: []vrState{{mode: mTopLevel}, {mode: mDocument}}, frame: 1} wanterr := (&valueReader{stack: []vrState{{mode: mTopLevel}, {mode: mDocument}}, frame: 1}). - invalidTransitionErr(0, "ReadValueBytes", []mode{mElement, mValue}) + invalidTransitionErr(0, "readValueBytes", []mode{mElement, mValue}) _, _, goterr := vr.readValueBytes(nil) if !cmp.Equal(goterr, wanterr, cmp.Comparer(assert.CompareErrors)) { t.Errorf("Expected correct invalid transition error. got %v; want %v", goterr, wanterr)