// Copyright (C) MongoDB, Inc. 2017-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" "bytes" _ "embed" "errors" "fmt" "io" "math" "testing" "github.com/google/go-cmp/cmp" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" ) //go:embed testdata/lorem.txt var lorem []byte var testcstring = append(lorem, []byte{0x00}...) 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) { 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, } 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) } }) 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{ src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader(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) } }) }) } } 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("streaming", func(t *testing.T) { vr := &valueReader{ src: &streamingByteSrc{br: bufio.NewReader(bytes.NewReader([]byte{0x00, 0x00}))}, stack: []vrState{{mode: mTopLevel}}, frame: 0, } _, 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()) } }) } 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, } _, 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("streaming", func(t *testing.T) { 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) } }) } 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, } 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.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})), }, stack: []vrState{ {mode: mTopLevel}, {mode: mElement, vType: TypeBoolean}, }, frame: 1, } 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{ src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { mode: mElement, vType: tc.vType, }, }, frame: 1, } _, _, 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("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, } _, _, 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("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 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{ src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { mode: mElement, vType: tc.vType, }, }, frame: 1, } 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 ns != tc.ns { t.Errorf("Incorrect namespace returned. got %v; want %v", ns, tc.ns) } 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, } 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 ns != tc.ns { t.Errorf("Incorrect namespace returned. got %v; want %v", ns, tc.ns) } if oid != tc.oid { t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid) } }) }) } } 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{ src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { mode: mElement, vType: tc.vType, }, }, frame: 1, } 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 dt != tc.dt { t.Errorf("Incorrect datetime returned. got %d; want %d", dt, tc.dt) } }) 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, } 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 dt != tc.dt { t.Errorf("Incorrect datetime returned. got %d; want %d", dt, tc.dt) } }) }) } } 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{ src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { mode: mElement, vType: tc.vType, }, }, frame: 1, } dc128, err := vr.ReadDecimal128() 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("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("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, } dc128, err := vr.ReadDecimal128() 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("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) } }) }) } } 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{ src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { mode: mElement, vType: tc.vType, }, }, frame: 1, } 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("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, } 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) } }) }) } } 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{ src: &bufferedByteSrc{buf: tc.data}, stack: []vrState{ {mode: mTopLevel}, { mode: mElement, vType: tc.vType, }, }, frame: 1, } 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 i32 != tc.i32 { t.Errorf("Incorrect int32 returned. got %d; want %d", i32, tc.i32) } }) 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, } 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 i32 != tc.i32 { t.Errorf("Incorrect int32 returned. got %d; want %d", i32, tc.i32) } }) }) } } 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("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, } 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}, { 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) } }) 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 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.EOF, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { 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) } } }) 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:]) } }) }) } 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{ 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) } }) } }) } 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}) _, _, 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) } }) } func errequal(t *testing.T, err1, err2 error) bool { t.Helper() if err1 == nil && err2 == nil { // If they are both nil, they are equal return true } if err1 == nil || err2 == nil { // If only one is nil, they are not equal return false } if errors.Is(err1, err2) { // They are the same error, they are equal return true } if err1.Error() == err2.Error() { // They string formats match, they are equal return true } return false }