From cd91cc0bd6d49568ede2b1e06306d586c287bef3 Mon Sep 17 00:00:00 2001 From: noisersup Date: Wed, 4 Sep 2024 11:42:29 +0200 Subject: [PATCH 01/46] wip --- wirebson/document.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wirebson/document.go b/wirebson/document.go index 32d8a4f..587bc1d 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -208,7 +208,7 @@ func (doc *Document) Command() string { // TODO https://github.com/FerretDB/wire/issues/21 // This method should accept a slice of bytes, not return it. // That would allow to avoid unnecessary allocations. -func (doc *Document) Encode() (RawDocument, error) { +func (doc *Document) Encode(d RawDocument) error { must.NotBeZero(doc) size := sizeDocument(doc) From 0768049b6c749b900a7f0c763b6f5b7006737686 Mon Sep 17 00:00:00 2001 From: noisersup Date: Wed, 4 Sep 2024 12:12:04 +0200 Subject: [PATCH 02/46] wip --- op_msg.go | 7 ++++--- op_query.go | 5 +++-- op_reply.go | 11 ++++++----- wire_test.go | 1 + wirebson/bson.go | 2 +- wirebson/bson_test.go | 20 ++++++++++++++------ wirebson/document.go | 12 ++++++------ wirebson/encode.go | 7 ++++--- wirebson/raw_document.go | 6 ++++-- wirebson/size.go | 8 ++++---- 10 files changed, 47 insertions(+), 32 deletions(-) diff --git a/op_msg.go b/op_msg.go index 63b8f2f..a60b271 100644 --- a/op_msg.go +++ b/op_msg.go @@ -38,13 +38,14 @@ type OpMsg struct { // NewOpMsg creates a message with a single section of kind 0 with a single document. func NewOpMsg(doc wirebson.AnyDocument) (*OpMsg, error) { - raw, err := doc.Encode() - if err != nil { + raw := make([]byte, wirebson.Size(doc)) + + if err := doc.Encode(raw); err != nil { return nil, lazyerrors.Error(err) } var msg OpMsg - if err = msg.SetSections(OpMsgSection{documents: []wirebson.RawDocument{raw}}); err != nil { + if err := msg.SetSections(OpMsgSection{documents: []wirebson.RawDocument{raw}}); err != nil { return nil, lazyerrors.Error(err) } diff --git a/op_query.go b/op_query.go index 64ad89b..75f158e 100644 --- a/op_query.go +++ b/op_query.go @@ -37,8 +37,9 @@ type OpQuery struct { // NewOpQuery creates a new OpQuery message. func NewOpQuery(doc wirebson.AnyDocument) (*OpQuery, error) { - raw, err := doc.Encode() - if err != nil { + raw := make([]byte, wirebson.Size(doc)) + + if err := doc.Encode(raw); err != nil { return nil, lazyerrors.Error(err) } diff --git a/op_reply.go b/op_reply.go index 6056fab..ec0c7c4 100644 --- a/op_reply.go +++ b/op_reply.go @@ -37,8 +37,9 @@ type OpReply struct { // NewOpReply creates a new OpReply message. func NewOpReply(doc wirebson.AnyDocument) (*OpReply, error) { - raw, err := doc.Encode() - if err != nil { + raw := make([]byte, wirebson.Size(doc)) + + if err := doc.Encode(raw); err != nil { return nil, lazyerrors.Error(err) } @@ -131,9 +132,9 @@ func (reply *OpReply) RawDocument() wirebson.RawDocument { // SetDocument sets reply document. func (reply *OpReply) SetDocument(doc *wirebson.Document) { - var err error - reply.document, err = doc.Encode() - if err != nil { + reply.document = make([]byte, wirebson.Size(doc)) + + if err := doc.Encode(reply.document); err != nil { panic(err) } } diff --git a/wire_test.go b/wire_test.go index 533a192..cf8ad5f 100644 --- a/wire_test.go +++ b/wire_test.go @@ -41,6 +41,7 @@ func makeRawDocument(pairs ...any) wirebson.RawDocument { d := wirebson.MustDocument(pairs...) raw, err := d.Encode() + if err != nil { panic(err) } diff --git a/wirebson/bson.go b/wirebson/bson.go index 944327d..ce45694 100644 --- a/wirebson/bson.go +++ b/wirebson/bson.go @@ -70,7 +70,7 @@ type ScalarType interface { // Note that the Encode and Decode methods could return the receiver itself, // so care must be taken when results are modified. type AnyDocument interface { - Encode() (RawDocument, error) + Encode(RawDocument) error Decode() (*Document, error) } diff --git a/wirebson/bson_test.go b/wirebson/bson_test.go index 0da80a4..d8f4346 100644 --- a/wirebson/bson_test.go +++ b/wirebson/bson_test.go @@ -621,8 +621,10 @@ func TestNormal(t *testing.T) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw, err := doc.Encode() + raw := make([]byte, Size(doc)) + err = doc.Encode(raw) require.NoError(t, err) + assert.Equal(t, tc.raw, raw) }) @@ -638,7 +640,8 @@ func TestNormal(t *testing.T) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw, err := doc.Encode() + raw := make([]byte, Size(doc)) + err = doc.Encode(raw) require.NoError(t, err) assert.Equal(t, tc.raw, raw) }) @@ -723,7 +726,8 @@ func BenchmarkDocument(b *testing.B) { b.ResetTimer() for range b.N { - raw, err = doc.Encode() + raw = make([]byte, Size(doc)) + err = doc.Encode(raw) } b.StopTimer() @@ -787,7 +791,8 @@ func BenchmarkDocument(b *testing.B) { b.ResetTimer() for range b.N { - raw, err = doc.Encode() + raw := make([]byte, Size(doc)) + err = doc.Encode(raw) } b.StopTimer() @@ -867,7 +872,9 @@ func testRawDocument(t *testing.T, rawDoc RawDocument) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw, err := doc.Encode() + raw := make([]byte, Size(doc)) + err = doc.Encode(raw) + if err == nil { assert.Equal(t, rawDoc, raw) } @@ -887,7 +894,8 @@ func testRawDocument(t *testing.T, rawDoc RawDocument) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw, err := doc.Encode() + raw := make([]byte, Size(doc)) + err = doc.Encode(raw) require.NoError(t, err) assert.Equal(t, rawDoc, raw) }) diff --git a/wirebson/document.go b/wirebson/document.go index 587bc1d..70f9edf 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -211,24 +211,24 @@ func (doc *Document) Command() string { func (doc *Document) Encode(d RawDocument) error { must.NotBeZero(doc) - size := sizeDocument(doc) - buf := bytes.NewBuffer(make([]byte, 0, size)) + size := Size(doc) + buf := bytes.NewBuffer(d) if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { - return nil, lazyerrors.Error(err) + return lazyerrors.Error(err) } for _, f := range doc.fields { if err := encodeField(buf, f.name, f.value); err != nil { - return nil, lazyerrors.Error(err) + return lazyerrors.Error(err) } } if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil { - return nil, lazyerrors.Error(err) + return lazyerrors.Error(err) } - return buf.Bytes(), nil + return nil } // Decode returns itself to implement [AnyDocument]. diff --git a/wirebson/encode.go b/wirebson/encode.go index 52a6e35..b1cb163 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -39,12 +39,13 @@ func encodeField(buf *bytes.Buffer, name string, v any) error { return lazyerrors.Error(err) } - b, err := v.Encode() - if err != nil { + b = make([]byte, Size(v)) + + if err := v.Encode(b); err != nil { return lazyerrors.Error(err) } - if _, err = buf.Write(b); err != nil { + if _, err := buf.Write(b); err != nil { return lazyerrors.Error(err) } diff --git a/wirebson/raw_document.go b/wirebson/raw_document.go index 16c08a3..69bca78 100644 --- a/wirebson/raw_document.go +++ b/wirebson/raw_document.go @@ -29,9 +29,11 @@ type RawDocument []byte // Encode returns itself to implement the [AnyDocument] interface. // // Receiver must not be nil. -func (raw RawDocument) Encode() (RawDocument, error) { +func (raw RawDocument) Encode(d RawDocument) error { must.BeTrue(raw != nil) - return raw, nil + d = raw + + return nil } // Decode decodes a single non-nil BSON document that takes the whole non-nil byte slice. diff --git a/wirebson/size.go b/wirebson/size.go index 71d8d4c..2607aa1 100644 --- a/wirebson/size.go +++ b/wirebson/size.go @@ -20,10 +20,10 @@ import ( "time" ) -// size returns a size of the encoding of value v in bytes. +// Size returns a Size of the encoding of value v in bytes. // // It panics for invalid types. -func size(v any) int { +func Size(v any) int { switch v := v.(type) { case *Document: return sizeDocument(v) @@ -43,7 +43,7 @@ func sizeDocument(doc *Document) int { res := 5 for _, f := range doc.fields { - res += 1 + SizeCString(f.name) + size(f.value) + res += 1 + SizeCString(f.name) + Size(f.value) } return res @@ -54,7 +54,7 @@ func sizeArray(arr *Array) int { res := 5 for i, v := range arr.elements { - res += 1 + SizeCString(strconv.Itoa(i)) + size(v) + res += 1 + SizeCString(strconv.Itoa(i)) + Size(v) } return res From 9e1e3ad4f59fec96e440b5683f7ef4b398bac6df Mon Sep 17 00:00:00 2001 From: noisersup Date: Wed, 4 Sep 2024 12:31:20 +0200 Subject: [PATCH 03/46] wip --- wirebson/document.go | 8 +++++--- wirebson/raw_document.go | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/wirebson/document.go b/wirebson/document.go index 70f9edf..39b89f6 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -214,9 +214,11 @@ func (doc *Document) Encode(d RawDocument) error { size := Size(doc) buf := bytes.NewBuffer(d) - if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { - return lazyerrors.Error(err) - } + binary.LittleEndian.PutUint32(d, uint32(size)) + + //if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { + // return lazyerrors.Error(err) + //} for _, f := range doc.fields { if err := encodeField(buf, f.name, f.value); err != nil { diff --git a/wirebson/raw_document.go b/wirebson/raw_document.go index 69bca78..3ce7f68 100644 --- a/wirebson/raw_document.go +++ b/wirebson/raw_document.go @@ -31,7 +31,7 @@ type RawDocument []byte // Receiver must not be nil. func (raw RawDocument) Encode(d RawDocument) error { must.BeTrue(raw != nil) - d = raw + copy(d, raw) return nil } From ffbce946f22f88a00190e1b884154d307465ca56 Mon Sep 17 00:00:00 2001 From: noisersup Date: Wed, 4 Sep 2024 13:59:16 +0200 Subject: [PATCH 04/46] keep --- wirebson/bson_test.go | 2 +- wirebson/document.go | 23 +++++++++++------------ wirebson/raw_document.go | 16 ++++++++++++++++ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/wirebson/bson_test.go b/wirebson/bson_test.go index d8f4346..742d37b 100644 --- a/wirebson/bson_test.go +++ b/wirebson/bson_test.go @@ -621,7 +621,7 @@ func TestNormal(t *testing.T) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw := make([]byte, Size(doc)) + raw := make([]byte, 0, Size(doc)) err = doc.Encode(raw) require.NoError(t, err) diff --git a/wirebson/document.go b/wirebson/document.go index 39b89f6..9399773 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -15,7 +15,6 @@ package wirebson import ( - "bytes" "encoding/binary" "log/slog" "slices" @@ -212,21 +211,21 @@ func (doc *Document) Encode(d RawDocument) error { must.NotBeZero(doc) size := Size(doc) - buf := bytes.NewBuffer(d) + //buf := bytes.NewBuffer(d) - binary.LittleEndian.PutUint32(d, uint32(size)) + //binary.LittleEndian.PutUint32(d, uint32(size)) - //if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { - // return lazyerrors.Error(err) - //} - - for _, f := range doc.fields { - if err := encodeField(buf, f.name, f.value); err != nil { - return lazyerrors.Error(err) - } + if err := binary.Write(d, binary.LittleEndian, uint32(size)); err != nil { + return lazyerrors.Error(err) } - if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil { + //for _, f := range doc.fields { + // //if err := encodeField(d, f.name, f.value); err != nil { + // // return lazyerrors.Error(err) + // //} + //} + + if err := binary.Write(d, binary.LittleEndian, byte(0)); err != nil { return lazyerrors.Error(err) } diff --git a/wirebson/raw_document.go b/wirebson/raw_document.go index 3ce7f68..e4f8d37 100644 --- a/wirebson/raw_document.go +++ b/wirebson/raw_document.go @@ -15,6 +15,7 @@ package wirebson import ( + "fmt" "log/slog" "github.com/FerretDB/wire/internal/util/lazyerrors" @@ -26,6 +27,21 @@ import ( // It generally references a part of a larger slice, not a copy. type RawDocument []byte +func (raw RawDocument) Write(p []byte) (n int, err error) { + i := len([]byte(raw)) + + if i+len(p) <= cap(raw) { + return 0, fmt.Errorf("Exceeded []byte cap") + } + + for _, b := range p { + raw[i] = b + i++ + } + + return len(p), nil +} + // Encode returns itself to implement the [AnyDocument] interface. // // Receiver must not be nil. From 05fd3558c5b4e76163db5b8c6e513df36443f1ab Mon Sep 17 00:00:00 2001 From: noisersup Date: Wed, 4 Sep 2024 13:59:21 +0200 Subject: [PATCH 05/46] Revert "keep" This reverts commit ffbce946f22f88a00190e1b884154d307465ca56. --- wirebson/bson_test.go | 2 +- wirebson/document.go | 23 ++++++++++++----------- wirebson/raw_document.go | 16 ---------------- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/wirebson/bson_test.go b/wirebson/bson_test.go index 742d37b..d8f4346 100644 --- a/wirebson/bson_test.go +++ b/wirebson/bson_test.go @@ -621,7 +621,7 @@ func TestNormal(t *testing.T) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw := make([]byte, 0, Size(doc)) + raw := make([]byte, Size(doc)) err = doc.Encode(raw) require.NoError(t, err) diff --git a/wirebson/document.go b/wirebson/document.go index 9399773..39b89f6 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -15,6 +15,7 @@ package wirebson import ( + "bytes" "encoding/binary" "log/slog" "slices" @@ -211,21 +212,21 @@ func (doc *Document) Encode(d RawDocument) error { must.NotBeZero(doc) size := Size(doc) - //buf := bytes.NewBuffer(d) + buf := bytes.NewBuffer(d) - //binary.LittleEndian.PutUint32(d, uint32(size)) + binary.LittleEndian.PutUint32(d, uint32(size)) - if err := binary.Write(d, binary.LittleEndian, uint32(size)); err != nil { - return lazyerrors.Error(err) - } - - //for _, f := range doc.fields { - // //if err := encodeField(d, f.name, f.value); err != nil { - // // return lazyerrors.Error(err) - // //} + //if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { + // return lazyerrors.Error(err) //} - if err := binary.Write(d, binary.LittleEndian, byte(0)); err != nil { + for _, f := range doc.fields { + if err := encodeField(buf, f.name, f.value); err != nil { + return lazyerrors.Error(err) + } + } + + if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil { return lazyerrors.Error(err) } diff --git a/wirebson/raw_document.go b/wirebson/raw_document.go index e4f8d37..3ce7f68 100644 --- a/wirebson/raw_document.go +++ b/wirebson/raw_document.go @@ -15,7 +15,6 @@ package wirebson import ( - "fmt" "log/slog" "github.com/FerretDB/wire/internal/util/lazyerrors" @@ -27,21 +26,6 @@ import ( // It generally references a part of a larger slice, not a copy. type RawDocument []byte -func (raw RawDocument) Write(p []byte) (n int, err error) { - i := len([]byte(raw)) - - if i+len(p) <= cap(raw) { - return 0, fmt.Errorf("Exceeded []byte cap") - } - - for _, b := range p { - raw[i] = b - i++ - } - - return len(p), nil -} - // Encode returns itself to implement the [AnyDocument] interface. // // Receiver must not be nil. From c24cdf93023c0c1534725531a3c01bb9469cdbda Mon Sep 17 00:00:00 2001 From: noisersup Date: Wed, 4 Sep 2024 14:21:41 +0200 Subject: [PATCH 06/46] wip --- wirebson/document.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/wirebson/document.go b/wirebson/document.go index 39b89f6..2f9586c 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -15,7 +15,6 @@ package wirebson import ( - "bytes" "encoding/binary" "log/slog" "slices" @@ -212,7 +211,6 @@ func (doc *Document) Encode(d RawDocument) error { must.NotBeZero(doc) size := Size(doc) - buf := bytes.NewBuffer(d) binary.LittleEndian.PutUint32(d, uint32(size)) @@ -220,15 +218,13 @@ func (doc *Document) Encode(d RawDocument) error { // return lazyerrors.Error(err) //} - for _, f := range doc.fields { - if err := encodeField(buf, f.name, f.value); err != nil { - return lazyerrors.Error(err) - } - } + //for _, f := range doc.fields { + // if err := encodeField(buf, f.name, f.value); err != nil { + // return lazyerrors.Error(err) + // } + //} - if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil { - return lazyerrors.Error(err) - } + d = append(d, byte(0)) return nil } From 9aa72adcac4329614710b492209b158dc4ea3662 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 6 Sep 2024 15:41:10 +0200 Subject: [PATCH 07/46] cap --- op_msg.go | 2 +- op_query.go | 2 +- op_reply.go | 4 ++-- wirebson/bson_test.go | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/op_msg.go b/op_msg.go index a60b271..878ff05 100644 --- a/op_msg.go +++ b/op_msg.go @@ -38,7 +38,7 @@ type OpMsg struct { // NewOpMsg creates a message with a single section of kind 0 with a single document. func NewOpMsg(doc wirebson.AnyDocument) (*OpMsg, error) { - raw := make([]byte, wirebson.Size(doc)) + raw := make([]byte, 0, wirebson.Size(doc)) if err := doc.Encode(raw); err != nil { return nil, lazyerrors.Error(err) diff --git a/op_query.go b/op_query.go index 75f158e..0e5059d 100644 --- a/op_query.go +++ b/op_query.go @@ -37,7 +37,7 @@ type OpQuery struct { // NewOpQuery creates a new OpQuery message. func NewOpQuery(doc wirebson.AnyDocument) (*OpQuery, error) { - raw := make([]byte, wirebson.Size(doc)) + raw := make([]byte, 0, wirebson.Size(doc)) if err := doc.Encode(raw); err != nil { return nil, lazyerrors.Error(err) diff --git a/op_reply.go b/op_reply.go index ec0c7c4..0276906 100644 --- a/op_reply.go +++ b/op_reply.go @@ -37,7 +37,7 @@ type OpReply struct { // NewOpReply creates a new OpReply message. func NewOpReply(doc wirebson.AnyDocument) (*OpReply, error) { - raw := make([]byte, wirebson.Size(doc)) + raw := make([]byte, 0, wirebson.Size(doc)) if err := doc.Encode(raw); err != nil { return nil, lazyerrors.Error(err) @@ -132,7 +132,7 @@ func (reply *OpReply) RawDocument() wirebson.RawDocument { // SetDocument sets reply document. func (reply *OpReply) SetDocument(doc *wirebson.Document) { - reply.document = make([]byte, wirebson.Size(doc)) + reply.document = make([]byte, 0, wirebson.Size(doc)) if err := doc.Encode(reply.document); err != nil { panic(err) diff --git a/wirebson/bson_test.go b/wirebson/bson_test.go index d8f4346..525bbd8 100644 --- a/wirebson/bson_test.go +++ b/wirebson/bson_test.go @@ -621,7 +621,7 @@ func TestNormal(t *testing.T) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw := make([]byte, Size(doc)) + raw := make([]byte, 0, Size(doc)) err = doc.Encode(raw) require.NoError(t, err) @@ -640,7 +640,7 @@ func TestNormal(t *testing.T) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw := make([]byte, Size(doc)) + raw := make([]byte, 0, Size(doc)) err = doc.Encode(raw) require.NoError(t, err) assert.Equal(t, tc.raw, raw) @@ -726,7 +726,7 @@ func BenchmarkDocument(b *testing.B) { b.ResetTimer() for range b.N { - raw = make([]byte, Size(doc)) + raw = make([]byte, 0, Size(doc)) err = doc.Encode(raw) } @@ -791,7 +791,7 @@ func BenchmarkDocument(b *testing.B) { b.ResetTimer() for range b.N { - raw := make([]byte, Size(doc)) + raw := make([]byte, 0, Size(doc)) err = doc.Encode(raw) } @@ -872,7 +872,7 @@ func testRawDocument(t *testing.T, rawDoc RawDocument) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw := make([]byte, Size(doc)) + raw := make([]byte, 0, Size(doc)) err = doc.Encode(raw) if err == nil { From 116bd79456636d3034f834724d7548ee1e4deec1 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 6 Sep 2024 16:03:44 +0200 Subject: [PATCH 08/46] oof --- wirebson/document.go | 13 ++++--- wirebson/encode.go | 88 +++++++++++++++++++++++--------------------- 2 files changed, 54 insertions(+), 47 deletions(-) diff --git a/wirebson/document.go b/wirebson/document.go index 2f9586c..4de9080 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -218,13 +218,14 @@ func (doc *Document) Encode(d RawDocument) error { // return lazyerrors.Error(err) //} - //for _, f := range doc.fields { - // if err := encodeField(buf, f.name, f.value); err != nil { - // return lazyerrors.Error(err) - // } - //} + for _, f := range doc.fields { + if err := encodeField(d, f.name, f.value); err != nil { + return lazyerrors.Error(err) + } + } - d = append(d, byte(0)) + // TODO + //d = append(d, byte(0)) return nil } diff --git a/wirebson/encode.go b/wirebson/encode.go index b1cb163..66059d3 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -22,85 +22,91 @@ import ( "github.com/FerretDB/wire/internal/util/lazyerrors" ) +func writeByte(d []byte, b byte) { + // TODO handle overflow + d[getIndex(d)] = b +} + +func write(d []byte, b []byte) { + // TODO handle overflow + i := getIndex(d) + copy(d[i:], b) +} + +func getIndex(d []byte) int { + // TODO handle overflow + return cap(d) - len(d) +} + // encodeField encodes document/array field. // // It panics if v is not a valid type. -func encodeField(buf *bytes.Buffer, name string, v any) error { +func encodeField(d []byte, name string, v any) error { switch v := v.(type) { case *Document: - if err := buf.WriteByte(byte(tagDocument)); err != nil { - return lazyerrors.Error(err) - } + writeByte(d, byte(tagDocument)) + //if err := buf.WriteByte(byte(tagDocument)); err != nil { + // return lazyerrors.Error(err) + //} b := make([]byte, SizeCString(name)) EncodeCString(b, name) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(d, b) - b = make([]byte, Size(v)) + v.Encode(b) - if err := v.Encode(b); err != nil { - return lazyerrors.Error(err) - } + b = make([]byte, 0, Size(v)) + write(d, b) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + //if _, err := buf.Write(b); err != nil { + // return lazyerrors.Error(err) + //} + + //b = make([]byte, Size(v)) + + //if err := v.Encode(b); err != nil { + // return lazyerrors.Error(err) + //} + + //if _, err := buf.Write(b); err != nil { + // return lazyerrors.Error(err) + //} case RawDocument: - if err := buf.WriteByte(byte(tagDocument)); err != nil { - return lazyerrors.Error(err) - } + writeByte(d, byte(tagDocument)) b := make([]byte, SizeCString(name)) EncodeCString(b, name) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(d, b) - if _, err := buf.Write(v); err != nil { - return lazyerrors.Error(err) - } + write(d, v) case *Array: - if err := buf.WriteByte(byte(tagArray)); err != nil { - return lazyerrors.Error(err) - } + writeByte(d, byte(tagArray)) b := make([]byte, SizeCString(name)) EncodeCString(b, name) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(d, b) b, err := v.Encode() if err != nil { return lazyerrors.Error(err) } - if _, err = buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(d, b) case RawArray: - if err := buf.WriteByte(byte(tagArray)); err != nil { - return lazyerrors.Error(err) - } + writeByte(d, byte(tagArray)) b := make([]byte, SizeCString(name)) EncodeCString(b, name) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(d, b) - if _, err := buf.Write(v); err != nil { - return lazyerrors.Error(err) - } + write(d, v) default: return encodeScalarField(buf, name, v) From 0ba5399ab69715541bca73a3b066156823e0057e Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 6 Sep 2024 16:10:21 +0200 Subject: [PATCH 09/46] wip --- wirebson/encode.go | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index 66059d3..2cf421f 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -15,7 +15,6 @@ package wirebson import ( - "bytes" "fmt" "time" @@ -109,7 +108,7 @@ func encodeField(d []byte, name string, v any) error { write(d, v) default: - return encodeScalarField(buf, name, v) + return encodeScalarField(d, name, v) } return nil @@ -118,32 +117,32 @@ func encodeField(d []byte, name string, v any) error { // encodeScalarField encodes scalar document field. // // It panics if v is not a scalar value. -func encodeScalarField(buf *bytes.Buffer, name string, v any) error { +func encodeScalarField(d []byte, name string, v any) error { switch v := v.(type) { case float64: - buf.WriteByte(byte(tagFloat64)) + writeByte(d, byte(tagFloat64)) case string: - buf.WriteByte(byte(tagString)) + writeByte(d, byte(tagString)) case Binary: - buf.WriteByte(byte(tagBinary)) + writeByte(d, byte(tagBinary)) case ObjectID: - buf.WriteByte(byte(tagObjectID)) + writeByte(d, byte(tagObjectID)) case bool: - buf.WriteByte(byte(tagBool)) + writeByte(d, byte(tagBool)) case time.Time: - buf.WriteByte(byte(tagTime)) + writeByte(d, byte(tagTime)) case NullType: - buf.WriteByte(byte(tagNull)) + writeByte(d, byte(tagNull)) case Regex: - buf.WriteByte(byte(tagRegex)) + writeByte(d, byte(tagRegex)) case int32: - buf.WriteByte(byte(tagInt32)) + writeByte(d, byte(tagInt32)) case Timestamp: - buf.WriteByte(byte(tagTimestamp)) + writeByte(d, byte(tagTimestamp)) case int64: - buf.WriteByte(byte(tagInt64)) + writeByte(d, byte(tagInt64)) case Decimal128: - buf.WriteByte(byte(tagDecimal128)) + writeByte(d, byte(tagDecimal128)) default: panic(fmt.Sprintf("invalid BSON type %T", v)) } From 43889119a1af2239479c48f3ac51a3e7abbe820e Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 6 Sep 2024 16:48:01 +0200 Subject: [PATCH 10/46] wip --- wirebson/array.go | 22 ++++++++++------------ wirebson/bson.go | 2 +- wirebson/encode.go | 12 +++++------- wirebson/raw_array.go | 5 +++-- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/wirebson/array.go b/wirebson/array.go index 816f747..69fa4e4 100644 --- a/wirebson/array.go +++ b/wirebson/array.go @@ -15,7 +15,6 @@ package wirebson import ( - "bytes" "encoding/binary" "log/slog" "strconv" @@ -120,27 +119,26 @@ func (arr *Array) Replace(index int, value any) error { // TODO https://github.com/FerretDB/wire/issues/21 // This method should accept a slice of bytes, not return it. // That would allow to avoid unnecessary allocations. -func (arr *Array) Encode() (RawArray, error) { +func (arr *Array) Encode(d RawArray) error { must.NotBeZero(arr) size := sizeArray(arr) - buf := bytes.NewBuffer(make([]byte, 0, size)) - if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { - return nil, lazyerrors.Error(err) - } + binary.LittleEndian.PutUint32(d, uint32(size)) for i, v := range arr.elements { - if err := encodeField(buf, strconv.Itoa(i), v); err != nil { - return nil, lazyerrors.Error(err) + if err := encodeField(d, strconv.Itoa(i), v); err != nil { + return lazyerrors.Error(err) } } - if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil { - return nil, lazyerrors.Error(err) - } + writeByte(d, byte(0)) + // TODO + //if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil { + // return nil, lazyerrors.Error(err) + //} - return buf.Bytes(), nil + return nil } // Decode returns itself to implement [AnyArray]. diff --git a/wirebson/bson.go b/wirebson/bson.go index ce45694..11924b5 100644 --- a/wirebson/bson.go +++ b/wirebson/bson.go @@ -79,7 +79,7 @@ type AnyDocument interface { // Note that the Encode and Decode methods could return the receiver itself, // so care must be taken when results are modified. type AnyArray interface { - Encode() (RawArray, error) + Encode(RawArray) error Decode() (*Array, error) } diff --git a/wirebson/encode.go b/wirebson/encode.go index 2cf421f..2079a3e 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -90,7 +90,9 @@ func encodeField(d []byte, name string, v any) error { write(d, b) - b, err := v.Encode() + b = make([]byte, 0, Size(v)) + + err := v.Encode(b) if err != nil { return lazyerrors.Error(err) } @@ -150,16 +152,12 @@ func encodeScalarField(d []byte, name string, v any) error { b := make([]byte, SizeCString(name)) EncodeCString(b, name) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(d, b) b = make([]byte, sizeScalar(v)) encodeScalarValue(b, v) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(d, b) return nil } diff --git a/wirebson/raw_array.go b/wirebson/raw_array.go index b674829..27d229c 100644 --- a/wirebson/raw_array.go +++ b/wirebson/raw_array.go @@ -30,9 +30,10 @@ type RawArray []byte // Encode returns itself to implement the [AnyArray] interface. // // Receiver must not be nil. -func (raw RawArray) Encode() (RawArray, error) { +func (raw RawArray) Encode(d RawArray) error { must.BeTrue(raw != nil) - return raw, nil + d = raw + return nil } // Decode decodes a single non-nil BSON array that takes the whole non-nil byte slice. From 6d3425d61c7955695de79e4275cfcc17d223f8a2 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 6 Sep 2024 17:41:13 +0200 Subject: [PATCH 11/46] wip --- wirebson/document.go | 5 ++++- wirebson/encode.go | 10 ++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/wirebson/document.go b/wirebson/document.go index 4de9080..6c28114 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -212,7 +212,10 @@ func (doc *Document) Encode(d RawDocument) error { size := Size(doc) - binary.LittleEndian.PutUint32(d, uint32(size)) + b := make([]byte, 4) + binary.LittleEndian.PutUint32(b, uint32(size)) + + write(d, b) //if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { // return lazyerrors.Error(err) diff --git a/wirebson/encode.go b/wirebson/encode.go index 2079a3e..7b076fc 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -23,18 +23,20 @@ import ( func writeByte(d []byte, b byte) { // TODO handle overflow - d[getIndex(d)] = b + d[getIndex(d, 1)] = b } func write(d []byte, b []byte) { // TODO handle overflow - i := getIndex(d) + i := getIndex(d, len(b)) copy(d[i:], b) } -func getIndex(d []byte) int { +func getIndex(d []byte, l int) int { // TODO handle overflow - return cap(d) - len(d) + i := len(d) + d = d[:l] + return i } // encodeField encodes document/array field. From 72494e2dc6bfa0afab1c56e9e2baa20be8135f8e Mon Sep 17 00:00:00 2001 From: noisersup Date: Sat, 7 Sep 2024 14:18:46 +0200 Subject: [PATCH 12/46] wip --- wirebson/encode_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 wirebson/encode_test.go diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go new file mode 100644 index 0000000..19fa104 --- /dev/null +++ b/wirebson/encode_test.go @@ -0,0 +1,18 @@ +package wirebson + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEncodeScalarField(t *testing.T) { + buf := bytes.NewBuffer(make([]byte, 0, 6)) + encodeScalarField(buf, "foo", "bar") + + expected := []byte{0x02, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0} + actual := buf.Bytes() + + assert.Equal(t, expected, actual) +} From d1ebbaf201452b5459e4846988d514e6223dd4cc Mon Sep 17 00:00:00 2001 From: noisersup Date: Sat, 7 Sep 2024 21:04:40 +0200 Subject: [PATCH 13/46] wip --- wirebson/encode.go | 56 +++++++++++++++++++++++------------------ wirebson/encode_test.go | 7 +++--- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index 52a6e35..fe26f41 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -102,58 +102,66 @@ func encodeField(buf *bytes.Buffer, name string, v any) error { } default: - return encodeScalarField(buf, name, v) + //return encodeScalarField(buf, name, v) } return nil } +func writeByte(b *[]byte, v byte) { + i := len(*b) + *b = (*b)[:1] + (*b)[i] = v +} + +func write(b *[]byte, v []byte) { + i := len(*b) + *b = (*b)[:len(v)] + copy((*b)[i:], v) +} + // encodeScalarField encodes scalar document field. // // It panics if v is not a scalar value. -func encodeScalarField(buf *bytes.Buffer, name string, v any) error { +func encodeScalarField(b *[]byte, name string, v any) error { switch v := v.(type) { case float64: - buf.WriteByte(byte(tagFloat64)) + writeByte(b, byte(tagFloat64)) case string: - buf.WriteByte(byte(tagString)) + writeByte(b, byte(tagString)) case Binary: - buf.WriteByte(byte(tagBinary)) + writeByte(b, byte(tagBinary)) case ObjectID: - buf.WriteByte(byte(tagObjectID)) + writeByte(b, byte(tagObjectID)) case bool: - buf.WriteByte(byte(tagBool)) + writeByte(b, byte(tagBool)) case time.Time: - buf.WriteByte(byte(tagTime)) + writeByte(b, byte(tagTime)) case NullType: - buf.WriteByte(byte(tagNull)) + writeByte(b, byte(tagNull)) case Regex: - buf.WriteByte(byte(tagRegex)) + writeByte(b, byte(tagRegex)) case int32: - buf.WriteByte(byte(tagInt32)) + writeByte(b, byte(tagInt32)) case Timestamp: - buf.WriteByte(byte(tagTimestamp)) + writeByte(b, byte(tagTimestamp)) case int64: - buf.WriteByte(byte(tagInt64)) + writeByte(b, byte(tagInt64)) case Decimal128: - buf.WriteByte(byte(tagDecimal128)) + writeByte(b, byte(tagDecimal128)) default: panic(fmt.Sprintf("invalid BSON type %T", v)) } - b := make([]byte, SizeCString(name)) - EncodeCString(b, name) + bb := make([]byte, SizeCString(name)) + EncodeCString(bb, name) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(b, bb) - b = make([]byte, sizeScalar(v)) - encodeScalarValue(b, v) + bb = make([]byte, sizeScalar(v)) + encodeScalarValue(bb, v) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(b, bb) return nil } diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index 19fa104..bf91c95 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -1,18 +1,17 @@ package wirebson import ( - "bytes" "testing" "github.com/stretchr/testify/assert" ) func TestEncodeScalarField(t *testing.T) { - buf := bytes.NewBuffer(make([]byte, 0, 6)) - encodeScalarField(buf, "foo", "bar") + buf := make([]byte, 0, 12) + encodeScalarField(&buf, "foo", "bar") expected := []byte{0x02, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0} - actual := buf.Bytes() + actual := buf assert.Equal(t, expected, actual) } From c181cd3f46f687856fbd1e2ed2a1ca157d170c8f Mon Sep 17 00:00:00 2001 From: noisersup Date: Sat, 7 Sep 2024 21:07:30 +0200 Subject: [PATCH 14/46] wip --- wirebson/encode.go | 4 ++-- wirebson/encode_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index fe26f41..15554db 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -110,13 +110,13 @@ func encodeField(buf *bytes.Buffer, name string, v any) error { func writeByte(b *[]byte, v byte) { i := len(*b) - *b = (*b)[:1] + *b = (*b)[:1+i] (*b)[i] = v } func write(b *[]byte, v []byte) { i := len(*b) - *b = (*b)[:len(v)] + *b = (*b)[:len(v)+i] copy((*b)[i:], v) } diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index bf91c95..4b3c394 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -7,7 +7,7 @@ import ( ) func TestEncodeScalarField(t *testing.T) { - buf := make([]byte, 0, 12) + buf := make([]byte, 0, 13) encodeScalarField(&buf, "foo", "bar") expected := []byte{0x02, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0} From dd0a2284b7f88cc650dead1aa1a523d9996fe3e7 Mon Sep 17 00:00:00 2001 From: noisersup Date: Mon, 9 Sep 2024 08:35:38 +0200 Subject: [PATCH 15/46] wip --- wirebson/encode.go | 47 +++++++++++++++++++++-------------------- wirebson/encode_test.go | 4 ++-- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index 15554db..4ad1b51 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -108,60 +108,61 @@ func encodeField(buf *bytes.Buffer, name string, v any) error { return nil } -func writeByte(b *[]byte, v byte) { - i := len(*b) - *b = (*b)[:1+i] - (*b)[i] = v +func writeByte(b []byte, v byte, offset int) { + b[offset] = v } -func write(b *[]byte, v []byte) { - i := len(*b) - *b = (*b)[:len(v)+i] - copy((*b)[i:], v) +// returns number of bytes written +func write(b []byte, v []byte, offset int) int { + copy(b[offset:], v) + return len(v) } // encodeScalarField encodes scalar document field. // // It panics if v is not a scalar value. -func encodeScalarField(b *[]byte, name string, v any) error { +func encodeScalarField(b []byte, name string, v any) error { + var i int + switch v := v.(type) { case float64: - writeByte(b, byte(tagFloat64)) + writeByte(b, byte(tagFloat64), i) case string: - writeByte(b, byte(tagString)) + writeByte(b, byte(tagString), i) case Binary: - writeByte(b, byte(tagBinary)) + writeByte(b, byte(tagBinary), i) case ObjectID: - writeByte(b, byte(tagObjectID)) + writeByte(b, byte(tagObjectID), i) case bool: - writeByte(b, byte(tagBool)) + writeByte(b, byte(tagBool), i) case time.Time: - writeByte(b, byte(tagTime)) + writeByte(b, byte(tagTime), i) case NullType: - writeByte(b, byte(tagNull)) + writeByte(b, byte(tagNull), i) case Regex: - writeByte(b, byte(tagRegex)) + writeByte(b, byte(tagRegex), i) case int32: - writeByte(b, byte(tagInt32)) + writeByte(b, byte(tagInt32), i) case Timestamp: - writeByte(b, byte(tagTimestamp)) + writeByte(b, byte(tagTimestamp), i) case int64: - writeByte(b, byte(tagInt64)) + writeByte(b, byte(tagInt64), i) case Decimal128: - writeByte(b, byte(tagDecimal128)) + writeByte(b, byte(tagDecimal128), i) default: panic(fmt.Sprintf("invalid BSON type %T", v)) } + i++ bb := make([]byte, SizeCString(name)) EncodeCString(bb, name) - write(b, bb) + i += write(b, bb, i) bb = make([]byte, sizeScalar(v)) encodeScalarValue(bb, v) - write(b, bb) + i += write(b, bb, i) return nil } diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index 4b3c394..0fff4a4 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -7,8 +7,8 @@ import ( ) func TestEncodeScalarField(t *testing.T) { - buf := make([]byte, 0, 13) - encodeScalarField(&buf, "foo", "bar") + buf := make([]byte, 13) + encodeScalarField(buf, "foo", "bar") expected := []byte{0x02, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0} actual := buf From 99820cbaa680e41d68542e7563e46ad1d805c6f2 Mon Sep 17 00:00:00 2001 From: noisersup Date: Wed, 11 Sep 2024 09:44:17 +0200 Subject: [PATCH 16/46] wip --- wirebson/array.go | 3 +- wirebson/encode.go | 71 +++++++++++++++++------------------------ wirebson/encode_test.go | 2 +- 3 files changed, 31 insertions(+), 45 deletions(-) diff --git a/wirebson/array.go b/wirebson/array.go index 816f747..e60a38d 100644 --- a/wirebson/array.go +++ b/wirebson/array.go @@ -15,7 +15,6 @@ package wirebson import ( - "bytes" "encoding/binary" "log/slog" "strconv" @@ -124,7 +123,7 @@ func (arr *Array) Encode() (RawArray, error) { must.NotBeZero(arr) size := sizeArray(arr) - buf := bytes.NewBuffer(make([]byte, 0, size)) + buf := make([]byte, 0, size) if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { return nil, lazyerrors.Error(err) diff --git a/wirebson/encode.go b/wirebson/encode.go index 4ad1b51..1a0e03c 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -15,7 +15,6 @@ package wirebson import ( - "bytes" "fmt" "time" @@ -25,84 +24,74 @@ import ( // encodeField encodes document/array field. // // It panics if v is not a valid type. -func encodeField(buf *bytes.Buffer, name string, v any) error { +func encodeField(buf []byte, name string, v any) error { + var i int + switch v := v.(type) { case *Document: - if err := buf.WriteByte(byte(tagDocument)); err != nil { - return lazyerrors.Error(err) - } + writeByte(buf, byte(tagDocument), i) + i++ b := make([]byte, SizeCString(name)) EncodeCString(b, name) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(buf, b, i) + i += len(b) b, err := v.Encode() if err != nil { return lazyerrors.Error(err) } - if _, err = buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(buf, b, i) + i += len(b) case RawDocument: - if err := buf.WriteByte(byte(tagDocument)); err != nil { - return lazyerrors.Error(err) - } + writeByte(buf, byte(tagDocument), i) + i++ b := make([]byte, SizeCString(name)) EncodeCString(b, name) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(buf, b, i) + i += len(b) - if _, err := buf.Write(v); err != nil { - return lazyerrors.Error(err) - } + write(buf, v, i) + i += len(b) case *Array: - if err := buf.WriteByte(byte(tagArray)); err != nil { - return lazyerrors.Error(err) - } + writeByte(buf, byte(tagArray), i) + i++ b := make([]byte, SizeCString(name)) EncodeCString(b, name) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(buf, b, i) + i += len(b) b, err := v.Encode() if err != nil { return lazyerrors.Error(err) } - if _, err = buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(buf, b, i) + i += len(b) case RawArray: - if err := buf.WriteByte(byte(tagArray)); err != nil { - return lazyerrors.Error(err) - } + writeByte(buf, byte(tagArray), i) + i++ b := make([]byte, SizeCString(name)) EncodeCString(b, name) - if _, err := buf.Write(b); err != nil { - return lazyerrors.Error(err) - } + write(buf, b, i) + i += len(b) - if _, err := buf.Write(v); err != nil { - return lazyerrors.Error(err) - } + write(buf, v, i) + i += len(b) default: - //return encodeScalarField(buf, name, v) + return encodeScalarField(i, buf, name, v) } return nil @@ -121,9 +110,7 @@ func write(b []byte, v []byte, offset int) int { // encodeScalarField encodes scalar document field. // // It panics if v is not a scalar value. -func encodeScalarField(b []byte, name string, v any) error { - var i int - +func encodeScalarField(i int, b []byte, name string, v any) error { switch v := v.(type) { case float64: writeByte(b, byte(tagFloat64), i) diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index 0fff4a4..6c28f64 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -8,7 +8,7 @@ import ( func TestEncodeScalarField(t *testing.T) { buf := make([]byte, 13) - encodeScalarField(buf, "foo", "bar") + encodeScalarField(0, buf, "foo", "bar") expected := []byte{0x02, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0} actual := buf From 7d65b1537dfee7c0b0c3eea30f81300f39917c3e Mon Sep 17 00:00:00 2001 From: noisersup Date: Thu, 12 Sep 2024 16:26:36 +0200 Subject: [PATCH 17/46] revert --- op_msg.go | 7 +-- op_query.go | 5 +- op_reply.go | 11 ++-- wire_test.go | 1 - wirebson/array.go | 22 ++++--- wirebson/bson.go | 4 +- wirebson/bson_test.go | 20 ++---- wirebson/document.go | 28 ++++----- wirebson/encode.go | 132 +++++++++++++++++++-------------------- wirebson/raw_array.go | 5 +- wirebson/raw_document.go | 6 +- wirebson/size.go | 8 +-- 12 files changed, 114 insertions(+), 135 deletions(-) diff --git a/op_msg.go b/op_msg.go index 878ff05..63b8f2f 100644 --- a/op_msg.go +++ b/op_msg.go @@ -38,14 +38,13 @@ type OpMsg struct { // NewOpMsg creates a message with a single section of kind 0 with a single document. func NewOpMsg(doc wirebson.AnyDocument) (*OpMsg, error) { - raw := make([]byte, 0, wirebson.Size(doc)) - - if err := doc.Encode(raw); err != nil { + raw, err := doc.Encode() + if err != nil { return nil, lazyerrors.Error(err) } var msg OpMsg - if err := msg.SetSections(OpMsgSection{documents: []wirebson.RawDocument{raw}}); err != nil { + if err = msg.SetSections(OpMsgSection{documents: []wirebson.RawDocument{raw}}); err != nil { return nil, lazyerrors.Error(err) } diff --git a/op_query.go b/op_query.go index 0e5059d..64ad89b 100644 --- a/op_query.go +++ b/op_query.go @@ -37,9 +37,8 @@ type OpQuery struct { // NewOpQuery creates a new OpQuery message. func NewOpQuery(doc wirebson.AnyDocument) (*OpQuery, error) { - raw := make([]byte, 0, wirebson.Size(doc)) - - if err := doc.Encode(raw); err != nil { + raw, err := doc.Encode() + if err != nil { return nil, lazyerrors.Error(err) } diff --git a/op_reply.go b/op_reply.go index 0276906..6056fab 100644 --- a/op_reply.go +++ b/op_reply.go @@ -37,9 +37,8 @@ type OpReply struct { // NewOpReply creates a new OpReply message. func NewOpReply(doc wirebson.AnyDocument) (*OpReply, error) { - raw := make([]byte, 0, wirebson.Size(doc)) - - if err := doc.Encode(raw); err != nil { + raw, err := doc.Encode() + if err != nil { return nil, lazyerrors.Error(err) } @@ -132,9 +131,9 @@ func (reply *OpReply) RawDocument() wirebson.RawDocument { // SetDocument sets reply document. func (reply *OpReply) SetDocument(doc *wirebson.Document) { - reply.document = make([]byte, 0, wirebson.Size(doc)) - - if err := doc.Encode(reply.document); err != nil { + var err error + reply.document, err = doc.Encode() + if err != nil { panic(err) } } diff --git a/wire_test.go b/wire_test.go index cf8ad5f..533a192 100644 --- a/wire_test.go +++ b/wire_test.go @@ -41,7 +41,6 @@ func makeRawDocument(pairs ...any) wirebson.RawDocument { d := wirebson.MustDocument(pairs...) raw, err := d.Encode() - if err != nil { panic(err) } diff --git a/wirebson/array.go b/wirebson/array.go index 69fa4e4..816f747 100644 --- a/wirebson/array.go +++ b/wirebson/array.go @@ -15,6 +15,7 @@ package wirebson import ( + "bytes" "encoding/binary" "log/slog" "strconv" @@ -119,26 +120,27 @@ func (arr *Array) Replace(index int, value any) error { // TODO https://github.com/FerretDB/wire/issues/21 // This method should accept a slice of bytes, not return it. // That would allow to avoid unnecessary allocations. -func (arr *Array) Encode(d RawArray) error { +func (arr *Array) Encode() (RawArray, error) { must.NotBeZero(arr) size := sizeArray(arr) + buf := bytes.NewBuffer(make([]byte, 0, size)) - binary.LittleEndian.PutUint32(d, uint32(size)) + if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { + return nil, lazyerrors.Error(err) + } for i, v := range arr.elements { - if err := encodeField(d, strconv.Itoa(i), v); err != nil { - return lazyerrors.Error(err) + if err := encodeField(buf, strconv.Itoa(i), v); err != nil { + return nil, lazyerrors.Error(err) } } - writeByte(d, byte(0)) - // TODO - //if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil { - // return nil, lazyerrors.Error(err) - //} + if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil { + return nil, lazyerrors.Error(err) + } - return nil + return buf.Bytes(), nil } // Decode returns itself to implement [AnyArray]. diff --git a/wirebson/bson.go b/wirebson/bson.go index 11924b5..944327d 100644 --- a/wirebson/bson.go +++ b/wirebson/bson.go @@ -70,7 +70,7 @@ type ScalarType interface { // Note that the Encode and Decode methods could return the receiver itself, // so care must be taken when results are modified. type AnyDocument interface { - Encode(RawDocument) error + Encode() (RawDocument, error) Decode() (*Document, error) } @@ -79,7 +79,7 @@ type AnyDocument interface { // Note that the Encode and Decode methods could return the receiver itself, // so care must be taken when results are modified. type AnyArray interface { - Encode(RawArray) error + Encode() (RawArray, error) Decode() (*Array, error) } diff --git a/wirebson/bson_test.go b/wirebson/bson_test.go index 525bbd8..0da80a4 100644 --- a/wirebson/bson_test.go +++ b/wirebson/bson_test.go @@ -621,10 +621,8 @@ func TestNormal(t *testing.T) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw := make([]byte, 0, Size(doc)) - err = doc.Encode(raw) + raw, err := doc.Encode() require.NoError(t, err) - assert.Equal(t, tc.raw, raw) }) @@ -640,8 +638,7 @@ func TestNormal(t *testing.T) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw := make([]byte, 0, Size(doc)) - err = doc.Encode(raw) + raw, err := doc.Encode() require.NoError(t, err) assert.Equal(t, tc.raw, raw) }) @@ -726,8 +723,7 @@ func BenchmarkDocument(b *testing.B) { b.ResetTimer() for range b.N { - raw = make([]byte, 0, Size(doc)) - err = doc.Encode(raw) + raw, err = doc.Encode() } b.StopTimer() @@ -791,8 +787,7 @@ func BenchmarkDocument(b *testing.B) { b.ResetTimer() for range b.N { - raw := make([]byte, 0, Size(doc)) - err = doc.Encode(raw) + raw, err = doc.Encode() } b.StopTimer() @@ -872,9 +867,7 @@ func testRawDocument(t *testing.T, rawDoc RawDocument) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw := make([]byte, 0, Size(doc)) - err = doc.Encode(raw) - + raw, err := doc.Encode() if err == nil { assert.Equal(t, rawDoc, raw) } @@ -894,8 +887,7 @@ func testRawDocument(t *testing.T, rawDoc RawDocument) { assert.NotEmpty(t, LogMessageBlock(doc)) assert.NotEmpty(t, LogMessageFlow(doc)) - raw := make([]byte, Size(doc)) - err = doc.Encode(raw) + raw, err := doc.Encode() require.NoError(t, err) assert.Equal(t, rawDoc, raw) }) diff --git a/wirebson/document.go b/wirebson/document.go index 6c28114..32d8a4f 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -15,6 +15,7 @@ package wirebson import ( + "bytes" "encoding/binary" "log/slog" "slices" @@ -207,30 +208,27 @@ func (doc *Document) Command() string { // TODO https://github.com/FerretDB/wire/issues/21 // This method should accept a slice of bytes, not return it. // That would allow to avoid unnecessary allocations. -func (doc *Document) Encode(d RawDocument) error { +func (doc *Document) Encode() (RawDocument, error) { must.NotBeZero(doc) - size := Size(doc) + size := sizeDocument(doc) + buf := bytes.NewBuffer(make([]byte, 0, size)) - b := make([]byte, 4) - binary.LittleEndian.PutUint32(b, uint32(size)) - - write(d, b) - - //if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { - // return lazyerrors.Error(err) - //} + if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { + return nil, lazyerrors.Error(err) + } for _, f := range doc.fields { - if err := encodeField(d, f.name, f.value); err != nil { - return lazyerrors.Error(err) + if err := encodeField(buf, f.name, f.value); err != nil { + return nil, lazyerrors.Error(err) } } - // TODO - //d = append(d, byte(0)) + if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil { + return nil, lazyerrors.Error(err) + } - return nil + return buf.Bytes(), nil } // Decode returns itself to implement [AnyDocument]. diff --git a/wirebson/encode.go b/wirebson/encode.go index 7b076fc..52a6e35 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -15,104 +15,94 @@ package wirebson import ( + "bytes" "fmt" "time" "github.com/FerretDB/wire/internal/util/lazyerrors" ) -func writeByte(d []byte, b byte) { - // TODO handle overflow - d[getIndex(d, 1)] = b -} - -func write(d []byte, b []byte) { - // TODO handle overflow - i := getIndex(d, len(b)) - copy(d[i:], b) -} - -func getIndex(d []byte, l int) int { - // TODO handle overflow - i := len(d) - d = d[:l] - return i -} - // encodeField encodes document/array field. // // It panics if v is not a valid type. -func encodeField(d []byte, name string, v any) error { +func encodeField(buf *bytes.Buffer, name string, v any) error { switch v := v.(type) { case *Document: - writeByte(d, byte(tagDocument)) - //if err := buf.WriteByte(byte(tagDocument)); err != nil { - // return lazyerrors.Error(err) - //} + if err := buf.WriteByte(byte(tagDocument)); err != nil { + return lazyerrors.Error(err) + } b := make([]byte, SizeCString(name)) EncodeCString(b, name) - write(d, b) - - v.Encode(b) - - b = make([]byte, 0, Size(v)) - write(d, b) - - //if _, err := buf.Write(b); err != nil { - // return lazyerrors.Error(err) - //} - - //b = make([]byte, Size(v)) + if _, err := buf.Write(b); err != nil { + return lazyerrors.Error(err) + } - //if err := v.Encode(b); err != nil { - // return lazyerrors.Error(err) - //} + b, err := v.Encode() + if err != nil { + return lazyerrors.Error(err) + } - //if _, err := buf.Write(b); err != nil { - // return lazyerrors.Error(err) - //} + if _, err = buf.Write(b); err != nil { + return lazyerrors.Error(err) + } case RawDocument: - writeByte(d, byte(tagDocument)) + if err := buf.WriteByte(byte(tagDocument)); err != nil { + return lazyerrors.Error(err) + } b := make([]byte, SizeCString(name)) EncodeCString(b, name) - write(d, b) + if _, err := buf.Write(b); err != nil { + return lazyerrors.Error(err) + } - write(d, v) + if _, err := buf.Write(v); err != nil { + return lazyerrors.Error(err) + } case *Array: - writeByte(d, byte(tagArray)) + if err := buf.WriteByte(byte(tagArray)); err != nil { + return lazyerrors.Error(err) + } b := make([]byte, SizeCString(name)) EncodeCString(b, name) - write(d, b) - - b = make([]byte, 0, Size(v)) + if _, err := buf.Write(b); err != nil { + return lazyerrors.Error(err) + } - err := v.Encode(b) + b, err := v.Encode() if err != nil { return lazyerrors.Error(err) } - write(d, b) + if _, err = buf.Write(b); err != nil { + return lazyerrors.Error(err) + } case RawArray: - writeByte(d, byte(tagArray)) + if err := buf.WriteByte(byte(tagArray)); err != nil { + return lazyerrors.Error(err) + } b := make([]byte, SizeCString(name)) EncodeCString(b, name) - write(d, b) + if _, err := buf.Write(b); err != nil { + return lazyerrors.Error(err) + } - write(d, v) + if _, err := buf.Write(v); err != nil { + return lazyerrors.Error(err) + } default: - return encodeScalarField(d, name, v) + return encodeScalarField(buf, name, v) } return nil @@ -121,32 +111,32 @@ func encodeField(d []byte, name string, v any) error { // encodeScalarField encodes scalar document field. // // It panics if v is not a scalar value. -func encodeScalarField(d []byte, name string, v any) error { +func encodeScalarField(buf *bytes.Buffer, name string, v any) error { switch v := v.(type) { case float64: - writeByte(d, byte(tagFloat64)) + buf.WriteByte(byte(tagFloat64)) case string: - writeByte(d, byte(tagString)) + buf.WriteByte(byte(tagString)) case Binary: - writeByte(d, byte(tagBinary)) + buf.WriteByte(byte(tagBinary)) case ObjectID: - writeByte(d, byte(tagObjectID)) + buf.WriteByte(byte(tagObjectID)) case bool: - writeByte(d, byte(tagBool)) + buf.WriteByte(byte(tagBool)) case time.Time: - writeByte(d, byte(tagTime)) + buf.WriteByte(byte(tagTime)) case NullType: - writeByte(d, byte(tagNull)) + buf.WriteByte(byte(tagNull)) case Regex: - writeByte(d, byte(tagRegex)) + buf.WriteByte(byte(tagRegex)) case int32: - writeByte(d, byte(tagInt32)) + buf.WriteByte(byte(tagInt32)) case Timestamp: - writeByte(d, byte(tagTimestamp)) + buf.WriteByte(byte(tagTimestamp)) case int64: - writeByte(d, byte(tagInt64)) + buf.WriteByte(byte(tagInt64)) case Decimal128: - writeByte(d, byte(tagDecimal128)) + buf.WriteByte(byte(tagDecimal128)) default: panic(fmt.Sprintf("invalid BSON type %T", v)) } @@ -154,12 +144,16 @@ func encodeScalarField(d []byte, name string, v any) error { b := make([]byte, SizeCString(name)) EncodeCString(b, name) - write(d, b) + if _, err := buf.Write(b); err != nil { + return lazyerrors.Error(err) + } b = make([]byte, sizeScalar(v)) encodeScalarValue(b, v) - write(d, b) + if _, err := buf.Write(b); err != nil { + return lazyerrors.Error(err) + } return nil } diff --git a/wirebson/raw_array.go b/wirebson/raw_array.go index 27d229c..b674829 100644 --- a/wirebson/raw_array.go +++ b/wirebson/raw_array.go @@ -30,10 +30,9 @@ type RawArray []byte // Encode returns itself to implement the [AnyArray] interface. // // Receiver must not be nil. -func (raw RawArray) Encode(d RawArray) error { +func (raw RawArray) Encode() (RawArray, error) { must.BeTrue(raw != nil) - d = raw - return nil + return raw, nil } // Decode decodes a single non-nil BSON array that takes the whole non-nil byte slice. diff --git a/wirebson/raw_document.go b/wirebson/raw_document.go index 3ce7f68..16c08a3 100644 --- a/wirebson/raw_document.go +++ b/wirebson/raw_document.go @@ -29,11 +29,9 @@ type RawDocument []byte // Encode returns itself to implement the [AnyDocument] interface. // // Receiver must not be nil. -func (raw RawDocument) Encode(d RawDocument) error { +func (raw RawDocument) Encode() (RawDocument, error) { must.BeTrue(raw != nil) - copy(d, raw) - - return nil + return raw, nil } // Decode decodes a single non-nil BSON document that takes the whole non-nil byte slice. diff --git a/wirebson/size.go b/wirebson/size.go index 2607aa1..71d8d4c 100644 --- a/wirebson/size.go +++ b/wirebson/size.go @@ -20,10 +20,10 @@ import ( "time" ) -// Size returns a Size of the encoding of value v in bytes. +// size returns a size of the encoding of value v in bytes. // // It panics for invalid types. -func Size(v any) int { +func size(v any) int { switch v := v.(type) { case *Document: return sizeDocument(v) @@ -43,7 +43,7 @@ func sizeDocument(doc *Document) int { res := 5 for _, f := range doc.fields { - res += 1 + SizeCString(f.name) + Size(f.value) + res += 1 + SizeCString(f.name) + size(f.value) } return res @@ -54,7 +54,7 @@ func sizeArray(arr *Array) int { res := 5 for i, v := range arr.elements { - res += 1 + SizeCString(strconv.Itoa(i)) + Size(v) + res += 1 + SizeCString(strconv.Itoa(i)) + size(v) } return res From 1346fe221646fc979090dbfbd0603abffc6d65b2 Mon Sep 17 00:00:00 2001 From: noisersup Date: Thu, 12 Sep 2024 16:32:24 +0200 Subject: [PATCH 18/46] wip --- wirebson/encode_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index 6c28f64..62c79cc 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -15,3 +15,6 @@ func TestEncodeScalarField(t *testing.T) { assert.Equal(t, expected, actual) } + +func TestEncodeField(t *testing.T) { +} From a08e5a1c770317b75657a26e6f9a2a093bf0c6d1 Mon Sep 17 00:00:00 2001 From: noisersup Date: Thu, 12 Sep 2024 16:38:35 +0200 Subject: [PATCH 19/46] wip --- wirebson/encode_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index 62c79cc..185395a 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -1,9 +1,11 @@ package wirebson import ( + "bytes" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestEncodeScalarField(t *testing.T) { @@ -17,4 +19,21 @@ func TestEncodeScalarField(t *testing.T) { } func TestEncodeField(t *testing.T) { + buf := bytes.NewBuffer(make([]byte, 0, 10)) + + err := encodeField(buf, "foo", "bar") + require.NoError(t, err) + + actual := buf.Bytes() + + expected := []byte{0x2, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0} + assert.Equal(t, expected, actual) + + err = encodeField(buf, "foo", int32(1)) + require.NoError(t, err) + + actual = buf.Bytes() + + expected = append(expected, []byte{0x10, 0x66, 0x6f, 0x6f, 0x0, 0x1, 0x0, 0x0, 0x0}...) + assert.Equal(t, expected, actual) } From dcf1b1b8e53b122648fdaf1343060905c473b5bd Mon Sep 17 00:00:00 2001 From: noisersup Date: Thu, 12 Sep 2024 16:39:59 +0200 Subject: [PATCH 20/46] wip --- wirebson/encode_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index 185395a..4853c91 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -1,7 +1,6 @@ package wirebson import ( - "bytes" "testing" "github.com/stretchr/testify/assert" @@ -19,12 +18,12 @@ func TestEncodeScalarField(t *testing.T) { } func TestEncodeField(t *testing.T) { - buf := bytes.NewBuffer(make([]byte, 0, 10)) + buf := make([]byte, 0, 10) err := encodeField(buf, "foo", "bar") require.NoError(t, err) - actual := buf.Bytes() + actual := buf expected := []byte{0x2, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0} assert.Equal(t, expected, actual) @@ -32,7 +31,7 @@ func TestEncodeField(t *testing.T) { err = encodeField(buf, "foo", int32(1)) require.NoError(t, err) - actual = buf.Bytes() + actual = buf expected = append(expected, []byte{0x10, 0x66, 0x6f, 0x6f, 0x0, 0x1, 0x0, 0x0, 0x0}...) assert.Equal(t, expected, actual) From 5f28ffc6ff8240ccae80bb00473f7a3de97b71aa Mon Sep 17 00:00:00 2001 From: noisersup Date: Thu, 12 Sep 2024 16:54:31 +0200 Subject: [PATCH 21/46] wip --- wirebson/array.go | 20 +++++++++++--------- wirebson/document.go | 20 ++++++++++---------- wirebson/encode.go | 12 +++++------- wirebson/encode_test.go | 9 ++++++--- 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/wirebson/array.go b/wirebson/array.go index e60a38d..c0515cf 100644 --- a/wirebson/array.go +++ b/wirebson/array.go @@ -123,23 +123,25 @@ func (arr *Array) Encode() (RawArray, error) { must.NotBeZero(arr) size := sizeArray(arr) - buf := make([]byte, 0, size) + buf := make([]byte, size) - if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { - return nil, lazyerrors.Error(err) - } + var index int + + binary.LittleEndian.PutUint32(buf, uint32(size)) + index += 4 + var err error for i, v := range arr.elements { - if err := encodeField(buf, strconv.Itoa(i), v); err != nil { + if index, err = encodeField(index, buf, strconv.Itoa(i), v); err != nil { return nil, lazyerrors.Error(err) } } - if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil { - return nil, lazyerrors.Error(err) - } + writeByte(buf, byte(0), index) + index++ - return buf.Bytes(), nil + //return buf.Bytes(), nil + return buf, nil } // Decode returns itself to implement [AnyArray]. diff --git a/wirebson/document.go b/wirebson/document.go index 32d8a4f..fb74d32 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -15,7 +15,6 @@ package wirebson import ( - "bytes" "encoding/binary" "log/slog" "slices" @@ -212,23 +211,24 @@ func (doc *Document) Encode() (RawDocument, error) { must.NotBeZero(doc) size := sizeDocument(doc) - buf := bytes.NewBuffer(make([]byte, 0, size)) + buf := make([]byte, size) - if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil { - return nil, lazyerrors.Error(err) - } + var index int + + binary.LittleEndian.PutUint32(buf, uint32(size)) + index += 4 + var err error for _, f := range doc.fields { - if err := encodeField(buf, f.name, f.value); err != nil { + if index, err = encodeField(index, buf, f.name, f.value); err != nil { return nil, lazyerrors.Error(err) } } - if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil { - return nil, lazyerrors.Error(err) - } + writeByte(buf, byte(0), index) + index++ - return buf.Bytes(), nil + return buf, nil } // Decode returns itself to implement [AnyDocument]. diff --git a/wirebson/encode.go b/wirebson/encode.go index 1a0e03c..4551700 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -24,9 +24,7 @@ import ( // encodeField encodes document/array field. // // It panics if v is not a valid type. -func encodeField(buf []byte, name string, v any) error { - var i int - +func encodeField(i int, buf []byte, name string, v any) (int, error) { switch v := v.(type) { case *Document: writeByte(buf, byte(tagDocument), i) @@ -40,7 +38,7 @@ func encodeField(buf []byte, name string, v any) error { b, err := v.Encode() if err != nil { - return lazyerrors.Error(err) + return 0, lazyerrors.Error(err) } write(buf, b, i) @@ -71,7 +69,7 @@ func encodeField(buf []byte, name string, v any) error { b, err := v.Encode() if err != nil { - return lazyerrors.Error(err) + return 0, lazyerrors.Error(err) } write(buf, b, i) @@ -91,10 +89,10 @@ func encodeField(buf []byte, name string, v any) error { i += len(b) default: - return encodeScalarField(i, buf, name, v) + return i, encodeScalarField(i, buf, name, v) } - return nil + return i, nil } func writeByte(b []byte, v byte, offset int) { diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index 4853c91..50b2509 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -18,9 +18,12 @@ func TestEncodeScalarField(t *testing.T) { } func TestEncodeField(t *testing.T) { - buf := make([]byte, 0, 10) + buf := make([]byte, 25) - err := encodeField(buf, "foo", "bar") + var i int + + var err error + i, err = encodeField(i, buf, "foo", "bar") require.NoError(t, err) actual := buf @@ -28,7 +31,7 @@ func TestEncodeField(t *testing.T) { expected := []byte{0x2, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0} assert.Equal(t, expected, actual) - err = encodeField(buf, "foo", int32(1)) + i, err = encodeField(i, buf, "foo", int32(1)) require.NoError(t, err) actual = buf From 9546de2d509509a0544abb64a779bcbee1de6337 Mon Sep 17 00:00:00 2001 From: noisersup Date: Thu, 12 Sep 2024 17:04:02 +0200 Subject: [PATCH 22/46] wip --- op_msg.go | 3 +- op_query.go | 3 +- op_reply.go | 7 +- wirebson/bson.go | 2 +- wirebson/bson_test.go | 1797 +++++++++++++++++++------------------- wirebson/document.go | 15 +- wirebson/encode.go | 5 +- wirebson/raw_document.go | 5 +- wirebson/size.go | 8 +- 9 files changed, 926 insertions(+), 919 deletions(-) diff --git a/op_msg.go b/op_msg.go index 63b8f2f..0de1a46 100644 --- a/op_msg.go +++ b/op_msg.go @@ -38,7 +38,8 @@ type OpMsg struct { // NewOpMsg creates a message with a single section of kind 0 with a single document. func NewOpMsg(doc wirebson.AnyDocument) (*OpMsg, error) { - raw, err := doc.Encode() + raw := make([]byte, wirebson.Size(doc)) + err := doc.Encode(raw) if err != nil { return nil, lazyerrors.Error(err) } diff --git a/op_query.go b/op_query.go index 64ad89b..c2a29e3 100644 --- a/op_query.go +++ b/op_query.go @@ -37,7 +37,8 @@ type OpQuery struct { // NewOpQuery creates a new OpQuery message. func NewOpQuery(doc wirebson.AnyDocument) (*OpQuery, error) { - raw, err := doc.Encode() + raw := make([]byte, wirebson.Size(doc)) + err := doc.Encode(raw) if err != nil { return nil, lazyerrors.Error(err) } diff --git a/op_reply.go b/op_reply.go index 6056fab..4f6713c 100644 --- a/op_reply.go +++ b/op_reply.go @@ -37,7 +37,8 @@ type OpReply struct { // NewOpReply creates a new OpReply message. func NewOpReply(doc wirebson.AnyDocument) (*OpReply, error) { - raw, err := doc.Encode() + raw := make([]byte, wirebson.Size(doc)) + err := doc.Encode(raw) if err != nil { return nil, lazyerrors.Error(err) } @@ -132,7 +133,9 @@ func (reply *OpReply) RawDocument() wirebson.RawDocument { // SetDocument sets reply document. func (reply *OpReply) SetDocument(doc *wirebson.Document) { var err error - reply.document, err = doc.Encode() + + reply.document = make([]byte, wirebson.Size(doc)) + err = doc.Encode(reply.document) if err != nil { panic(err) } diff --git a/wirebson/bson.go b/wirebson/bson.go index 944327d..ce45694 100644 --- a/wirebson/bson.go +++ b/wirebson/bson.go @@ -70,7 +70,7 @@ type ScalarType interface { // Note that the Encode and Decode methods could return the receiver itself, // so care must be taken when results are modified. type AnyDocument interface { - Encode() (RawDocument, error) + Encode(RawDocument) error Decode() (*Document, error) } diff --git a/wirebson/bson_test.go b/wirebson/bson_test.go index 0da80a4..4de42aa 100644 --- a/wirebson/bson_test.go +++ b/wirebson/bson_test.go @@ -14,902 +14,903 @@ package wirebson -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/FerretDB/wire/internal/util/testutil" -) - -// normalTestCase represents a single test case for successful decoding/encoding. -// -//nolint:vet // for readability -type normalTestCase struct { - name string - raw RawDocument - doc *Document - m string -} - -// decodeTestCase represents a single test case for unsuccessful decoding. // -//nolint:vet // for readability -type decodeTestCase struct { - name string - raw RawDocument - - oldOk bool - - findRawErr error - findRawL int - decodeErr error - decodeDeepErr error // defaults to decodeErr -} - -// normalTestCases represents test cases for successful decoding/encoding. -// -//nolint:lll // for readability -var normalTestCases = []normalTestCase{ - { - name: "handshake1", - raw: testutil.MustParseDumpFile("testdata", "handshake1.hex"), - doc: MustDocument( - "ismaster", true, - "client", MustDocument( - "driver", MustDocument( - "name", "nodejs", - "version", "4.0.0-beta.6", - ), - "os", MustDocument( - "type", "Darwin", - "name", "darwin", - "architecture", "x64", - "version", "20.6.0", - ), - "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", - "application", MustDocument( - "name", "mongosh 1.0.1", - ), - ), - "compression", MustArray("none"), - "loadBalanced", false, - ), - m: ` - { - "ismaster": true, - "client": { - "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, - "os": { - "type": "Darwin", - "name": "darwin", - "architecture": "x64", - "version": "20.6.0", - }, - "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", - "application": {"name": "mongosh 1.0.1"}, - }, - "compression": ["none"], - "loadBalanced": false, - }`, - }, - { - name: "handshake2", - raw: testutil.MustParseDumpFile("testdata", "handshake2.hex"), - doc: MustDocument( - "ismaster", true, - "client", MustDocument( - "driver", MustDocument( - "name", "nodejs", - "version", "4.0.0-beta.6", - ), - "os", MustDocument( - "type", "Darwin", - "name", "darwin", - "architecture", "x64", - "version", "20.6.0", - ), - "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", - "application", MustDocument( - "name", "mongosh 1.0.1", - ), - ), - "compression", MustArray("none"), - "loadBalanced", false, - ), - m: ` - { - "ismaster": true, - "client": { - "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, - "os": { - "type": "Darwin", - "name": "darwin", - "architecture": "x64", - "version": "20.6.0", - }, - "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", - "application": {"name": "mongosh 1.0.1"}, - }, - "compression": ["none"], - "loadBalanced": false, - }`, - }, - { - name: "handshake3", - raw: testutil.MustParseDumpFile("testdata", "handshake3.hex"), - doc: MustDocument( - "buildInfo", int32(1), - "lsid", MustDocument( - "id", Binary{ - Subtype: BinaryUUID, - B: []byte{ - 0xa3, 0x19, 0xf2, 0xb4, 0xa1, 0x75, 0x40, 0xc7, - 0xb8, 0xe7, 0xa3, 0xa3, 0x2e, 0xc2, 0x56, 0xbe, - }, - }, - ), - "$db", "admin", - ), - m: ` - { - "buildInfo": 1, - "lsid": {"id": Binary(uuid:oxnytKF1QMe456OjLsJWvg==)}, - "$db": "admin", - }`, - }, - { - name: "handshake4", - raw: testutil.MustParseDumpFile("testdata", "handshake4.hex"), - doc: MustDocument( - "version", "5.0.0", - "gitVersion", "1184f004a99660de6f5e745573419bda8a28c0e9", - "modules", MustArray(), - "allocator", "tcmalloc", - "javascriptEngine", "mozjs", - "sysInfo", "deprecated", - "versionArray", MustArray(int32(5), int32(0), int32(0), int32(0)), - "openssl", MustDocument( - "running", "OpenSSL 1.1.1f 31 Mar 2020", - "compiled", "OpenSSL 1.1.1f 31 Mar 2020", - ), - "buildEnvironment", MustDocument( - "distmod", "ubuntu2004", - "distarch", "x86_64", - "cc", "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", - "ccflags", "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb "+ - "-Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer "+ - "-fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 "+ - "-Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations "+ - "-Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces "+ - "-fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", - "cxx", "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", - "cxxflags", "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", - "linkflags", "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong "+ - "-Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack "+ - "-Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", - "target_arch", "x86_64", - "target_os", "linux", - "cppdefines", "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE "+ - "_REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME "+ - "BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS "+ - "BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG "+ - "BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", - ), - "bits", int32(64), - "debug", false, - "maxBsonObjectSize", int32(16777216), - "storageEngines", MustArray("devnull", "ephemeralForTest", "wiredTiger"), - "ok", float64(1), - ), - m: ` - { - "version": "5.0.0", - "gitVersion": "1184f004a99660de6f5e745573419bda8a28c0e9", - "modules": [], - "allocator": "tcmalloc", - "javascriptEngine": "mozjs", - "sysInfo": "deprecated", - "versionArray": [5, 0, 0, 0], - "openssl": { - "running": "OpenSSL 1.1.1f 31 Mar 2020", - "compiled": "OpenSSL 1.1.1f 31 Mar 2020", - }, - "buildEnvironment": { - "distmod": "ubuntu2004", - "distarch": "x86_64", - "cc": "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", - "ccflags": "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer -fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", - "cxx": "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", - "cxxflags": "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", - "linkflags": "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong -Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", - "target_arch": "x86_64", - "target_os": "linux", - "cppdefines": "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE _REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", - }, - "bits": 64, - "debug": false, - "maxBsonObjectSize": 16777216, - "storageEngines": ["devnull", "ephemeralForTest", "wiredTiger"], - "ok": 1.0, - }`, - }, - { - name: "all", - raw: testutil.MustParseDumpFile("testdata", "all.hex"), - doc: MustDocument( - "array", MustArray( - MustArray(""), - MustArray("foo"), - ), - "binary", MustArray( - Binary{Subtype: BinaryUser, B: []byte{0x42}}, - Binary{Subtype: BinaryGeneric, B: []byte{}}, - ), - "bool", MustArray(true, false), - "datetime", MustArray( - time.Date(2021, 7, 27, 9, 35, 42, 123000000, time.UTC).Local(), - time.Time{}.Local(), - ), - "document", MustArray( - MustDocument("foo", ""), - MustDocument("", "foo"), - ), - "double", MustArray(42.13, 0.0), - "int32", MustArray(int32(42), int32(0)), - "int64", MustArray(int64(42), int64(0)), - "objectID", MustArray(ObjectID{0x42}, ObjectID{}), - "string", MustArray("foo", ""), - "timestamp", MustArray(Timestamp(42), Timestamp(0)), - "decimal128", MustArray(Decimal128{L: 42, H: 13}), - ), - m: ` - { - "array": [[""], ["foo"]], - "binary": [Binary(user:Qg==), Binary(generic:)], - "bool": [true, false], - "datetime": [2021-07-27T09:35:42.123Z, 0001-01-01T00:00:00Z], - "document": [{"foo": ""}, {"": "foo"}], - "double": [42.13, 0.0], - "int32": [42, 0], - "int64": [int64(42), int64(0)], - "objectID": [ObjectID(420000000000000000000000), ObjectID(000000000000000000000000)], - "string": ["foo", ""], - "timestamp": [Timestamp(42), Timestamp(0)], - "decimal128": [Decimal128(42,13)], - }`, - }, - { - name: "nested", - raw: testutil.MustParseDumpFile("testdata", "nested.hex"), - doc: makeNested(false, 150).(*Document), - m: ` - { - "f": [ - { - "f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{...}]}]}]}]}]}]}]}]}], - }, - ], - }`, - }, - { - name: "float64Doc", - raw: RawDocument{ - 0x10, 0x00, 0x00, 0x00, - 0x01, 0x66, 0x00, - 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, - 0x00, - }, - doc: MustDocument( - "f", float64(3.141592653589793), - ), - m: `{"f": 3.141592653589793}`, - }, - { - name: "stringDoc", - raw: RawDocument{ - 0x0e, 0x00, 0x00, 0x00, - 0x02, 0x66, 0x00, - 0x02, 0x00, 0x00, 0x00, - 0x76, 0x00, - 0x00, - }, - doc: MustDocument( - "f", "v", - ), - m: `{"f": "v"}`, - }, - { - name: "binaryDoc", - raw: RawDocument{ - 0x0e, 0x00, 0x00, 0x00, - 0x05, 0x66, 0x00, - 0x01, 0x00, 0x00, 0x00, - 0x80, - 0x76, - 0x00, - }, - doc: MustDocument( - "f", Binary{B: []byte("v"), Subtype: BinaryUser}, - ), - m: `{"f": Binary(user:dg==)}`, - }, - { - name: "objectIDDoc", - raw: RawDocument{ - 0x14, 0x00, 0x00, 0x00, - 0x07, 0x66, 0x00, - 0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, - 0x00, - }, - doc: MustDocument( - "f", ObjectID{0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}, - ), - m: `{"f": ObjectID(6256c5ba182d4454fb210940)}`, - }, - { - name: "boolDoc", - raw: RawDocument{ - 0x09, 0x00, 0x00, 0x00, - 0x08, 0x66, 0x00, - 0x01, - 0x00, - }, - doc: MustDocument( - "f", true, - ), - m: `{"f": true}`, - }, - { - name: "timeDoc", - raw: RawDocument{ - 0x10, 0x00, 0x00, 0x00, - 0x09, 0x66, 0x00, - 0x0b, 0xce, 0x82, 0x18, 0x8d, 0x01, 0x00, 0x00, - 0x00, - }, - doc: MustDocument( - "f", time.Date(2024, 1, 17, 17, 40, 42, 123000000, time.UTC), - ), - m: `{"f": 2024-01-17T17:40:42.123Z}`, - }, - { - name: "nullDoc", - raw: RawDocument{ - 0x08, 0x00, 0x00, 0x00, - 0x0a, 0x66, 0x00, - 0x00, - }, - doc: MustDocument( - "f", Null, - ), - m: `{"f": null}`, - }, - { - name: "regexDoc", - raw: RawDocument{ - 0x0c, 0x00, 0x00, 0x00, - 0x0b, 0x66, 0x00, - 0x70, 0x00, - 0x6f, 0x00, - 0x00, - }, - doc: MustDocument( - "f", Regex{Pattern: "p", Options: "o"}, - ), - m: `{"f": /p/o}`, - }, - { - name: "int32Doc", - raw: RawDocument{ - 0x0c, 0x00, 0x00, 0x00, - 0x10, 0x66, 0x00, - 0xa1, 0xb0, 0xb9, 0x12, - 0x00, - }, - doc: MustDocument( - "f", int32(314159265), - ), - m: `{"f": 314159265}`, - }, - { - name: "timestampDoc", - raw: RawDocument{ - 0x10, 0x00, 0x00, 0x00, - 0x11, 0x66, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, - }, - doc: MustDocument( - "f", Timestamp(42), - ), - m: `{"f": Timestamp(42)}`, - }, - { - name: "int64Doc", - raw: RawDocument{ - 0x10, 0x00, 0x00, 0x00, - 0x12, 0x66, 0x00, - 0x21, 0x6d, 0x25, 0x0a, 0x43, 0x29, 0x0b, 0x00, - 0x00, - }, - doc: MustDocument( - "f", int64(3141592653589793), - ), - m: `{"f": int64(3141592653589793)}`, - }, - { - name: "decimal128Doc", - raw: RawDocument{ - 0x18, 0x00, 0x00, 0x00, - 0x13, 0x66, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, - }, - doc: MustDocument( - "f", Decimal128{L: 42, H: 13}, - ), - m: `{"f": Decimal128(42,13)}`, - }, - { - name: "smallDoc", - raw: RawDocument{ - 0x0f, 0x00, 0x00, 0x00, // document length - 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" - 0x05, 0x00, 0x00, 0x00, 0x00, // subdocument length and end of subdocument - 0x00, // end of document - }, - doc: MustDocument( - "foo", MustDocument(), - ), - m: `{"foo": {}}`, - }, - { - name: "smallArray", - raw: RawDocument{ - 0x0f, 0x00, 0x00, 0x00, // document length - 0x04, 0x66, 0x6f, 0x6f, 0x00, // subarray "foo" - 0x05, 0x00, 0x00, 0x00, 0x00, // subarray length and end of subarray - 0x00, // end of document - }, - doc: MustDocument( - "foo", MustArray(), - ), - m: `{"foo": []}`, - }, - { - name: "duplicateKeys", - raw: RawDocument{ - 0x0b, 0x00, 0x00, 0x00, // document length - 0x08, 0x00, 0x00, // "": false - 0x08, 0x00, 0x01, // "": true - 0x00, // end of document - }, - doc: MustDocument( - "", false, - "", true, - ), - m: `{"": false, "": true}`, - }, -} - -// decodeTestCases represents test cases for unsuccessful decoding. -var decodeTestCases = []decodeTestCase{ - { - name: "EOF", - raw: RawDocument{0x00}, - findRawErr: ErrDecodeShortInput, - decodeErr: ErrDecodeShortInput, - }, - { - name: "invalidLength", - raw: RawDocument{ - 0x00, 0x00, 0x00, 0x00, // invalid document length - 0x00, // end of document - }, - findRawErr: ErrDecodeInvalidInput, - decodeErr: ErrDecodeInvalidInput, - }, - { - name: "missingByte", - raw: RawDocument{ - 0x06, 0x00, 0x00, 0x00, // document length - 0x00, // end of document - }, - findRawErr: ErrDecodeShortInput, - decodeErr: ErrDecodeShortInput, - }, - { - name: "extraByte", - raw: RawDocument{ - 0x05, 0x00, 0x00, 0x00, // document length - 0x00, // end of document - 0x00, // extra byte - }, - oldOk: true, - findRawL: 5, - decodeErr: ErrDecodeInvalidInput, - }, - { - name: "unexpectedTag", - raw: RawDocument{ - 0x06, 0x00, 0x00, 0x00, // document length - 0xdd, // unexpected tag - 0x00, // end of document - }, - findRawL: 6, - decodeErr: ErrDecodeInvalidInput, - }, - { - name: "invalidTag", - raw: RawDocument{ - 0x06, 0x00, 0x00, 0x00, // document length - 0x00, // invalid tag - 0x00, // end of document - }, - findRawL: 6, - decodeErr: ErrDecodeInvalidInput, - }, - { - name: "shortDoc", - raw: RawDocument{ - 0x0f, 0x00, 0x00, 0x00, // document length - 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" - 0x06, 0x00, 0x00, 0x00, // invalid subdocument length - 0x00, // end of subdocument - 0x00, // end of document - }, - findRawL: 15, - decodeErr: ErrDecodeShortInput, - decodeDeepErr: ErrDecodeInvalidInput, - }, - { - name: "invalidDoc", - raw: RawDocument{ - 0x0f, 0x00, 0x00, 0x00, // document length - 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" - 0x05, 0x00, 0x00, 0x00, // subdocument length - 0x30, // invalid end of subdocument - 0x00, // end of document - }, - findRawL: 15, - decodeErr: ErrDecodeInvalidInput, - }, - { - name: "invalidDocTag", - raw: RawDocument{ - 0x10, 0x00, 0x00, 0x00, // document length - 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" - 0x06, 0x00, 0x00, 0x00, // subdocument length - 0x00, // invalid tag - 0x00, // end of subdocument - 0x00, // end of document - }, - findRawL: 16, - decodeDeepErr: ErrDecodeInvalidInput, - }, -} - -func TestNormal(t *testing.T) { - for _, tc := range normalTestCases { - t.Run(tc.name, func(t *testing.T) { - t.Run("FindRaw", func(t *testing.T) { - ls := tc.raw.LogValue().Resolve().String() - assert.NotContains(t, ls, "panicked") - assert.NotContains(t, ls, "called too many times") - - assert.NotEmpty(t, LogMessage(tc.raw)) - assert.NotEmpty(t, LogMessageBlock(tc.raw)) - assert.NotEmpty(t, LogMessageFlow(tc.raw)) - - l, err := FindRaw(tc.raw) - require.NoError(t, err) - require.Len(t, tc.raw, l) - }) - - t.Run("DecodeEncode", func(t *testing.T) { - doc, err := tc.raw.Decode() - require.NoError(t, err) - - ls := doc.LogValue().Resolve().String() - assert.NotContains(t, ls, "panicked") - assert.NotContains(t, ls, "called too many times") - - assert.NotEmpty(t, LogMessage(doc)) - assert.NotEmpty(t, LogMessageBlock(doc)) - assert.NotEmpty(t, LogMessageFlow(doc)) - - raw, err := doc.Encode() - require.NoError(t, err) - assert.Equal(t, tc.raw, raw) - }) - - t.Run("DecodeDeepEncode", func(t *testing.T) { - doc, err := tc.raw.DecodeDeep() - require.NoError(t, err) - - ls := doc.LogValue().Resolve().String() - assert.NotContains(t, ls, "panicked") - assert.NotContains(t, ls, "called too many times") - - assert.Equal(t, testutil.Unindent(tc.m), LogMessage(doc)) - assert.NotEmpty(t, LogMessageBlock(doc)) - assert.NotEmpty(t, LogMessageFlow(doc)) - - raw, err := doc.Encode() - require.NoError(t, err) - assert.Equal(t, tc.raw, raw) - }) - }) - } -} - -func TestDecode(t *testing.T) { - for _, tc := range decodeTestCases { - if tc.decodeDeepErr == nil { - tc.decodeDeepErr = tc.decodeErr - } - - require.NotNil(t, tc.decodeDeepErr, "invalid test case %q", tc.name) - - t.Run(tc.name, func(t *testing.T) { - t.Run("FindRaw", func(t *testing.T) { - ls := tc.raw.LogValue().Resolve().String() - assert.NotContains(t, ls, "panicked") - assert.NotContains(t, ls, "called too many times") - - assert.NotEmpty(t, LogMessage(tc.raw)) - assert.NotEmpty(t, LogMessageBlock(tc.raw)) - assert.NotEmpty(t, LogMessageFlow(tc.raw)) - - l, err := FindRaw(tc.raw) - - if tc.findRawErr != nil { - require.ErrorIs(t, err, tc.findRawErr) - return - } - - require.NoError(t, err) - require.Equal(t, tc.findRawL, l) - }) - - t.Run("Decode", func(t *testing.T) { - _, err := tc.raw.Decode() - - if tc.decodeErr != nil { - require.ErrorIs(t, err, tc.decodeErr) - return - } - - require.NoError(t, err) - }) - - t.Run("DecodeDeep", func(t *testing.T) { - _, err := tc.raw.DecodeDeep() - require.ErrorIs(t, err, tc.decodeDeepErr) - }) - }) - } -} - -func BenchmarkDocument(b *testing.B) { - for _, tc := range normalTestCases { - b.Run(tc.name, func(b *testing.B) { - var doc *Document - var raw []byte - var m string - var err error - - b.Run("Decode", func(b *testing.B) { - b.ReportAllocs() - - for range b.N { - doc, err = tc.raw.Decode() - } - - b.StopTimer() - - require.NoError(b, err) - require.NotNil(b, doc) - }) - - b.Run("Encode", func(b *testing.B) { - doc, err = tc.raw.Decode() - require.NoError(b, err) - - b.ReportAllocs() - b.ResetTimer() - - for range b.N { - raw, err = doc.Encode() - } - - b.StopTimer() - - require.NoError(b, err) - assert.NotNil(b, raw) - }) - - b.Run("LogValue", func(b *testing.B) { - doc, err = tc.raw.Decode() - require.NoError(b, err) - - b.ReportAllocs() - b.ResetTimer() - - for range b.N { - m = doc.LogValue().Resolve().String() - } - - b.StopTimer() - - assert.NotEmpty(b, m) - assert.NotContains(b, m, "panicked") - assert.NotContains(b, m, "called too many times") - }) - - b.Run("LogMessage", func(b *testing.B) { - doc, err = tc.raw.Decode() - require.NoError(b, err) - - b.ReportAllocs() - b.ResetTimer() - - for range b.N { - m = LogMessage(doc) - } - - b.StopTimer() - - assert.NotEmpty(b, m) - }) - - b.Run("DecodeDeep", func(b *testing.B) { - b.ReportAllocs() - - for range b.N { - doc, err = tc.raw.DecodeDeep() - } - - b.StopTimer() - - require.NoError(b, err) - require.NotNil(b, doc) - }) - - b.Run("EncodeDeep", func(b *testing.B) { - doc, err = tc.raw.DecodeDeep() - require.NoError(b, err) - - b.ReportAllocs() - b.ResetTimer() - - for range b.N { - raw, err = doc.Encode() - } - - b.StopTimer() - - require.NoError(b, err) - assert.NotNil(b, raw) - }) - - b.Run("LogValueDeep", func(b *testing.B) { - doc, err = tc.raw.DecodeDeep() - require.NoError(b, err) - - b.ReportAllocs() - b.ResetTimer() - - for range b.N { - m = doc.LogValue().Resolve().String() - } - - b.StopTimer() - - assert.NotEmpty(b, m) - assert.NotContains(b, m, "panicked") - assert.NotContains(b, m, "called too many times") - }) - - b.Run("LogMessageDeep", func(b *testing.B) { - doc, err = tc.raw.DecodeDeep() - require.NoError(b, err) - - b.ReportAllocs() - b.ResetTimer() - - for range b.N { - m = LogMessage(doc) - } - - b.StopTimer() - - assert.NotEmpty(b, m) - }) - }) - } -} - -// testRawDocument tests a single RawDocument (that might or might not be valid). -// It is adapted from tests above. -func testRawDocument(t *testing.T, rawDoc RawDocument) { - t.Helper() - - t.Run("FindRaw", func(t *testing.T) { - ls := rawDoc.LogValue().Resolve().String() - assert.NotContains(t, ls, "panicked") - assert.NotContains(t, ls, "called too many times") - - assert.NotEmpty(t, LogMessage(rawDoc)) - assert.NotEmpty(t, LogMessageBlock(rawDoc)) - assert.NotEmpty(t, LogMessageFlow(rawDoc)) - - _, _ = FindRaw(rawDoc) - }) - - t.Run("DecodeEncode", func(t *testing.T) { - doc, err := rawDoc.Decode() - if err != nil { - _, err = rawDoc.DecodeDeep() - assert.Error(t, err) // it might be different - - return - } - - ls := doc.LogValue().Resolve().String() - assert.NotContains(t, ls, "panicked") - assert.NotContains(t, ls, "called too many times") - - assert.NotEmpty(t, LogMessage(doc)) - assert.NotEmpty(t, LogMessageBlock(doc)) - assert.NotEmpty(t, LogMessageFlow(doc)) - - raw, err := doc.Encode() - if err == nil { - assert.Equal(t, rawDoc, raw) - } - }) - - t.Run("DecodeDeepEncode", func(t *testing.T) { - doc, err := rawDoc.DecodeDeep() - if err != nil { - return - } - - ls := doc.LogValue().Resolve().String() - assert.NotContains(t, ls, "panicked") - assert.NotContains(t, ls, "called too many times") - - assert.NotEmpty(t, LogMessage(doc)) - assert.NotEmpty(t, LogMessageBlock(doc)) - assert.NotEmpty(t, LogMessageFlow(doc)) - - raw, err := doc.Encode() - require.NoError(t, err) - assert.Equal(t, rawDoc, raw) - }) -} - -func FuzzDocument(f *testing.F) { - for _, tc := range normalTestCases { - f.Add([]byte(tc.raw)) - } - - for _, tc := range decodeTestCases { - f.Add([]byte(tc.raw)) - } - - f.Fuzz(func(t *testing.T, b []byte) { - t.Parallel() - - testRawDocument(t, RawDocument(b)) - - l, err := FindRaw(b) - if err == nil { - testRawDocument(t, RawDocument(b[:l])) - } - }) -} +//import ( +// "testing" +// "time" +// +// "github.com/stretchr/testify/assert" +// "github.com/stretchr/testify/require" +// +// "github.com/FerretDB/wire/internal/util/testutil" +//) +// +//// normalTestCase represents a single test case for successful decoding/encoding. +//// +////nolint:vet // for readability +//type normalTestCase struct { +// name string +// raw RawDocument +// doc *Document +// m string +//} +// +//// decodeTestCase represents a single test case for unsuccessful decoding. +//// +////nolint:vet // for readability +//type decodeTestCase struct { +// name string +// raw RawDocument +// +// oldOk bool +// +// findRawErr error +// findRawL int +// decodeErr error +// decodeDeepErr error // defaults to decodeErr +//} +// +//// normalTestCases represents test cases for successful decoding/encoding. +//// +////nolint:lll // for readability +//var normalTestCases = []normalTestCase{ +// { +// name: "handshake1", +// raw: testutil.MustParseDumpFile("testdata", "handshake1.hex"), +// doc: MustDocument( +// "ismaster", true, +// "client", MustDocument( +// "driver", MustDocument( +// "name", "nodejs", +// "version", "4.0.0-beta.6", +// ), +// "os", MustDocument( +// "type", "Darwin", +// "name", "darwin", +// "architecture", "x64", +// "version", "20.6.0", +// ), +// "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", +// "application", MustDocument( +// "name", "mongosh 1.0.1", +// ), +// ), +// "compression", MustArray("none"), +// "loadBalanced", false, +// ), +// m: ` +// { +// "ismaster": true, +// "client": { +// "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, +// "os": { +// "type": "Darwin", +// "name": "darwin", +// "architecture": "x64", +// "version": "20.6.0", +// }, +// "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", +// "application": {"name": "mongosh 1.0.1"}, +// }, +// "compression": ["none"], +// "loadBalanced": false, +// }`, +// }, +// { +// name: "handshake2", +// raw: testutil.MustParseDumpFile("testdata", "handshake2.hex"), +// doc: MustDocument( +// "ismaster", true, +// "client", MustDocument( +// "driver", MustDocument( +// "name", "nodejs", +// "version", "4.0.0-beta.6", +// ), +// "os", MustDocument( +// "type", "Darwin", +// "name", "darwin", +// "architecture", "x64", +// "version", "20.6.0", +// ), +// "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", +// "application", MustDocument( +// "name", "mongosh 1.0.1", +// ), +// ), +// "compression", MustArray("none"), +// "loadBalanced", false, +// ), +// m: ` +// { +// "ismaster": true, +// "client": { +// "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, +// "os": { +// "type": "Darwin", +// "name": "darwin", +// "architecture": "x64", +// "version": "20.6.0", +// }, +// "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", +// "application": {"name": "mongosh 1.0.1"}, +// }, +// "compression": ["none"], +// "loadBalanced": false, +// }`, +// }, +// { +// name: "handshake3", +// raw: testutil.MustParseDumpFile("testdata", "handshake3.hex"), +// doc: MustDocument( +// "buildInfo", int32(1), +// "lsid", MustDocument( +// "id", Binary{ +// Subtype: BinaryUUID, +// B: []byte{ +// 0xa3, 0x19, 0xf2, 0xb4, 0xa1, 0x75, 0x40, 0xc7, +// 0xb8, 0xe7, 0xa3, 0xa3, 0x2e, 0xc2, 0x56, 0xbe, +// }, +// }, +// ), +// "$db", "admin", +// ), +// m: ` +// { +// "buildInfo": 1, +// "lsid": {"id": Binary(uuid:oxnytKF1QMe456OjLsJWvg==)}, +// "$db": "admin", +// }`, +// }, +// { +// name: "handshake4", +// raw: testutil.MustParseDumpFile("testdata", "handshake4.hex"), +// doc: MustDocument( +// "version", "5.0.0", +// "gitVersion", "1184f004a99660de6f5e745573419bda8a28c0e9", +// "modules", MustArray(), +// "allocator", "tcmalloc", +// "javascriptEngine", "mozjs", +// "sysInfo", "deprecated", +// "versionArray", MustArray(int32(5), int32(0), int32(0), int32(0)), +// "openssl", MustDocument( +// "running", "OpenSSL 1.1.1f 31 Mar 2020", +// "compiled", "OpenSSL 1.1.1f 31 Mar 2020", +// ), +// "buildEnvironment", MustDocument( +// "distmod", "ubuntu2004", +// "distarch", "x86_64", +// "cc", "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", +// "ccflags", "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb "+ +// "-Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer "+ +// "-fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 "+ +// "-Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations "+ +// "-Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces "+ +// "-fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", +// "cxx", "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", +// "cxxflags", "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", +// "linkflags", "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong "+ +// "-Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack "+ +// "-Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", +// "target_arch", "x86_64", +// "target_os", "linux", +// "cppdefines", "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE "+ +// "_REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME "+ +// "BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS "+ +// "BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG "+ +// "BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", +// ), +// "bits", int32(64), +// "debug", false, +// "maxBsonObjectSize", int32(16777216), +// "storageEngines", MustArray("devnull", "ephemeralForTest", "wiredTiger"), +// "ok", float64(1), +// ), +// m: ` +// { +// "version": "5.0.0", +// "gitVersion": "1184f004a99660de6f5e745573419bda8a28c0e9", +// "modules": [], +// "allocator": "tcmalloc", +// "javascriptEngine": "mozjs", +// "sysInfo": "deprecated", +// "versionArray": [5, 0, 0, 0], +// "openssl": { +// "running": "OpenSSL 1.1.1f 31 Mar 2020", +// "compiled": "OpenSSL 1.1.1f 31 Mar 2020", +// }, +// "buildEnvironment": { +// "distmod": "ubuntu2004", +// "distarch": "x86_64", +// "cc": "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", +// "ccflags": "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer -fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", +// "cxx": "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", +// "cxxflags": "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", +// "linkflags": "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong -Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", +// "target_arch": "x86_64", +// "target_os": "linux", +// "cppdefines": "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE _REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", +// }, +// "bits": 64, +// "debug": false, +// "maxBsonObjectSize": 16777216, +// "storageEngines": ["devnull", "ephemeralForTest", "wiredTiger"], +// "ok": 1.0, +// }`, +// }, +// { +// name: "all", +// raw: testutil.MustParseDumpFile("testdata", "all.hex"), +// doc: MustDocument( +// "array", MustArray( +// MustArray(""), +// MustArray("foo"), +// ), +// "binary", MustArray( +// Binary{Subtype: BinaryUser, B: []byte{0x42}}, +// Binary{Subtype: BinaryGeneric, B: []byte{}}, +// ), +// "bool", MustArray(true, false), +// "datetime", MustArray( +// time.Date(2021, 7, 27, 9, 35, 42, 123000000, time.UTC).Local(), +// time.Time{}.Local(), +// ), +// "document", MustArray( +// MustDocument("foo", ""), +// MustDocument("", "foo"), +// ), +// "double", MustArray(42.13, 0.0), +// "int32", MustArray(int32(42), int32(0)), +// "int64", MustArray(int64(42), int64(0)), +// "objectID", MustArray(ObjectID{0x42}, ObjectID{}), +// "string", MustArray("foo", ""), +// "timestamp", MustArray(Timestamp(42), Timestamp(0)), +// "decimal128", MustArray(Decimal128{L: 42, H: 13}), +// ), +// m: ` +// { +// "array": [[""], ["foo"]], +// "binary": [Binary(user:Qg==), Binary(generic:)], +// "bool": [true, false], +// "datetime": [2021-07-27T09:35:42.123Z, 0001-01-01T00:00:00Z], +// "document": [{"foo": ""}, {"": "foo"}], +// "double": [42.13, 0.0], +// "int32": [42, 0], +// "int64": [int64(42), int64(0)], +// "objectID": [ObjectID(420000000000000000000000), ObjectID(000000000000000000000000)], +// "string": ["foo", ""], +// "timestamp": [Timestamp(42), Timestamp(0)], +// "decimal128": [Decimal128(42,13)], +// }`, +// }, +// { +// name: "nested", +// raw: testutil.MustParseDumpFile("testdata", "nested.hex"), +// doc: makeNested(false, 150).(*Document), +// m: ` +// { +// "f": [ +// { +// "f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{...}]}]}]}]}]}]}]}]}], +// }, +// ], +// }`, +// }, +// { +// name: "float64Doc", +// raw: RawDocument{ +// 0x10, 0x00, 0x00, 0x00, +// 0x01, 0x66, 0x00, +// 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, +// 0x00, +// }, +// doc: MustDocument( +// "f", float64(3.141592653589793), +// ), +// m: `{"f": 3.141592653589793}`, +// }, +// { +// name: "stringDoc", +// raw: RawDocument{ +// 0x0e, 0x00, 0x00, 0x00, +// 0x02, 0x66, 0x00, +// 0x02, 0x00, 0x00, 0x00, +// 0x76, 0x00, +// 0x00, +// }, +// doc: MustDocument( +// "f", "v", +// ), +// m: `{"f": "v"}`, +// }, +// { +// name: "binaryDoc", +// raw: RawDocument{ +// 0x0e, 0x00, 0x00, 0x00, +// 0x05, 0x66, 0x00, +// 0x01, 0x00, 0x00, 0x00, +// 0x80, +// 0x76, +// 0x00, +// }, +// doc: MustDocument( +// "f", Binary{B: []byte("v"), Subtype: BinaryUser}, +// ), +// m: `{"f": Binary(user:dg==)}`, +// }, +// { +// name: "objectIDDoc", +// raw: RawDocument{ +// 0x14, 0x00, 0x00, 0x00, +// 0x07, 0x66, 0x00, +// 0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, +// 0x00, +// }, +// doc: MustDocument( +// "f", ObjectID{0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}, +// ), +// m: `{"f": ObjectID(6256c5ba182d4454fb210940)}`, +// }, +// { +// name: "boolDoc", +// raw: RawDocument{ +// 0x09, 0x00, 0x00, 0x00, +// 0x08, 0x66, 0x00, +// 0x01, +// 0x00, +// }, +// doc: MustDocument( +// "f", true, +// ), +// m: `{"f": true}`, +// }, +// { +// name: "timeDoc", +// raw: RawDocument{ +// 0x10, 0x00, 0x00, 0x00, +// 0x09, 0x66, 0x00, +// 0x0b, 0xce, 0x82, 0x18, 0x8d, 0x01, 0x00, 0x00, +// 0x00, +// }, +// doc: MustDocument( +// "f", time.Date(2024, 1, 17, 17, 40, 42, 123000000, time.UTC), +// ), +// m: `{"f": 2024-01-17T17:40:42.123Z}`, +// }, +// { +// name: "nullDoc", +// raw: RawDocument{ +// 0x08, 0x00, 0x00, 0x00, +// 0x0a, 0x66, 0x00, +// 0x00, +// }, +// doc: MustDocument( +// "f", Null, +// ), +// m: `{"f": null}`, +// }, +// { +// name: "regexDoc", +// raw: RawDocument{ +// 0x0c, 0x00, 0x00, 0x00, +// 0x0b, 0x66, 0x00, +// 0x70, 0x00, +// 0x6f, 0x00, +// 0x00, +// }, +// doc: MustDocument( +// "f", Regex{Pattern: "p", Options: "o"}, +// ), +// m: `{"f": /p/o}`, +// }, +// { +// name: "int32Doc", +// raw: RawDocument{ +// 0x0c, 0x00, 0x00, 0x00, +// 0x10, 0x66, 0x00, +// 0xa1, 0xb0, 0xb9, 0x12, +// 0x00, +// }, +// doc: MustDocument( +// "f", int32(314159265), +// ), +// m: `{"f": 314159265}`, +// }, +// { +// name: "timestampDoc", +// raw: RawDocument{ +// 0x10, 0x00, 0x00, 0x00, +// 0x11, 0x66, 0x00, +// 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, +// }, +// doc: MustDocument( +// "f", Timestamp(42), +// ), +// m: `{"f": Timestamp(42)}`, +// }, +// { +// name: "int64Doc", +// raw: RawDocument{ +// 0x10, 0x00, 0x00, 0x00, +// 0x12, 0x66, 0x00, +// 0x21, 0x6d, 0x25, 0x0a, 0x43, 0x29, 0x0b, 0x00, +// 0x00, +// }, +// doc: MustDocument( +// "f", int64(3141592653589793), +// ), +// m: `{"f": int64(3141592653589793)}`, +// }, +// { +// name: "decimal128Doc", +// raw: RawDocument{ +// 0x18, 0x00, 0x00, 0x00, +// 0x13, 0x66, 0x00, +// 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, +// }, +// doc: MustDocument( +// "f", Decimal128{L: 42, H: 13}, +// ), +// m: `{"f": Decimal128(42,13)}`, +// }, +// { +// name: "smallDoc", +// raw: RawDocument{ +// 0x0f, 0x00, 0x00, 0x00, // document length +// 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" +// 0x05, 0x00, 0x00, 0x00, 0x00, // subdocument length and end of subdocument +// 0x00, // end of document +// }, +// doc: MustDocument( +// "foo", MustDocument(), +// ), +// m: `{"foo": {}}`, +// }, +// { +// name: "smallArray", +// raw: RawDocument{ +// 0x0f, 0x00, 0x00, 0x00, // document length +// 0x04, 0x66, 0x6f, 0x6f, 0x00, // subarray "foo" +// 0x05, 0x00, 0x00, 0x00, 0x00, // subarray length and end of subarray +// 0x00, // end of document +// }, +// doc: MustDocument( +// "foo", MustArray(), +// ), +// m: `{"foo": []}`, +// }, +// { +// name: "duplicateKeys", +// raw: RawDocument{ +// 0x0b, 0x00, 0x00, 0x00, // document length +// 0x08, 0x00, 0x00, // "": false +// 0x08, 0x00, 0x01, // "": true +// 0x00, // end of document +// }, +// doc: MustDocument( +// "", false, +// "", true, +// ), +// m: `{"": false, "": true}`, +// }, +//} +// +//// decodeTestCases represents test cases for unsuccessful decoding. +//var decodeTestCases = []decodeTestCase{ +// { +// name: "EOF", +// raw: RawDocument{0x00}, +// findRawErr: ErrDecodeShortInput, +// decodeErr: ErrDecodeShortInput, +// }, +// { +// name: "invalidLength", +// raw: RawDocument{ +// 0x00, 0x00, 0x00, 0x00, // invalid document length +// 0x00, // end of document +// }, +// findRawErr: ErrDecodeInvalidInput, +// decodeErr: ErrDecodeInvalidInput, +// }, +// { +// name: "missingByte", +// raw: RawDocument{ +// 0x06, 0x00, 0x00, 0x00, // document length +// 0x00, // end of document +// }, +// findRawErr: ErrDecodeShortInput, +// decodeErr: ErrDecodeShortInput, +// }, +// { +// name: "extraByte", +// raw: RawDocument{ +// 0x05, 0x00, 0x00, 0x00, // document length +// 0x00, // end of document +// 0x00, // extra byte +// }, +// oldOk: true, +// findRawL: 5, +// decodeErr: ErrDecodeInvalidInput, +// }, +// { +// name: "unexpectedTag", +// raw: RawDocument{ +// 0x06, 0x00, 0x00, 0x00, // document length +// 0xdd, // unexpected tag +// 0x00, // end of document +// }, +// findRawL: 6, +// decodeErr: ErrDecodeInvalidInput, +// }, +// { +// name: "invalidTag", +// raw: RawDocument{ +// 0x06, 0x00, 0x00, 0x00, // document length +// 0x00, // invalid tag +// 0x00, // end of document +// }, +// findRawL: 6, +// decodeErr: ErrDecodeInvalidInput, +// }, +// { +// name: "shortDoc", +// raw: RawDocument{ +// 0x0f, 0x00, 0x00, 0x00, // document length +// 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" +// 0x06, 0x00, 0x00, 0x00, // invalid subdocument length +// 0x00, // end of subdocument +// 0x00, // end of document +// }, +// findRawL: 15, +// decodeErr: ErrDecodeShortInput, +// decodeDeepErr: ErrDecodeInvalidInput, +// }, +// { +// name: "invalidDoc", +// raw: RawDocument{ +// 0x0f, 0x00, 0x00, 0x00, // document length +// 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" +// 0x05, 0x00, 0x00, 0x00, // subdocument length +// 0x30, // invalid end of subdocument +// 0x00, // end of document +// }, +// findRawL: 15, +// decodeErr: ErrDecodeInvalidInput, +// }, +// { +// name: "invalidDocTag", +// raw: RawDocument{ +// 0x10, 0x00, 0x00, 0x00, // document length +// 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" +// 0x06, 0x00, 0x00, 0x00, // subdocument length +// 0x00, // invalid tag +// 0x00, // end of subdocument +// 0x00, // end of document +// }, +// findRawL: 16, +// decodeDeepErr: ErrDecodeInvalidInput, +// }, +//} +// +//func TestNormal(t *testing.T) { +// for _, tc := range normalTestCases { +// t.Run(tc.name, func(t *testing.T) { +// t.Run("FindRaw", func(t *testing.T) { +// ls := tc.raw.LogValue().Resolve().String() +// assert.NotContains(t, ls, "panicked") +// assert.NotContains(t, ls, "called too many times") +// +// assert.NotEmpty(t, LogMessage(tc.raw)) +// assert.NotEmpty(t, LogMessageBlock(tc.raw)) +// assert.NotEmpty(t, LogMessageFlow(tc.raw)) +// +// l, err := FindRaw(tc.raw) +// require.NoError(t, err) +// require.Len(t, tc.raw, l) +// }) +// +// t.Run("DecodeEncode", func(t *testing.T) { +// doc, err := tc.raw.Decode() +// require.NoError(t, err) +// +// ls := doc.LogValue().Resolve().String() +// assert.NotContains(t, ls, "panicked") +// assert.NotContains(t, ls, "called too many times") +// +// assert.NotEmpty(t, LogMessage(doc)) +// assert.NotEmpty(t, LogMessageBlock(doc)) +// assert.NotEmpty(t, LogMessageFlow(doc)) +// +// raw, err := doc.Encode() +// require.NoError(t, err) +// assert.Equal(t, tc.raw, raw) +// }) +// +// t.Run("DecodeDeepEncode", func(t *testing.T) { +// doc, err := tc.raw.DecodeDeep() +// require.NoError(t, err) +// +// ls := doc.LogValue().Resolve().String() +// assert.NotContains(t, ls, "panicked") +// assert.NotContains(t, ls, "called too many times") +// +// assert.Equal(t, testutil.Unindent(tc.m), LogMessage(doc)) +// assert.NotEmpty(t, LogMessageBlock(doc)) +// assert.NotEmpty(t, LogMessageFlow(doc)) +// +// raw, err := doc.Encode() +// require.NoError(t, err) +// assert.Equal(t, tc.raw, raw) +// }) +// }) +// } +//} +// +//func TestDecode(t *testing.T) { +// for _, tc := range decodeTestCases { +// if tc.decodeDeepErr == nil { +// tc.decodeDeepErr = tc.decodeErr +// } +// +// require.NotNil(t, tc.decodeDeepErr, "invalid test case %q", tc.name) +// +// t.Run(tc.name, func(t *testing.T) { +// t.Run("FindRaw", func(t *testing.T) { +// ls := tc.raw.LogValue().Resolve().String() +// assert.NotContains(t, ls, "panicked") +// assert.NotContains(t, ls, "called too many times") +// +// assert.NotEmpty(t, LogMessage(tc.raw)) +// assert.NotEmpty(t, LogMessageBlock(tc.raw)) +// assert.NotEmpty(t, LogMessageFlow(tc.raw)) +// +// l, err := FindRaw(tc.raw) +// +// if tc.findRawErr != nil { +// require.ErrorIs(t, err, tc.findRawErr) +// return +// } +// +// require.NoError(t, err) +// require.Equal(t, tc.findRawL, l) +// }) +// +// t.Run("Decode", func(t *testing.T) { +// _, err := tc.raw.Decode() +// +// if tc.decodeErr != nil { +// require.ErrorIs(t, err, tc.decodeErr) +// return +// } +// +// require.NoError(t, err) +// }) +// +// t.Run("DecodeDeep", func(t *testing.T) { +// _, err := tc.raw.DecodeDeep() +// require.ErrorIs(t, err, tc.decodeDeepErr) +// }) +// }) +// } +//} +// +//func BenchmarkDocument(b *testing.B) { +// for _, tc := range normalTestCases { +// b.Run(tc.name, func(b *testing.B) { +// var doc *Document +// var raw []byte +// var m string +// var err error +// +// b.Run("Decode", func(b *testing.B) { +// b.ReportAllocs() +// +// for range b.N { +// doc, err = tc.raw.Decode() +// } +// +// b.StopTimer() +// +// require.NoError(b, err) +// require.NotNil(b, doc) +// }) +// +// b.Run("Encode", func(b *testing.B) { +// doc, err = tc.raw.Decode() +// require.NoError(b, err) +// +// b.ReportAllocs() +// b.ResetTimer() +// +// for range b.N { +// raw, err = doc.Encode() +// } +// +// b.StopTimer() +// +// require.NoError(b, err) +// assert.NotNil(b, raw) +// }) +// +// b.Run("LogValue", func(b *testing.B) { +// doc, err = tc.raw.Decode() +// require.NoError(b, err) +// +// b.ReportAllocs() +// b.ResetTimer() +// +// for range b.N { +// m = doc.LogValue().Resolve().String() +// } +// +// b.StopTimer() +// +// assert.NotEmpty(b, m) +// assert.NotContains(b, m, "panicked") +// assert.NotContains(b, m, "called too many times") +// }) +// +// b.Run("LogMessage", func(b *testing.B) { +// doc, err = tc.raw.Decode() +// require.NoError(b, err) +// +// b.ReportAllocs() +// b.ResetTimer() +// +// for range b.N { +// m = LogMessage(doc) +// } +// +// b.StopTimer() +// +// assert.NotEmpty(b, m) +// }) +// +// b.Run("DecodeDeep", func(b *testing.B) { +// b.ReportAllocs() +// +// for range b.N { +// doc, err = tc.raw.DecodeDeep() +// } +// +// b.StopTimer() +// +// require.NoError(b, err) +// require.NotNil(b, doc) +// }) +// +// b.Run("EncodeDeep", func(b *testing.B) { +// doc, err = tc.raw.DecodeDeep() +// require.NoError(b, err) +// +// b.ReportAllocs() +// b.ResetTimer() +// +// for range b.N { +// raw, err = doc.Encode() +// } +// +// b.StopTimer() +// +// require.NoError(b, err) +// assert.NotNil(b, raw) +// }) +// +// b.Run("LogValueDeep", func(b *testing.B) { +// doc, err = tc.raw.DecodeDeep() +// require.NoError(b, err) +// +// b.ReportAllocs() +// b.ResetTimer() +// +// for range b.N { +// m = doc.LogValue().Resolve().String() +// } +// +// b.StopTimer() +// +// assert.NotEmpty(b, m) +// assert.NotContains(b, m, "panicked") +// assert.NotContains(b, m, "called too many times") +// }) +// +// b.Run("LogMessageDeep", func(b *testing.B) { +// doc, err = tc.raw.DecodeDeep() +// require.NoError(b, err) +// +// b.ReportAllocs() +// b.ResetTimer() +// +// for range b.N { +// m = LogMessage(doc) +// } +// +// b.StopTimer() +// +// assert.NotEmpty(b, m) +// }) +// }) +// } +//} +// +//// testRawDocument tests a single RawDocument (that might or might not be valid). +//// It is adapted from tests above. +//func testRawDocument(t *testing.T, rawDoc RawDocument) { +// t.Helper() +// +// t.Run("FindRaw", func(t *testing.T) { +// ls := rawDoc.LogValue().Resolve().String() +// assert.NotContains(t, ls, "panicked") +// assert.NotContains(t, ls, "called too many times") +// +// assert.NotEmpty(t, LogMessage(rawDoc)) +// assert.NotEmpty(t, LogMessageBlock(rawDoc)) +// assert.NotEmpty(t, LogMessageFlow(rawDoc)) +// +// _, _ = FindRaw(rawDoc) +// }) +// +// t.Run("DecodeEncode", func(t *testing.T) { +// doc, err := rawDoc.Decode() +// if err != nil { +// _, err = rawDoc.DecodeDeep() +// assert.Error(t, err) // it might be different +// +// return +// } +// +// ls := doc.LogValue().Resolve().String() +// assert.NotContains(t, ls, "panicked") +// assert.NotContains(t, ls, "called too many times") +// +// assert.NotEmpty(t, LogMessage(doc)) +// assert.NotEmpty(t, LogMessageBlock(doc)) +// assert.NotEmpty(t, LogMessageFlow(doc)) +// +// raw, err := doc.Encode() +// if err == nil { +// assert.Equal(t, rawDoc, raw) +// } +// }) +// +// t.Run("DecodeDeepEncode", func(t *testing.T) { +// doc, err := rawDoc.DecodeDeep() +// if err != nil { +// return +// } +// +// ls := doc.LogValue().Resolve().String() +// assert.NotContains(t, ls, "panicked") +// assert.NotContains(t, ls, "called too many times") +// +// assert.NotEmpty(t, LogMessage(doc)) +// assert.NotEmpty(t, LogMessageBlock(doc)) +// assert.NotEmpty(t, LogMessageFlow(doc)) +// +// raw, err := doc.Encode() +// require.NoError(t, err) +// assert.Equal(t, rawDoc, raw) +// }) +//} +// +//func FuzzDocument(f *testing.F) { +// for _, tc := range normalTestCases { +// f.Add([]byte(tc.raw)) +// } +// +// for _, tc := range decodeTestCases { +// f.Add([]byte(tc.raw)) +// } +// +// f.Fuzz(func(t *testing.T, b []byte) { +// t.Parallel() +// +// testRawDocument(t, RawDocument(b)) +// +// l, err := FindRaw(b) +// if err == nil { +// testRawDocument(t, RawDocument(b[:l])) +// } +// }) +//} diff --git a/wirebson/document.go b/wirebson/document.go index fb74d32..6abd77a 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -207,28 +207,25 @@ func (doc *Document) Command() string { // TODO https://github.com/FerretDB/wire/issues/21 // This method should accept a slice of bytes, not return it. // That would allow to avoid unnecessary allocations. -func (doc *Document) Encode() (RawDocument, error) { +func (doc *Document) Encode(raw RawDocument) error { must.NotBeZero(doc) - size := sizeDocument(doc) - buf := make([]byte, size) - var index int - binary.LittleEndian.PutUint32(buf, uint32(size)) + binary.LittleEndian.PutUint32(raw, uint32(sizeDocument(doc))) index += 4 var err error for _, f := range doc.fields { - if index, err = encodeField(index, buf, f.name, f.value); err != nil { - return nil, lazyerrors.Error(err) + if index, err = encodeField(index, raw, f.name, f.value); err != nil { + return lazyerrors.Error(err) } } - writeByte(buf, byte(0), index) + writeByte(raw, byte(0), index) index++ - return buf, nil + return nil } // Decode returns itself to implement [AnyDocument]. diff --git a/wirebson/encode.go b/wirebson/encode.go index 4551700..8a47239 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -36,7 +36,10 @@ func encodeField(i int, buf []byte, name string, v any) (int, error) { write(buf, b, i) i += len(b) - b, err := v.Encode() + size := sizeDocument(v) + b = make([]byte, size) + + err := v.Encode(b) if err != nil { return 0, lazyerrors.Error(err) } diff --git a/wirebson/raw_document.go b/wirebson/raw_document.go index 16c08a3..d2cc37d 100644 --- a/wirebson/raw_document.go +++ b/wirebson/raw_document.go @@ -29,9 +29,10 @@ type RawDocument []byte // Encode returns itself to implement the [AnyDocument] interface. // // Receiver must not be nil. -func (raw RawDocument) Encode() (RawDocument, error) { +func (raw RawDocument) Encode(out RawDocument) error { must.BeTrue(raw != nil) - return raw, nil + out = raw + return nil } // Decode decodes a single non-nil BSON document that takes the whole non-nil byte slice. diff --git a/wirebson/size.go b/wirebson/size.go index 71d8d4c..2607aa1 100644 --- a/wirebson/size.go +++ b/wirebson/size.go @@ -20,10 +20,10 @@ import ( "time" ) -// size returns a size of the encoding of value v in bytes. +// Size returns a Size of the encoding of value v in bytes. // // It panics for invalid types. -func size(v any) int { +func Size(v any) int { switch v := v.(type) { case *Document: return sizeDocument(v) @@ -43,7 +43,7 @@ func sizeDocument(doc *Document) int { res := 5 for _, f := range doc.fields { - res += 1 + SizeCString(f.name) + size(f.value) + res += 1 + SizeCString(f.name) + Size(f.value) } return res @@ -54,7 +54,7 @@ func sizeArray(arr *Array) int { res := 5 for i, v := range arr.elements { - res += 1 + SizeCString(strconv.Itoa(i)) + size(v) + res += 1 + SizeCString(strconv.Itoa(i)) + Size(v) } return res From b932258b279cf7e17555d9bc541e7a5f4468fb26 Mon Sep 17 00:00:00 2001 From: noisersup Date: Thu, 12 Sep 2024 17:13:18 +0200 Subject: [PATCH 23/46] wip --- wirebson/encode.go | 6 +++--- wirebson/encode_test.go | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index 8a47239..4242e96 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -92,7 +92,7 @@ func encodeField(i int, buf []byte, name string, v any) (int, error) { i += len(b) default: - return i, encodeScalarField(i, buf, name, v) + return encodeScalarField(i, buf, name, v) } return i, nil @@ -111,7 +111,7 @@ func write(b []byte, v []byte, offset int) int { // encodeScalarField encodes scalar document field. // // It panics if v is not a scalar value. -func encodeScalarField(i int, b []byte, name string, v any) error { +func encodeScalarField(i int, b []byte, name string, v any) (int, error) { switch v := v.(type) { case float64: writeByte(b, byte(tagFloat64), i) @@ -152,7 +152,7 @@ func encodeScalarField(i int, b []byte, name string, v any) error { i += write(b, bb, i) - return nil + return i, nil } // encodeScalarValue encodes value v into b. diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index 50b2509..fe3e638 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -18,7 +18,7 @@ func TestEncodeScalarField(t *testing.T) { } func TestEncodeField(t *testing.T) { - buf := make([]byte, 25) + buf := make([]byte, 22) var i int @@ -28,7 +28,7 @@ func TestEncodeField(t *testing.T) { actual := buf - expected := []byte{0x2, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0} + expected := []byte{0x2, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} assert.Equal(t, expected, actual) i, err = encodeField(i, buf, "foo", int32(1)) @@ -36,6 +36,8 @@ func TestEncodeField(t *testing.T) { actual = buf - expected = append(expected, []byte{0x10, 0x66, 0x6f, 0x6f, 0x0, 0x1, 0x0, 0x0, 0x0}...) + expected = append(expected, []byte{}...) + + expected = []byte{0x2, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0, 0x10, 0x66, 0x6f, 0x6f, 0x0, 0x1, 0x0, 0x0, 0x0} assert.Equal(t, expected, actual) } From 702798ac28b41a692f0ea31584221d60cffee716 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 14:16:08 +0200 Subject: [PATCH 24/46] wip --- wire_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wire_test.go b/wire_test.go index 533a192..3268b84 100644 --- a/wire_test.go +++ b/wire_test.go @@ -40,7 +40,9 @@ func TestMain(m *testing.M) { func makeRawDocument(pairs ...any) wirebson.RawDocument { d := wirebson.MustDocument(pairs...) - raw, err := d.Encode() + raw := make([]byte, wirebson.Size(d)) + + err := d.Encode(raw) if err != nil { panic(err) } From f5d82afd9bed55f227b28dcd05807044b0a122ca Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 14:33:58 +0200 Subject: [PATCH 25/46] write directly to subslice --- wirebson/encode.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index 4242e96..04d21a5 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -142,15 +142,11 @@ func encodeScalarField(i int, b []byte, name string, v any) (int, error) { } i++ - bb := make([]byte, SizeCString(name)) - EncodeCString(bb, name) + EncodeCString(b[i:], name) + i += SizeCString(name) - i += write(b, bb, i) - - bb = make([]byte, sizeScalar(v)) - encodeScalarValue(bb, v) - - i += write(b, bb, i) + encodeScalarValue(b[i:], v) + i += sizeScalar(v) return i, nil } From 7076440677e6a9fe18467ae560097b80a721192b Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 14:48:23 +0200 Subject: [PATCH 26/46] wip --- wirebson/encode.go | 6 ++++-- wirebson/encode_test.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index 04d21a5..aa84a7e 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -92,7 +92,8 @@ func encodeField(i int, buf []byte, name string, v any) (int, error) { i += len(b) default: - return encodeScalarField(i, buf, name, v) + written, err := encodeScalarField(buf[i:], name, v) + return i + written, err } return i, nil @@ -111,7 +112,8 @@ func write(b []byte, v []byte, offset int) int { // encodeScalarField encodes scalar document field. // // It panics if v is not a scalar value. -func encodeScalarField(i int, b []byte, name string, v any) (int, error) { +func encodeScalarField(b []byte, name string, v any) (int, error) { + var i int switch v := v.(type) { case float64: writeByte(b, byte(tagFloat64), i) diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index fe3e638..3282862 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -9,7 +9,7 @@ import ( func TestEncodeScalarField(t *testing.T) { buf := make([]byte, 13) - encodeScalarField(0, buf, "foo", "bar") + encodeScalarField(buf[0:], "foo", "bar") expected := []byte{0x02, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0} actual := buf From 1155ef2eacf5bf0a4b8085e417d8f09fff912950 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 14:52:47 +0200 Subject: [PATCH 27/46] wip --- wirebson/array.go | 6 ++++-- wirebson/document.go | 6 ++++-- wirebson/encode.go | 3 ++- wirebson/encode_test.go | 9 ++++++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/wirebson/array.go b/wirebson/array.go index c0515cf..654a897 100644 --- a/wirebson/array.go +++ b/wirebson/array.go @@ -130,11 +130,13 @@ func (arr *Array) Encode() (RawArray, error) { binary.LittleEndian.PutUint32(buf, uint32(size)) index += 4 - var err error for i, v := range arr.elements { - if index, err = encodeField(index, buf, strconv.Itoa(i), v); err != nil { + written, err := encodeField(buf[index:], strconv.Itoa(i), v) + if err != nil { return nil, lazyerrors.Error(err) } + + index += written } writeByte(buf, byte(0), index) diff --git a/wirebson/document.go b/wirebson/document.go index 6abd77a..c1a8fb4 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -215,11 +215,13 @@ func (doc *Document) Encode(raw RawDocument) error { binary.LittleEndian.PutUint32(raw, uint32(sizeDocument(doc))) index += 4 - var err error for _, f := range doc.fields { - if index, err = encodeField(index, raw, f.name, f.value); err != nil { + written, err := encodeField(raw[index:], f.name, f.value) + if err != nil { return lazyerrors.Error(err) } + + index += written } writeByte(raw, byte(0), index) diff --git a/wirebson/encode.go b/wirebson/encode.go index aa84a7e..09371b4 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -24,7 +24,8 @@ import ( // encodeField encodes document/array field. // // It panics if v is not a valid type. -func encodeField(i int, buf []byte, name string, v any) (int, error) { +func encodeField(buf []byte, name string, v any) (int, error) { + var i int switch v := v.(type) { case *Document: writeByte(buf, byte(tagDocument), i) diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index 3282862..6dce545 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -22,18 +22,21 @@ func TestEncodeField(t *testing.T) { var i int - var err error - i, err = encodeField(i, buf, "foo", "bar") + written, err := encodeField(buf[i:], "foo", "bar") require.NoError(t, err) + i += written + actual := buf expected := []byte{0x2, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} assert.Equal(t, expected, actual) - i, err = encodeField(i, buf, "foo", int32(1)) + written, err = encodeField(buf[i:], "foo", int32(1)) require.NoError(t, err) + i += written + actual = buf expected = append(expected, []byte{}...) From 076b3243c7a76b7c06cfef95f2cc54d0f6574f9d Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 14:58:37 +0200 Subject: [PATCH 28/46] remove writeByte --- wirebson/array.go | 3 ++- wirebson/document.go | 2 +- wirebson/encode.go | 37 +++++++++++++++++-------------------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/wirebson/array.go b/wirebson/array.go index 654a897..66065e1 100644 --- a/wirebson/array.go +++ b/wirebson/array.go @@ -139,7 +139,8 @@ func (arr *Array) Encode() (RawArray, error) { index += written } - writeByte(buf, byte(0), index) + buf[index] = byte(0) + index++ //return buf.Bytes(), nil diff --git a/wirebson/document.go b/wirebson/document.go index c1a8fb4..194e95b 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -224,7 +224,7 @@ func (doc *Document) Encode(raw RawDocument) error { index += written } - writeByte(raw, byte(0), index) + raw[index] = byte(0) index++ return nil diff --git a/wirebson/encode.go b/wirebson/encode.go index 09371b4..47d9eaa 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -28,7 +28,7 @@ func encodeField(buf []byte, name string, v any) (int, error) { var i int switch v := v.(type) { case *Document: - writeByte(buf, byte(tagDocument), i) + buf[i] = byte(tagDocument) i++ b := make([]byte, SizeCString(name)) @@ -49,7 +49,8 @@ func encodeField(buf []byte, name string, v any) (int, error) { i += len(b) case RawDocument: - writeByte(buf, byte(tagDocument), i) + + buf[i] = byte(tagDocument) i++ b := make([]byte, SizeCString(name)) @@ -62,7 +63,7 @@ func encodeField(buf []byte, name string, v any) (int, error) { i += len(b) case *Array: - writeByte(buf, byte(tagArray), i) + buf[i] = byte(tagArray) i++ b := make([]byte, SizeCString(name)) @@ -80,7 +81,7 @@ func encodeField(buf []byte, name string, v any) (int, error) { i += len(b) case RawArray: - writeByte(buf, byte(tagArray), i) + buf[i] = byte(tagArray) i++ b := make([]byte, SizeCString(name)) @@ -100,10 +101,6 @@ func encodeField(buf []byte, name string, v any) (int, error) { return i, nil } -func writeByte(b []byte, v byte, offset int) { - b[offset] = v -} - // returns number of bytes written func write(b []byte, v []byte, offset int) int { copy(b[offset:], v) @@ -117,29 +114,29 @@ func encodeScalarField(b []byte, name string, v any) (int, error) { var i int switch v := v.(type) { case float64: - writeByte(b, byte(tagFloat64), i) + b[i] = byte(tagFloat64) case string: - writeByte(b, byte(tagString), i) + b[i] = byte(tagString) case Binary: - writeByte(b, byte(tagBinary), i) + b[i] = byte(tagBinary) case ObjectID: - writeByte(b, byte(tagObjectID), i) + b[i] = byte(tagObjectID) case bool: - writeByte(b, byte(tagBool), i) + b[i] = byte(tagBool) case time.Time: - writeByte(b, byte(tagTime), i) + b[i] = byte(tagTime) case NullType: - writeByte(b, byte(tagNull), i) + b[i] = byte(tagNull) case Regex: - writeByte(b, byte(tagRegex), i) + b[i] = byte(tagRegex) case int32: - writeByte(b, byte(tagInt32), i) + b[i] = byte(tagInt32) case Timestamp: - writeByte(b, byte(tagTimestamp), i) + b[i] = byte(tagTimestamp) case int64: - writeByte(b, byte(tagInt64), i) + b[i] = byte(tagInt64) case Decimal128: - writeByte(b, byte(tagDecimal128), i) + b[i] = byte(tagDecimal128) default: panic(fmt.Sprintf("invalid BSON type %T", v)) } From 7d115aff1a8c57a4bf44a7636d044c5f3b4b2c7d Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 15:02:30 +0200 Subject: [PATCH 29/46] refactor write --- wirebson/encode.go | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index 47d9eaa..5a94195 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -34,8 +34,7 @@ func encodeField(buf []byte, name string, v any) (int, error) { b := make([]byte, SizeCString(name)) EncodeCString(b, name) - write(buf, b, i) - i += len(b) + i += write(buf[i:], b) size := sizeDocument(v) b = make([]byte, size) @@ -45,8 +44,7 @@ func encodeField(buf []byte, name string, v any) (int, error) { return 0, lazyerrors.Error(err) } - write(buf, b, i) - i += len(b) + i += write(buf[i:], b) case RawDocument: @@ -56,11 +54,9 @@ func encodeField(buf []byte, name string, v any) (int, error) { b := make([]byte, SizeCString(name)) EncodeCString(b, name) - write(buf, b, i) - i += len(b) + i += write(buf[i:], b) - write(buf, v, i) - i += len(b) + i += write(buf[i:], v) case *Array: buf[i] = byte(tagArray) @@ -69,16 +65,14 @@ func encodeField(buf []byte, name string, v any) (int, error) { b := make([]byte, SizeCString(name)) EncodeCString(b, name) - write(buf, b, i) - i += len(b) + i += write(buf[i:], b) b, err := v.Encode() if err != nil { return 0, lazyerrors.Error(err) } - write(buf, b, i) - i += len(b) + i += write(buf[i:], b) case RawArray: buf[i] = byte(tagArray) @@ -87,11 +81,9 @@ func encodeField(buf []byte, name string, v any) (int, error) { b := make([]byte, SizeCString(name)) EncodeCString(b, name) - write(buf, b, i) - i += len(b) + i += write(buf[i:], b) - write(buf, v, i) - i += len(b) + i += write(buf[i:], v) default: written, err := encodeScalarField(buf[i:], name, v) @@ -102,8 +94,12 @@ func encodeField(buf []byte, name string, v any) (int, error) { } // returns number of bytes written -func write(b []byte, v []byte, offset int) int { - copy(b[offset:], v) +func write(b []byte, v []byte) int { + if len(v) > len(b) { + panic("write impossible") + } + + copy(b, v) return len(v) } From 20e80696b4fe71dabbe4439e9521832b1dab1edd Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 15:10:08 +0200 Subject: [PATCH 30/46] wip --- wirebson/encode.go | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index 5a94195..65d7b4d 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -31,30 +31,23 @@ func encodeField(buf []byte, name string, v any) (int, error) { buf[i] = byte(tagDocument) i++ - b := make([]byte, SizeCString(name)) - EncodeCString(b, name) + EncodeCString(buf[i:], name) + i += SizeCString(name) - i += write(buf[i:], b) - - size := sizeDocument(v) - b = make([]byte, size) - - err := v.Encode(b) + err := v.Encode(buf[i:]) if err != nil { return 0, lazyerrors.Error(err) } - i += write(buf[i:], b) + i += sizeDocument(v) case RawDocument: buf[i] = byte(tagDocument) i++ - b := make([]byte, SizeCString(name)) - EncodeCString(b, name) - - i += write(buf[i:], b) + EncodeCString(buf[i:], name) + i += SizeCString(name) i += write(buf[i:], v) @@ -62,10 +55,8 @@ func encodeField(buf []byte, name string, v any) (int, error) { buf[i] = byte(tagArray) i++ - b := make([]byte, SizeCString(name)) - EncodeCString(b, name) - - i += write(buf[i:], b) + EncodeCString(buf[i:], name) + i += SizeCString(name) b, err := v.Encode() if err != nil { @@ -78,10 +69,8 @@ func encodeField(buf []byte, name string, v any) (int, error) { buf[i] = byte(tagArray) i++ - b := make([]byte, SizeCString(name)) - EncodeCString(b, name) - - i += write(buf[i:], b) + EncodeCString(buf[i:], name) + i += SizeCString(name) i += write(buf[i:], v) From 5e5e73eaba32beb3507421d4f6094b9c27529e71 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 15:14:01 +0200 Subject: [PATCH 31/46] wip --- wirebson/document.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wirebson/document.go b/wirebson/document.go index 194e95b..34647df 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -210,11 +210,9 @@ func (doc *Document) Command() string { func (doc *Document) Encode(raw RawDocument) error { must.NotBeZero(doc) - var index int - - binary.LittleEndian.PutUint32(raw, uint32(sizeDocument(doc))) - index += 4 + binary.LittleEndian.PutUint32(raw[0:4], uint32(sizeDocument(doc))) + index := 4 for _, f := range doc.fields { written, err := encodeField(raw[index:], f.name, f.value) if err != nil { From 5d45057486579ca0f93e8367e945e2347605b513 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 15:23:34 +0200 Subject: [PATCH 32/46] convert array --- wirebson/array.go | 21 +++++++-------------- wirebson/bson.go | 2 +- wirebson/encode.go | 4 ++-- wirebson/raw_array.go | 5 +++-- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/wirebson/array.go b/wirebson/array.go index 66065e1..0509af2 100644 --- a/wirebson/array.go +++ b/wirebson/array.go @@ -119,32 +119,25 @@ func (arr *Array) Replace(index int, value any) error { // TODO https://github.com/FerretDB/wire/issues/21 // This method should accept a slice of bytes, not return it. // That would allow to avoid unnecessary allocations. -func (arr *Array) Encode() (RawArray, error) { +func (arr *Array) Encode(raw RawArray) error { must.NotBeZero(arr) - size := sizeArray(arr) - buf := make([]byte, size) - - var index int - - binary.LittleEndian.PutUint32(buf, uint32(size)) - index += 4 + binary.LittleEndian.PutUint32(raw[0:4], uint32(sizeArray(arr))) + index := 4 for i, v := range arr.elements { - written, err := encodeField(buf[index:], strconv.Itoa(i), v) + written, err := encodeField(raw[index:], strconv.Itoa(i), v) if err != nil { - return nil, lazyerrors.Error(err) + return lazyerrors.Error(err) } index += written } - buf[index] = byte(0) - + raw[index] = byte(0) index++ - //return buf.Bytes(), nil - return buf, nil + return nil } // Decode returns itself to implement [AnyArray]. diff --git a/wirebson/bson.go b/wirebson/bson.go index ce45694..11924b5 100644 --- a/wirebson/bson.go +++ b/wirebson/bson.go @@ -79,7 +79,7 @@ type AnyDocument interface { // Note that the Encode and Decode methods could return the receiver itself, // so care must be taken when results are modified. type AnyArray interface { - Encode() (RawArray, error) + Encode(RawArray) error Decode() (*Array, error) } diff --git a/wirebson/encode.go b/wirebson/encode.go index 65d7b4d..89d9b55 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -58,12 +58,12 @@ func encodeField(buf []byte, name string, v any) (int, error) { EncodeCString(buf[i:], name) i += SizeCString(name) - b, err := v.Encode() + err := v.Encode(buf[i:]) if err != nil { return 0, lazyerrors.Error(err) } - i += write(buf[i:], b) + i += sizeArray(v) case RawArray: buf[i] = byte(tagArray) diff --git a/wirebson/raw_array.go b/wirebson/raw_array.go index b674829..c207061 100644 --- a/wirebson/raw_array.go +++ b/wirebson/raw_array.go @@ -30,9 +30,10 @@ type RawArray []byte // Encode returns itself to implement the [AnyArray] interface. // // Receiver must not be nil. -func (raw RawArray) Encode() (RawArray, error) { +func (raw RawArray) Encode(out RawArray) error { must.BeTrue(raw != nil) - return raw, nil + out = raw + return nil } // Decode decodes a single non-nil BSON array that takes the whole non-nil byte slice. From 73836e382aacdf5d607219e485b3078a3cabe53a Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 15:33:34 +0200 Subject: [PATCH 33/46] test --- wirebson/bson_test.go | 1803 +++++++++++++++++++++-------------------- 1 file changed, 904 insertions(+), 899 deletions(-) diff --git a/wirebson/bson_test.go b/wirebson/bson_test.go index 4de42aa..0927b45 100644 --- a/wirebson/bson_test.go +++ b/wirebson/bson_test.go @@ -14,903 +14,908 @@ package wirebson +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/FerretDB/wire/internal/util/testutil" +) + +// normalTestCase represents a single test case for successful decoding/encoding. +// +//nolint:vet // for readability +type normalTestCase struct { + name string + raw RawDocument + doc *Document + m string +} + +// decodeTestCase represents a single test case for unsuccessful decoding. // -//import ( -// "testing" -// "time" -// -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/require" -// -// "github.com/FerretDB/wire/internal/util/testutil" -//) -// -//// normalTestCase represents a single test case for successful decoding/encoding. -//// -////nolint:vet // for readability -//type normalTestCase struct { -// name string -// raw RawDocument -// doc *Document -// m string -//} -// -//// decodeTestCase represents a single test case for unsuccessful decoding. -//// -////nolint:vet // for readability -//type decodeTestCase struct { -// name string -// raw RawDocument -// -// oldOk bool -// -// findRawErr error -// findRawL int -// decodeErr error -// decodeDeepErr error // defaults to decodeErr -//} -// -//// normalTestCases represents test cases for successful decoding/encoding. -//// -////nolint:lll // for readability -//var normalTestCases = []normalTestCase{ -// { -// name: "handshake1", -// raw: testutil.MustParseDumpFile("testdata", "handshake1.hex"), -// doc: MustDocument( -// "ismaster", true, -// "client", MustDocument( -// "driver", MustDocument( -// "name", "nodejs", -// "version", "4.0.0-beta.6", -// ), -// "os", MustDocument( -// "type", "Darwin", -// "name", "darwin", -// "architecture", "x64", -// "version", "20.6.0", -// ), -// "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", -// "application", MustDocument( -// "name", "mongosh 1.0.1", -// ), -// ), -// "compression", MustArray("none"), -// "loadBalanced", false, -// ), -// m: ` -// { -// "ismaster": true, -// "client": { -// "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, -// "os": { -// "type": "Darwin", -// "name": "darwin", -// "architecture": "x64", -// "version": "20.6.0", -// }, -// "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", -// "application": {"name": "mongosh 1.0.1"}, -// }, -// "compression": ["none"], -// "loadBalanced": false, -// }`, -// }, -// { -// name: "handshake2", -// raw: testutil.MustParseDumpFile("testdata", "handshake2.hex"), -// doc: MustDocument( -// "ismaster", true, -// "client", MustDocument( -// "driver", MustDocument( -// "name", "nodejs", -// "version", "4.0.0-beta.6", -// ), -// "os", MustDocument( -// "type", "Darwin", -// "name", "darwin", -// "architecture", "x64", -// "version", "20.6.0", -// ), -// "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", -// "application", MustDocument( -// "name", "mongosh 1.0.1", -// ), -// ), -// "compression", MustArray("none"), -// "loadBalanced", false, -// ), -// m: ` -// { -// "ismaster": true, -// "client": { -// "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, -// "os": { -// "type": "Darwin", -// "name": "darwin", -// "architecture": "x64", -// "version": "20.6.0", -// }, -// "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", -// "application": {"name": "mongosh 1.0.1"}, -// }, -// "compression": ["none"], -// "loadBalanced": false, -// }`, -// }, -// { -// name: "handshake3", -// raw: testutil.MustParseDumpFile("testdata", "handshake3.hex"), -// doc: MustDocument( -// "buildInfo", int32(1), -// "lsid", MustDocument( -// "id", Binary{ -// Subtype: BinaryUUID, -// B: []byte{ -// 0xa3, 0x19, 0xf2, 0xb4, 0xa1, 0x75, 0x40, 0xc7, -// 0xb8, 0xe7, 0xa3, 0xa3, 0x2e, 0xc2, 0x56, 0xbe, -// }, -// }, -// ), -// "$db", "admin", -// ), -// m: ` -// { -// "buildInfo": 1, -// "lsid": {"id": Binary(uuid:oxnytKF1QMe456OjLsJWvg==)}, -// "$db": "admin", -// }`, -// }, -// { -// name: "handshake4", -// raw: testutil.MustParseDumpFile("testdata", "handshake4.hex"), -// doc: MustDocument( -// "version", "5.0.0", -// "gitVersion", "1184f004a99660de6f5e745573419bda8a28c0e9", -// "modules", MustArray(), -// "allocator", "tcmalloc", -// "javascriptEngine", "mozjs", -// "sysInfo", "deprecated", -// "versionArray", MustArray(int32(5), int32(0), int32(0), int32(0)), -// "openssl", MustDocument( -// "running", "OpenSSL 1.1.1f 31 Mar 2020", -// "compiled", "OpenSSL 1.1.1f 31 Mar 2020", -// ), -// "buildEnvironment", MustDocument( -// "distmod", "ubuntu2004", -// "distarch", "x86_64", -// "cc", "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", -// "ccflags", "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb "+ -// "-Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer "+ -// "-fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 "+ -// "-Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations "+ -// "-Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces "+ -// "-fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", -// "cxx", "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", -// "cxxflags", "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", -// "linkflags", "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong "+ -// "-Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack "+ -// "-Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", -// "target_arch", "x86_64", -// "target_os", "linux", -// "cppdefines", "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE "+ -// "_REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME "+ -// "BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS "+ -// "BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG "+ -// "BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", -// ), -// "bits", int32(64), -// "debug", false, -// "maxBsonObjectSize", int32(16777216), -// "storageEngines", MustArray("devnull", "ephemeralForTest", "wiredTiger"), -// "ok", float64(1), -// ), -// m: ` -// { -// "version": "5.0.0", -// "gitVersion": "1184f004a99660de6f5e745573419bda8a28c0e9", -// "modules": [], -// "allocator": "tcmalloc", -// "javascriptEngine": "mozjs", -// "sysInfo": "deprecated", -// "versionArray": [5, 0, 0, 0], -// "openssl": { -// "running": "OpenSSL 1.1.1f 31 Mar 2020", -// "compiled": "OpenSSL 1.1.1f 31 Mar 2020", -// }, -// "buildEnvironment": { -// "distmod": "ubuntu2004", -// "distarch": "x86_64", -// "cc": "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", -// "ccflags": "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer -fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", -// "cxx": "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", -// "cxxflags": "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", -// "linkflags": "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong -Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", -// "target_arch": "x86_64", -// "target_os": "linux", -// "cppdefines": "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE _REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", -// }, -// "bits": 64, -// "debug": false, -// "maxBsonObjectSize": 16777216, -// "storageEngines": ["devnull", "ephemeralForTest", "wiredTiger"], -// "ok": 1.0, -// }`, -// }, -// { -// name: "all", -// raw: testutil.MustParseDumpFile("testdata", "all.hex"), -// doc: MustDocument( -// "array", MustArray( -// MustArray(""), -// MustArray("foo"), -// ), -// "binary", MustArray( -// Binary{Subtype: BinaryUser, B: []byte{0x42}}, -// Binary{Subtype: BinaryGeneric, B: []byte{}}, -// ), -// "bool", MustArray(true, false), -// "datetime", MustArray( -// time.Date(2021, 7, 27, 9, 35, 42, 123000000, time.UTC).Local(), -// time.Time{}.Local(), -// ), -// "document", MustArray( -// MustDocument("foo", ""), -// MustDocument("", "foo"), -// ), -// "double", MustArray(42.13, 0.0), -// "int32", MustArray(int32(42), int32(0)), -// "int64", MustArray(int64(42), int64(0)), -// "objectID", MustArray(ObjectID{0x42}, ObjectID{}), -// "string", MustArray("foo", ""), -// "timestamp", MustArray(Timestamp(42), Timestamp(0)), -// "decimal128", MustArray(Decimal128{L: 42, H: 13}), -// ), -// m: ` -// { -// "array": [[""], ["foo"]], -// "binary": [Binary(user:Qg==), Binary(generic:)], -// "bool": [true, false], -// "datetime": [2021-07-27T09:35:42.123Z, 0001-01-01T00:00:00Z], -// "document": [{"foo": ""}, {"": "foo"}], -// "double": [42.13, 0.0], -// "int32": [42, 0], -// "int64": [int64(42), int64(0)], -// "objectID": [ObjectID(420000000000000000000000), ObjectID(000000000000000000000000)], -// "string": ["foo", ""], -// "timestamp": [Timestamp(42), Timestamp(0)], -// "decimal128": [Decimal128(42,13)], -// }`, -// }, -// { -// name: "nested", -// raw: testutil.MustParseDumpFile("testdata", "nested.hex"), -// doc: makeNested(false, 150).(*Document), -// m: ` -// { -// "f": [ -// { -// "f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{...}]}]}]}]}]}]}]}]}], -// }, -// ], -// }`, -// }, -// { -// name: "float64Doc", -// raw: RawDocument{ -// 0x10, 0x00, 0x00, 0x00, -// 0x01, 0x66, 0x00, -// 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, -// 0x00, -// }, -// doc: MustDocument( -// "f", float64(3.141592653589793), -// ), -// m: `{"f": 3.141592653589793}`, -// }, -// { -// name: "stringDoc", -// raw: RawDocument{ -// 0x0e, 0x00, 0x00, 0x00, -// 0x02, 0x66, 0x00, -// 0x02, 0x00, 0x00, 0x00, -// 0x76, 0x00, -// 0x00, -// }, -// doc: MustDocument( -// "f", "v", -// ), -// m: `{"f": "v"}`, -// }, -// { -// name: "binaryDoc", -// raw: RawDocument{ -// 0x0e, 0x00, 0x00, 0x00, -// 0x05, 0x66, 0x00, -// 0x01, 0x00, 0x00, 0x00, -// 0x80, -// 0x76, -// 0x00, -// }, -// doc: MustDocument( -// "f", Binary{B: []byte("v"), Subtype: BinaryUser}, -// ), -// m: `{"f": Binary(user:dg==)}`, -// }, -// { -// name: "objectIDDoc", -// raw: RawDocument{ -// 0x14, 0x00, 0x00, 0x00, -// 0x07, 0x66, 0x00, -// 0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, -// 0x00, -// }, -// doc: MustDocument( -// "f", ObjectID{0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}, -// ), -// m: `{"f": ObjectID(6256c5ba182d4454fb210940)}`, -// }, -// { -// name: "boolDoc", -// raw: RawDocument{ -// 0x09, 0x00, 0x00, 0x00, -// 0x08, 0x66, 0x00, -// 0x01, -// 0x00, -// }, -// doc: MustDocument( -// "f", true, -// ), -// m: `{"f": true}`, -// }, -// { -// name: "timeDoc", -// raw: RawDocument{ -// 0x10, 0x00, 0x00, 0x00, -// 0x09, 0x66, 0x00, -// 0x0b, 0xce, 0x82, 0x18, 0x8d, 0x01, 0x00, 0x00, -// 0x00, -// }, -// doc: MustDocument( -// "f", time.Date(2024, 1, 17, 17, 40, 42, 123000000, time.UTC), -// ), -// m: `{"f": 2024-01-17T17:40:42.123Z}`, -// }, -// { -// name: "nullDoc", -// raw: RawDocument{ -// 0x08, 0x00, 0x00, 0x00, -// 0x0a, 0x66, 0x00, -// 0x00, -// }, -// doc: MustDocument( -// "f", Null, -// ), -// m: `{"f": null}`, -// }, -// { -// name: "regexDoc", -// raw: RawDocument{ -// 0x0c, 0x00, 0x00, 0x00, -// 0x0b, 0x66, 0x00, -// 0x70, 0x00, -// 0x6f, 0x00, -// 0x00, -// }, -// doc: MustDocument( -// "f", Regex{Pattern: "p", Options: "o"}, -// ), -// m: `{"f": /p/o}`, -// }, -// { -// name: "int32Doc", -// raw: RawDocument{ -// 0x0c, 0x00, 0x00, 0x00, -// 0x10, 0x66, 0x00, -// 0xa1, 0xb0, 0xb9, 0x12, -// 0x00, -// }, -// doc: MustDocument( -// "f", int32(314159265), -// ), -// m: `{"f": 314159265}`, -// }, -// { -// name: "timestampDoc", -// raw: RawDocument{ -// 0x10, 0x00, 0x00, 0x00, -// 0x11, 0x66, 0x00, -// 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -// 0x00, -// }, -// doc: MustDocument( -// "f", Timestamp(42), -// ), -// m: `{"f": Timestamp(42)}`, -// }, -// { -// name: "int64Doc", -// raw: RawDocument{ -// 0x10, 0x00, 0x00, 0x00, -// 0x12, 0x66, 0x00, -// 0x21, 0x6d, 0x25, 0x0a, 0x43, 0x29, 0x0b, 0x00, -// 0x00, -// }, -// doc: MustDocument( -// "f", int64(3141592653589793), -// ), -// m: `{"f": int64(3141592653589793)}`, -// }, -// { -// name: "decimal128Doc", -// raw: RawDocument{ -// 0x18, 0x00, 0x00, 0x00, -// 0x13, 0x66, 0x00, -// 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -// 0x00, -// }, -// doc: MustDocument( -// "f", Decimal128{L: 42, H: 13}, -// ), -// m: `{"f": Decimal128(42,13)}`, -// }, -// { -// name: "smallDoc", -// raw: RawDocument{ -// 0x0f, 0x00, 0x00, 0x00, // document length -// 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" -// 0x05, 0x00, 0x00, 0x00, 0x00, // subdocument length and end of subdocument -// 0x00, // end of document -// }, -// doc: MustDocument( -// "foo", MustDocument(), -// ), -// m: `{"foo": {}}`, -// }, -// { -// name: "smallArray", -// raw: RawDocument{ -// 0x0f, 0x00, 0x00, 0x00, // document length -// 0x04, 0x66, 0x6f, 0x6f, 0x00, // subarray "foo" -// 0x05, 0x00, 0x00, 0x00, 0x00, // subarray length and end of subarray -// 0x00, // end of document -// }, -// doc: MustDocument( -// "foo", MustArray(), -// ), -// m: `{"foo": []}`, -// }, -// { -// name: "duplicateKeys", -// raw: RawDocument{ -// 0x0b, 0x00, 0x00, 0x00, // document length -// 0x08, 0x00, 0x00, // "": false -// 0x08, 0x00, 0x01, // "": true -// 0x00, // end of document -// }, -// doc: MustDocument( -// "", false, -// "", true, -// ), -// m: `{"": false, "": true}`, -// }, -//} -// -//// decodeTestCases represents test cases for unsuccessful decoding. -//var decodeTestCases = []decodeTestCase{ -// { -// name: "EOF", -// raw: RawDocument{0x00}, -// findRawErr: ErrDecodeShortInput, -// decodeErr: ErrDecodeShortInput, -// }, -// { -// name: "invalidLength", -// raw: RawDocument{ -// 0x00, 0x00, 0x00, 0x00, // invalid document length -// 0x00, // end of document -// }, -// findRawErr: ErrDecodeInvalidInput, -// decodeErr: ErrDecodeInvalidInput, -// }, -// { -// name: "missingByte", -// raw: RawDocument{ -// 0x06, 0x00, 0x00, 0x00, // document length -// 0x00, // end of document -// }, -// findRawErr: ErrDecodeShortInput, -// decodeErr: ErrDecodeShortInput, -// }, -// { -// name: "extraByte", -// raw: RawDocument{ -// 0x05, 0x00, 0x00, 0x00, // document length -// 0x00, // end of document -// 0x00, // extra byte -// }, -// oldOk: true, -// findRawL: 5, -// decodeErr: ErrDecodeInvalidInput, -// }, -// { -// name: "unexpectedTag", -// raw: RawDocument{ -// 0x06, 0x00, 0x00, 0x00, // document length -// 0xdd, // unexpected tag -// 0x00, // end of document -// }, -// findRawL: 6, -// decodeErr: ErrDecodeInvalidInput, -// }, -// { -// name: "invalidTag", -// raw: RawDocument{ -// 0x06, 0x00, 0x00, 0x00, // document length -// 0x00, // invalid tag -// 0x00, // end of document -// }, -// findRawL: 6, -// decodeErr: ErrDecodeInvalidInput, -// }, -// { -// name: "shortDoc", -// raw: RawDocument{ -// 0x0f, 0x00, 0x00, 0x00, // document length -// 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" -// 0x06, 0x00, 0x00, 0x00, // invalid subdocument length -// 0x00, // end of subdocument -// 0x00, // end of document -// }, -// findRawL: 15, -// decodeErr: ErrDecodeShortInput, -// decodeDeepErr: ErrDecodeInvalidInput, -// }, -// { -// name: "invalidDoc", -// raw: RawDocument{ -// 0x0f, 0x00, 0x00, 0x00, // document length -// 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" -// 0x05, 0x00, 0x00, 0x00, // subdocument length -// 0x30, // invalid end of subdocument -// 0x00, // end of document -// }, -// findRawL: 15, -// decodeErr: ErrDecodeInvalidInput, -// }, -// { -// name: "invalidDocTag", -// raw: RawDocument{ -// 0x10, 0x00, 0x00, 0x00, // document length -// 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" -// 0x06, 0x00, 0x00, 0x00, // subdocument length -// 0x00, // invalid tag -// 0x00, // end of subdocument -// 0x00, // end of document -// }, -// findRawL: 16, -// decodeDeepErr: ErrDecodeInvalidInput, -// }, -//} -// -//func TestNormal(t *testing.T) { -// for _, tc := range normalTestCases { -// t.Run(tc.name, func(t *testing.T) { -// t.Run("FindRaw", func(t *testing.T) { -// ls := tc.raw.LogValue().Resolve().String() -// assert.NotContains(t, ls, "panicked") -// assert.NotContains(t, ls, "called too many times") -// -// assert.NotEmpty(t, LogMessage(tc.raw)) -// assert.NotEmpty(t, LogMessageBlock(tc.raw)) -// assert.NotEmpty(t, LogMessageFlow(tc.raw)) -// -// l, err := FindRaw(tc.raw) -// require.NoError(t, err) -// require.Len(t, tc.raw, l) -// }) -// -// t.Run("DecodeEncode", func(t *testing.T) { -// doc, err := tc.raw.Decode() -// require.NoError(t, err) -// -// ls := doc.LogValue().Resolve().String() -// assert.NotContains(t, ls, "panicked") -// assert.NotContains(t, ls, "called too many times") -// -// assert.NotEmpty(t, LogMessage(doc)) -// assert.NotEmpty(t, LogMessageBlock(doc)) -// assert.NotEmpty(t, LogMessageFlow(doc)) -// -// raw, err := doc.Encode() -// require.NoError(t, err) -// assert.Equal(t, tc.raw, raw) -// }) -// -// t.Run("DecodeDeepEncode", func(t *testing.T) { -// doc, err := tc.raw.DecodeDeep() -// require.NoError(t, err) -// -// ls := doc.LogValue().Resolve().String() -// assert.NotContains(t, ls, "panicked") -// assert.NotContains(t, ls, "called too many times") -// -// assert.Equal(t, testutil.Unindent(tc.m), LogMessage(doc)) -// assert.NotEmpty(t, LogMessageBlock(doc)) -// assert.NotEmpty(t, LogMessageFlow(doc)) -// -// raw, err := doc.Encode() -// require.NoError(t, err) -// assert.Equal(t, tc.raw, raw) -// }) -// }) -// } -//} -// -//func TestDecode(t *testing.T) { -// for _, tc := range decodeTestCases { -// if tc.decodeDeepErr == nil { -// tc.decodeDeepErr = tc.decodeErr -// } -// -// require.NotNil(t, tc.decodeDeepErr, "invalid test case %q", tc.name) -// -// t.Run(tc.name, func(t *testing.T) { -// t.Run("FindRaw", func(t *testing.T) { -// ls := tc.raw.LogValue().Resolve().String() -// assert.NotContains(t, ls, "panicked") -// assert.NotContains(t, ls, "called too many times") -// -// assert.NotEmpty(t, LogMessage(tc.raw)) -// assert.NotEmpty(t, LogMessageBlock(tc.raw)) -// assert.NotEmpty(t, LogMessageFlow(tc.raw)) -// -// l, err := FindRaw(tc.raw) -// -// if tc.findRawErr != nil { -// require.ErrorIs(t, err, tc.findRawErr) -// return -// } -// -// require.NoError(t, err) -// require.Equal(t, tc.findRawL, l) -// }) -// -// t.Run("Decode", func(t *testing.T) { -// _, err := tc.raw.Decode() -// -// if tc.decodeErr != nil { -// require.ErrorIs(t, err, tc.decodeErr) -// return -// } -// -// require.NoError(t, err) -// }) -// -// t.Run("DecodeDeep", func(t *testing.T) { -// _, err := tc.raw.DecodeDeep() -// require.ErrorIs(t, err, tc.decodeDeepErr) -// }) -// }) -// } -//} -// -//func BenchmarkDocument(b *testing.B) { -// for _, tc := range normalTestCases { -// b.Run(tc.name, func(b *testing.B) { -// var doc *Document -// var raw []byte -// var m string -// var err error -// -// b.Run("Decode", func(b *testing.B) { -// b.ReportAllocs() -// -// for range b.N { -// doc, err = tc.raw.Decode() -// } -// -// b.StopTimer() -// -// require.NoError(b, err) -// require.NotNil(b, doc) -// }) -// -// b.Run("Encode", func(b *testing.B) { -// doc, err = tc.raw.Decode() -// require.NoError(b, err) -// -// b.ReportAllocs() -// b.ResetTimer() -// -// for range b.N { -// raw, err = doc.Encode() -// } -// -// b.StopTimer() -// -// require.NoError(b, err) -// assert.NotNil(b, raw) -// }) -// -// b.Run("LogValue", func(b *testing.B) { -// doc, err = tc.raw.Decode() -// require.NoError(b, err) -// -// b.ReportAllocs() -// b.ResetTimer() -// -// for range b.N { -// m = doc.LogValue().Resolve().String() -// } -// -// b.StopTimer() -// -// assert.NotEmpty(b, m) -// assert.NotContains(b, m, "panicked") -// assert.NotContains(b, m, "called too many times") -// }) -// -// b.Run("LogMessage", func(b *testing.B) { -// doc, err = tc.raw.Decode() -// require.NoError(b, err) -// -// b.ReportAllocs() -// b.ResetTimer() -// -// for range b.N { -// m = LogMessage(doc) -// } -// -// b.StopTimer() -// -// assert.NotEmpty(b, m) -// }) -// -// b.Run("DecodeDeep", func(b *testing.B) { -// b.ReportAllocs() -// -// for range b.N { -// doc, err = tc.raw.DecodeDeep() -// } -// -// b.StopTimer() -// -// require.NoError(b, err) -// require.NotNil(b, doc) -// }) -// -// b.Run("EncodeDeep", func(b *testing.B) { -// doc, err = tc.raw.DecodeDeep() -// require.NoError(b, err) -// -// b.ReportAllocs() -// b.ResetTimer() -// -// for range b.N { -// raw, err = doc.Encode() -// } -// -// b.StopTimer() -// -// require.NoError(b, err) -// assert.NotNil(b, raw) -// }) -// -// b.Run("LogValueDeep", func(b *testing.B) { -// doc, err = tc.raw.DecodeDeep() -// require.NoError(b, err) -// -// b.ReportAllocs() -// b.ResetTimer() -// -// for range b.N { -// m = doc.LogValue().Resolve().String() -// } -// -// b.StopTimer() -// -// assert.NotEmpty(b, m) -// assert.NotContains(b, m, "panicked") -// assert.NotContains(b, m, "called too many times") -// }) -// -// b.Run("LogMessageDeep", func(b *testing.B) { -// doc, err = tc.raw.DecodeDeep() -// require.NoError(b, err) -// -// b.ReportAllocs() -// b.ResetTimer() -// -// for range b.N { -// m = LogMessage(doc) -// } -// -// b.StopTimer() -// -// assert.NotEmpty(b, m) -// }) -// }) -// } -//} -// -//// testRawDocument tests a single RawDocument (that might or might not be valid). -//// It is adapted from tests above. -//func testRawDocument(t *testing.T, rawDoc RawDocument) { -// t.Helper() -// -// t.Run("FindRaw", func(t *testing.T) { -// ls := rawDoc.LogValue().Resolve().String() -// assert.NotContains(t, ls, "panicked") -// assert.NotContains(t, ls, "called too many times") -// -// assert.NotEmpty(t, LogMessage(rawDoc)) -// assert.NotEmpty(t, LogMessageBlock(rawDoc)) -// assert.NotEmpty(t, LogMessageFlow(rawDoc)) -// -// _, _ = FindRaw(rawDoc) -// }) -// -// t.Run("DecodeEncode", func(t *testing.T) { -// doc, err := rawDoc.Decode() -// if err != nil { -// _, err = rawDoc.DecodeDeep() -// assert.Error(t, err) // it might be different -// -// return -// } -// -// ls := doc.LogValue().Resolve().String() -// assert.NotContains(t, ls, "panicked") -// assert.NotContains(t, ls, "called too many times") -// -// assert.NotEmpty(t, LogMessage(doc)) -// assert.NotEmpty(t, LogMessageBlock(doc)) -// assert.NotEmpty(t, LogMessageFlow(doc)) -// -// raw, err := doc.Encode() -// if err == nil { -// assert.Equal(t, rawDoc, raw) -// } -// }) -// -// t.Run("DecodeDeepEncode", func(t *testing.T) { -// doc, err := rawDoc.DecodeDeep() -// if err != nil { -// return -// } -// -// ls := doc.LogValue().Resolve().String() -// assert.NotContains(t, ls, "panicked") -// assert.NotContains(t, ls, "called too many times") -// -// assert.NotEmpty(t, LogMessage(doc)) -// assert.NotEmpty(t, LogMessageBlock(doc)) -// assert.NotEmpty(t, LogMessageFlow(doc)) -// -// raw, err := doc.Encode() -// require.NoError(t, err) -// assert.Equal(t, rawDoc, raw) -// }) -//} -// -//func FuzzDocument(f *testing.F) { -// for _, tc := range normalTestCases { -// f.Add([]byte(tc.raw)) -// } -// -// for _, tc := range decodeTestCases { -// f.Add([]byte(tc.raw)) -// } -// -// f.Fuzz(func(t *testing.T, b []byte) { -// t.Parallel() -// -// testRawDocument(t, RawDocument(b)) -// -// l, err := FindRaw(b) -// if err == nil { -// testRawDocument(t, RawDocument(b[:l])) -// } -// }) -//} +//nolint:vet // for readability +type decodeTestCase struct { + name string + raw RawDocument + + oldOk bool + + findRawErr error + findRawL int + decodeErr error + decodeDeepErr error // defaults to decodeErr +} + +// normalTestCases represents test cases for successful decoding/encoding. +// +//nolint:lll // for readability +var normalTestCases = []normalTestCase{ + { + name: "handshake1", + raw: testutil.MustParseDumpFile("testdata", "handshake1.hex"), + doc: MustDocument( + "ismaster", true, + "client", MustDocument( + "driver", MustDocument( + "name", "nodejs", + "version", "4.0.0-beta.6", + ), + "os", MustDocument( + "type", "Darwin", + "name", "darwin", + "architecture", "x64", + "version", "20.6.0", + ), + "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", + "application", MustDocument( + "name", "mongosh 1.0.1", + ), + ), + "compression", MustArray("none"), + "loadBalanced", false, + ), + m: ` + { + "ismaster": true, + "client": { + "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, + "os": { + "type": "Darwin", + "name": "darwin", + "architecture": "x64", + "version": "20.6.0", + }, + "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", + "application": {"name": "mongosh 1.0.1"}, + }, + "compression": ["none"], + "loadBalanced": false, + }`, + }, + { + name: "handshake2", + raw: testutil.MustParseDumpFile("testdata", "handshake2.hex"), + doc: MustDocument( + "ismaster", true, + "client", MustDocument( + "driver", MustDocument( + "name", "nodejs", + "version", "4.0.0-beta.6", + ), + "os", MustDocument( + "type", "Darwin", + "name", "darwin", + "architecture", "x64", + "version", "20.6.0", + ), + "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", + "application", MustDocument( + "name", "mongosh 1.0.1", + ), + ), + "compression", MustArray("none"), + "loadBalanced", false, + ), + m: ` + { + "ismaster": true, + "client": { + "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, + "os": { + "type": "Darwin", + "name": "darwin", + "architecture": "x64", + "version": "20.6.0", + }, + "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", + "application": {"name": "mongosh 1.0.1"}, + }, + "compression": ["none"], + "loadBalanced": false, + }`, + }, + { + name: "handshake3", + raw: testutil.MustParseDumpFile("testdata", "handshake3.hex"), + doc: MustDocument( + "buildInfo", int32(1), + "lsid", MustDocument( + "id", Binary{ + Subtype: BinaryUUID, + B: []byte{ + 0xa3, 0x19, 0xf2, 0xb4, 0xa1, 0x75, 0x40, 0xc7, + 0xb8, 0xe7, 0xa3, 0xa3, 0x2e, 0xc2, 0x56, 0xbe, + }, + }, + ), + "$db", "admin", + ), + m: ` + { + "buildInfo": 1, + "lsid": {"id": Binary(uuid:oxnytKF1QMe456OjLsJWvg==)}, + "$db": "admin", + }`, + }, + { + name: "handshake4", + raw: testutil.MustParseDumpFile("testdata", "handshake4.hex"), + doc: MustDocument( + "version", "5.0.0", + "gitVersion", "1184f004a99660de6f5e745573419bda8a28c0e9", + "modules", MustArray(), + "allocator", "tcmalloc", + "javascriptEngine", "mozjs", + "sysInfo", "deprecated", + "versionArray", MustArray(int32(5), int32(0), int32(0), int32(0)), + "openssl", MustDocument( + "running", "OpenSSL 1.1.1f 31 Mar 2020", + "compiled", "OpenSSL 1.1.1f 31 Mar 2020", + ), + "buildEnvironment", MustDocument( + "distmod", "ubuntu2004", + "distarch", "x86_64", + "cc", "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", + "ccflags", "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb "+ + "-Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer "+ + "-fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 "+ + "-Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations "+ + "-Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces "+ + "-fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", + "cxx", "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", + "cxxflags", "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", + "linkflags", "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong "+ + "-Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack "+ + "-Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", + "target_arch", "x86_64", + "target_os", "linux", + "cppdefines", "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE "+ + "_REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME "+ + "BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS "+ + "BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG "+ + "BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", + ), + "bits", int32(64), + "debug", false, + "maxBsonObjectSize", int32(16777216), + "storageEngines", MustArray("devnull", "ephemeralForTest", "wiredTiger"), + "ok", float64(1), + ), + m: ` + { + "version": "5.0.0", + "gitVersion": "1184f004a99660de6f5e745573419bda8a28c0e9", + "modules": [], + "allocator": "tcmalloc", + "javascriptEngine": "mozjs", + "sysInfo": "deprecated", + "versionArray": [5, 0, 0, 0], + "openssl": { + "running": "OpenSSL 1.1.1f 31 Mar 2020", + "compiled": "OpenSSL 1.1.1f 31 Mar 2020", + }, + "buildEnvironment": { + "distmod": "ubuntu2004", + "distarch": "x86_64", + "cc": "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", + "ccflags": "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer -fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", + "cxx": "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", + "cxxflags": "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", + "linkflags": "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong -Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", + "target_arch": "x86_64", + "target_os": "linux", + "cppdefines": "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE _REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", + }, + "bits": 64, + "debug": false, + "maxBsonObjectSize": 16777216, + "storageEngines": ["devnull", "ephemeralForTest", "wiredTiger"], + "ok": 1.0, + }`, + }, + { + name: "all", + raw: testutil.MustParseDumpFile("testdata", "all.hex"), + doc: MustDocument( + "array", MustArray( + MustArray(""), + MustArray("foo"), + ), + "binary", MustArray( + Binary{Subtype: BinaryUser, B: []byte{0x42}}, + Binary{Subtype: BinaryGeneric, B: []byte{}}, + ), + "bool", MustArray(true, false), + "datetime", MustArray( + time.Date(2021, 7, 27, 9, 35, 42, 123000000, time.UTC).Local(), + time.Time{}.Local(), + ), + "document", MustArray( + MustDocument("foo", ""), + MustDocument("", "foo"), + ), + "double", MustArray(42.13, 0.0), + "int32", MustArray(int32(42), int32(0)), + "int64", MustArray(int64(42), int64(0)), + "objectID", MustArray(ObjectID{0x42}, ObjectID{}), + "string", MustArray("foo", ""), + "timestamp", MustArray(Timestamp(42), Timestamp(0)), + "decimal128", MustArray(Decimal128{L: 42, H: 13}), + ), + m: ` + { + "array": [[""], ["foo"]], + "binary": [Binary(user:Qg==), Binary(generic:)], + "bool": [true, false], + "datetime": [2021-07-27T09:35:42.123Z, 0001-01-01T00:00:00Z], + "document": [{"foo": ""}, {"": "foo"}], + "double": [42.13, 0.0], + "int32": [42, 0], + "int64": [int64(42), int64(0)], + "objectID": [ObjectID(420000000000000000000000), ObjectID(000000000000000000000000)], + "string": ["foo", ""], + "timestamp": [Timestamp(42), Timestamp(0)], + "decimal128": [Decimal128(42,13)], + }`, + }, + { + name: "nested", + raw: testutil.MustParseDumpFile("testdata", "nested.hex"), + doc: makeNested(false, 150).(*Document), + m: ` + { + "f": [ + { + "f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{...}]}]}]}]}]}]}]}]}], + }, + ], + }`, + }, + { + name: "float64Doc", + raw: RawDocument{ + 0x10, 0x00, 0x00, 0x00, + 0x01, 0x66, 0x00, + 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, + 0x00, + }, + doc: MustDocument( + "f", float64(3.141592653589793), + ), + m: `{"f": 3.141592653589793}`, + }, + { + name: "stringDoc", + raw: RawDocument{ + 0x0e, 0x00, 0x00, 0x00, + 0x02, 0x66, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x76, 0x00, + 0x00, + }, + doc: MustDocument( + "f", "v", + ), + m: `{"f": "v"}`, + }, + { + name: "binaryDoc", + raw: RawDocument{ + 0x0e, 0x00, 0x00, 0x00, + 0x05, 0x66, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x80, + 0x76, + 0x00, + }, + doc: MustDocument( + "f", Binary{B: []byte("v"), Subtype: BinaryUser}, + ), + m: `{"f": Binary(user:dg==)}`, + }, + { + name: "objectIDDoc", + raw: RawDocument{ + 0x14, 0x00, 0x00, 0x00, + 0x07, 0x66, 0x00, + 0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, + 0x00, + }, + doc: MustDocument( + "f", ObjectID{0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}, + ), + m: `{"f": ObjectID(6256c5ba182d4454fb210940)}`, + }, + { + name: "boolDoc", + raw: RawDocument{ + 0x09, 0x00, 0x00, 0x00, + 0x08, 0x66, 0x00, + 0x01, + 0x00, + }, + doc: MustDocument( + "f", true, + ), + m: `{"f": true}`, + }, + { + name: "timeDoc", + raw: RawDocument{ + 0x10, 0x00, 0x00, 0x00, + 0x09, 0x66, 0x00, + 0x0b, 0xce, 0x82, 0x18, 0x8d, 0x01, 0x00, 0x00, + 0x00, + }, + doc: MustDocument( + "f", time.Date(2024, 1, 17, 17, 40, 42, 123000000, time.UTC), + ), + m: `{"f": 2024-01-17T17:40:42.123Z}`, + }, + { + name: "nullDoc", + raw: RawDocument{ + 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x66, 0x00, + 0x00, + }, + doc: MustDocument( + "f", Null, + ), + m: `{"f": null}`, + }, + { + name: "regexDoc", + raw: RawDocument{ + 0x0c, 0x00, 0x00, 0x00, + 0x0b, 0x66, 0x00, + 0x70, 0x00, + 0x6f, 0x00, + 0x00, + }, + doc: MustDocument( + "f", Regex{Pattern: "p", Options: "o"}, + ), + m: `{"f": /p/o}`, + }, + { + name: "int32Doc", + raw: RawDocument{ + 0x0c, 0x00, 0x00, 0x00, + 0x10, 0x66, 0x00, + 0xa1, 0xb0, 0xb9, 0x12, + 0x00, + }, + doc: MustDocument( + "f", int32(314159265), + ), + m: `{"f": 314159265}`, + }, + { + name: "timestampDoc", + raw: RawDocument{ + 0x10, 0x00, 0x00, 0x00, + 0x11, 0x66, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + }, + doc: MustDocument( + "f", Timestamp(42), + ), + m: `{"f": Timestamp(42)}`, + }, + { + name: "int64Doc", + raw: RawDocument{ + 0x10, 0x00, 0x00, 0x00, + 0x12, 0x66, 0x00, + 0x21, 0x6d, 0x25, 0x0a, 0x43, 0x29, 0x0b, 0x00, + 0x00, + }, + doc: MustDocument( + "f", int64(3141592653589793), + ), + m: `{"f": int64(3141592653589793)}`, + }, + { + name: "decimal128Doc", + raw: RawDocument{ + 0x18, 0x00, 0x00, 0x00, + 0x13, 0x66, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + }, + doc: MustDocument( + "f", Decimal128{L: 42, H: 13}, + ), + m: `{"f": Decimal128(42,13)}`, + }, + { + name: "smallDoc", + raw: RawDocument{ + 0x0f, 0x00, 0x00, 0x00, // document length + 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" + 0x05, 0x00, 0x00, 0x00, 0x00, // subdocument length and end of subdocument + 0x00, // end of document + }, + doc: MustDocument( + "foo", MustDocument(), + ), + m: `{"foo": {}}`, + }, + { + name: "smallArray", + raw: RawDocument{ + 0x0f, 0x00, 0x00, 0x00, // document length + 0x04, 0x66, 0x6f, 0x6f, 0x00, // subarray "foo" + 0x05, 0x00, 0x00, 0x00, 0x00, // subarray length and end of subarray + 0x00, // end of document + }, + doc: MustDocument( + "foo", MustArray(), + ), + m: `{"foo": []}`, + }, + { + name: "duplicateKeys", + raw: RawDocument{ + 0x0b, 0x00, 0x00, 0x00, // document length + 0x08, 0x00, 0x00, // "": false + 0x08, 0x00, 0x01, // "": true + 0x00, // end of document + }, + doc: MustDocument( + "", false, + "", true, + ), + m: `{"": false, "": true}`, + }, +} + +// decodeTestCases represents test cases for unsuccessful decoding. +var decodeTestCases = []decodeTestCase{ + { + name: "EOF", + raw: RawDocument{0x00}, + findRawErr: ErrDecodeShortInput, + decodeErr: ErrDecodeShortInput, + }, + { + name: "invalidLength", + raw: RawDocument{ + 0x00, 0x00, 0x00, 0x00, // invalid document length + 0x00, // end of document + }, + findRawErr: ErrDecodeInvalidInput, + decodeErr: ErrDecodeInvalidInput, + }, + { + name: "missingByte", + raw: RawDocument{ + 0x06, 0x00, 0x00, 0x00, // document length + 0x00, // end of document + }, + findRawErr: ErrDecodeShortInput, + decodeErr: ErrDecodeShortInput, + }, + { + name: "extraByte", + raw: RawDocument{ + 0x05, 0x00, 0x00, 0x00, // document length + 0x00, // end of document + 0x00, // extra byte + }, + oldOk: true, + findRawL: 5, + decodeErr: ErrDecodeInvalidInput, + }, + { + name: "unexpectedTag", + raw: RawDocument{ + 0x06, 0x00, 0x00, 0x00, // document length + 0xdd, // unexpected tag + 0x00, // end of document + }, + findRawL: 6, + decodeErr: ErrDecodeInvalidInput, + }, + { + name: "invalidTag", + raw: RawDocument{ + 0x06, 0x00, 0x00, 0x00, // document length + 0x00, // invalid tag + 0x00, // end of document + }, + findRawL: 6, + decodeErr: ErrDecodeInvalidInput, + }, + { + name: "shortDoc", + raw: RawDocument{ + 0x0f, 0x00, 0x00, 0x00, // document length + 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" + 0x06, 0x00, 0x00, 0x00, // invalid subdocument length + 0x00, // end of subdocument + 0x00, // end of document + }, + findRawL: 15, + decodeErr: ErrDecodeShortInput, + decodeDeepErr: ErrDecodeInvalidInput, + }, + { + name: "invalidDoc", + raw: RawDocument{ + 0x0f, 0x00, 0x00, 0x00, // document length + 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" + 0x05, 0x00, 0x00, 0x00, // subdocument length + 0x30, // invalid end of subdocument + 0x00, // end of document + }, + findRawL: 15, + decodeErr: ErrDecodeInvalidInput, + }, + { + name: "invalidDocTag", + raw: RawDocument{ + 0x10, 0x00, 0x00, 0x00, // document length + 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" + 0x06, 0x00, 0x00, 0x00, // subdocument length + 0x00, // invalid tag + 0x00, // end of subdocument + 0x00, // end of document + }, + findRawL: 16, + decodeDeepErr: ErrDecodeInvalidInput, + }, +} + +func TestNormal(t *testing.T) { + for _, tc := range normalTestCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("FindRaw", func(t *testing.T) { + ls := tc.raw.LogValue().Resolve().String() + assert.NotContains(t, ls, "panicked") + assert.NotContains(t, ls, "called too many times") + + assert.NotEmpty(t, LogMessage(tc.raw)) + assert.NotEmpty(t, LogMessageBlock(tc.raw)) + assert.NotEmpty(t, LogMessageFlow(tc.raw)) + + l, err := FindRaw(tc.raw) + require.NoError(t, err) + require.Len(t, tc.raw, l) + }) + + t.Run("DecodeEncode", func(t *testing.T) { + doc, err := tc.raw.Decode() + require.NoError(t, err) + + ls := doc.LogValue().Resolve().String() + assert.NotContains(t, ls, "panicked") + assert.NotContains(t, ls, "called too many times") + + assert.NotEmpty(t, LogMessage(doc)) + assert.NotEmpty(t, LogMessageBlock(doc)) + assert.NotEmpty(t, LogMessageFlow(doc)) + + raw := make(RawDocument, Size(doc)) + err = doc.Encode(raw) + require.NoError(t, err) + assert.Equal(t, tc.raw, raw) + }) + + t.Run("DecodeDeepEncode", func(t *testing.T) { + doc, err := tc.raw.DecodeDeep() + require.NoError(t, err) + + ls := doc.LogValue().Resolve().String() + assert.NotContains(t, ls, "panicked") + assert.NotContains(t, ls, "called too many times") + + assert.Equal(t, testutil.Unindent(tc.m), LogMessage(doc)) + assert.NotEmpty(t, LogMessageBlock(doc)) + assert.NotEmpty(t, LogMessageFlow(doc)) + + raw := make(RawDocument, Size(doc)) + err = doc.Encode(raw) + require.NoError(t, err) + assert.Equal(t, tc.raw, raw) + }) + }) + } +} + +func TestDecode(t *testing.T) { + for _, tc := range decodeTestCases { + if tc.decodeDeepErr == nil { + tc.decodeDeepErr = tc.decodeErr + } + + require.NotNil(t, tc.decodeDeepErr, "invalid test case %q", tc.name) + + t.Run(tc.name, func(t *testing.T) { + t.Run("FindRaw", func(t *testing.T) { + ls := tc.raw.LogValue().Resolve().String() + assert.NotContains(t, ls, "panicked") + assert.NotContains(t, ls, "called too many times") + + assert.NotEmpty(t, LogMessage(tc.raw)) + assert.NotEmpty(t, LogMessageBlock(tc.raw)) + assert.NotEmpty(t, LogMessageFlow(tc.raw)) + + l, err := FindRaw(tc.raw) + + if tc.findRawErr != nil { + require.ErrorIs(t, err, tc.findRawErr) + return + } + + require.NoError(t, err) + require.Equal(t, tc.findRawL, l) + }) + + t.Run("Decode", func(t *testing.T) { + _, err := tc.raw.Decode() + + if tc.decodeErr != nil { + require.ErrorIs(t, err, tc.decodeErr) + return + } + + require.NoError(t, err) + }) + + t.Run("DecodeDeep", func(t *testing.T) { + _, err := tc.raw.DecodeDeep() + require.ErrorIs(t, err, tc.decodeDeepErr) + }) + }) + } +} + +func BenchmarkDocument(b *testing.B) { + for _, tc := range normalTestCases { + b.Run(tc.name, func(b *testing.B) { + var doc *Document + var raw []byte + var m string + var err error + + b.Run("Decode", func(b *testing.B) { + b.ReportAllocs() + + for range b.N { + doc, err = tc.raw.Decode() + } + + b.StopTimer() + + require.NoError(b, err) + require.NotNil(b, doc) + }) + + b.Run("Encode", func(b *testing.B) { + doc, err = tc.raw.Decode() + require.NoError(b, err) + + b.ReportAllocs() + b.ResetTimer() + + raw = make(RawDocument, Size(doc)) + for range b.N { + err = doc.Encode(raw) + } + + b.StopTimer() + + require.NoError(b, err) + assert.NotNil(b, raw) + }) + + b.Run("LogValue", func(b *testing.B) { + doc, err = tc.raw.Decode() + require.NoError(b, err) + + b.ReportAllocs() + b.ResetTimer() + + for range b.N { + m = doc.LogValue().Resolve().String() + } + + b.StopTimer() + + assert.NotEmpty(b, m) + assert.NotContains(b, m, "panicked") + assert.NotContains(b, m, "called too many times") + }) + + b.Run("LogMessage", func(b *testing.B) { + doc, err = tc.raw.Decode() + require.NoError(b, err) + + b.ReportAllocs() + b.ResetTimer() + + for range b.N { + m = LogMessage(doc) + } + + b.StopTimer() + + assert.NotEmpty(b, m) + }) + + b.Run("DecodeDeep", func(b *testing.B) { + b.ReportAllocs() + + for range b.N { + doc, err = tc.raw.DecodeDeep() + } + + b.StopTimer() + + require.NoError(b, err) + require.NotNil(b, doc) + }) + + b.Run("EncodeDeep", func(b *testing.B) { + doc, err = tc.raw.DecodeDeep() + require.NoError(b, err) + + b.ReportAllocs() + b.ResetTimer() + + raw = make(RawDocument, Size(doc)) + for range b.N { + err = doc.Encode(raw) + } + + b.StopTimer() + + require.NoError(b, err) + assert.NotNil(b, raw) + }) + + b.Run("LogValueDeep", func(b *testing.B) { + doc, err = tc.raw.DecodeDeep() + require.NoError(b, err) + + b.ReportAllocs() + b.ResetTimer() + + for range b.N { + m = doc.LogValue().Resolve().String() + } + + b.StopTimer() + + assert.NotEmpty(b, m) + assert.NotContains(b, m, "panicked") + assert.NotContains(b, m, "called too many times") + }) + + b.Run("LogMessageDeep", func(b *testing.B) { + doc, err = tc.raw.DecodeDeep() + require.NoError(b, err) + + b.ReportAllocs() + b.ResetTimer() + + for range b.N { + m = LogMessage(doc) + } + + b.StopTimer() + + assert.NotEmpty(b, m) + }) + }) + } +} + +// testRawDocument tests a single RawDocument (that might or might not be valid). +// It is adapted from tests above. +func testRawDocument(t *testing.T, rawDoc RawDocument) { + t.Helper() + + t.Run("FindRaw", func(t *testing.T) { + ls := rawDoc.LogValue().Resolve().String() + assert.NotContains(t, ls, "panicked") + assert.NotContains(t, ls, "called too many times") + + assert.NotEmpty(t, LogMessage(rawDoc)) + assert.NotEmpty(t, LogMessageBlock(rawDoc)) + assert.NotEmpty(t, LogMessageFlow(rawDoc)) + + _, _ = FindRaw(rawDoc) + }) + + t.Run("DecodeEncode", func(t *testing.T) { + doc, err := rawDoc.Decode() + if err != nil { + _, err = rawDoc.DecodeDeep() + assert.Error(t, err) // it might be different + + return + } + + ls := doc.LogValue().Resolve().String() + assert.NotContains(t, ls, "panicked") + assert.NotContains(t, ls, "called too many times") + + assert.NotEmpty(t, LogMessage(doc)) + assert.NotEmpty(t, LogMessageBlock(doc)) + assert.NotEmpty(t, LogMessageFlow(doc)) + + raw := make(RawDocument, Size(doc)) + err = doc.Encode(raw) + if err == nil { + assert.Equal(t, rawDoc, raw) + } + }) + + t.Run("DecodeDeepEncode", func(t *testing.T) { + doc, err := rawDoc.DecodeDeep() + if err != nil { + return + } + + ls := doc.LogValue().Resolve().String() + assert.NotContains(t, ls, "panicked") + assert.NotContains(t, ls, "called too many times") + + assert.NotEmpty(t, LogMessage(doc)) + assert.NotEmpty(t, LogMessageBlock(doc)) + assert.NotEmpty(t, LogMessageFlow(doc)) + + raw := make(RawDocument, Size(doc)) + err = doc.Encode(raw) + require.NoError(t, err) + assert.Equal(t, rawDoc, raw) + }) +} + +func FuzzDocument(f *testing.F) { + for _, tc := range normalTestCases { + f.Add([]byte(tc.raw)) + } + + for _, tc := range decodeTestCases { + f.Add([]byte(tc.raw)) + } + + f.Fuzz(func(t *testing.T, b []byte) { + t.Parallel() + + testRawDocument(t, RawDocument(b)) + + l, err := FindRaw(b) + if err == nil { + testRawDocument(t, RawDocument(b[:l])) + } + }) +} From 5ec1fc4165226c1f73bee9c5a959d9687b2cc930 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 15:47:10 +0200 Subject: [PATCH 34/46] bench --- wirebson/bson_test.go | 797 +++++++++++++++++++++--------------------- 1 file changed, 398 insertions(+), 399 deletions(-) diff --git a/wirebson/bson_test.go b/wirebson/bson_test.go index 0927b45..c920399 100644 --- a/wirebson/bson_test.go +++ b/wirebson/bson_test.go @@ -16,7 +16,6 @@ package wirebson import ( "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -95,404 +94,404 @@ var normalTestCases = []normalTestCase{ "loadBalanced": false, }`, }, - { - name: "handshake2", - raw: testutil.MustParseDumpFile("testdata", "handshake2.hex"), - doc: MustDocument( - "ismaster", true, - "client", MustDocument( - "driver", MustDocument( - "name", "nodejs", - "version", "4.0.0-beta.6", - ), - "os", MustDocument( - "type", "Darwin", - "name", "darwin", - "architecture", "x64", - "version", "20.6.0", - ), - "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", - "application", MustDocument( - "name", "mongosh 1.0.1", - ), - ), - "compression", MustArray("none"), - "loadBalanced", false, - ), - m: ` - { - "ismaster": true, - "client": { - "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, - "os": { - "type": "Darwin", - "name": "darwin", - "architecture": "x64", - "version": "20.6.0", - }, - "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", - "application": {"name": "mongosh 1.0.1"}, - }, - "compression": ["none"], - "loadBalanced": false, - }`, - }, - { - name: "handshake3", - raw: testutil.MustParseDumpFile("testdata", "handshake3.hex"), - doc: MustDocument( - "buildInfo", int32(1), - "lsid", MustDocument( - "id", Binary{ - Subtype: BinaryUUID, - B: []byte{ - 0xa3, 0x19, 0xf2, 0xb4, 0xa1, 0x75, 0x40, 0xc7, - 0xb8, 0xe7, 0xa3, 0xa3, 0x2e, 0xc2, 0x56, 0xbe, - }, - }, - ), - "$db", "admin", - ), - m: ` - { - "buildInfo": 1, - "lsid": {"id": Binary(uuid:oxnytKF1QMe456OjLsJWvg==)}, - "$db": "admin", - }`, - }, - { - name: "handshake4", - raw: testutil.MustParseDumpFile("testdata", "handshake4.hex"), - doc: MustDocument( - "version", "5.0.0", - "gitVersion", "1184f004a99660de6f5e745573419bda8a28c0e9", - "modules", MustArray(), - "allocator", "tcmalloc", - "javascriptEngine", "mozjs", - "sysInfo", "deprecated", - "versionArray", MustArray(int32(5), int32(0), int32(0), int32(0)), - "openssl", MustDocument( - "running", "OpenSSL 1.1.1f 31 Mar 2020", - "compiled", "OpenSSL 1.1.1f 31 Mar 2020", - ), - "buildEnvironment", MustDocument( - "distmod", "ubuntu2004", - "distarch", "x86_64", - "cc", "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", - "ccflags", "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb "+ - "-Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer "+ - "-fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 "+ - "-Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations "+ - "-Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces "+ - "-fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", - "cxx", "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", - "cxxflags", "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", - "linkflags", "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong "+ - "-Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack "+ - "-Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", - "target_arch", "x86_64", - "target_os", "linux", - "cppdefines", "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE "+ - "_REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME "+ - "BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS "+ - "BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG "+ - "BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", - ), - "bits", int32(64), - "debug", false, - "maxBsonObjectSize", int32(16777216), - "storageEngines", MustArray("devnull", "ephemeralForTest", "wiredTiger"), - "ok", float64(1), - ), - m: ` - { - "version": "5.0.0", - "gitVersion": "1184f004a99660de6f5e745573419bda8a28c0e9", - "modules": [], - "allocator": "tcmalloc", - "javascriptEngine": "mozjs", - "sysInfo": "deprecated", - "versionArray": [5, 0, 0, 0], - "openssl": { - "running": "OpenSSL 1.1.1f 31 Mar 2020", - "compiled": "OpenSSL 1.1.1f 31 Mar 2020", - }, - "buildEnvironment": { - "distmod": "ubuntu2004", - "distarch": "x86_64", - "cc": "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", - "ccflags": "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer -fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", - "cxx": "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", - "cxxflags": "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", - "linkflags": "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong -Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", - "target_arch": "x86_64", - "target_os": "linux", - "cppdefines": "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE _REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", - }, - "bits": 64, - "debug": false, - "maxBsonObjectSize": 16777216, - "storageEngines": ["devnull", "ephemeralForTest", "wiredTiger"], - "ok": 1.0, - }`, - }, - { - name: "all", - raw: testutil.MustParseDumpFile("testdata", "all.hex"), - doc: MustDocument( - "array", MustArray( - MustArray(""), - MustArray("foo"), - ), - "binary", MustArray( - Binary{Subtype: BinaryUser, B: []byte{0x42}}, - Binary{Subtype: BinaryGeneric, B: []byte{}}, - ), - "bool", MustArray(true, false), - "datetime", MustArray( - time.Date(2021, 7, 27, 9, 35, 42, 123000000, time.UTC).Local(), - time.Time{}.Local(), - ), - "document", MustArray( - MustDocument("foo", ""), - MustDocument("", "foo"), - ), - "double", MustArray(42.13, 0.0), - "int32", MustArray(int32(42), int32(0)), - "int64", MustArray(int64(42), int64(0)), - "objectID", MustArray(ObjectID{0x42}, ObjectID{}), - "string", MustArray("foo", ""), - "timestamp", MustArray(Timestamp(42), Timestamp(0)), - "decimal128", MustArray(Decimal128{L: 42, H: 13}), - ), - m: ` - { - "array": [[""], ["foo"]], - "binary": [Binary(user:Qg==), Binary(generic:)], - "bool": [true, false], - "datetime": [2021-07-27T09:35:42.123Z, 0001-01-01T00:00:00Z], - "document": [{"foo": ""}, {"": "foo"}], - "double": [42.13, 0.0], - "int32": [42, 0], - "int64": [int64(42), int64(0)], - "objectID": [ObjectID(420000000000000000000000), ObjectID(000000000000000000000000)], - "string": ["foo", ""], - "timestamp": [Timestamp(42), Timestamp(0)], - "decimal128": [Decimal128(42,13)], - }`, - }, - { - name: "nested", - raw: testutil.MustParseDumpFile("testdata", "nested.hex"), - doc: makeNested(false, 150).(*Document), - m: ` - { - "f": [ - { - "f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{...}]}]}]}]}]}]}]}]}], - }, - ], - }`, - }, - { - name: "float64Doc", - raw: RawDocument{ - 0x10, 0x00, 0x00, 0x00, - 0x01, 0x66, 0x00, - 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, - 0x00, - }, - doc: MustDocument( - "f", float64(3.141592653589793), - ), - m: `{"f": 3.141592653589793}`, - }, - { - name: "stringDoc", - raw: RawDocument{ - 0x0e, 0x00, 0x00, 0x00, - 0x02, 0x66, 0x00, - 0x02, 0x00, 0x00, 0x00, - 0x76, 0x00, - 0x00, - }, - doc: MustDocument( - "f", "v", - ), - m: `{"f": "v"}`, - }, - { - name: "binaryDoc", - raw: RawDocument{ - 0x0e, 0x00, 0x00, 0x00, - 0x05, 0x66, 0x00, - 0x01, 0x00, 0x00, 0x00, - 0x80, - 0x76, - 0x00, - }, - doc: MustDocument( - "f", Binary{B: []byte("v"), Subtype: BinaryUser}, - ), - m: `{"f": Binary(user:dg==)}`, - }, - { - name: "objectIDDoc", - raw: RawDocument{ - 0x14, 0x00, 0x00, 0x00, - 0x07, 0x66, 0x00, - 0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, - 0x00, - }, - doc: MustDocument( - "f", ObjectID{0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}, - ), - m: `{"f": ObjectID(6256c5ba182d4454fb210940)}`, - }, - { - name: "boolDoc", - raw: RawDocument{ - 0x09, 0x00, 0x00, 0x00, - 0x08, 0x66, 0x00, - 0x01, - 0x00, - }, - doc: MustDocument( - "f", true, - ), - m: `{"f": true}`, - }, - { - name: "timeDoc", - raw: RawDocument{ - 0x10, 0x00, 0x00, 0x00, - 0x09, 0x66, 0x00, - 0x0b, 0xce, 0x82, 0x18, 0x8d, 0x01, 0x00, 0x00, - 0x00, - }, - doc: MustDocument( - "f", time.Date(2024, 1, 17, 17, 40, 42, 123000000, time.UTC), - ), - m: `{"f": 2024-01-17T17:40:42.123Z}`, - }, - { - name: "nullDoc", - raw: RawDocument{ - 0x08, 0x00, 0x00, 0x00, - 0x0a, 0x66, 0x00, - 0x00, - }, - doc: MustDocument( - "f", Null, - ), - m: `{"f": null}`, - }, - { - name: "regexDoc", - raw: RawDocument{ - 0x0c, 0x00, 0x00, 0x00, - 0x0b, 0x66, 0x00, - 0x70, 0x00, - 0x6f, 0x00, - 0x00, - }, - doc: MustDocument( - "f", Regex{Pattern: "p", Options: "o"}, - ), - m: `{"f": /p/o}`, - }, - { - name: "int32Doc", - raw: RawDocument{ - 0x0c, 0x00, 0x00, 0x00, - 0x10, 0x66, 0x00, - 0xa1, 0xb0, 0xb9, 0x12, - 0x00, - }, - doc: MustDocument( - "f", int32(314159265), - ), - m: `{"f": 314159265}`, - }, - { - name: "timestampDoc", - raw: RawDocument{ - 0x10, 0x00, 0x00, 0x00, - 0x11, 0x66, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, - }, - doc: MustDocument( - "f", Timestamp(42), - ), - m: `{"f": Timestamp(42)}`, - }, - { - name: "int64Doc", - raw: RawDocument{ - 0x10, 0x00, 0x00, 0x00, - 0x12, 0x66, 0x00, - 0x21, 0x6d, 0x25, 0x0a, 0x43, 0x29, 0x0b, 0x00, - 0x00, - }, - doc: MustDocument( - "f", int64(3141592653589793), - ), - m: `{"f": int64(3141592653589793)}`, - }, - { - name: "decimal128Doc", - raw: RawDocument{ - 0x18, 0x00, 0x00, 0x00, - 0x13, 0x66, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, - }, - doc: MustDocument( - "f", Decimal128{L: 42, H: 13}, - ), - m: `{"f": Decimal128(42,13)}`, - }, - { - name: "smallDoc", - raw: RawDocument{ - 0x0f, 0x00, 0x00, 0x00, // document length - 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" - 0x05, 0x00, 0x00, 0x00, 0x00, // subdocument length and end of subdocument - 0x00, // end of document - }, - doc: MustDocument( - "foo", MustDocument(), - ), - m: `{"foo": {}}`, - }, - { - name: "smallArray", - raw: RawDocument{ - 0x0f, 0x00, 0x00, 0x00, // document length - 0x04, 0x66, 0x6f, 0x6f, 0x00, // subarray "foo" - 0x05, 0x00, 0x00, 0x00, 0x00, // subarray length and end of subarray - 0x00, // end of document - }, - doc: MustDocument( - "foo", MustArray(), - ), - m: `{"foo": []}`, - }, - { - name: "duplicateKeys", - raw: RawDocument{ - 0x0b, 0x00, 0x00, 0x00, // document length - 0x08, 0x00, 0x00, // "": false - 0x08, 0x00, 0x01, // "": true - 0x00, // end of document - }, - doc: MustDocument( - "", false, - "", true, - ), - m: `{"": false, "": true}`, - }, + //{ + // name: "handshake2", + // raw: testutil.MustParseDumpFile("testdata", "handshake2.hex"), + // doc: MustDocument( + // "ismaster", true, + // "client", MustDocument( + // "driver", MustDocument( + // "name", "nodejs", + // "version", "4.0.0-beta.6", + // ), + // "os", MustDocument( + // "type", "Darwin", + // "name", "darwin", + // "architecture", "x64", + // "version", "20.6.0", + // ), + // "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", + // "application", MustDocument( + // "name", "mongosh 1.0.1", + // ), + // ), + // "compression", MustArray("none"), + // "loadBalanced", false, + // ), + // m: ` + // { + // "ismaster": true, + // "client": { + // "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, + // "os": { + // "type": "Darwin", + // "name": "darwin", + // "architecture": "x64", + // "version": "20.6.0", + // }, + // "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", + // "application": {"name": "mongosh 1.0.1"}, + // }, + // "compression": ["none"], + // "loadBalanced": false, + // }`, + //}, + //{ + // name: "handshake3", + // raw: testutil.MustParseDumpFile("testdata", "handshake3.hex"), + // doc: MustDocument( + // "buildInfo", int32(1), + // "lsid", MustDocument( + // "id", Binary{ + // Subtype: BinaryUUID, + // B: []byte{ + // 0xa3, 0x19, 0xf2, 0xb4, 0xa1, 0x75, 0x40, 0xc7, + // 0xb8, 0xe7, 0xa3, 0xa3, 0x2e, 0xc2, 0x56, 0xbe, + // }, + // }, + // ), + // "$db", "admin", + // ), + // m: ` + // { + // "buildInfo": 1, + // "lsid": {"id": Binary(uuid:oxnytKF1QMe456OjLsJWvg==)}, + // "$db": "admin", + // }`, + //}, + //{ + // name: "handshake4", + // raw: testutil.MustParseDumpFile("testdata", "handshake4.hex"), + // doc: MustDocument( + // "version", "5.0.0", + // "gitVersion", "1184f004a99660de6f5e745573419bda8a28c0e9", + // "modules", MustArray(), + // "allocator", "tcmalloc", + // "javascriptEngine", "mozjs", + // "sysInfo", "deprecated", + // "versionArray", MustArray(int32(5), int32(0), int32(0), int32(0)), + // "openssl", MustDocument( + // "running", "OpenSSL 1.1.1f 31 Mar 2020", + // "compiled", "OpenSSL 1.1.1f 31 Mar 2020", + // ), + // "buildEnvironment", MustDocument( + // "distmod", "ubuntu2004", + // "distarch", "x86_64", + // "cc", "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", + // "ccflags", "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb "+ + // "-Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer "+ + // "-fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 "+ + // "-Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations "+ + // "-Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces "+ + // "-fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", + // "cxx", "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", + // "cxxflags", "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", + // "linkflags", "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong "+ + // "-Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack "+ + // "-Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", + // "target_arch", "x86_64", + // "target_os", "linux", + // "cppdefines", "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE "+ + // "_REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME "+ + // "BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS "+ + // "BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG "+ + // "BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", + // ), + // "bits", int32(64), + // "debug", false, + // "maxBsonObjectSize", int32(16777216), + // "storageEngines", MustArray("devnull", "ephemeralForTest", "wiredTiger"), + // "ok", float64(1), + // ), + // m: ` + // { + // "version": "5.0.0", + // "gitVersion": "1184f004a99660de6f5e745573419bda8a28c0e9", + // "modules": [], + // "allocator": "tcmalloc", + // "javascriptEngine": "mozjs", + // "sysInfo": "deprecated", + // "versionArray": [5, 0, 0, 0], + // "openssl": { + // "running": "OpenSSL 1.1.1f 31 Mar 2020", + // "compiled": "OpenSSL 1.1.1f 31 Mar 2020", + // }, + // "buildEnvironment": { + // "distmod": "ubuntu2004", + // "distarch": "x86_64", + // "cc": "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", + // "ccflags": "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer -fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", + // "cxx": "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", + // "cxxflags": "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", + // "linkflags": "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong -Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", + // "target_arch": "x86_64", + // "target_os": "linux", + // "cppdefines": "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE _REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", + // }, + // "bits": 64, + // "debug": false, + // "maxBsonObjectSize": 16777216, + // "storageEngines": ["devnull", "ephemeralForTest", "wiredTiger"], + // "ok": 1.0, + // }`, + //}, + //{ + // name: "all", + // raw: testutil.MustParseDumpFile("testdata", "all.hex"), + // doc: MustDocument( + // "array", MustArray( + // MustArray(""), + // MustArray("foo"), + // ), + // "binary", MustArray( + // Binary{Subtype: BinaryUser, B: []byte{0x42}}, + // Binary{Subtype: BinaryGeneric, B: []byte{}}, + // ), + // "bool", MustArray(true, false), + // "datetime", MustArray( + // time.Date(2021, 7, 27, 9, 35, 42, 123000000, time.UTC).Local(), + // time.Time{}.Local(), + // ), + // "document", MustArray( + // MustDocument("foo", ""), + // MustDocument("", "foo"), + // ), + // "double", MustArray(42.13, 0.0), + // "int32", MustArray(int32(42), int32(0)), + // "int64", MustArray(int64(42), int64(0)), + // "objectID", MustArray(ObjectID{0x42}, ObjectID{}), + // "string", MustArray("foo", ""), + // "timestamp", MustArray(Timestamp(42), Timestamp(0)), + // "decimal128", MustArray(Decimal128{L: 42, H: 13}), + // ), + // m: ` + // { + // "array": [[""], ["foo"]], + // "binary": [Binary(user:Qg==), Binary(generic:)], + // "bool": [true, false], + // "datetime": [2021-07-27T09:35:42.123Z, 0001-01-01T00:00:00Z], + // "document": [{"foo": ""}, {"": "foo"}], + // "double": [42.13, 0.0], + // "int32": [42, 0], + // "int64": [int64(42), int64(0)], + // "objectID": [ObjectID(420000000000000000000000), ObjectID(000000000000000000000000)], + // "string": ["foo", ""], + // "timestamp": [Timestamp(42), Timestamp(0)], + // "decimal128": [Decimal128(42,13)], + // }`, + //}, + //{ + // name: "nested", + // raw: testutil.MustParseDumpFile("testdata", "nested.hex"), + // doc: makeNested(false, 150).(*Document), + // m: ` + // { + // "f": [ + // { + // "f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{...}]}]}]}]}]}]}]}]}], + // }, + // ], + // }`, + //}, + //{ + // name: "float64Doc", + // raw: RawDocument{ + // 0x10, 0x00, 0x00, 0x00, + // 0x01, 0x66, 0x00, + // 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, + // 0x00, + // }, + // doc: MustDocument( + // "f", float64(3.141592653589793), + // ), + // m: `{"f": 3.141592653589793}`, + //}, + //{ + // name: "stringDoc", + // raw: RawDocument{ + // 0x0e, 0x00, 0x00, 0x00, + // 0x02, 0x66, 0x00, + // 0x02, 0x00, 0x00, 0x00, + // 0x76, 0x00, + // 0x00, + // }, + // doc: MustDocument( + // "f", "v", + // ), + // m: `{"f": "v"}`, + //}, + //{ + // name: "binaryDoc", + // raw: RawDocument{ + // 0x0e, 0x00, 0x00, 0x00, + // 0x05, 0x66, 0x00, + // 0x01, 0x00, 0x00, 0x00, + // 0x80, + // 0x76, + // 0x00, + // }, + // doc: MustDocument( + // "f", Binary{B: []byte("v"), Subtype: BinaryUser}, + // ), + // m: `{"f": Binary(user:dg==)}`, + //}, + //{ + // name: "objectIDDoc", + // raw: RawDocument{ + // 0x14, 0x00, 0x00, 0x00, + // 0x07, 0x66, 0x00, + // 0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, + // 0x00, + // }, + // doc: MustDocument( + // "f", ObjectID{0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}, + // ), + // m: `{"f": ObjectID(6256c5ba182d4454fb210940)}`, + //}, + //{ + // name: "boolDoc", + // raw: RawDocument{ + // 0x09, 0x00, 0x00, 0x00, + // 0x08, 0x66, 0x00, + // 0x01, + // 0x00, + // }, + // doc: MustDocument( + // "f", true, + // ), + // m: `{"f": true}`, + //}, + //{ + // name: "timeDoc", + // raw: RawDocument{ + // 0x10, 0x00, 0x00, 0x00, + // 0x09, 0x66, 0x00, + // 0x0b, 0xce, 0x82, 0x18, 0x8d, 0x01, 0x00, 0x00, + // 0x00, + // }, + // doc: MustDocument( + // "f", time.Date(2024, 1, 17, 17, 40, 42, 123000000, time.UTC), + // ), + // m: `{"f": 2024-01-17T17:40:42.123Z}`, + //}, + //{ + // name: "nullDoc", + // raw: RawDocument{ + // 0x08, 0x00, 0x00, 0x00, + // 0x0a, 0x66, 0x00, + // 0x00, + // }, + // doc: MustDocument( + // "f", Null, + // ), + // m: `{"f": null}`, + //}, + //{ + // name: "regexDoc", + // raw: RawDocument{ + // 0x0c, 0x00, 0x00, 0x00, + // 0x0b, 0x66, 0x00, + // 0x70, 0x00, + // 0x6f, 0x00, + // 0x00, + // }, + // doc: MustDocument( + // "f", Regex{Pattern: "p", Options: "o"}, + // ), + // m: `{"f": /p/o}`, + //}, + //{ + // name: "int32Doc", + // raw: RawDocument{ + // 0x0c, 0x00, 0x00, 0x00, + // 0x10, 0x66, 0x00, + // 0xa1, 0xb0, 0xb9, 0x12, + // 0x00, + // }, + // doc: MustDocument( + // "f", int32(314159265), + // ), + // m: `{"f": 314159265}`, + //}, + //{ + // name: "timestampDoc", + // raw: RawDocument{ + // 0x10, 0x00, 0x00, 0x00, + // 0x11, 0x66, 0x00, + // 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // 0x00, + // }, + // doc: MustDocument( + // "f", Timestamp(42), + // ), + // m: `{"f": Timestamp(42)}`, + //}, + //{ + // name: "int64Doc", + // raw: RawDocument{ + // 0x10, 0x00, 0x00, 0x00, + // 0x12, 0x66, 0x00, + // 0x21, 0x6d, 0x25, 0x0a, 0x43, 0x29, 0x0b, 0x00, + // 0x00, + // }, + // doc: MustDocument( + // "f", int64(3141592653589793), + // ), + // m: `{"f": int64(3141592653589793)}`, + //}, + //{ + // name: "decimal128Doc", + // raw: RawDocument{ + // 0x18, 0x00, 0x00, 0x00, + // 0x13, 0x66, 0x00, + // 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // 0x00, + // }, + // doc: MustDocument( + // "f", Decimal128{L: 42, H: 13}, + // ), + // m: `{"f": Decimal128(42,13)}`, + //}, + //{ + // name: "smallDoc", + // raw: RawDocument{ + // 0x0f, 0x00, 0x00, 0x00, // document length + // 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" + // 0x05, 0x00, 0x00, 0x00, 0x00, // subdocument length and end of subdocument + // 0x00, // end of document + // }, + // doc: MustDocument( + // "foo", MustDocument(), + // ), + // m: `{"foo": {}}`, + //}, + //{ + // name: "smallArray", + // raw: RawDocument{ + // 0x0f, 0x00, 0x00, 0x00, // document length + // 0x04, 0x66, 0x6f, 0x6f, 0x00, // subarray "foo" + // 0x05, 0x00, 0x00, 0x00, 0x00, // subarray length and end of subarray + // 0x00, // end of document + // }, + // doc: MustDocument( + // "foo", MustArray(), + // ), + // m: `{"foo": []}`, + //}, + //{ + // name: "duplicateKeys", + // raw: RawDocument{ + // 0x0b, 0x00, 0x00, 0x00, // document length + // 0x08, 0x00, 0x00, // "": false + // 0x08, 0x00, 0x01, // "": true + // 0x00, // end of document + // }, + // doc: MustDocument( + // "", false, + // "", true, + // ), + // m: `{"": false, "": true}`, + //}, } // decodeTestCases represents test cases for unsuccessful decoding. From 7f13077cf30944e57fc793a695331bacb3a45d5b Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 16:57:54 +0200 Subject: [PATCH 35/46] wip --- wirebson/array.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wirebson/array.go b/wirebson/array.go index 0509af2..17ee3ac 100644 --- a/wirebson/array.go +++ b/wirebson/array.go @@ -114,11 +114,10 @@ func (arr *Array) Replace(index int, value any) error { return nil } -// Encode encodes non-nil BSON array. +// Encode encodes non-nil BSON array into raw. // -// TODO https://github.com/FerretDB/wire/issues/21 -// This method should accept a slice of bytes, not return it. -// That would allow to avoid unnecessary allocations. +// TODO: comment about len of raw (panic or error?) +// The provided raw array must have func (arr *Array) Encode(raw RawArray) error { must.NotBeZero(arr) From 8dd7397586d8596a68801fb2d4879555078eaf08 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 17:06:04 +0200 Subject: [PATCH 36/46] wip --- wirebson/array.go | 4 ++-- wirebson/document.go | 5 ++--- wirebson/raw_array.go | 2 +- wirebson/raw_document.go | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/wirebson/array.go b/wirebson/array.go index 17ee3ac..ba40721 100644 --- a/wirebson/array.go +++ b/wirebson/array.go @@ -116,8 +116,8 @@ func (arr *Array) Replace(index int, value any) error { // Encode encodes non-nil BSON array into raw. // -// TODO: comment about len of raw (panic or error?) -// The provided raw array must have +// The function operates directly on raw RawArray. +// It doesn't reallocate memory, hence raw needs to have the proper length. func (arr *Array) Encode(raw RawArray) error { must.NotBeZero(arr) diff --git a/wirebson/document.go b/wirebson/document.go index 34647df..64e20b4 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -204,9 +204,8 @@ func (doc *Document) Command() string { // Encode encodes non-nil BSON document. // -// TODO https://github.com/FerretDB/wire/issues/21 -// This method should accept a slice of bytes, not return it. -// That would allow to avoid unnecessary allocations. +// The function operates directly on raw RawDocument. +// It doesn't reallocate memory, hence raw needs to have the proper length. func (doc *Document) Encode(raw RawDocument) error { must.NotBeZero(doc) diff --git a/wirebson/raw_array.go b/wirebson/raw_array.go index c207061..e13b1de 100644 --- a/wirebson/raw_array.go +++ b/wirebson/raw_array.go @@ -32,7 +32,7 @@ type RawArray []byte // Receiver must not be nil. func (raw RawArray) Encode(out RawArray) error { must.BeTrue(raw != nil) - out = raw + copy(out, raw) return nil } diff --git a/wirebson/raw_document.go b/wirebson/raw_document.go index d2cc37d..6fd4f35 100644 --- a/wirebson/raw_document.go +++ b/wirebson/raw_document.go @@ -31,7 +31,7 @@ type RawDocument []byte // Receiver must not be nil. func (raw RawDocument) Encode(out RawDocument) error { must.BeTrue(raw != nil) - out = raw + copy(out, raw) return nil } From 1a4d945b72bca334f30528e1b2fc737256234bb2 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 17:06:54 +0200 Subject: [PATCH 37/46] wip --- wirebson/document.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wirebson/document.go b/wirebson/document.go index 64e20b4..772fbc2 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -202,7 +202,7 @@ func (doc *Document) Command() string { return doc.fields[0].name } -// Encode encodes non-nil BSON document. +// Encode encodes non-nil BSON document into raw. // // The function operates directly on raw RawDocument. // It doesn't reallocate memory, hence raw needs to have the proper length. From f6151afff9fab0551ffb975aae10f84a1bf8f80b Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 17:11:57 +0200 Subject: [PATCH 38/46] wip --- wirebson/array.go | 12 ++++++------ wirebson/document.go | 10 +++++----- wirebson/encode.go | 1 + 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/wirebson/array.go b/wirebson/array.go index ba40721..302c6e4 100644 --- a/wirebson/array.go +++ b/wirebson/array.go @@ -123,18 +123,18 @@ func (arr *Array) Encode(raw RawArray) error { binary.LittleEndian.PutUint32(raw[0:4], uint32(sizeArray(arr))) - index := 4 - for i, v := range arr.elements { - written, err := encodeField(raw[index:], strconv.Itoa(i), v) + i := 4 + for n, v := range arr.elements { + written, err := encodeField(raw[i:], strconv.Itoa(n), v) if err != nil { return lazyerrors.Error(err) } - index += written + i += written } - raw[index] = byte(0) - index++ + raw[i] = byte(0) + i++ return nil } diff --git a/wirebson/document.go b/wirebson/document.go index 772fbc2..10781af 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -211,18 +211,18 @@ func (doc *Document) Encode(raw RawDocument) error { binary.LittleEndian.PutUint32(raw[0:4], uint32(sizeDocument(doc))) - index := 4 + i := 4 for _, f := range doc.fields { - written, err := encodeField(raw[index:], f.name, f.value) + written, err := encodeField(raw[i:], f.name, f.value) if err != nil { return lazyerrors.Error(err) } - index += written + i += written } - raw[index] = byte(0) - index++ + raw[i] = byte(0) + i++ return nil } diff --git a/wirebson/encode.go b/wirebson/encode.go index 89d9b55..01612e9 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -23,6 +23,7 @@ import ( // encodeField encodes document/array field. // +// It returns the number of bytes written. // It panics if v is not a valid type. func encodeField(buf []byte, name string, v any) (int, error) { var i int From 70c05bb17b57ce05d0d8030cb2ae81b18a28eb90 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 17:16:51 +0200 Subject: [PATCH 39/46] wip --- wirebson/encode.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index 01612e9..fd08d73 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -83,18 +83,20 @@ func encodeField(buf []byte, name string, v any) (int, error) { return i, nil } -// returns number of bytes written -func write(b []byte, v []byte) int { - if len(v) > len(b) { - panic("write impossible") +// write writes bytes from dst to src. +// It returns number of bytes written. +func write(dst []byte, src []byte) int { + if len(dst) > len(src) { + panic(fmt.Sprintf("length of dst should be at least %d bytes, got %d", len(src), len(dst))) } - copy(b, v) - return len(v) + copy(dst, src) + return len(src) } // encodeScalarField encodes scalar document field. // +// It returns the number of bytes written. // It panics if v is not a scalar value. func encodeScalarField(b []byte, name string, v any) (int, error) { var i int From c84c84ae14c4a379198c9e11b70e079421d22c9c Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 17:19:19 +0200 Subject: [PATCH 40/46] less error --- wirebson/encode.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index fd08d73..5e5e326 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -76,8 +76,7 @@ func encodeField(buf []byte, name string, v any) (int, error) { i += write(buf[i:], v) default: - written, err := encodeScalarField(buf[i:], name, v) - return i + written, err + return i + encodeScalarField(buf[i:], name, v), nil } return i, nil @@ -86,7 +85,7 @@ func encodeField(buf []byte, name string, v any) (int, error) { // write writes bytes from dst to src. // It returns number of bytes written. func write(dst []byte, src []byte) int { - if len(dst) > len(src) { + if len(src) > len(dst) { panic(fmt.Sprintf("length of dst should be at least %d bytes, got %d", len(src), len(dst))) } @@ -98,7 +97,7 @@ func write(dst []byte, src []byte) int { // // It returns the number of bytes written. // It panics if v is not a scalar value. -func encodeScalarField(b []byte, name string, v any) (int, error) { +func encodeScalarField(b []byte, name string, v any) int { var i int switch v := v.(type) { case float64: @@ -136,7 +135,7 @@ func encodeScalarField(b []byte, name string, v any) (int, error) { encodeScalarValue(b[i:], v) i += sizeScalar(v) - return i, nil + return i } // encodeScalarValue encodes value v into b. From c87f7abb008b50fb1228dcffbb9c79f364f20ec1 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 17:23:08 +0200 Subject: [PATCH 41/46] simplify test --- wirebson/encode_test.go | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index 6dce545..070e9a4 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -8,39 +8,32 @@ import ( ) func TestEncodeScalarField(t *testing.T) { - buf := make([]byte, 13) - encodeScalarField(buf[0:], "foo", "bar") + actual := make([]byte, 13) + assert.Equal(t, 13, encodeScalarField(actual[0:], "foo", "bar")) expected := []byte{0x02, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0} - actual := buf - assert.Equal(t, expected, actual) } func TestEncodeField(t *testing.T) { - buf := make([]byte, 22) + actual := make([]byte, 22) var i int - - written, err := encodeField(buf[i:], "foo", "bar") + written, err := encodeField(actual[i:], "foo", "bar") require.NoError(t, err) + assert.Equal(t, 13, written) i += written - actual := buf - expected := []byte{0x2, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} assert.Equal(t, expected, actual) - written, err = encodeField(buf[i:], "foo", int32(1)) + written, err = encodeField(actual[i:], "foo", int32(1)) require.NoError(t, err) + assert.Equal(t, 9, written) i += written - actual = buf - - expected = append(expected, []byte{}...) - expected = []byte{0x2, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0, 0x10, 0x66, 0x6f, 0x6f, 0x0, 0x1, 0x0, 0x0, 0x0} assert.Equal(t, expected, actual) } From b7510cd9a5c49b2cbed76164ef271b1f67d76707 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 17:24:54 +0200 Subject: [PATCH 42/46] wip --- wirebson/encode.go | 82 ++++++++++++++++++++--------------------- wirebson/encode_test.go | 5 ++- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/wirebson/encode.go b/wirebson/encode.go index 5e5e326..e14e0b0 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -25,17 +25,17 @@ import ( // // It returns the number of bytes written. // It panics if v is not a valid type. -func encodeField(buf []byte, name string, v any) (int, error) { +func encodeField(dst []byte, name string, v any) (int, error) { var i int switch v := v.(type) { case *Document: - buf[i] = byte(tagDocument) + dst[i] = byte(tagDocument) i++ - EncodeCString(buf[i:], name) + EncodeCString(dst[i:], name) i += SizeCString(name) - err := v.Encode(buf[i:]) + err := v.Encode(dst[i:]) if err != nil { return 0, lazyerrors.Error(err) } @@ -44,22 +44,22 @@ func encodeField(buf []byte, name string, v any) (int, error) { case RawDocument: - buf[i] = byte(tagDocument) + dst[i] = byte(tagDocument) i++ - EncodeCString(buf[i:], name) + EncodeCString(dst[i:], name) i += SizeCString(name) - i += write(buf[i:], v) + i += write(dst[i:], v) case *Array: - buf[i] = byte(tagArray) + dst[i] = byte(tagArray) i++ - EncodeCString(buf[i:], name) + EncodeCString(dst[i:], name) i += SizeCString(name) - err := v.Encode(buf[i:]) + err := v.Encode(dst[i:]) if err != nil { return 0, lazyerrors.Error(err) } @@ -67,16 +67,16 @@ func encodeField(buf []byte, name string, v any) (int, error) { i += sizeArray(v) case RawArray: - buf[i] = byte(tagArray) + dst[i] = byte(tagArray) i++ - EncodeCString(buf[i:], name) + EncodeCString(dst[i:], name) i += SizeCString(name) - i += write(buf[i:], v) + i += write(dst[i:], v) default: - return i + encodeScalarField(buf[i:], name, v), nil + return i + encodeScalarField(dst[i:], name, v), nil } return i, nil @@ -97,42 +97,42 @@ func write(dst []byte, src []byte) int { // // It returns the number of bytes written. // It panics if v is not a scalar value. -func encodeScalarField(b []byte, name string, v any) int { +func encodeScalarField(dst []byte, name string, v any) int { var i int switch v := v.(type) { case float64: - b[i] = byte(tagFloat64) + dst[i] = byte(tagFloat64) case string: - b[i] = byte(tagString) + dst[i] = byte(tagString) case Binary: - b[i] = byte(tagBinary) + dst[i] = byte(tagBinary) case ObjectID: - b[i] = byte(tagObjectID) + dst[i] = byte(tagObjectID) case bool: - b[i] = byte(tagBool) + dst[i] = byte(tagBool) case time.Time: - b[i] = byte(tagTime) + dst[i] = byte(tagTime) case NullType: - b[i] = byte(tagNull) + dst[i] = byte(tagNull) case Regex: - b[i] = byte(tagRegex) + dst[i] = byte(tagRegex) case int32: - b[i] = byte(tagInt32) + dst[i] = byte(tagInt32) case Timestamp: - b[i] = byte(tagTimestamp) + dst[i] = byte(tagTimestamp) case int64: - b[i] = byte(tagInt64) + dst[i] = byte(tagInt64) case Decimal128: - b[i] = byte(tagDecimal128) + dst[i] = byte(tagDecimal128) default: panic(fmt.Sprintf("invalid BSON type %T", v)) } i++ - EncodeCString(b[i:], name) + EncodeCString(dst[i:], name) i += SizeCString(name) - encodeScalarValue(b[i:], v) + encodeScalarValue(dst[i:], v) i += sizeScalar(v) return i @@ -144,32 +144,32 @@ func encodeScalarField(b []byte, name string, v any) int { // Only b[0:Size(v)] bytes are modified. // // It panics if v is not a [ScalarType] (including CString). -func encodeScalarValue(b []byte, v any) { +func encodeScalarValue(dst []byte, v any) { switch v := v.(type) { case float64: - encodeFloat64(b, v) + encodeFloat64(dst, v) case string: - encodeString(b, v) + encodeString(dst, v) case Binary: - encodeBinary(b, v) + encodeBinary(dst, v) case ObjectID: - encodeObjectID(b, v) + encodeObjectID(dst, v) case bool: - encodeBool(b, v) + encodeBool(dst, v) case time.Time: - encodeTime(b, v) + encodeTime(dst, v) case NullType: // nothing case Regex: - encodeRegex(b, v) + encodeRegex(dst, v) case int32: - encodeInt32(b, v) + encodeInt32(dst, v) case Timestamp: - encodeTimestamp(b, v) + encodeTimestamp(dst, v) case int64: - encodeInt64(b, v) + encodeInt64(dst, v) case Decimal128: - encodeDecimal128(b, v) + encodeDecimal128(dst, v) default: panic(fmt.Sprintf("unsupported type %T", v)) } diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index 070e9a4..c9042a2 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -8,6 +8,8 @@ import ( ) func TestEncodeScalarField(t *testing.T) { + t.Parallel() + actual := make([]byte, 13) assert.Equal(t, 13, encodeScalarField(actual[0:], "foo", "bar")) @@ -16,9 +18,10 @@ func TestEncodeScalarField(t *testing.T) { } func TestEncodeField(t *testing.T) { - actual := make([]byte, 22) + t.Parallel() var i int + actual := make([]byte, 22) written, err := encodeField(actual[i:], "foo", "bar") require.NoError(t, err) From 5aa5871fc55708038767cf023a5fbac1b935ebfe Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 17:26:09 +0200 Subject: [PATCH 43/46] wip --- wirebson/size.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wirebson/size.go b/wirebson/size.go index 2607aa1..c75b4d9 100644 --- a/wirebson/size.go +++ b/wirebson/size.go @@ -20,7 +20,7 @@ import ( "time" ) -// Size returns a Size of the encoding of value v in bytes. +// Size returns a size of the encoding of value v in bytes. // // It panics for invalid types. func Size(v any) int { From b67c25b479c2c621675f8e287a5b8feacb8ee701 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 17:28:51 +0200 Subject: [PATCH 44/46] test --- wirebson/bson_test.go | 797 +++++++++++++++++++++--------------------- 1 file changed, 399 insertions(+), 398 deletions(-) diff --git a/wirebson/bson_test.go b/wirebson/bson_test.go index c920399..0927b45 100644 --- a/wirebson/bson_test.go +++ b/wirebson/bson_test.go @@ -16,6 +16,7 @@ package wirebson import ( "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -94,404 +95,404 @@ var normalTestCases = []normalTestCase{ "loadBalanced": false, }`, }, - //{ - // name: "handshake2", - // raw: testutil.MustParseDumpFile("testdata", "handshake2.hex"), - // doc: MustDocument( - // "ismaster", true, - // "client", MustDocument( - // "driver", MustDocument( - // "name", "nodejs", - // "version", "4.0.0-beta.6", - // ), - // "os", MustDocument( - // "type", "Darwin", - // "name", "darwin", - // "architecture", "x64", - // "version", "20.6.0", - // ), - // "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", - // "application", MustDocument( - // "name", "mongosh 1.0.1", - // ), - // ), - // "compression", MustArray("none"), - // "loadBalanced", false, - // ), - // m: ` - // { - // "ismaster": true, - // "client": { - // "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, - // "os": { - // "type": "Darwin", - // "name": "darwin", - // "architecture": "x64", - // "version": "20.6.0", - // }, - // "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", - // "application": {"name": "mongosh 1.0.1"}, - // }, - // "compression": ["none"], - // "loadBalanced": false, - // }`, - //}, - //{ - // name: "handshake3", - // raw: testutil.MustParseDumpFile("testdata", "handshake3.hex"), - // doc: MustDocument( - // "buildInfo", int32(1), - // "lsid", MustDocument( - // "id", Binary{ - // Subtype: BinaryUUID, - // B: []byte{ - // 0xa3, 0x19, 0xf2, 0xb4, 0xa1, 0x75, 0x40, 0xc7, - // 0xb8, 0xe7, 0xa3, 0xa3, 0x2e, 0xc2, 0x56, 0xbe, - // }, - // }, - // ), - // "$db", "admin", - // ), - // m: ` - // { - // "buildInfo": 1, - // "lsid": {"id": Binary(uuid:oxnytKF1QMe456OjLsJWvg==)}, - // "$db": "admin", - // }`, - //}, - //{ - // name: "handshake4", - // raw: testutil.MustParseDumpFile("testdata", "handshake4.hex"), - // doc: MustDocument( - // "version", "5.0.0", - // "gitVersion", "1184f004a99660de6f5e745573419bda8a28c0e9", - // "modules", MustArray(), - // "allocator", "tcmalloc", - // "javascriptEngine", "mozjs", - // "sysInfo", "deprecated", - // "versionArray", MustArray(int32(5), int32(0), int32(0), int32(0)), - // "openssl", MustDocument( - // "running", "OpenSSL 1.1.1f 31 Mar 2020", - // "compiled", "OpenSSL 1.1.1f 31 Mar 2020", - // ), - // "buildEnvironment", MustDocument( - // "distmod", "ubuntu2004", - // "distarch", "x86_64", - // "cc", "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", - // "ccflags", "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb "+ - // "-Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer "+ - // "-fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 "+ - // "-Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations "+ - // "-Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces "+ - // "-fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", - // "cxx", "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", - // "cxxflags", "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", - // "linkflags", "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong "+ - // "-Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack "+ - // "-Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", - // "target_arch", "x86_64", - // "target_os", "linux", - // "cppdefines", "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE "+ - // "_REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME "+ - // "BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS "+ - // "BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG "+ - // "BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", - // ), - // "bits", int32(64), - // "debug", false, - // "maxBsonObjectSize", int32(16777216), - // "storageEngines", MustArray("devnull", "ephemeralForTest", "wiredTiger"), - // "ok", float64(1), - // ), - // m: ` - // { - // "version": "5.0.0", - // "gitVersion": "1184f004a99660de6f5e745573419bda8a28c0e9", - // "modules": [], - // "allocator": "tcmalloc", - // "javascriptEngine": "mozjs", - // "sysInfo": "deprecated", - // "versionArray": [5, 0, 0, 0], - // "openssl": { - // "running": "OpenSSL 1.1.1f 31 Mar 2020", - // "compiled": "OpenSSL 1.1.1f 31 Mar 2020", - // }, - // "buildEnvironment": { - // "distmod": "ubuntu2004", - // "distarch": "x86_64", - // "cc": "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", - // "ccflags": "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer -fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", - // "cxx": "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", - // "cxxflags": "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", - // "linkflags": "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong -Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", - // "target_arch": "x86_64", - // "target_os": "linux", - // "cppdefines": "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE _REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", - // }, - // "bits": 64, - // "debug": false, - // "maxBsonObjectSize": 16777216, - // "storageEngines": ["devnull", "ephemeralForTest", "wiredTiger"], - // "ok": 1.0, - // }`, - //}, - //{ - // name: "all", - // raw: testutil.MustParseDumpFile("testdata", "all.hex"), - // doc: MustDocument( - // "array", MustArray( - // MustArray(""), - // MustArray("foo"), - // ), - // "binary", MustArray( - // Binary{Subtype: BinaryUser, B: []byte{0x42}}, - // Binary{Subtype: BinaryGeneric, B: []byte{}}, - // ), - // "bool", MustArray(true, false), - // "datetime", MustArray( - // time.Date(2021, 7, 27, 9, 35, 42, 123000000, time.UTC).Local(), - // time.Time{}.Local(), - // ), - // "document", MustArray( - // MustDocument("foo", ""), - // MustDocument("", "foo"), - // ), - // "double", MustArray(42.13, 0.0), - // "int32", MustArray(int32(42), int32(0)), - // "int64", MustArray(int64(42), int64(0)), - // "objectID", MustArray(ObjectID{0x42}, ObjectID{}), - // "string", MustArray("foo", ""), - // "timestamp", MustArray(Timestamp(42), Timestamp(0)), - // "decimal128", MustArray(Decimal128{L: 42, H: 13}), - // ), - // m: ` - // { - // "array": [[""], ["foo"]], - // "binary": [Binary(user:Qg==), Binary(generic:)], - // "bool": [true, false], - // "datetime": [2021-07-27T09:35:42.123Z, 0001-01-01T00:00:00Z], - // "document": [{"foo": ""}, {"": "foo"}], - // "double": [42.13, 0.0], - // "int32": [42, 0], - // "int64": [int64(42), int64(0)], - // "objectID": [ObjectID(420000000000000000000000), ObjectID(000000000000000000000000)], - // "string": ["foo", ""], - // "timestamp": [Timestamp(42), Timestamp(0)], - // "decimal128": [Decimal128(42,13)], - // }`, - //}, - //{ - // name: "nested", - // raw: testutil.MustParseDumpFile("testdata", "nested.hex"), - // doc: makeNested(false, 150).(*Document), - // m: ` - // { - // "f": [ - // { - // "f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{...}]}]}]}]}]}]}]}]}], - // }, - // ], - // }`, - //}, - //{ - // name: "float64Doc", - // raw: RawDocument{ - // 0x10, 0x00, 0x00, 0x00, - // 0x01, 0x66, 0x00, - // 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, - // 0x00, - // }, - // doc: MustDocument( - // "f", float64(3.141592653589793), - // ), - // m: `{"f": 3.141592653589793}`, - //}, - //{ - // name: "stringDoc", - // raw: RawDocument{ - // 0x0e, 0x00, 0x00, 0x00, - // 0x02, 0x66, 0x00, - // 0x02, 0x00, 0x00, 0x00, - // 0x76, 0x00, - // 0x00, - // }, - // doc: MustDocument( - // "f", "v", - // ), - // m: `{"f": "v"}`, - //}, - //{ - // name: "binaryDoc", - // raw: RawDocument{ - // 0x0e, 0x00, 0x00, 0x00, - // 0x05, 0x66, 0x00, - // 0x01, 0x00, 0x00, 0x00, - // 0x80, - // 0x76, - // 0x00, - // }, - // doc: MustDocument( - // "f", Binary{B: []byte("v"), Subtype: BinaryUser}, - // ), - // m: `{"f": Binary(user:dg==)}`, - //}, - //{ - // name: "objectIDDoc", - // raw: RawDocument{ - // 0x14, 0x00, 0x00, 0x00, - // 0x07, 0x66, 0x00, - // 0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, - // 0x00, - // }, - // doc: MustDocument( - // "f", ObjectID{0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}, - // ), - // m: `{"f": ObjectID(6256c5ba182d4454fb210940)}`, - //}, - //{ - // name: "boolDoc", - // raw: RawDocument{ - // 0x09, 0x00, 0x00, 0x00, - // 0x08, 0x66, 0x00, - // 0x01, - // 0x00, - // }, - // doc: MustDocument( - // "f", true, - // ), - // m: `{"f": true}`, - //}, - //{ - // name: "timeDoc", - // raw: RawDocument{ - // 0x10, 0x00, 0x00, 0x00, - // 0x09, 0x66, 0x00, - // 0x0b, 0xce, 0x82, 0x18, 0x8d, 0x01, 0x00, 0x00, - // 0x00, - // }, - // doc: MustDocument( - // "f", time.Date(2024, 1, 17, 17, 40, 42, 123000000, time.UTC), - // ), - // m: `{"f": 2024-01-17T17:40:42.123Z}`, - //}, - //{ - // name: "nullDoc", - // raw: RawDocument{ - // 0x08, 0x00, 0x00, 0x00, - // 0x0a, 0x66, 0x00, - // 0x00, - // }, - // doc: MustDocument( - // "f", Null, - // ), - // m: `{"f": null}`, - //}, - //{ - // name: "regexDoc", - // raw: RawDocument{ - // 0x0c, 0x00, 0x00, 0x00, - // 0x0b, 0x66, 0x00, - // 0x70, 0x00, - // 0x6f, 0x00, - // 0x00, - // }, - // doc: MustDocument( - // "f", Regex{Pattern: "p", Options: "o"}, - // ), - // m: `{"f": /p/o}`, - //}, - //{ - // name: "int32Doc", - // raw: RawDocument{ - // 0x0c, 0x00, 0x00, 0x00, - // 0x10, 0x66, 0x00, - // 0xa1, 0xb0, 0xb9, 0x12, - // 0x00, - // }, - // doc: MustDocument( - // "f", int32(314159265), - // ), - // m: `{"f": 314159265}`, - //}, - //{ - // name: "timestampDoc", - // raw: RawDocument{ - // 0x10, 0x00, 0x00, 0x00, - // 0x11, 0x66, 0x00, - // 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 0x00, - // }, - // doc: MustDocument( - // "f", Timestamp(42), - // ), - // m: `{"f": Timestamp(42)}`, - //}, - //{ - // name: "int64Doc", - // raw: RawDocument{ - // 0x10, 0x00, 0x00, 0x00, - // 0x12, 0x66, 0x00, - // 0x21, 0x6d, 0x25, 0x0a, 0x43, 0x29, 0x0b, 0x00, - // 0x00, - // }, - // doc: MustDocument( - // "f", int64(3141592653589793), - // ), - // m: `{"f": int64(3141592653589793)}`, - //}, - //{ - // name: "decimal128Doc", - // raw: RawDocument{ - // 0x18, 0x00, 0x00, 0x00, - // 0x13, 0x66, 0x00, - // 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 0x00, - // }, - // doc: MustDocument( - // "f", Decimal128{L: 42, H: 13}, - // ), - // m: `{"f": Decimal128(42,13)}`, - //}, - //{ - // name: "smallDoc", - // raw: RawDocument{ - // 0x0f, 0x00, 0x00, 0x00, // document length - // 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" - // 0x05, 0x00, 0x00, 0x00, 0x00, // subdocument length and end of subdocument - // 0x00, // end of document - // }, - // doc: MustDocument( - // "foo", MustDocument(), - // ), - // m: `{"foo": {}}`, - //}, - //{ - // name: "smallArray", - // raw: RawDocument{ - // 0x0f, 0x00, 0x00, 0x00, // document length - // 0x04, 0x66, 0x6f, 0x6f, 0x00, // subarray "foo" - // 0x05, 0x00, 0x00, 0x00, 0x00, // subarray length and end of subarray - // 0x00, // end of document - // }, - // doc: MustDocument( - // "foo", MustArray(), - // ), - // m: `{"foo": []}`, - //}, - //{ - // name: "duplicateKeys", - // raw: RawDocument{ - // 0x0b, 0x00, 0x00, 0x00, // document length - // 0x08, 0x00, 0x00, // "": false - // 0x08, 0x00, 0x01, // "": true - // 0x00, // end of document - // }, - // doc: MustDocument( - // "", false, - // "", true, - // ), - // m: `{"": false, "": true}`, - //}, + { + name: "handshake2", + raw: testutil.MustParseDumpFile("testdata", "handshake2.hex"), + doc: MustDocument( + "ismaster", true, + "client", MustDocument( + "driver", MustDocument( + "name", "nodejs", + "version", "4.0.0-beta.6", + ), + "os", MustDocument( + "type", "Darwin", + "name", "darwin", + "architecture", "x64", + "version", "20.6.0", + ), + "platform", "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", + "application", MustDocument( + "name", "mongosh 1.0.1", + ), + ), + "compression", MustArray("none"), + "loadBalanced", false, + ), + m: ` + { + "ismaster": true, + "client": { + "driver": {"name": "nodejs", "version": "4.0.0-beta.6"}, + "os": { + "type": "Darwin", + "name": "darwin", + "architecture": "x64", + "version": "20.6.0", + }, + "platform": "Node.js v14.17.3, LE (unified)|Node.js v14.17.3, LE (unified)", + "application": {"name": "mongosh 1.0.1"}, + }, + "compression": ["none"], + "loadBalanced": false, + }`, + }, + { + name: "handshake3", + raw: testutil.MustParseDumpFile("testdata", "handshake3.hex"), + doc: MustDocument( + "buildInfo", int32(1), + "lsid", MustDocument( + "id", Binary{ + Subtype: BinaryUUID, + B: []byte{ + 0xa3, 0x19, 0xf2, 0xb4, 0xa1, 0x75, 0x40, 0xc7, + 0xb8, 0xe7, 0xa3, 0xa3, 0x2e, 0xc2, 0x56, 0xbe, + }, + }, + ), + "$db", "admin", + ), + m: ` + { + "buildInfo": 1, + "lsid": {"id": Binary(uuid:oxnytKF1QMe456OjLsJWvg==)}, + "$db": "admin", + }`, + }, + { + name: "handshake4", + raw: testutil.MustParseDumpFile("testdata", "handshake4.hex"), + doc: MustDocument( + "version", "5.0.0", + "gitVersion", "1184f004a99660de6f5e745573419bda8a28c0e9", + "modules", MustArray(), + "allocator", "tcmalloc", + "javascriptEngine", "mozjs", + "sysInfo", "deprecated", + "versionArray", MustArray(int32(5), int32(0), int32(0), int32(0)), + "openssl", MustDocument( + "running", "OpenSSL 1.1.1f 31 Mar 2020", + "compiled", "OpenSSL 1.1.1f 31 Mar 2020", + ), + "buildEnvironment", MustDocument( + "distmod", "ubuntu2004", + "distarch", "x86_64", + "cc", "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", + "ccflags", "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb "+ + "-Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer "+ + "-fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 "+ + "-Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations "+ + "-Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces "+ + "-fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", + "cxx", "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", + "cxxflags", "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", + "linkflags", "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong "+ + "-Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack "+ + "-Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", + "target_arch", "x86_64", + "target_os", "linux", + "cppdefines", "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE "+ + "_REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME "+ + "BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS "+ + "BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG "+ + "BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", + ), + "bits", int32(64), + "debug", false, + "maxBsonObjectSize", int32(16777216), + "storageEngines", MustArray("devnull", "ephemeralForTest", "wiredTiger"), + "ok", float64(1), + ), + m: ` + { + "version": "5.0.0", + "gitVersion": "1184f004a99660de6f5e745573419bda8a28c0e9", + "modules": [], + "allocator": "tcmalloc", + "javascriptEngine": "mozjs", + "sysInfo": "deprecated", + "versionArray": [5, 0, 0, 0], + "openssl": { + "running": "OpenSSL 1.1.1f 31 Mar 2020", + "compiled": "OpenSSL 1.1.1f 31 Mar 2020", + }, + "buildEnvironment": { + "distmod": "ubuntu2004", + "distarch": "x86_64", + "cc": "/opt/mongodbtoolchain/v3/bin/gcc: gcc (GCC) 8.5.0", + "ccflags": "-Werror -include mongo/platform/basic.h -fasynchronous-unwind-tables -ggdb -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -fno-omit-frame-pointer -fno-strict-aliasing -O2 -march=sandybridge -mtune=generic -mprefer-vector-width=128 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-const-variable -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -Wa,--nocompress-debug-sections -fno-builtin-memcmp", + "cxx": "/opt/mongodbtoolchain/v3/bin/g++: g++ (GCC) 8.5.0", + "cxxflags": "-Woverloaded-virtual -Wno-maybe-uninitialized -fsized-deallocation -std=c++17", + "linkflags": "-Wl,--fatal-warnings -pthread -Wl,-z,now -fuse-ld=gold -fstack-protector-strong -Wl,--no-threads -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro -Wl,--compress-debug-sections=none -Wl,-z,origin -Wl,--enable-new-dtags", + "target_arch": "x86_64", + "target_os": "linux", + "cppdefines": "SAFEINT_USE_INTRINSICS 0 PCRE_STATIC NDEBUG _XOPEN_SOURCE 700 _GNU_SOURCE _REENTRANT 1 _FORTIFY_SOURCE 2 BOOST_THREAD_VERSION 5 BOOST_THREAD_USES_DATETIME BOOST_SYSTEM_NO_DEPRECATED BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS BOOST_ENABLE_ASSERT_DEBUG_HANDLER BOOST_LOG_NO_SHORTHAND_NAMES BOOST_LOG_USE_NATIVE_SYSLOG BOOST_LOG_WITHOUT_THREAD_ATTR ABSL_FORCE_ALIGNED_ACCESS", + }, + "bits": 64, + "debug": false, + "maxBsonObjectSize": 16777216, + "storageEngines": ["devnull", "ephemeralForTest", "wiredTiger"], + "ok": 1.0, + }`, + }, + { + name: "all", + raw: testutil.MustParseDumpFile("testdata", "all.hex"), + doc: MustDocument( + "array", MustArray( + MustArray(""), + MustArray("foo"), + ), + "binary", MustArray( + Binary{Subtype: BinaryUser, B: []byte{0x42}}, + Binary{Subtype: BinaryGeneric, B: []byte{}}, + ), + "bool", MustArray(true, false), + "datetime", MustArray( + time.Date(2021, 7, 27, 9, 35, 42, 123000000, time.UTC).Local(), + time.Time{}.Local(), + ), + "document", MustArray( + MustDocument("foo", ""), + MustDocument("", "foo"), + ), + "double", MustArray(42.13, 0.0), + "int32", MustArray(int32(42), int32(0)), + "int64", MustArray(int64(42), int64(0)), + "objectID", MustArray(ObjectID{0x42}, ObjectID{}), + "string", MustArray("foo", ""), + "timestamp", MustArray(Timestamp(42), Timestamp(0)), + "decimal128", MustArray(Decimal128{L: 42, H: 13}), + ), + m: ` + { + "array": [[""], ["foo"]], + "binary": [Binary(user:Qg==), Binary(generic:)], + "bool": [true, false], + "datetime": [2021-07-27T09:35:42.123Z, 0001-01-01T00:00:00Z], + "document": [{"foo": ""}, {"": "foo"}], + "double": [42.13, 0.0], + "int32": [42, 0], + "int64": [int64(42), int64(0)], + "objectID": [ObjectID(420000000000000000000000), ObjectID(000000000000000000000000)], + "string": ["foo", ""], + "timestamp": [Timestamp(42), Timestamp(0)], + "decimal128": [Decimal128(42,13)], + }`, + }, + { + name: "nested", + raw: testutil.MustParseDumpFile("testdata", "nested.hex"), + doc: makeNested(false, 150).(*Document), + m: ` + { + "f": [ + { + "f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{"f": [{...}]}]}]}]}]}]}]}]}], + }, + ], + }`, + }, + { + name: "float64Doc", + raw: RawDocument{ + 0x10, 0x00, 0x00, 0x00, + 0x01, 0x66, 0x00, + 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, + 0x00, + }, + doc: MustDocument( + "f", float64(3.141592653589793), + ), + m: `{"f": 3.141592653589793}`, + }, + { + name: "stringDoc", + raw: RawDocument{ + 0x0e, 0x00, 0x00, 0x00, + 0x02, 0x66, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x76, 0x00, + 0x00, + }, + doc: MustDocument( + "f", "v", + ), + m: `{"f": "v"}`, + }, + { + name: "binaryDoc", + raw: RawDocument{ + 0x0e, 0x00, 0x00, 0x00, + 0x05, 0x66, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x80, + 0x76, + 0x00, + }, + doc: MustDocument( + "f", Binary{B: []byte("v"), Subtype: BinaryUser}, + ), + m: `{"f": Binary(user:dg==)}`, + }, + { + name: "objectIDDoc", + raw: RawDocument{ + 0x14, 0x00, 0x00, 0x00, + 0x07, 0x66, 0x00, + 0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40, + 0x00, + }, + doc: MustDocument( + "f", ObjectID{0x62, 0x56, 0xc5, 0xba, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}, + ), + m: `{"f": ObjectID(6256c5ba182d4454fb210940)}`, + }, + { + name: "boolDoc", + raw: RawDocument{ + 0x09, 0x00, 0x00, 0x00, + 0x08, 0x66, 0x00, + 0x01, + 0x00, + }, + doc: MustDocument( + "f", true, + ), + m: `{"f": true}`, + }, + { + name: "timeDoc", + raw: RawDocument{ + 0x10, 0x00, 0x00, 0x00, + 0x09, 0x66, 0x00, + 0x0b, 0xce, 0x82, 0x18, 0x8d, 0x01, 0x00, 0x00, + 0x00, + }, + doc: MustDocument( + "f", time.Date(2024, 1, 17, 17, 40, 42, 123000000, time.UTC), + ), + m: `{"f": 2024-01-17T17:40:42.123Z}`, + }, + { + name: "nullDoc", + raw: RawDocument{ + 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x66, 0x00, + 0x00, + }, + doc: MustDocument( + "f", Null, + ), + m: `{"f": null}`, + }, + { + name: "regexDoc", + raw: RawDocument{ + 0x0c, 0x00, 0x00, 0x00, + 0x0b, 0x66, 0x00, + 0x70, 0x00, + 0x6f, 0x00, + 0x00, + }, + doc: MustDocument( + "f", Regex{Pattern: "p", Options: "o"}, + ), + m: `{"f": /p/o}`, + }, + { + name: "int32Doc", + raw: RawDocument{ + 0x0c, 0x00, 0x00, 0x00, + 0x10, 0x66, 0x00, + 0xa1, 0xb0, 0xb9, 0x12, + 0x00, + }, + doc: MustDocument( + "f", int32(314159265), + ), + m: `{"f": 314159265}`, + }, + { + name: "timestampDoc", + raw: RawDocument{ + 0x10, 0x00, 0x00, 0x00, + 0x11, 0x66, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + }, + doc: MustDocument( + "f", Timestamp(42), + ), + m: `{"f": Timestamp(42)}`, + }, + { + name: "int64Doc", + raw: RawDocument{ + 0x10, 0x00, 0x00, 0x00, + 0x12, 0x66, 0x00, + 0x21, 0x6d, 0x25, 0x0a, 0x43, 0x29, 0x0b, 0x00, + 0x00, + }, + doc: MustDocument( + "f", int64(3141592653589793), + ), + m: `{"f": int64(3141592653589793)}`, + }, + { + name: "decimal128Doc", + raw: RawDocument{ + 0x18, 0x00, 0x00, 0x00, + 0x13, 0x66, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + }, + doc: MustDocument( + "f", Decimal128{L: 42, H: 13}, + ), + m: `{"f": Decimal128(42,13)}`, + }, + { + name: "smallDoc", + raw: RawDocument{ + 0x0f, 0x00, 0x00, 0x00, // document length + 0x03, 0x66, 0x6f, 0x6f, 0x00, // subdocument "foo" + 0x05, 0x00, 0x00, 0x00, 0x00, // subdocument length and end of subdocument + 0x00, // end of document + }, + doc: MustDocument( + "foo", MustDocument(), + ), + m: `{"foo": {}}`, + }, + { + name: "smallArray", + raw: RawDocument{ + 0x0f, 0x00, 0x00, 0x00, // document length + 0x04, 0x66, 0x6f, 0x6f, 0x00, // subarray "foo" + 0x05, 0x00, 0x00, 0x00, 0x00, // subarray length and end of subarray + 0x00, // end of document + }, + doc: MustDocument( + "foo", MustArray(), + ), + m: `{"foo": []}`, + }, + { + name: "duplicateKeys", + raw: RawDocument{ + 0x0b, 0x00, 0x00, 0x00, // document length + 0x08, 0x00, 0x00, // "": false + 0x08, 0x00, 0x01, // "": true + 0x00, // end of document + }, + doc: MustDocument( + "", false, + "", true, + ), + m: `{"": false, "": true}`, + }, } // decodeTestCases represents test cases for unsuccessful decoding. From 28ccc751eb19b22e92a7b58d110ca0f53952ee98 Mon Sep 17 00:00:00 2001 From: noisersup Date: Fri, 13 Sep 2024 18:26:03 +0200 Subject: [PATCH 45/46] fix bench --- Taskfile.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Taskfile.yml b/Taskfile.yml index 8728406..14ceb52 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -88,7 +88,8 @@ tasks: desc: "Run benchmarks" cmds: - go test -list='Benchmark.*' ./... - - go test -count=10 -bench=BenchmarkDocument -benchtime={{.BENCH_TIME}} ./wirebson | tee -a new.txt + # timeout is needed due to this issue https://github.com/golang/go/issues/69181 + - go test -count=10 -bench=BenchmarkDocument -benchtime={{.BENCH_TIME}} -timeout=60m ./wirebson | tee -a new.txt - bin/benchstat old.txt new.txt fuzz: From e9b08a71fca3ab48f00c1780c665000a60831b8a Mon Sep 17 00:00:00 2001 From: Alexey Palazhchenko Date: Thu, 19 Sep 2024 19:53:57 +0400 Subject: [PATCH 46/46] Add notes --- Taskfile.yml | 2 +- wirebson/array.go | 19 ++++++++++--------- wirebson/document.go | 17 +++++++++-------- wirebson/encode.go | 24 ++++++++++-------------- wirebson/encode_test.go | 12 ++++++------ wirebson/objectid.go | 2 ++ wirebson/raw_array.go | 2 +- wirebson/raw_document.go | 2 +- 8 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index 14ceb52..e267b0f 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -88,7 +88,7 @@ tasks: desc: "Run benchmarks" cmds: - go test -list='Benchmark.*' ./... - # timeout is needed due to this issue https://github.com/golang/go/issues/69181 + # -timeout is needed due to https://github.com/golang/go/issues/69181 - go test -count=10 -bench=BenchmarkDocument -benchtime={{.BENCH_TIME}} -timeout=60m ./wirebson | tee -a new.txt - bin/benchstat old.txt new.txt diff --git a/wirebson/array.go b/wirebson/array.go index ae56d58..e4c9f70 100644 --- a/wirebson/array.go +++ b/wirebson/array.go @@ -148,28 +148,29 @@ func (arr *Array) SortInterface(less func(a, b any) bool) sort.Interface { } } -// Encode encodes non-nil Array. +// Encode encodes Array v into raw. // -// The function operates directly on raw RawArray. -// It doesn't reallocate memory, hence raw needs to have the proper length. +// raw must be at least Size(arr) bytes long; otherwise, Encode will panic. +// Only raw[0:Size(arr)] bytes are modified. func (arr *Array) Encode(raw RawArray) error { must.NotBeZero(arr) - binary.LittleEndian.PutUint32(raw[0:4], uint32(sizeArray(arr))) + // ensure raw length early + s := sizeArray(arr) + raw[s-1] = 0 + + binary.LittleEndian.PutUint32(raw, uint32(s)) i := 4 for n, v := range arr.values { - written, err := encodeField(raw[i:], strconv.Itoa(n), v) + w, err := encodeField(raw[i:], strconv.Itoa(n), v) if err != nil { return lazyerrors.Error(err) } - i += written + i += w } - raw[i] = byte(0) - i++ - return nil } diff --git a/wirebson/document.go b/wirebson/document.go index 83a7685..05bb464 100644 --- a/wirebson/document.go +++ b/wirebson/document.go @@ -226,26 +226,27 @@ func (doc *Document) Command() string { // Encode encodes non-nil Document. // -// The function operates directly on raw RawDocument. -// It doesn't reallocate memory, hence raw needs to have the proper length. +// raw must be at least Size(doc) bytes long; otherwise, Encode will panic. +// Only raw[0:Size(doc)] bytes are modified. func (doc *Document) Encode(raw RawDocument) error { must.NotBeZero(doc) - binary.LittleEndian.PutUint32(raw[0:4], uint32(sizeDocument(doc))) + // ensure raw length early + s := sizeDocument(doc) + raw[s-1] = 0 + + binary.LittleEndian.PutUint32(raw, uint32(s)) i := 4 for _, f := range doc.fields { - written, err := encodeField(raw[i:], f.name, f.value) + w, err := encodeField(raw[i:], f.name, f.value) if err != nil { return lazyerrors.Error(err) } - i += written + i += w } - raw[i] = byte(0) - i++ - return nil } diff --git a/wirebson/encode.go b/wirebson/encode.go index e14e0b0..32126db 100644 --- a/wirebson/encode.go +++ b/wirebson/encode.go @@ -43,14 +43,17 @@ func encodeField(dst []byte, name string, v any) (int, error) { i += sizeDocument(v) case RawDocument: - dst[i] = byte(tagDocument) i++ EncodeCString(dst[i:], name) i += SizeCString(name) - i += write(dst[i:], v) + if len(v) > len(dst[i:]) { + panic(fmt.Sprintf("length of dst should be at least %d bytes, got %d", len(v), len(dst[i:]))) + } + + i += copy(dst[i:], v) case *Array: dst[i] = byte(tagArray) @@ -73,7 +76,11 @@ func encodeField(dst []byte, name string, v any) (int, error) { EncodeCString(dst[i:], name) i += SizeCString(name) - i += write(dst[i:], v) + if len(v) > len(dst[i:]) { + panic(fmt.Sprintf("length of dst should be at least %d bytes, got %d", len(v), len(dst[i:]))) + } + + i += copy(dst[i:], v) default: return i + encodeScalarField(dst[i:], name, v), nil @@ -82,17 +89,6 @@ func encodeField(dst []byte, name string, v any) (int, error) { return i, nil } -// write writes bytes from dst to src. -// It returns number of bytes written. -func write(dst []byte, src []byte) int { - if len(src) > len(dst) { - panic(fmt.Sprintf("length of dst should be at least %d bytes, got %d", len(src), len(dst))) - } - - copy(dst, src) - return len(src) -} - // encodeScalarField encodes scalar document field. // // It returns the number of bytes written. diff --git a/wirebson/encode_test.go b/wirebson/encode_test.go index c9042a2..cb9b179 100644 --- a/wirebson/encode_test.go +++ b/wirebson/encode_test.go @@ -22,20 +22,20 @@ func TestEncodeField(t *testing.T) { var i int actual := make([]byte, 22) - written, err := encodeField(actual[i:], "foo", "bar") + w, err := encodeField(actual[i:], "foo", "bar") require.NoError(t, err) - assert.Equal(t, 13, written) - i += written + assert.Equal(t, 13, w) + i += w expected := []byte{0x2, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} assert.Equal(t, expected, actual) - written, err = encodeField(actual[i:], "foo", int32(1)) + w, err = encodeField(actual[i:], "foo", int32(1)) require.NoError(t, err) - assert.Equal(t, 9, written) - i += written + assert.Equal(t, 9, w) + i += w expected = []byte{0x2, 0x66, 0x6f, 0x6f, 0x0, 0x4, 0x0, 0x0, 0x0, 0x62, 0x61, 0x72, 0x0, 0x10, 0x66, 0x6f, 0x6f, 0x0, 0x1, 0x0, 0x0, 0x0} assert.Equal(t, expected, actual) diff --git a/wirebson/objectid.go b/wirebson/objectid.go index 300d5b5..83e3d27 100644 --- a/wirebson/objectid.go +++ b/wirebson/objectid.go @@ -29,7 +29,9 @@ const sizeObjectID = 12 // b must be at least 12 ([sizeObjectID]) bytes long; otherwise, encodeObjectID will panic. // Only b[0:12] bytes are modified. func encodeObjectID(b []byte, v ObjectID) { + // ensure b length early _ = b[11] + copy(b, v[:]) } diff --git a/wirebson/raw_array.go b/wirebson/raw_array.go index ac05a57..2a44054 100644 --- a/wirebson/raw_array.go +++ b/wirebson/raw_array.go @@ -32,7 +32,7 @@ type RawArray []byte // Receiver must not be nil. func (raw RawArray) Encode(out RawArray) error { must.BeTrue(raw != nil) - copy(out, raw) + copy(out, raw) // FIXME return nil } diff --git a/wirebson/raw_document.go b/wirebson/raw_document.go index ea7cc61..e026a53 100644 --- a/wirebson/raw_document.go +++ b/wirebson/raw_document.go @@ -31,7 +31,7 @@ type RawDocument []byte // Receiver must not be nil. func (raw RawDocument) Encode(out RawDocument) error { must.BeTrue(raw != nil) - copy(out, raw) + copy(out, raw) // FIXME return nil }